[
  {
    "path": ".editorconfig",
    "content": "# This file is for unifying the coding style for different editors and IDEs\n# editorconfig.org\n\nroot = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n# Tabs in JS unless otherwise specified\n[**.js]\nindent_style = space\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true\n  },\n  \"rules\": {\n    \"comma-dangle\": 2,\n    \"no-cond-assign\": 2,\n    \"no-control-regex\": 2,\n    \"no-debugger\": 2,\n    \"no-dupe-args\": 2,\n    \"no-dupe-keys\": 2,\n    \"no-duplicate-case\": 2,\n    \"no-empty-character-class\": 2,\n    \"no-empty\": 2,\n    \"no-ex-assign\": 2,\n    \"no-extra-boolean-cast\": 2,\n    \"no-extra-parens\": 2,\n    \"no-extra-semi\": 2,\n    \"no-func-assign\": 2,\n    \"no-inner-declarations\": 2,\n    \"no-invalid-regexp\": 2,\n    \"no-negated-in-lhs\": 2,\n    \"no-regex-spaces\": 2,\n    \"no-sparse-arrays\": 2,\n    \"no-unexpected-multiline\": 2,\n    \"no-unreachable\": 2,\n    \"valid-typeof\": 2,\n    \"accessor-pairs\": 2,\n    \"block-scoped-var\": 2,\n    \"complexity\": 2,\n    \"curly\": 2,\n    \"dot-notation\": [2, {\"allowKeywords\": false}],\n    \"eqeqeq\": 2,\n    \"guard-for-in\": 2,\n    \"semi\": 2,\n    \"no-alert\": 2,\n    \"no-caller\": 2,\n    \"no-case-declarations\": 2,\n    \"no-div-regex\": 2,\n    \"no-else-return\": 2,\n    \"no-empty-label\": 2,\n    \"no-empty-pattern\": 2,\n    \"no-eq-null\": 2,\n    \"no-eval\": 2,\n    \"no-extend-native\": 2,\n    \"no-extra-bind\": 2,\n    \"no-floating-decimal\": 2,\n    \"no-implied-eval\": 2,\n    \"no-invalid-this\": 2,\n    \"no-iterator\": 2,\n    \"no-lone-blocks\": 2,\n    \"no-loop-func\": 2,\n    \"no-multi-spaces\": 2,\n    \"no-native-reassign\": 2,\n    \"no-new-func\": 2,\n    \"no-new-wrappers\": 2,\n    \"no-new\": 2,\n    \"no-octal-escape\": 2,\n    \"no-octal\": 2,\n    \"no-proto\": 2,\n    \"no-redeclare\": 2,\n    \"no-script-url\": 2,\n    \"no-self-compare\": 2,\n    \"no-sequences\": 2,\n    \"no-throw-literal\": 2,\n    \"no-unused-expressions\": 2,\n    \"no-useless-call\": 2,\n    \"no-useless-concat\": 2,\n    \"no-void\": 2,\n    \"no-with\": 2,\n    \"radix\": 2,\n    \"wrap-iife\": 2,\n    \"yoda\": 2\n  }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.html  eol=lf\r\n*.css   eol=lf\r\n*.js    eol=lf\r\n*.md    eol=lf\r\n*.json  eol=lf\r\n*.yml   eol=lf\r\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "# PLEASE READ\n\nAs per the [README](https://github.com/angular-ui/bootstrap/blob/master/README.md), this project is no longer being maintained.  Any issues entered will remain uninvestigated and unresolved.\n\nWe thank you for your contributions over the years.  This library would not have been successful without them.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# PLEASE READ\n\nAs per the [README](https://github.com/angular-ui/bootstrap/blob/master/README.md), this project is no longer being maintained.  Any PRs entered will not be reviewed or merged and will remain open.\n\nWe thank you for your contributions over the years.  This library would not have been successful without them.\n"
  },
  {
    "path": ".gitignore",
    "content": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n*.swp\n*.swo\n.DS_Store\n.idea\n\npids\nlogs\nresults\ndist\n# test coverage files\n.coverage/\n\nnode_modules\nnpm-debug.log\n\ntemplate/**/*.js\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n  \"curly\": true,\n  \"immed\": true,\n  \"newcap\": true,\n  \"noarg\": true,\n  \"sub\": true,\n  \"boss\": true,\n  \"eqnull\": true,\n  \"quotmark\": \"single\",\n  \"trailing\": true,\n  \"undef\": true,\n  \"browser\": true,\n  \"jquery\": true,\n  \"globals\": {\n    \"angular\": false,\n\n    // For Jasmine\n    \"after\"      : false,\n    \"afterEach\"  : false,\n    \"before\"     : false,\n    \"beforeEach\" : false,\n    \"describe\"   : false,\n    \"expect\"     : false,\n    \"jasmine\"    : false,\n    \"module\"     : false,\n    \"spyOn\"      : false,\n    \"inject\"     : false,\n    \"it\"         : false\n  }\n}"
  },
  {
    "path": ".npmignore",
    "content": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n*.swp\n*.swo\n.DS_Store\n\npids\nlogs\nresults\n# test coverage files\n.coverage/\n\nnode_modules\nnpm-debug.log\n\n.git\ndocs\nmisc\n.editorconfig\n.gitattributes\n.gitignore\n.jshintrc\n.travis.yml\nCONTRIBUTING.md\nGruntfile.js\nkarma.conf.js\nROADMAP.md\n\ndist/assets\ndist/index.html\ndist/versions-mapping.json\ndist/*-SNAPSHOT*\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"5.9\"\nenv:\n  - CXX=g++-4.8\ndist: trusty\naddons:\n  apt:\n    sources:\n      - ubuntu-toolchain-r-test\n    packages:\n      - g++-4.8\n\nbefore_install:\n  - export DISPLAY=:99.0\n  - sh -e /etc/init.d/xvfb start\n  - npm install --quiet -g karma\n\nscript: grunt\nsudo: false\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<a name=\"2.5.0\"></a>\n# [2.5.0](https://github.com/angular-ui/bootstrap/compare/2.4.0...v2.5.0) (2017-01-28)\n\n\n### Bug Fixes\n\n* **angular:** add compatibility with Angular 1.6([0d79005](https://github.com/angular-ui/bootstrap/commit/0d79005)), closes [#6427](https://github.com/angular-ui/bootstrap/issues/6427) [#6360](https://github.com/angular-ui/bootstrap/issues/6360)\n* **carousel:** remove transition buffering([86ee770](https://github.com/angular-ui/bootstrap/commit/86ee770)), closes [#6367](https://github.com/angular-ui/bootstrap/issues/6367) [#5967](https://github.com/angular-ui/bootstrap/issues/5967)\n* **dropdown:** do nothing if not open when clicking([761db7b](https://github.com/angular-ui/bootstrap/commit/761db7b)), closes [#6414](https://github.com/angular-ui/bootstrap/issues/6414)\n* **tooltip:** unbind keypress listener on hide([f5b357f](https://github.com/angular-ui/bootstrap/commit/f5b357f)), closes [#6423](https://github.com/angular-ui/bootstrap/issues/6423) [#6405](https://github.com/angular-ui/bootstrap/issues/6405)\n\n\n### Features\n\n* **dropdown:** make dropdown-append-to-body configurable ([#6356](https://github.com/angular-ui/bootstrap/issues/6356))([7d3a750](https://github.com/angular-ui/bootstrap/commit/7d3a750))\n* **pagination:** Added menu and menuitem roles (closes [#6383](https://github.com/angular-ui/bootstrap/issues/6383)) ([#6386](https://github.com/angular-ui/bootstrap/issues/6386))([71dc691](https://github.com/angular-ui/bootstrap/commit/71dc691)), closes [#6383](https://github.com/angular-ui/bootstrap/issues/6383) [(#6386](https://github.com/(/issues/6386)\n\n\n\n<a name=\"2.4.0\"></a>\n# [2.4.0](https://github.com/angular-ui/bootstrap/compare/2.3.2...v2.4.0) (2016-12-30)\n\n\n### Features\n\n* **dateparser:** allow overriding of parsers([5a3e44a](https://github.com/angular-ui/bootstrap/commit/5a3e44a)), closes [#6370](https://github.com/angular-ui/bootstrap/issues/6370) [#6373](https://github.com/angular-ui/bootstrap/issues/6373)\n\n\n\n<a name=\"2.3.2\"></a>\n## [2.3.2](https://github.com/angular-ui/bootstrap/compare/2.3.1...v2.3.2) (2016-12-27)\n\n\n### Bug Fixes\n\n* **dropdown:** re-add close([955848c](https://github.com/angular-ui/bootstrap/commit/955848c)), closes [#6382](https://github.com/angular-ui/bootstrap/issues/6382) [#6321](https://github.com/angular-ui/bootstrap/issues/6321) [#6357](https://github.com/angular-ui/bootstrap/issues/6357) [#6364](https://github.com/angular-ui/bootstrap/issues/6364)\n\n\n\n<a name=\"2.3.1\"></a>\n## [2.3.1](https://github.com/angular-ui/bootstrap/compare/2.3.0...v2.3.1) (2016-12-10)\n\n\n### Bug Fixes\n\n* **dateparser:** add new date format for angular 1.5+ only([f2722b5](https://github.com/angular-ui/bootstrap/commit/f2722b5)), closes [#6349](https://github.com/angular-ui/bootstrap/issues/6349)\n\n* **datepickerPopup:** change to toTimezone only([1962485](https://github.com/angular-ui/bootstrap/commit/1962485)), fixes [#6235](https://github.com/angular-ui/bootstrap/issues/6235)\n\n* **modal:** revert focus behavior on open([8a4f625](https://github.com/angular-ui/bootstrap/commit/8a4f625)), closes [#6295](https://github.com/angular-ui/bootstrap/issues/6295)\n\n<a name=\"2.3.0\"></a>\n# [2.3.0](https://github.com/angular-ui/bootstrap/compare/2.2.0...2.3.0) (2016-11-26)\n\n\n### Features\n\n* **dateparser:** add LLLL support([25ff206](https://github.com/angular-ui/bootstrap/commit/25ff206)), closes [#6281](https://github.com/angular-ui/bootstrap/issues/6281)\n\n\n\n<a name=\"2.2.0\"></a>\n# [2.2.0](https://github.com/angular-ui/bootstrap/compare/2.1.4...2.2.0) (2016-10-10)\n\n\n### Bug Fixes\n\n* **dropdown:** exit keybind is not open ([14384fc](https://github.com/angular-ui/bootstrap/commit/14384fc)), closes [#6278](https://github.com/angular-ui/bootstrap/issues/6278) [#6208](https://github.com/angular-ui/bootstrap/issues/6208)\n* **modal:** improve ARIA support. ([f9f7e02](https://github.com/angular-ui/bootstrap/commit/f9f7e02)), closes [#6203](https://github.com/angular-ui/bootstrap/issues/6203)\n* **position:** correct scrollbar width calculation ([58f1813](https://github.com/angular-ui/bootstrap/commit/58f1813)), closes [#6273](https://github.com/angular-ui/bootstrap/issues/6273)\n* **tooltip:** cancel timeout when hidden ([7855976](https://github.com/angular-ui/bootstrap/commit/7855976)), closes [#6226](https://github.com/angular-ui/bootstrap/issues/6226) [#6221](https://github.com/angular-ui/bootstrap/issues/6221)\n\n### Features\n\n* **timepicker:** add validation information ([9666c64](https://github.com/angular-ui/bootstrap/commit/9666c64)), closes [#6230](https://github.com/angular-ui/bootstrap/issues/6230) [#6259](https://github.com/angular-ui/bootstrap/issues/6259)\n\n\n\n<a name=\"2.1.4\"></a>\n## [2.1.4](https://github.com/angular-ui/bootstrap/compare/2.1.3...2.1.4) (2016-09-24)\n\n\n### Bug Fixes\n\n* **datepicker:** improve accessibility ([3f70d76](https://github.com/angular-ui/bootstrap/commit/3f70d76)), closes [#6247](https://github.com/angular-ui/bootstrap/issues/6247)\n* **dropdown:** prevent premature scope removal ([08ee30a](https://github.com/angular-ui/bootstrap/commit/08ee30a)), closes [#6238](https://github.com/angular-ui/bootstrap/issues/6238) [#6225](https://github.com/angular-ui/bootstrap/issues/6225)\n\n\n\n<a name=\"2.1.3\"></a>\n## [2.1.3](https://github.com/angular-ui/bootstrap/compare/2.1.2...2.1.3) (2016-08-25)\n\n\n### Bug Fixes\n\n* **modal:** compile only once with component ([969eb9c](https://github.com/angular-ui/bootstrap/commit/969eb9c)), closes [#6202](https://github.com/angular-ui/bootstrap/issues/6202) [#6201](https://github.com/angular-ui/bootstrap/issues/6201)\n\n\n\n<a name=\"2.1.2\"></a>\n## [2.1.2](https://github.com/angular-ui/bootstrap/compare/2.1.1...2.1.2) (2016-08-22)\n\n\n### Bug Fixes\n\n* **collapse:** revert change to transition css ([515bcf2](https://github.com/angular-ui/bootstrap/commit/515bcf2)), closes [#6196](https://github.com/angular-ui/bootstrap/issues/6196) [#6194](https://github.com/angular-ui/bootstrap/issues/6194)\n* **datepicker:** fix accidental global ([ddcacb7](https://github.com/angular-ui/bootstrap/commit/ddcacb7)), closes [#6188](https://github.com/angular-ui/bootstrap/issues/6188)\n* **modal:** close and dismiss bindings on component ([3e8ecff](https://github.com/angular-ui/bootstrap/commit/3e8ecff)), closes [#6192](https://github.com/angular-ui/bootstrap/issues/6192) [#6191](https://github.com/angular-ui/bootstrap/issues/6191)\n\n\n\n<a name=\"2.1.1\"></a>\n## [2.1.1](https://github.com/angular-ui/bootstrap/compare/2.1.0...2.1.1) (2016-08-21)\n\n\n### Bug Fixes\n\n* **collapse:** default to css([aef24cd](https://github.com/angular-ui/bootstrap/commit/aef24cd)), closes [#6182](https://github.com/angular-ui/bootstrap/issues/6182) [#6045](https://github.com/angular-ui/bootstrap/issues/6045)\n* **modal:** switch to .append([fb5fabf](https://github.com/angular-ui/bootstrap/commit/fb5fabf)), closes [#6187](https://github.com/angular-ui/bootstrap/issues/6187) [#6186](https://github.com/angular-ui/bootstrap/issues/6186)\n\n\n\n<a name=\"2.1.0\"></a>\n# [2.1.0](https://github.com/angular-ui/bootstrap/compare/2.0.2...2.1.0) (2016-08-19)\n\n\n### Bug Fixes\n\n* **collapse:** remove unnecessary inherit ([ca20be4](https://github.com/angular-ui/bootstrap/commit/ca20be4)), closes [#6164](https://github.com/angular-ui/bootstrap/issues/6164) [#6163](https://github.com/angular-ui/bootstrap/issues/6163)\n* **collapse:** set overflow to hidden on transition ([84cc2cf](https://github.com/angular-ui/bootstrap/commit/84cc2cf)), closes [#6180](https://github.com/angular-ui/bootstrap/issues/6180) [#5474](https://github.com/angular-ui/bootstrap/issues/5474)\n* **datepickerPopup:** apply timezone conversion ([f147d22](https://github.com/angular-ui/bootstrap/commit/f147d22)), closes [#6173](https://github.com/angular-ui/bootstrap/issues/6173) [#6147](https://github.com/angular-ui/bootstrap/issues/6147)\n* **modal:** improve ARIA support ([4a5e6a7](https://github.com/angular-ui/bootstrap/commit/4a5e6a7)), closes [#4772](https://github.com/angular-ui/bootstrap/issues/4772)\n* **tooltip:** close tooltip on esc ([f5ff12c](https://github.com/angular-ui/bootstrap/commit/f5ff12c)), closes [#6177](https://github.com/angular-ui/bootstrap/issues/6177) [#6108](https://github.com/angular-ui/bootstrap/issues/6108)\n\n### Features\n\n* **modal:** add component support ([2ade054](https://github.com/angular-ui/bootstrap/commit/2ade054)), closes [#5683](https://github.com/angular-ui/bootstrap/issues/5683) [#6179](https://github.com/angular-ui/bootstrap/issues/6179)\n\n\n\n<a name=\"2.0.2\"></a>\n## [2.0.2](https://github.com/angular-ui/bootstrap/compare/2.0.1...2.0.2) (2016-08-15)\n\n\n### Bug Fixes\n\n* **datepickerPopup:** correctly format to timezone ([fbd0845](https://github.com/angular-ui/bootstrap/commit/fbd0845)), closes [#6159](https://github.com/angular-ui/bootstrap/issues/6159) [#6105](https://github.com/angular-ui/bootstrap/issues/6105) [#6146](https://github.com/angular-ui/bootstrap/issues/6146) [#6147](https://github.com/angular-ui/bootstrap/issues/6147)\n* **dropdown:** fix keyboard-nav ([6bad759](https://github.com/angular-ui/bootstrap/commit/6bad759)), closes [#6102](https://github.com/angular-ui/bootstrap/issues/6102) [#6154](https://github.com/angular-ui/bootstrap/issues/6154)\n\n\n\n<a name=\"2.0.1\"></a>\n## [2.0.1](https://github.com/angular-ui/bootstrap/compare/2.0.0...2.0.1) (2016-08-02)\n\n\n### Bug Fixes\n\n* **modal:** restore broken stacked modals([c61d16a](https://github.com/angular-ui/bootstrap/commit/c61d16a)), closes [#6103](https://github.com/angular-ui/bootstrap/issues/6103) [#6104](https://github.com/angular-ui/bootstrap/issues/6104)\n\n\n\n<a name=\"2.0.0\"></a>\n# [2.0.0](https://github.com/angular-ui/bootstrap/compare/1.3.3...2.0.0) (2016-07-20)\n\n\n### Bug Fixes\n\n* **dateparser:** correctly format with literals([d846e2d](https://github.com/angular-ui/bootstrap/commit/d846e2d)), closes [#6055](https://github.com/angular-ui/bootstrap/issues/6055) [#5620](https://github.com/angular-ui/bootstrap/issues/5620) [#5802](https://github.com/angular-ui/bootstrap/issues/5802)\n* **datepickerPopup:** clear date when button is clicked([b0466d9](https://github.com/angular-ui/bootstrap/commit/b0466d9)), closes [#5945](https://github.com/angular-ui/bootstrap/issues/5945) [#5906](https://github.com/angular-ui/bootstrap/issues/5906)\n* **datepickerPopup:** specify dependency on datepicker([4fef037](https://github.com/angular-ui/bootstrap/commit/4fef037)), closes [#5954](https://github.com/angular-ui/bootstrap/issues/5954)\n* **datepickerPopup:** use value instead of viewValue([7e320e0](https://github.com/angular-ui/bootstrap/commit/7e320e0)), closes [#6007](https://github.com/angular-ui/bootstrap/issues/6007)\n* **dropdown:** align position correctly with scrollbar([2d831bc](https://github.com/angular-ui/bootstrap/commit/2d831bc)), closes [#6008](https://github.com/angular-ui/bootstrap/issues/6008) [#5942](https://github.com/angular-ui/bootstrap/issues/5942)\n* **dropdown:** bind event listener on dropdown menu([6038403](https://github.com/angular-ui/bootstrap/commit/6038403)), closes [#5982](https://github.com/angular-ui/bootstrap/issues/5982)\n* **modal:** check for overflow hidden([433e536](https://github.com/angular-ui/bootstrap/commit/433e536)), closes [#6037](https://github.com/angular-ui/bootstrap/issues/6037) [#6041](https://github.com/angular-ui/bootstrap/issues/6041)\n* **modal:** filter out non-tabbable elements([35ced04](https://github.com/angular-ui/bootstrap/commit/35ced04)), closes [#5963](https://github.com/angular-ui/bootstrap/issues/5963) [#5955](https://github.com/angular-ui/bootstrap/issues/5955)\n* **modal:** remove unused template from modal([1de58a3](https://github.com/angular-ui/bootstrap/commit/1de58a3))\n* **modal:** remove window class after animation([409b7aa](https://github.com/angular-ui/bootstrap/commit/409b7aa)), closes [#6056](https://github.com/angular-ui/bootstrap/issues/6056) [#6051](https://github.com/angular-ui/bootstrap/issues/6051)\n* **tooltip:** missed dependency for cjs([164811a](https://github.com/angular-ui/bootstrap/commit/164811a)), closes [#5999](https://github.com/angular-ui/bootstrap/issues/5999)\n* **tooltip:** reposition for placement top([34a1443](https://github.com/angular-ui/bootstrap/commit/34a1443)), closes [#5959](https://github.com/angular-ui/bootstrap/issues/5959)\n* **typeahead:** change to select class([13c14af](https://github.com/angular-ui/bootstrap/commit/13c14af)), closes [#5922](https://github.com/angular-ui/bootstrap/issues/5922) [#5848](https://github.com/angular-ui/bootstrap/issues/5848)\n* **typeahead:** clear validity in $digest([ed3400b](https://github.com/angular-ui/bootstrap/commit/ed3400b)), closes [#6033](https://github.com/angular-ui/bootstrap/issues/6033) [#6032](https://github.com/angular-ui/bootstrap/issues/6032)\n* **typeahead:** remove duplicate id attribute([6d5b84a](https://github.com/angular-ui/bootstrap/commit/6d5b84a)), closes [#5936](https://github.com/angular-ui/bootstrap/issues/5936) [#5926](https://github.com/angular-ui/bootstrap/issues/5926)\n\n\n### Features\n\n* **accordion:** add appropriate tabindex on disabled([5f4eedd](https://github.com/angular-ui/bootstrap/commit/5f4eedd)), closes [#4067](https://github.com/angular-ui/bootstrap/issues/4067) [#5990](https://github.com/angular-ui/bootstrap/issues/5990)\n* **accordion:** remove replace: true usage([3819bbe](https://github.com/angular-ui/bootstrap/commit/3819bbe)), closes [#5985](https://github.com/angular-ui/bootstrap/issues/5985)\n* **alert:** remove replace: true usage([2458c28](https://github.com/angular-ui/bootstrap/commit/2458c28)), closes [#5986](https://github.com/angular-ui/bootstrap/issues/5986)\n* **carousel:** remove replace: true usage([5046bd4](https://github.com/angular-ui/bootstrap/commit/5046bd4)), closes [#5987](https://github.com/angular-ui/bootstrap/issues/5987)\n* **collapse:** add horizontal support([1ec0997](https://github.com/angular-ui/bootstrap/commit/1ec0997)), closes [#3593](https://github.com/angular-ui/bootstrap/issues/3593) [#6010](https://github.com/angular-ui/bootstrap/issues/6010)\n* **datepicker:** add monthColumns([39d9b98](https://github.com/angular-ui/bootstrap/commit/39d9b98)), closes [#5515](https://github.com/angular-ui/bootstrap/issues/5515) [#5935](https://github.com/angular-ui/bootstrap/issues/5935)\n* **datepicker:** add ngModelOptions support([23b91d9](https://github.com/angular-ui/bootstrap/commit/23b91d9)), closes [#5933](https://github.com/angular-ui/bootstrap/issues/5933) [#5825](https://github.com/angular-ui/bootstrap/issues/5825)\n* **datepicker:** remove replace: true usage([e92fb7f](https://github.com/angular-ui/bootstrap/commit/e92fb7f)), closes [#5988](https://github.com/angular-ui/bootstrap/issues/5988)\n* **datepickerPopup:** remove replace usage([a47bced](https://github.com/angular-ui/bootstrap/commit/a47bced)), closes [#5993](https://github.com/angular-ui/bootstrap/issues/5993)\n* **dropdown:** focus toggle on close from click([cce0097](https://github.com/angular-ui/bootstrap/commit/cce0097)), closes [#5934](https://github.com/angular-ui/bootstrap/issues/5934) [#5782](https://github.com/angular-ui/bootstrap/issues/5782)\n* **dropdown:** use .value() for uibDropdownConfig([7457fb0](https://github.com/angular-ui/bootstrap/commit/7457fb0)), closes [#6004](https://github.com/angular-ui/bootstrap/issues/6004) [#6006](https://github.com/angular-ui/bootstrap/issues/6006)\n* **modal:** append using $animate([1cbd73d](https://github.com/angular-ui/bootstrap/commit/1cbd73d)), closes [#6023](https://github.com/angular-ui/bootstrap/issues/6023) [#6029](https://github.com/angular-ui/bootstrap/issues/6029)\n* **modal:** remove replace usage([96d52ce](https://github.com/angular-ui/bootstrap/commit/96d52ce)), closes [#5989](https://github.com/angular-ui/bootstrap/issues/5989)\n* **pager:** remove replace usage([9b24e1d](https://github.com/angular-ui/bootstrap/commit/9b24e1d)), closes [#5991](https://github.com/angular-ui/bootstrap/issues/5991)\n* **pager:** toggle tabindex when disabled([0d8cec6](https://github.com/angular-ui/bootstrap/commit/0d8cec6)), closes [#5996](https://github.com/angular-ui/bootstrap/issues/5996)\n* **pagination:** disable tabbing when disabled([1a870a3](https://github.com/angular-ui/bootstrap/commit/1a870a3)), closes [#5984](https://github.com/angular-ui/bootstrap/issues/5984)\n* **pagination:** remove replace usage([387c6e7](https://github.com/angular-ui/bootstrap/commit/387c6e7)), closes [#5992](https://github.com/angular-ui/bootstrap/issues/5992)\n* **rating:** remove replace usage([d6fe9c7](https://github.com/angular-ui/bootstrap/commit/d6fe9c7)), closes [#5998](https://github.com/angular-ui/bootstrap/issues/5998)\n* **stackedMap:** improve perf of removeTop([a075824](https://github.com/angular-ui/bootstrap/commit/a075824)), closes [#5925](https://github.com/angular-ui/bootstrap/issues/5925) [#5932](https://github.com/angular-ui/bootstrap/issues/5932)\n* **timepicker:** avoid allowing to tab to controls([4e68778](https://github.com/angular-ui/bootstrap/commit/4e68778)), closes [#5930](https://github.com/angular-ui/bootstrap/issues/5930)\n* **timepicker:** remove replace usage([7518821](https://github.com/angular-ui/bootstrap/commit/7518821)), closes [#5997](https://github.com/angular-ui/bootstrap/issues/5997)\n* **tooltip:** add expression support([4b91222](https://github.com/angular-ui/bootstrap/commit/4b91222)), closes [#5221](https://github.com/angular-ui/bootstrap/issues/5221) [#5938](https://github.com/angular-ui/bootstrap/issues/5938)\n* **tooltip:** remove replace usage([1616e97](https://github.com/angular-ui/bootstrap/commit/1616e97)), closes [#5994](https://github.com/angular-ui/bootstrap/issues/5994)\n\n\n### Reverts\n\n* **dropdown:** change back to .constant()([4e0e34f](https://github.com/angular-ui/bootstrap/commit/4e0e34f))\n\n\n### BREAKING CHANGES\n\n* tooltip: The template structure changed slightly due to the removal of `replace: true` - see documentation examples in action to see differences in practice.\n* typeahead: This changes the selector used so that it doesn't select for the `li` tag specifically, but the elements with the class `uib-typeahead-match` - if using a custom template, this class needs to be added to the `li` element in the typeahead popup template.\n* rating: Due to the removal of `replace: true`, this results in a slight change to the HTML structure - see the documentation examples to see it in action.\n* timepicker: This removes `replace: true`, which changes the HTML structure slightly - see the documentation examples to see it in action.\n* datepickerPopup: Due to the nature of `replace: true`, this has a slight structural HTML change in the popup as a result - see documentation examples for the change in action.\n* pagination: Due to the removal of `replace: true`, this affects the HTML structure slightly - see documentation examples to see new usage.\n* pager: This removes `replace: true` usage from the pager, which causes a slight usage change - see documentation examples for new usage.\n* modal: This removes `replace: true` usage, causing some structural changes to the HTML - the major part here is that there is no more backdrop template, and the top level elements for the window/backdrop elements lose a little flexibility. See documentation examples for new structure.\n* modal: This introduces a minor behavior change in when the class is removed\n* carousel: Due to the removal of `replace: true`, this causes a slight HTML structure change to the carousel and the slide elements - see documentation demos to see how it changes. This also caused removal of the ngTouch built in support - if one is using ng-touch, one needs to add the `ng-swipe-left` and `ng-swipe-right` directives to the carousel element with relevant logic.\n* alert: This removes the `replace: true` usage, so this has an effect on how one uses the directive in the template - see documentation for reference\n* accordion: This removes usage of `replace: true` in the accordion group, which results in a template change where the template no longer needs to contain the panel itself, but its contents. The accordion group will add the `panel` class by default, so the user just needs to add the appropriate classes to the accordion group element. This allows the user to use ng-class as well to fully control the panel related classes, so `panel-class` now is unnecessary\n* tooltip: This removes support for plain strings and interpolations in tooltip-trigger and popover-trigger - please change these appropriately. See test changes in this commit for reference\n* typeahead: This change removes the `id` attribute on the first `<input>`\nelement placed into the DOM when the `typeahead-show-hint` attribute is used\nand there is an `id` attribute present on the original `uib-typeahead` element.\nThis could affect selectors if they are being used.\n* dropdown: This changes the focus behavior of the dropdown slightly, and potentially may break code built around current usage\n* datepicker: This modifies the current behavior around the datepicker & popup's ngModelOptions, which may affect custom components that are built around both\n* datepicker: As a result of removal of `replace: true`, there is the potential that this may break some CSS layout due to the slightly different HTML. Refer to the documentation examples to see the new structure.\n\n\n\n<a name=\"1.3.3\"></a>\n## [1.3.3](https://github.com/angular-ui/bootstrap/compare/1.3.2...1.3.3) (2016-05-23)\n\n\n### Bug Fixes\n\n* **datepicker:** add intermediary valid date check([a417ec2](https://github.com/angular-ui/bootstrap/commit/a417ec2)), closes [#5872](https://github.com/angular-ui/bootstrap/issues/5872) [#5865](https://github.com/angular-ui/bootstrap/issues/5865)\n* **datepickerPopup:** convert numbers to date before parsing([5188463](https://github.com/angular-ui/bootstrap/commit/5188463)), closes [#5873](https://github.com/angular-ui/bootstrap/issues/5873) [#5871](https://github.com/angular-ui/bootstrap/issues/5871)\n* **dropdown:** align position with vertical scrollbar([2b48259](https://github.com/angular-ui/bootstrap/commit/2b48259)), closes [#5830](https://github.com/angular-ui/bootstrap/issues/5830) [#4317](https://github.com/angular-ui/bootstrap/issues/4317)\n\n\n### Features\n\n* **accordion:** add alternative attribute support([4c40d9d](https://github.com/angular-ui/bootstrap/commit/4c40d9d)), closes [#5834](https://github.com/angular-ui/bootstrap/issues/5834) [#5839](https://github.com/angular-ui/bootstrap/issues/5839)\n* **modal:** add resolve values to template([dd09148](https://github.com/angular-ui/bootstrap/commit/dd09148)), closes [#5808](https://github.com/angular-ui/bootstrap/issues/5808) [#5857](https://github.com/angular-ui/bootstrap/issues/5857)\n* **tab:** allow strings for index([26d3995](https://github.com/angular-ui/bootstrap/commit/26d3995)), closes [#5713](https://github.com/angular-ui/bootstrap/issues/5713) [#5827](https://github.com/angular-ui/bootstrap/issues/5827)\n* **tabs:** pass the selected index to onDeselect([241fea8](https://github.com/angular-ui/bootstrap/commit/241fea8)), closes [#5820](https://github.com/angular-ui/bootstrap/issues/5820) [#5821](https://github.com/angular-ui/bootstrap/issues/5821)\n* **typeahead:** Add support for `should-select`([2ffb86f](https://github.com/angular-ui/bootstrap/commit/2ffb86f)), closes [#5671](https://github.com/angular-ui/bootstrap/issues/5671) [#5794](https://github.com/angular-ui/bootstrap/issues/5794)\n\n\n### BREAKING CHANGES\n\n* modal: Since this adds support for $resolve being exposed on $scope, it could potentially overwrite any pre-existing usage of it - this is an unlikely scenario, but marked as a breaking change in case this key is being used\n\n\n\n<a name=\"1.3.2\"></a>\n## [1.3.2](https://github.com/angular-ui/bootstrap/compare/1.3.1...1.3.2) (2016-04-14)\n\n\n### Bug Fixes\n\n* **datepicker:** ensure datepicker is focused ([33cbd0f](https://github.com/angular-ui/bootstrap/commit/33cbd0f)), closes [#5761](https://github.com/angular-ui/bootstrap/issues/5761) [#5754](https://github.com/angular-ui/bootstrap/issues/5754)\n* **datepickerPopup:** do not show incorrect warning ([98eb8f1](https://github.com/angular-ui/bootstrap/commit/98eb8f1)), closes [#5743](https://github.com/angular-ui/bootstrap/issues/5743) [#5747](https://github.com/angular-ui/bootstrap/issues/5747)\n* **dropdown:** stop esc keydown event ([68200bb](https://github.com/angular-ui/bootstrap/commit/68200bb)), closes [#5778](https://github.com/angular-ui/bootstrap/issues/5778) [#5787](https://github.com/angular-ui/bootstrap/issues/5787)\n* **modal:** missing require in modal ([a887d2b](https://github.com/angular-ui/bootstrap/commit/a887d2b)), closes [#5786](https://github.com/angular-ui/bootstrap/issues/5786)\n* **modal:** scroll padding only added once ([aef08d5](https://github.com/angular-ui/bootstrap/commit/aef08d5)), closes [#5790](https://github.com/angular-ui/bootstrap/issues/5790) [#5789](https://github.com/angular-ui/bootstrap/issues/5789)\n* **site:** allow FastClick to be blocked ([37f9cd2](https://github.com/angular-ui/bootstrap/commit/37f9cd2)), closes [#5756](https://github.com/angular-ui/bootstrap/issues/5756)\n* **tooltip:** arrow position ([d96d53e](https://github.com/angular-ui/bootstrap/commit/d96d53e)), closes [#5785](https://github.com/angular-ui/bootstrap/issues/5785) [#5779](https://github.com/angular-ui/bootstrap/issues/5779)\n* **typeahead:** use $setViewValue on blur ([bba3f27](https://github.com/angular-ui/bootstrap/commit/bba3f27)), closes [#5769](https://github.com/angular-ui/bootstrap/issues/5769) [#5694](https://github.com/angular-ui/bootstrap/issues/5694)\n\n### Features\n\n* **modal:** add no css import script ([a27a4e2](https://github.com/angular-ui/bootstrap/commit/a27a4e2)), closes [#5788](https://github.com/angular-ui/bootstrap/issues/5788)\n\n\n### BREAKING CHANGES\n\n* dropdown: Stops propagation of keydown event when escape key is pressed. Removes keydown event from the document and moves it to the dropdown element.\n\n\n\n<a name=\"1.3.1\"></a>\n## [1.3.1](https://github.com/angular-ui/bootstrap/compare/1.3.0...1.3.1) (2016-04-05)\n\n\n### Bug Fixes\n\n* **datepickerPopup:** register popup module ([86ee9c3](https://github.com/angular-ui/bootstrap/commit/86ee9c3)), closes [#5745](https://github.com/angular-ui/bootstrap/issues/5745)\n* **modal:** ensure correct index is set ([a08ad70](https://github.com/angular-ui/bootstrap/commit/a08ad70)), closes [#5733](https://github.com/angular-ui/bootstrap/issues/5733) [#5670](https://github.com/angular-ui/bootstrap/issues/5670)\n\n\n\n<a name=\"1.3.0\"></a>\n# [1.3.0](https://github.com/angular-ui/bootstrap/compare/1.2.5...1.3.0) (2016-04-05)\n\n\n### Bug Fixes\n\n* **carousel:** fix animation from programmatic trigger ([44fb19b](https://github.com/angular-ui/bootstrap/commit/44fb19b)), closes [#5674](https://github.com/angular-ui/bootstrap/issues/5674) [#5601](https://github.com/angular-ui/bootstrap/issues/5601)\n* modify CSS loaded for modules with dependencies ([8074899](https://github.com/angular-ui/bootstrap/commit/8074899)), closes [#5698](https://github.com/angular-ui/bootstrap/issues/5698)\n* **carousel:** use proper sort comparison ([434c1c5](https://github.com/angular-ui/bootstrap/commit/434c1c5)), closes [#5638](https://github.com/angular-ui/bootstrap/issues/5638) [#5702](https://github.com/angular-ui/bootstrap/issues/5702)\n* **modal:** body content shift ([c83d0a8](https://github.com/angular-ui/bootstrap/commit/c83d0a8)), closes [#2631](https://github.com/angular-ui/bootstrap/issues/2631) [#5711](https://github.com/angular-ui/bootstrap/issues/5711)\n* **popover:** rename title attribute ([cca1460](https://github.com/angular-ui/bootstrap/commit/cca1460)), closes [#5719](https://github.com/angular-ui/bootstrap/issues/5719) [#5721](https://github.com/angular-ui/bootstrap/issues/5721)\n* **tab:** add support for tab deselect prevention ([e0fcc00](https://github.com/angular-ui/bootstrap/commit/e0fcc00)), closes [#5720](https://github.com/angular-ui/bootstrap/issues/5720) [#5723](https://github.com/angular-ui/bootstrap/issues/5723)\n* **tab:** correctly identify index on removal ([03e6047](https://github.com/angular-ui/bootstrap/commit/03e6047)), closes [#5689](https://github.com/angular-ui/bootstrap/issues/5689)\n* **tab:** fix event handler call ([d767afb](https://github.com/angular-ui/bootstrap/commit/d767afb)), closes [#5720](https://github.com/angular-ui/bootstrap/issues/5720) [#5738](https://github.com/angular-ui/bootstrap/issues/5738)\n\n### Features\n\n* add support for loading without CSS ([de504fb](https://github.com/angular-ui/bootstrap/commit/de504fb)), closes [#5696](https://github.com/angular-ui/bootstrap/issues/5696)\n* **carousel:** disable prev/next arrows if at bounds ([a3964d4](https://github.com/angular-ui/bootstrap/commit/a3964d4)), closes [#5704](https://github.com/angular-ui/bootstrap/issues/5704) [#5708](https://github.com/angular-ui/bootstrap/issues/5708)\n* **dateparser:** use 68 as the yy format pivot year ([701e0cb](https://github.com/angular-ui/bootstrap/commit/701e0cb)), closes [#5735](https://github.com/angular-ui/bootstrap/issues/5735)\n* **datepicker:** deprecate literal usage ([fc02fd1](https://github.com/angular-ui/bootstrap/commit/fc02fd1)), closes [#5658](https://github.com/angular-ui/bootstrap/issues/5658) [#5732](https://github.com/angular-ui/bootstrap/issues/5732)\n* **datepicker:** remove deprecated code ([ad91c82](https://github.com/angular-ui/bootstrap/commit/ad91c82)), closes [#5660](https://github.com/angular-ui/bootstrap/issues/5660)\n* **datepicker:** watch for changes when falsy ([45165ba](https://github.com/angular-ui/bootstrap/commit/45165ba)), closes [#5672](https://github.com/angular-ui/bootstrap/issues/5672) [#5677](https://github.com/angular-ui/bootstrap/issues/5677)\n* **datepickerPopup:** split off popup from datepicker ([fbc873c](https://github.com/angular-ui/bootstrap/commit/fbc873c)), closes [#5676](https://github.com/angular-ui/bootstrap/issues/5676)\n* **dropdown:** remove $locationChangeSuccess listener ([c534cb4](https://github.com/angular-ui/bootstrap/commit/c534cb4)), closes [#5648](https://github.com/angular-ui/bootstrap/issues/5648) [#5680](https://github.com/angular-ui/bootstrap/issues/5680)\n* **timepicker:** remove automatic padding when empty ([449c0d1](https://github.com/angular-ui/bootstrap/commit/449c0d1)), closes [#5293](https://github.com/angular-ui/bootstrap/issues/5293) [#5299](https://github.com/angular-ui/bootstrap/issues/5299)\n\n\n### BREAKING CHANGES\n\n* carousel: This adds disabled CSS - there is a possibility this may break existing UI slightly for those adding custom CSS/making use of custom templates. Modify the template appropriately if necessary\n* timepicker: This removes automatic padding done by the timepicker\nwhen the input is empty - if that behavior is desired, write a custom\ndirective implementing it\n* dropdown: The dropdown no longer will remain open on $locationChangeSuccess with autoclose set to disabled. Implement this logic inside app along with usage of isOpen two-way binding if this functionality is desired.\n* datepickerPopup: The datepicker popup is no longer a part of the\ndatepicker module, but now a part of the datepickerPopup module. Please\nchange code accordingly if including specific modules\n* datepicker: This removes inline datepicker attribute support and\nattribute pass-throughs in the popup\n\n\n\n<a name=\"1.2.5\"></a>\n## [1.2.5](https://github.com/angular-ui/bootstrap/compare/1.2.4...1.2.5) (2016-03-20)\n\n\n### Bug Fixes\n\n* **datepicker:** dereference date initialization ([1b888d4](https://github.com/angular-ui/bootstrap/commit/1b888d4)), closes [#5643](https://github.com/angular-ui/bootstrap/issues/5643) [#5441](https://github.com/angular-ui/bootstrap/issues/5441)\n* **datepicker:** fix today button disabled condition ([316e96c](https://github.com/angular-ui/bootstrap/commit/316e96c)), closes [#5637](https://github.com/angular-ui/bootstrap/issues/5637) [#5622](https://github.com/angular-ui/bootstrap/issues/5622)\n* **modal:** dynamically fetch elements ([e15a22a](https://github.com/angular-ui/bootstrap/commit/e15a22a)), closes [#5630](https://github.com/angular-ui/bootstrap/issues/5630) [#5050](https://github.com/angular-ui/bootstrap/issues/5050) [#5421](https://github.com/angular-ui/bootstrap/issues/5421)\n* **popover:** add popover-html css ([54ac06a](https://github.com/angular-ui/bootstrap/commit/54ac06a)), closes [#5603](https://github.com/angular-ui/bootstrap/issues/5603) [#5602](https://github.com/angular-ui/bootstrap/issues/5602)\n* **tooltip:** css to support arrow positioning ([66c416c](https://github.com/angular-ui/bootstrap/commit/66c416c)), closes [#5610](https://github.com/angular-ui/bootstrap/issues/5610) [#5611](https://github.com/angular-ui/bootstrap/issues/5611)\n* **typeahead:** reset errors when clearing ([bc7c55a](https://github.com/angular-ui/bootstrap/commit/bc7c55a)), closes [#5641](https://github.com/angular-ui/bootstrap/issues/5641)\n\n### Features\n\n* **accordion:** add bound panel-class support ([ffb5529](https://github.com/angular-ui/bootstrap/commit/ffb5529)), closes [#5368](https://github.com/angular-ui/bootstrap/issues/5368) [#5596](https://github.com/angular-ui/bootstrap/issues/5596)\n* **modal:** exclude hidden elements from query ([5ef56e2](https://github.com/angular-ui/bootstrap/commit/5ef56e2)), closes [#5512](https://github.com/angular-ui/bootstrap/issues/5512) [#5644](https://github.com/angular-ui/bootstrap/issues/5644)\n* **rating:** ability to disable rating reset ([8747b58](https://github.com/angular-ui/bootstrap/commit/8747b58)), closes [#5532](https://github.com/angular-ui/bootstrap/issues/5532) [#5631](https://github.com/angular-ui/bootstrap/issues/5631)\n* **tab:** add select expressions ([bb36e40](https://github.com/angular-ui/bootstrap/commit/bb36e40)), closes [#5438](https://github.com/angular-ui/bootstrap/issues/5438)\n* **timepicker:** add pad-hours support ([c7be087](https://github.com/angular-ui/bootstrap/commit/c7be087)), closes [#4288](https://github.com/angular-ui/bootstrap/issues/4288) [#5633](https://github.com/angular-ui/bootstrap/issues/5633)\n\n\n\n<a name=\"1.2.4\"></a>\n## [1.2.4](https://github.com/angular-ui/bootstrap/compare/1.2.3...1.2.4) (2016-03-06)\n\n\n\n\n<a name=\"1.2.3\"></a>\n## [1.2.3](https://github.com/angular-ui/bootstrap/compare/1.2.2...1.2.3) (2016-03-06)\n\n\n### Bug Fixes\n\n* **dropdown:** correctly update isOpen ([aac0d2b](https://github.com/angular-ui/bootstrap/commit/aac0d2b)), closes [#5589](https://github.com/angular-ui/bootstrap/issues/5589) [#3261](https://github.com/angular-ui/bootstrap/issues/3261)\n* **modal:** switch to $animate ([dd62c73](https://github.com/angular-ui/bootstrap/commit/dd62c73)), closes [#5585](https://github.com/angular-ui/bootstrap/issues/5585) [#5298](https://github.com/angular-ui/bootstrap/issues/5298)\n* **position:** auto position append to body ([a516178](https://github.com/angular-ui/bootstrap/commit/a516178)), closes [#5582](https://github.com/angular-ui/bootstrap/issues/5582) [#5576](https://github.com/angular-ui/bootstrap/issues/5576)\n* **tabs:** support heading with : ([a945dd2](https://github.com/angular-ui/bootstrap/commit/a945dd2)), closes [#5590](https://github.com/angular-ui/bootstrap/issues/5590) [#5568](https://github.com/angular-ui/bootstrap/issues/5568)\n* **typeahead:** change to original scope ([7949494](https://github.com/angular-ui/bootstrap/commit/7949494)), closes [#5588](https://github.com/angular-ui/bootstrap/issues/5588) [#5467](https://github.com/angular-ui/bootstrap/issues/5467)\n* **typeahead:** update isOpen on escape ([313ba83](https://github.com/angular-ui/bootstrap/commit/313ba83)), closes [#5579](https://github.com/angular-ui/bootstrap/issues/5579) [#5587](https://github.com/angular-ui/bootstrap/issues/5587)\n\n### Features\n\n* **datepicker:** add helpers for styling ([c15dcbd](https://github.com/angular-ui/bootstrap/commit/c15dcbd)), closes [#5580](https://github.com/angular-ui/bootstrap/issues/5580)\n\n\n\n<a name=\"1.2.2\"></a>\n## [1.2.2](https://github.com/angular-ui/bootstrap/compare/1.2.1...1.2.2) (2016-03-03)\n\n\n### Bug Fixes\n\n* **Gruntfile:** add per module flag ([a816251](https://github.com/angular-ui/bootstrap/commit/a816251)), closes [#5567](https://github.com/angular-ui/bootstrap/issues/5567)\n* **modal:** fire ng-leave on close ([299f751](https://github.com/angular-ui/bootstrap/commit/299f751)), closes [#5556](https://github.com/angular-ui/bootstrap/issues/5556) [#5399](https://github.com/angular-ui/bootstrap/issues/5399)\n* **modal:** fix bindToController props ([5e43870](https://github.com/angular-ui/bootstrap/commit/5e43870)), closes [#5569](https://github.com/angular-ui/bootstrap/issues/5569)\n* **position:** add CSS for webpack support ([d68086f](https://github.com/angular-ui/bootstrap/commit/d68086f)), closes [#5561](https://github.com/angular-ui/bootstrap/issues/5561)\n* **rating:** fix usage of aria-valuetext ([4369800](https://github.com/angular-ui/bootstrap/commit/4369800)), closes [#5573](https://github.com/angular-ui/bootstrap/issues/5573) [#5571](https://github.com/angular-ui/bootstrap/issues/5571)\n* **tabs:** update doc and fix tab demo ([9d74d6c](https://github.com/angular-ui/bootstrap/commit/9d74d6c)), closes [#5575](https://github.com/angular-ui/bootstrap/issues/5575) [#5551](https://github.com/angular-ui/bootstrap/issues/5551)\n* **tests:** add style tag once ([cc2d1b9](https://github.com/angular-ui/bootstrap/commit/cc2d1b9)), closes [#5557](https://github.com/angular-ui/bootstrap/issues/5557) [#5548](https://github.com/angular-ui/bootstrap/issues/5548)\n* **tooltip:** import CSS ([c8922a2](https://github.com/angular-ui/bootstrap/commit/c8922a2)), closes [#5552](https://github.com/angular-ui/bootstrap/issues/5552)\n\n\n\n<a name=\"1.2.1\"></a>\n## [1.2.1](https://github.com/angular-ui/bootstrap/compare/1.2.0...1.2.1) (2016-02-27)\n\n\n### Bug Fixes\n\n* **popover:** add missing selector ([70c1c90](https://github.com/angular-ui/bootstrap/commit/70c1c90)), closes [#5549](https://github.com/angular-ui/bootstrap/issues/5549)\n\n\n\n<a name=\"1.2.0\"></a>\n# [1.2.0](https://github.com/angular-ui/bootstrap/compare/1.1.2...1.2.0) (2016-02-26)\n\n\n### Bug Fixes\n\n* **buttons:** fix uncheckable attribute ([b245242](https://github.com/angular-ui/bootstrap/commit/b245242)), closes [#5451](https://github.com/angular-ui/bootstrap/issues/5451) [#5412](https://github.com/angular-ui/bootstrap/issues/5412)\n* **dateparser:** append end of format ([5c565df](https://github.com/angular-ui/bootstrap/commit/5c565df)), closes [#5385](https://github.com/angular-ui/bootstrap/issues/5385) [#5387](https://github.com/angular-ui/bootstrap/issues/5387)\n* **datepicker:** adjust datepicker header buttons ([f125537](https://github.com/angular-ui/bootstrap/commit/f125537)), closes [#5393](https://github.com/angular-ui/bootstrap/issues/5393) [#5392](https://github.com/angular-ui/bootstrap/issues/5392)\n* **datepicker:** assign initDate correctly to the controller ([8769749](https://github.com/angular-ui/bootstrap/commit/8769749)), closes [#5541](https://github.com/angular-ui/bootstrap/issues/5541)\n* **datepicker:** check if initDate is valid ([ab59413](https://github.com/angular-ui/bootstrap/commit/ab59413)), closes [#5190](https://github.com/angular-ui/bootstrap/issues/5190) [#5266](https://github.com/angular-ui/bootstrap/issues/5266)\n* **datepicker:** default to current date ([4b2ee0f](https://github.com/angular-ui/bootstrap/commit/4b2ee0f)), closes [#5395](https://github.com/angular-ui/bootstrap/issues/5395) [#5190](https://github.com/angular-ui/bootstrap/issues/5190)\n* **datepicker:** fallback to current date ([e4fc201](https://github.com/angular-ui/bootstrap/commit/e4fc201)), closes [#5418](https://github.com/angular-ui/bootstrap/issues/5418) [#5417](https://github.com/angular-ui/bootstrap/issues/5417)\n* **datepicker:** fix popup updateOn: default support ([47e412e](https://github.com/angular-ui/bootstrap/commit/47e412e)), closes [#5529](https://github.com/angular-ui/bootstrap/issues/5529)\n* **datepicker:** stop event bubbling from buttons ([e283cea](https://github.com/angular-ui/bootstrap/commit/e283cea)), closes [#5400](https://github.com/angular-ui/bootstrap/issues/5400) [#5347](https://github.com/angular-ui/bootstrap/issues/5347)\n* **modal:** fix modal rendered promise ([42fb486](https://github.com/angular-ui/bootstrap/commit/42fb486)), closes [#5401](https://github.com/angular-ui/bootstrap/issues/5401) [#5331](https://github.com/angular-ui/bootstrap/issues/5331)\n* **modal:** fix race condition with openedClass ([979fe0b](https://github.com/angular-ui/bootstrap/commit/979fe0b)), closes [#5483](https://github.com/angular-ui/bootstrap/issues/5483)\n* **position:** getRawNode to handle select/ul elem ([8b3e86f](https://github.com/angular-ui/bootstrap/commit/8b3e86f)), closes [#5491](https://github.com/angular-ui/bootstrap/issues/5491) [#5354](https://github.com/angular-ui/bootstrap/issues/5354)\n* **position:** move inline styles to a class ([4bb178a](https://github.com/angular-ui/bootstrap/commit/4bb178a)), closes [#5535](https://github.com/angular-ui/bootstrap/issues/5535) [#5470](https://github.com/angular-ui/bootstrap/issues/5470)\n* **progressbar:** fix default max value ([258b341](https://github.com/angular-ui/bootstrap/commit/258b341)), closes [#5374](https://github.com/angular-ui/bootstrap/issues/5374) [#5373](https://github.com/angular-ui/bootstrap/issues/5373)\n* **rating:** change to read-only ([d5a757d](https://github.com/angular-ui/bootstrap/commit/d5a757d)), closes [#5371](https://github.com/angular-ui/bootstrap/issues/5371) [#5435](https://github.com/angular-ui/bootstrap/issues/5435)\n* **tab:** make active optional ([d1553a4](https://github.com/angular-ui/bootstrap/commit/d1553a4)), closes [#5489](https://github.com/angular-ui/bootstrap/issues/5489)\n* **tabs:** adding bootstrap 4 specific class ([3814fe3](https://github.com/angular-ui/bootstrap/commit/3814fe3)), closes [#5488](https://github.com/angular-ui/bootstrap/issues/5488)\n* **typeahead:** fix shift tab ([64e3127](https://github.com/angular-ui/bootstrap/commit/64e3127)), closes [#5494](https://github.com/angular-ui/bootstrap/issues/5494) [#5493](https://github.com/angular-ui/bootstrap/issues/5493)\n* **typeahead:** Fix tab index for hint input ([4178500](https://github.com/angular-ui/bootstrap/commit/4178500)), closes [#5478](https://github.com/angular-ui/bootstrap/issues/5478) [#5492](https://github.com/angular-ui/bootstrap/issues/5492)\n\n### Features\n\n* **accordion:** use attribute for heading insertion ([ca6b177](https://github.com/angular-ui/bootstrap/commit/ca6b177)), closes [#5324](https://github.com/angular-ui/bootstrap/issues/5324) [#5396](https://github.com/angular-ui/bootstrap/issues/5396)\n* **datepicker:** add deprecation notices ([10eac7c](https://github.com/angular-ui/bootstrap/commit/10eac7c)), closes [#5415](https://github.com/angular-ui/bootstrap/issues/5415)\n* **datepicker:** implement auto position ([f9903ba](https://github.com/angular-ui/bootstrap/commit/f9903ba)), closes [#5444](https://github.com/angular-ui/bootstrap/issues/5444)\n* **datepicker:** move attribute support to options ([d880aea](https://github.com/angular-ui/bootstrap/commit/d880aea)), closes [#5528](https://github.com/angular-ui/bootstrap/issues/5528)\n* **datepicker:** move datepicker mode to options ([74a1d04](https://github.com/angular-ui/bootstrap/commit/74a1d04)), closes [#5526](https://github.com/angular-ui/bootstrap/issues/5526)\n* **datepicker:** pass options from popup to inline ([3e10184](https://github.com/angular-ui/bootstrap/commit/3e10184)), closes [#5355](https://github.com/angular-ui/bootstrap/issues/5355)\n* **datepicker:** preserve popup attributes ([bbc4912](https://github.com/angular-ui/bootstrap/commit/bbc4912)), closes [#5537](https://github.com/angular-ui/bootstrap/issues/5537)\n* **modal:** Call $onInit on controller if defined ([8349758](https://github.com/angular-ui/bootstrap/commit/8349758)), closes [#5472](https://github.com/angular-ui/bootstrap/issues/5472) [#5509](https://github.com/angular-ui/bootstrap/issues/5509)\n* **pagination:** add custom label support ([785c373](https://github.com/angular-ui/bootstrap/commit/785c373)), closes [#2532](https://github.com/angular-ui/bootstrap/issues/2532) [#5547](https://github.com/angular-ui/bootstrap/issues/5547)\n* **position:** add isScrollable ([3c0a7cd](https://github.com/angular-ui/bootstrap/commit/3c0a7cd)), closes [#5508](https://github.com/angular-ui/bootstrap/issues/5508)\n* **tab:** add template-url support ([54c51c4](https://github.com/angular-ui/bootstrap/commit/54c51c4)), closes [#5405](https://github.com/angular-ui/bootstrap/issues/5405) [#5443](https://github.com/angular-ui/bootstrap/issues/5443)\n* **tabs:** support optional classes on tab ([8364e76](https://github.com/angular-ui/bootstrap/commit/8364e76)), closes [#5430](https://github.com/angular-ui/bootstrap/issues/5430)\n* **tooltip:** arrow position ([fa17c48](https://github.com/angular-ui/bootstrap/commit/fa17c48)), closes [#5464](https://github.com/angular-ui/bootstrap/issues/5464) [#5502](https://github.com/angular-ui/bootstrap/issues/5502)\n* **typeahead:** add dynamic min length support ([f81d440](https://github.com/angular-ui/bootstrap/commit/f81d440)), closes [#5363](https://github.com/angular-ui/bootstrap/issues/5363)\n\n\n### BREAKING CHANGES\n\n* rating: Attribute supported has been changed to `read-only`\nfrom `readonly`\n* accordion: This changes to use the `uibAccordionHeader` attribute instead of a `<span>` element for inserting the custom header HTML. If you use a custom template for the accordion group, please add this attribute to the appropriate location.\n* datepicker: This requires $event to be passed in the second\nargument of the select function and in the close argument for the popup\ntemplate\n* datepicker: This adds extra CSS for the datepicker for the left and right header buttons - one can override this appropriately with any selector of class priority higher than 1\n* datepicker: This breaks any snake-cased key usage, i.e.\n`show-weeks`. Convert all keys used to camelCase.\n* tab: Make the `active` attribute optional and move to `tabset` element.\n* carousel: the `active` attribute is now required on the `uib-carousel` element and a unique `index` attribute is required on each `uib-slide` element.\n\n\n\n<a name=\"1.1.2\"></a>\n## [1.1.2](https://github.com/angular-ui/bootstrap/compare/1.1.1...1.1.2) (2016-02-01)\n\n\n### Bug Fixes\n\n* **datepicker:** fix validation for M! & d! ([c3b1431](https://github.com/angular-ui/bootstrap/commit/c3b1431)), closes [#5376](https://github.com/angular-ui/bootstrap/issues/5376) [#5225](https://github.com/angular-ui/bootstrap/issues/5225)\n* **modal:** ensure shift+tab is trapped in modal ([d2621e3](https://github.com/angular-ui/bootstrap/commit/d2621e3)), closes [#5294](https://github.com/angular-ui/bootstrap/issues/5294) [#5229](https://github.com/angular-ui/bootstrap/issues/5229)\n* **tooltip:** prevent closing on $locationChangeSuccess ([48c9cd8](https://github.com/angular-ui/bootstrap/commit/48c9cd8)), closes [#5360](https://github.com/angular-ui/bootstrap/issues/5360) [#5337](https://github.com/angular-ui/bootstrap/issues/5337)\n* **typeahead:** add guard for no $viewValue ([dbe9e81](https://github.com/angular-ui/bootstrap/commit/dbe9e81)), closes [#5358](https://github.com/angular-ui/bootstrap/issues/5358) [#5357](https://github.com/angular-ui/bootstrap/issues/5357)\n\n### Features\n\n* **accordion:** add aria tags ([61cdef1](https://github.com/angular-ui/bootstrap/commit/61cdef1)), closes [#5338](https://github.com/angular-ui/bootstrap/issues/5338)\n* **datepicker:** add datepickerOptions support ([e58c42c](https://github.com/angular-ui/bootstrap/commit/e58c42c)), closes [#5340](https://github.com/angular-ui/bootstrap/issues/5340)\n\n\n\n<a name=\"1.1.1\"></a>\n## [1.1.1](https://github.com/angular-ui/bootstrap/compare/1.1.0...1.1.1) (2016-01-25)\n\n\n### Bug Fixes\n\n* **datepicker:** allow using min/max mode/date with datepickerOptions ([97af6a9](https://github.com/angular-ui/bootstrap/commit/97af6a9)), closes [#5334](https://github.com/angular-ui/bootstrap/issues/5334) [#5315](https://github.com/angular-ui/bootstrap/issues/5315)\n* **modal:** fix modal closed resolution ([d5a48ea](https://github.com/angular-ui/bootstrap/commit/d5a48ea)), closes [#5322](https://github.com/angular-ui/bootstrap/issues/5322) [#5326](https://github.com/angular-ui/bootstrap/issues/5326)\n\n\n\n<a name=\"1.1.0\"></a>\n# [1.1.0](https://github.com/angular-ui/bootstrap/compare/v1.0.3...v1.1.0) (2016-01-18)\n\n\n## Bug Fixes\n\n* **collapse:** avoid initial animation when expanded ([57219aa](https://github.com/angular-ui/bootstrap/commit/57219aa)), closes [#5199](https://github.com/angular-ui/bootstrap/issues/5199) [#4414](https://github.com/angular-ui/bootstrap/issues/4414) [#5192](https://github.com/angular-ui/bootstrap/issues/5192)\n* **datepicker:** allow expressions for minMode/maxMode ([7e93ec9](https://github.com/angular-ui/bootstrap/commit/7e93ec9)), closes [#5264](https://github.com/angular-ui/bootstrap/issues/5264) [#5268](https://github.com/angular-ui/bootstrap/issues/5268) [#5240](https://github.com/angular-ui/bootstrap/issues/5240)\n* **datepicker:** fix popup CSS ([b3e6d17](https://github.com/angular-ui/bootstrap/commit/b3e6d17)), closes [#5258](https://github.com/angular-ui/bootstrap/issues/5258)\n* **datepicker:** fix popup element garbage collection ([2cb3bc2](https://github.com/angular-ui/bootstrap/commit/2cb3bc2)), closes [#5274](https://github.com/angular-ui/bootstrap/issues/5274) [#5058](https://github.com/angular-ui/bootstrap/issues/5058)\n* **datepicker:** pass through null ([78ba137](https://github.com/angular-ui/bootstrap/commit/78ba137)), closes [#5275](https://github.com/angular-ui/bootstrap/issues/5275) [#5238](https://github.com/angular-ui/bootstrap/issues/5238)\n* **modal:** change to compile before appending ([1dbad8a](https://github.com/angular-ui/bootstrap/commit/1dbad8a)), closes [#5224](https://github.com/angular-ui/bootstrap/issues/5224) [#5183](https://github.com/angular-ui/bootstrap/issues/5183)\n* **paging:** garbage collect parent $watchers ([32b88f1](https://github.com/angular-ui/bootstrap/commit/32b88f1)), closes [#5276](https://github.com/angular-ui/bootstrap/issues/5276)\n* **position:** positionArrow descendant selector ([9f4c3a5](https://github.com/angular-ui/bootstrap/commit/9f4c3a5), [e8b6a83](https://github.com/angular-ui/bootstrap/commit/e8b6a83)), closes [#5246](https://github.com/angular-ui/bootstrap/issues/5246) [#5230](https://github.com/angular-ui/bootstrap/issues/5230)\n* **tab:** revert template change ([907c851](https://github.com/angular-ui/bootstrap/commit/907c851)), closes [#5262](https://github.com/angular-ui/bootstrap/issues/5262) [#5254](https://github.com/angular-ui/bootstrap/issues/5254)\n* **timepicker:** garbage collect parent watchers ([3f809af](https://github.com/angular-ui/bootstrap/commit/3f809af)), closes [#5277](https://github.com/angular-ui/bootstrap/issues/5277)\n* **tooltip:** fix addClass signature ([6a2d91b](https://github.com/angular-ui/bootstrap/commit/6a2d91b)), closes [#5235](https://github.com/angular-ui/bootstrap/issues/5235)\n* **typeahead:** add guard for destroyed $scope ([e105e85](https://github.com/angular-ui/bootstrap/commit/e105e85)), closes [#5267](https://github.com/angular-ui/bootstrap/issues/5267)\n* **typeahead:** fix css selector ([00ac93e](https://github.com/angular-ui/bootstrap/commit/00ac93e)), closes [#5251](https://github.com/angular-ui/bootstrap/issues/5251)\n\n## Features\n\n* **collapse:** add callback hooks ([446364a](https://github.com/angular-ui/bootstrap/commit/446364a)), closes [#5194](https://github.com/angular-ui/bootstrap/issues/5194) [#5226](https://github.com/angular-ui/bootstrap/issues/5226)\n* **datepicker:** extract inline styles to stylesheet ([6843ab6](https://github.com/angular-ui/bootstrap/commit/6843ab6)), closes [#5215](https://github.com/angular-ui/bootstrap/issues/5215)\n* **datepicker:** use $locale for starting day ([332eefb](https://github.com/angular-ui/bootstrap/commit/332eefb)), closes [#4954](https://github.com/angular-ui/bootstrap/issues/4954) [#5281](https://github.com/angular-ui/bootstrap/issues/5281)\n* **timepicker:** add placeholder for seconds input ([717ea69](https://github.com/angular-ui/bootstrap/commit/717ea69)), closes [#5257](https://github.com/angular-ui/bootstrap/issues/5257)\n* **timepicker:** move inline styles to stylesheet ([b658b03](https://github.com/angular-ui/bootstrap/commit/b658b03)), closes [#5218](https://github.com/angular-ui/bootstrap/issues/5218)\n* **typeahead:** add title to matches ([f523361](https://github.com/angular-ui/bootstrap/commit/f523361)), closes [#5252](https://github.com/angular-ui/bootstrap/issues/5252)\n* **typeahead:** move inline style to stylesheet ([e8201d1](https://github.com/angular-ui/bootstrap/commit/e8201d1)), closes [#5219](https://github.com/angular-ui/bootstrap/issues/5219)\n\n\n## BREAKING CHANGES\n\n* tab: This undoes the prior change to the template using div\nelements. If one wishes to use div elements, one must override the\ntemplate in one's app and provide the necessary CSS\n\n\n\n<a name=\"1.0.3\"></a>\n# [1.0.3](https://github.com/angular-ui/bootstrap/compare/1.0.2...1.0.3) (2016-01-12)\n\n\n## Bug Fixes\n\n* **timepicker:** fix injection ([627a451](https://github.com/angular-ui/bootstrap/commit/627a451))\n\n\n\n<a name=\"1.0.2\"></a>\n# [1.0.2](https://github.com/angular-ui/bootstrap/compare/1.0.1...1.0.2) (2016-01-12)\n\n\n## Bug Fixes\n\n* **tabs:** fix csp check ([74be568](https://github.com/angular-ui/bootstrap/commit/74be568)), closes [#5214](https://github.com/angular-ui/bootstrap/issues/5214)\n\n\n\n<a name=\"1.0.1\"></a>\n# [1.0.1](https://github.com/angular-ui/bootstrap/compare/1.0.0...1.0.1) (2016-01-12)\n\n\n## Bug Fixes\n\n* **build:** add ; after script ([9d0269e](https://github.com/angular-ui/bootstrap/commit/9d0269e)), closes [#5197](https://github.com/angular-ui/bootstrap/issues/5197) [#5196](https://github.com/angular-ui/bootstrap/issues/5196)\n* **carousel:** reset active for buffered transitions ([36fca74](https://github.com/angular-ui/bootstrap/commit/36fca74)), closes [#5195](https://github.com/angular-ui/bootstrap/issues/5195) [#5178](https://github.com/angular-ui/bootstrap/issues/5178)\n* **datepicker:** fix error message ([aa010b0](https://github.com/angular-ui/bootstrap/commit/aa010b0)), closes [#5198](https://github.com/angular-ui/bootstrap/issues/5198) [#5191](https://github.com/angular-ui/bootstrap/issues/5191)\n* **datepicker:** fix usage of non-standard format ([428beaf](https://github.com/angular-ui/bootstrap/commit/428beaf)), closes [#5188](https://github.com/angular-ui/bootstrap/issues/5188) [#5187](https://github.com/angular-ui/bootstrap/issues/5187)\n* **timepicker:** prevent mixture of numbers and letters ([9e9c6cf](https://github.com/angular-ui/bootstrap/commit/9e9c6cf)), closes [#5201](https://github.com/angular-ui/bootstrap/issues/5201) [#5085](https://github.com/angular-ui/bootstrap/issues/5085)\n* **timepicker:** use correct disabled expression ([71afeeb](https://github.com/angular-ui/bootstrap/commit/71afeeb)), closes [#5185](https://github.com/angular-ui/bootstrap/issues/5185) [#5182](https://github.com/angular-ui/bootstrap/issues/5182)\n* **typeahead:** scroll only parent element ([3dac5e3](https://github.com/angular-ui/bootstrap/commit/3dac5e3)), closes [#5212](https://github.com/angular-ui/bootstrap/issues/5212) [#5180](https://github.com/angular-ui/bootstrap/issues/5180)\n\n## Features\n\n* **tabs:** add CSS to css file ([db7adf7](https://github.com/angular-ui/bootstrap/commit/db7adf7)), closes [#5211](https://github.com/angular-ui/bootstrap/issues/5211)\n* **timepicker:** add templateUrl in timepickerConfig ([4b0e381](https://github.com/angular-ui/bootstrap/commit/4b0e381)), closes [#5087](https://github.com/angular-ui/bootstrap/issues/5087) [#5200](https://github.com/angular-ui/bootstrap/issues/5200)\n\n\n\n<a name=\"1.0.0\"></a>\n# [1.0.0](https://github.com/angular-ui/bootstrap/compare/0.14.3...1.0.0) (2016-01-08)\n\n\n## Bug Fixes\n\n* **accordion:** ensure panelOpen class is present ([0917623](https://github.com/angular-ui/bootstrap/commit/0917623)), closes [#4849](https://github.com/angular-ui/bootstrap/issues/4849) [#4870](https://github.com/angular-ui/bootstrap/issues/4870)\n* **accordion:** fix unexpected routing ([df211bd](https://github.com/angular-ui/bootstrap/commit/df211bd)), closes [#4792](https://github.com/angular-ui/bootstrap/issues/4792)\n* **carousel:** decouple animation information from DOM ([38c1b14](https://github.com/angular-ui/bootstrap/commit/38c1b14)), closes [#4737](https://github.com/angular-ui/bootstrap/issues/4737) [#4516](https://github.com/angular-ui/bootstrap/issues/4516)\n* **carousel:** fix conditions for animation ([12a37e0](https://github.com/angular-ui/bootstrap/commit/12a37e0)), closes [#4974](https://github.com/angular-ui/bootstrap/issues/4974) [#4972](https://github.com/angular-ui/bootstrap/issues/4972)\n* **carousel:** remove version checks ([9d93af1](https://github.com/angular-ui/bootstrap/commit/9d93af1)), closes [#4122](https://github.com/angular-ui/bootstrap/issues/4122) [#4774](https://github.com/angular-ui/bootstrap/issues/4774)\n* **carousel:** resolve rendering issues ([763cfd9](https://github.com/angular-ui/bootstrap/commit/763cfd9)), closes [#4984](https://github.com/angular-ui/bootstrap/issues/4984) [#4361](https://github.com/angular-ui/bootstrap/issues/4361) [#4823](https://github.com/angular-ui/bootstrap/issues/4823) [#4969](https://github.com/angular-ui/bootstrap/issues/4969)\n* **collapse:** set initial state to avoid animation ([5ad08ff](https://github.com/angular-ui/bootstrap/commit/5ad08ff)), closes [#5075](https://github.com/angular-ui/bootstrap/issues/5075) [#4848](https://github.com/angular-ui/bootstrap/issues/4848) [#4885](https://github.com/angular-ui/bootstrap/issues/4885)\n* **dateparser:** baseDate only care about dates ([21b2297](https://github.com/angular-ui/bootstrap/commit/21b2297)), closes [#4767](https://github.com/angular-ui/bootstrap/issues/4767)\n* **dateparser:** enforce order of regex construction ([83d1435](https://github.com/angular-ui/bootstrap/commit/83d1435)), closes [#4810](https://github.com/angular-ui/bootstrap/issues/4810) [#4808](https://github.com/angular-ui/bootstrap/issues/4808)\n* **datepicker:** active date should be model if present ([9019298](https://github.com/angular-ui/bootstrap/commit/9019298)), closes [#5082](https://github.com/angular-ui/bootstrap/issues/5082) [#5081](https://github.com/angular-ui/bootstrap/issues/5081)\n* **datepicker:** correctly display pre-100 years ([beabb4a](https://github.com/angular-ui/bootstrap/commit/beabb4a)), closes [#4812](https://github.com/angular-ui/bootstrap/issues/4812) [#4032](https://github.com/angular-ui/bootstrap/issues/4032)\n* **datepicker:** correctly set minMode/maxMode ([1524080](https://github.com/angular-ui/bootstrap/commit/1524080)), closes [#5093](https://github.com/angular-ui/bootstrap/issues/5093) [#5090](https://github.com/angular-ui/bootstrap/issues/5090)\n* **datepicker:** fix minDate/maxDate with literals ([e7f709f](https://github.com/angular-ui/bootstrap/commit/e7f709f)), closes [#4841](https://github.com/angular-ui/bootstrap/issues/4841) [#3437](https://github.com/angular-ui/bootstrap/issues/3437)\n* **datepicker:** stop propagation of esc in popup ([000d6c3](https://github.com/angular-ui/bootstrap/commit/000d6c3)), closes [#5074](https://github.com/angular-ui/bootstrap/issues/5074) [#5013](https://github.com/angular-ui/bootstrap/issues/5013)\n* **datepicker:** update with alternative format ([fd88dcb](https://github.com/angular-ui/bootstrap/commit/fd88dcb)), closes [#5014](https://github.com/angular-ui/bootstrap/issues/5014)\n* **debounce:** fix argument slicing ([e196be8](https://github.com/angular-ui/bootstrap/commit/e196be8)), closes [#4859](https://github.com/angular-ui/bootstrap/issues/4859) [#4860](https://github.com/angular-ui/bootstrap/issues/4860)\n* **dropdown:** do not close on right click ([bf1768e](https://github.com/angular-ui/bootstrap/commit/bf1768e)), closes [#5052](https://github.com/angular-ui/bootstrap/issues/5052) [#5051](https://github.com/angular-ui/bootstrap/issues/5051)\n* **dropdown:** remove class support for uib-dropdown-menu directive ([43535cf](https://github.com/angular-ui/bootstrap/commit/43535cf)), closes [#4753](https://github.com/angular-ui/bootstrap/issues/4753)\n* **dropdown:** remove extra uib-keyboard-nav ([57f72b2](https://github.com/angular-ui/bootstrap/commit/57f72b2)), closes [#4891](https://github.com/angular-ui/bootstrap/issues/4891)\n* **modal:** add focus check for IE ([a5c2a5b](https://github.com/angular-ui/bootstrap/commit/a5c2a5b)), closes [#5097](https://github.com/angular-ui/bootstrap/issues/5097) [#5096](https://github.com/angular-ui/bootstrap/issues/5096)\n* **modal:** clean up animation when disabled ([972dee6](https://github.com/angular-ui/bootstrap/commit/972dee6)), closes [#4740](https://github.com/angular-ui/bootstrap/issues/4740) [#4672](https://github.com/angular-ui/bootstrap/issues/4672)\n* **modal:** fix bindToController ([b8969d1](https://github.com/angular-ui/bootstrap/commit/b8969d1)), closes [#5048](https://github.com/angular-ui/bootstrap/issues/5048) [#5039](https://github.com/angular-ui/bootstrap/issues/5039)\n* **modal:** retain focus if child has focus ([726ccc3](https://github.com/angular-ui/bootstrap/commit/726ccc3)), closes [#4904](https://github.com/angular-ui/bootstrap/issues/4904) [#4903](https://github.com/angular-ui/bootstrap/issues/4903)\n* **modal:** trap tabbing regardless of config ([c0f1027](https://github.com/angular-ui/bootstrap/commit/c0f1027)), closes [#5010](https://github.com/angular-ui/bootstrap/issues/5010) [#4990](https://github.com/angular-ui/bootstrap/issues/4990)\n* **pagination:** retain model on initialization ([30099a0](https://github.com/angular-ui/bootstrap/commit/30099a0)), closes [#3786](https://github.com/angular-ui/bootstrap/issues/3786) [#4783](https://github.com/angular-ui/bootstrap/issues/4783) [#2956](https://github.com/angular-ui/bootstrap/issues/2956)\n* **tab:** fix unexpected routing ([d59083b](https://github.com/angular-ui/bootstrap/commit/d59083b)), closes [#4793](https://github.com/angular-ui/bootstrap/issues/4793) [#3266](https://github.com/angular-ui/bootstrap/issues/3266)\n* **timepicker:** correct meridian toggle condition ([09ad740](https://github.com/angular-ui/bootstrap/commit/09ad740)), closes [#4435](https://github.com/angular-ui/bootstrap/issues/4435)\n* **tooltip:** race condition when setting position ([429ddc1](https://github.com/angular-ui/bootstrap/commit/429ddc1)), closes [#4765](https://github.com/angular-ui/bootstrap/issues/4765) [#4757](https://github.com/angular-ui/bootstrap/issues/4757)\n* **typeahead:** allow parent to be required ([3aa41f0](https://github.com/angular-ui/bootstrap/commit/3aa41f0)), closes [#4800](https://github.com/angular-ui/bootstrap/issues/4800) [#2679](https://github.com/angular-ui/bootstrap/issues/2679)\n* **typeahead:** clear typeahead input when editable is false ([1d9294c](https://github.com/angular-ui/bootstrap/commit/1d9294c)), closes [#1620](https://github.com/angular-ui/bootstrap/issues/1620) [#4265](https://github.com/angular-ui/bootstrap/issues/4265) [#4752](https://github.com/angular-ui/bootstrap/issues/4752)\n* **typeahead:** fix programmatic focus issue ([cab0945](https://github.com/angular-ui/bootstrap/commit/cab0945)), closes [#5150](https://github.com/angular-ui/bootstrap/issues/5150) [#764](https://github.com/angular-ui/bootstrap/issues/764)\n* **typeahead:** use correct selector ([e1e6e1b](https://github.com/angular-ui/bootstrap/commit/e1e6e1b)), closes [#5168](https://github.com/angular-ui/bootstrap/issues/5168) [#5167](https://github.com/angular-ui/bootstrap/issues/5167)\n* allow library to be loaded async ([a851636](https://github.com/angular-ui/bootstrap/commit/a851636)), closes [#4775](https://github.com/angular-ui/bootstrap/issues/4775) [#3665](https://github.com/angular-ui/bootstrap/issues/3665)\n\n## Features\n\n* **accordion:** remove deprecated code ([0010aff](https://github.com/angular-ui/bootstrap/commit/0010aff)), closes [#4706](https://github.com/angular-ui/bootstrap/issues/4706)\n* **alert:** remove deprecated code ([21e852b](https://github.com/angular-ui/bootstrap/commit/21e852b)), closes [#4714](https://github.com/angular-ui/bootstrap/issues/4714)\n* **buttons:** add uib-uncheckable support ([b77618e](https://github.com/angular-ui/bootstrap/commit/b77618e)), closes [#3604](https://github.com/angular-ui/bootstrap/issues/3604) [#4791](https://github.com/angular-ui/bootstrap/issues/4791)\n* **buttons:** remove deprecated code ([b549263](https://github.com/angular-ui/bootstrap/commit/b549263)), closes [#4716](https://github.com/angular-ui/bootstrap/issues/4716)\n* **carousel:** expose next and prev on controller ([9b80ee1](https://github.com/angular-ui/bootstrap/commit/9b80ee1)), closes [#4851](https://github.com/angular-ui/bootstrap/issues/4851) [#4853](https://github.com/angular-ui/bootstrap/issues/4853)\n* **carousel:** remove deprecated code ([b159b21](https://github.com/angular-ui/bootstrap/commit/b159b21)), closes [#4717](https://github.com/angular-ui/bootstrap/issues/4717)\n* **collapse:** remove deprecated code ([bc004df](https://github.com/angular-ui/bootstrap/commit/bc004df)), closes [#4715](https://github.com/angular-ui/bootstrap/issues/4715)\n* **dateparser:** add M! and d! support ([b1cfc57](https://github.com/angular-ui/bootstrap/commit/b1cfc57)), closes [#4805](https://github.com/angular-ui/bootstrap/issues/4805) [#4809](https://github.com/angular-ui/bootstrap/issues/4809)\n* **dateparser:** add new format support ([ea388b3](https://github.com/angular-ui/bootstrap/commit/ea388b3)), closes [#3418](https://github.com/angular-ui/bootstrap/issues/3418) [#4833](https://github.com/angular-ui/bootstrap/issues/4833)\n* **dateparser:** add support for literals ([1c79888](https://github.com/angular-ui/bootstrap/commit/1c79888)), closes [#3914](https://github.com/angular-ui/bootstrap/issues/3914) [#4426](https://github.com/angular-ui/bootstrap/issues/4426)\n* **dateparser:** add Z support ([2fb812b](https://github.com/angular-ui/bootstrap/commit/2fb812b)), closes [#4831](https://github.com/angular-ui/bootstrap/issues/4831)\n* **dateparser:** change template literal from ' to ` ([9513f10](https://github.com/angular-ui/bootstrap/commit/9513f10)), closes [#4880](https://github.com/angular-ui/bootstrap/issues/4880) [#4936](https://github.com/angular-ui/bootstrap/issues/4936) [#4938](https://github.com/angular-ui/bootstrap/issues/4938)\n* **dateparser:** remove deprecated code ([2d68f41](https://github.com/angular-ui/bootstrap/commit/2d68f41)), closes [#4718](https://github.com/angular-ui/bootstrap/issues/4718)\n* **datepicker:** add allowInvalid support ([2460e42](https://github.com/angular-ui/bootstrap/commit/2460e42)), closes [#4694](https://github.com/angular-ui/bootstrap/issues/4694) [#4837](https://github.com/angular-ui/bootstrap/issues/4837)\n* **datepicker:** add disabled and ngDisabled support ([434c602](https://github.com/angular-ui/bootstrap/commit/434c602)), closes [#4059](https://github.com/angular-ui/bootstrap/issues/4059) [#4814](https://github.com/angular-ui/bootstrap/issues/4814)\n* **datepicker:** add semantic classes ([97c4333](https://github.com/angular-ui/bootstrap/commit/97c4333)), closes [#4761](https://github.com/angular-ui/bootstrap/issues/4761)\n* **datepicker:** add timezone support ([09098f8](https://github.com/angular-ui/bootstrap/commit/09098f8)), closes [#5062](https://github.com/angular-ui/bootstrap/issues/5062)\n* **datepicker:** implements alternative format support ([8bfeda0](https://github.com/angular-ui/bootstrap/commit/8bfeda0)), closes [#4951](https://github.com/angular-ui/bootstrap/issues/4951) [#4952](https://github.com/angular-ui/bootstrap/issues/4952)\n* **datepicker:** pass through attrs in popup ([26d3103](https://github.com/angular-ui/bootstrap/commit/26d3103)), closes [#4863](https://github.com/angular-ui/bootstrap/issues/4863) [#3338](https://github.com/angular-ui/bootstrap/issues/3338)\n* **datepicker:** remove deprecated code ([2fc3f21](https://github.com/angular-ui/bootstrap/commit/2fc3f21)), closes [#4708](https://github.com/angular-ui/bootstrap/issues/4708)\n* **datepicker:** yearRange -> yearRows and yearColumns ([b784422](https://github.com/angular-ui/bootstrap/commit/b784422)), closes [#3348](https://github.com/angular-ui/bootstrap/issues/3348) [#4970](https://github.com/angular-ui/bootstrap/issues/4970)\n* **dropdown:** add `append-to` support ([809ecdb](https://github.com/angular-ui/bootstrap/commit/809ecdb)), closes [#4467](https://github.com/angular-ui/bootstrap/issues/4467) [#4488](https://github.com/angular-ui/bootstrap/issues/4488)\n* **dropdown:** add open class support ([0495ff0](https://github.com/angular-ui/bootstrap/commit/0495ff0)), closes [#4466](https://github.com/angular-ui/bootstrap/issues/4466) [#4794](https://github.com/angular-ui/bootstrap/issues/4794)\n* **dropdown:** remove deprecated code ([ca3a343](https://github.com/angular-ui/bootstrap/commit/ca3a343)), closes [#4719](https://github.com/angular-ui/bootstrap/issues/4719)\n* **modal:** add appendTo support ([16d854c](https://github.com/angular-ui/bootstrap/commit/16d854c)), closes [#4599](https://github.com/angular-ui/bootstrap/issues/4599)\n* **modal:** add closed promise ([e9c4977](https://github.com/angular-ui/bootstrap/commit/e9c4977)), closes [#4979](https://github.com/angular-ui/bootstrap/issues/4979)\n* **modal:** add pluggable resolve support ([2635f0d](https://github.com/angular-ui/bootstrap/commit/2635f0d)), closes [#3405](https://github.com/angular-ui/bootstrap/issues/3405) [#5078](https://github.com/angular-ui/bootstrap/issues/5078)\n* **modal:** allow appending outside iframe ([80df015](https://github.com/angular-ui/bootstrap/commit/80df015)), closes [#4818](https://github.com/angular-ui/bootstrap/issues/4818)\n* **modal:** change to use $animate ([742132a](https://github.com/angular-ui/bootstrap/commit/742132a)), closes [#3418](https://github.com/angular-ui/bootstrap/issues/3418) [#4834](https://github.com/angular-ui/bootstrap/issues/4834)\n* **modal:** remove deprecated code ([a85d499](https://github.com/angular-ui/bootstrap/commit/a85d499)), closes [#4709](https://github.com/angular-ui/bootstrap/issues/4709)\n* **modal:** support requiring from parent directive ([e28cced](https://github.com/angular-ui/bootstrap/commit/e28cced)), closes [#3765](https://github.com/angular-ui/bootstrap/issues/3765) [#4844](https://github.com/angular-ui/bootstrap/issues/4844)\n* **pager:** change controllerAs to pager ([5890248](https://github.com/angular-ui/bootstrap/commit/5890248)), closes [#4961](https://github.com/angular-ui/bootstrap/issues/4961)\n* **pager:** move to separate component ([2a3314d](https://github.com/angular-ui/bootstrap/commit/2a3314d)), closes [#4935](https://github.com/angular-ui/bootstrap/issues/4935)\n* **pagination:** add force-ellipses option and boundaryLinkNumbers ([56642ea](https://github.com/angular-ui/bootstrap/commit/56642ea)), closes [#2924](https://github.com/angular-ui/bootstrap/issues/2924) [#3064](https://github.com/angular-ui/bootstrap/issues/3064) [#3565](https://github.com/angular-ui/bootstrap/issues/3565)\n* **pagination:** remove deprecated code ([75e493a](https://github.com/angular-ui/bootstrap/commit/75e493a)), closes [#4720](https://github.com/angular-ui/bootstrap/issues/4720)\n* **pagination:** Show ellipsis when rotating ([3f307e4](https://github.com/angular-ui/bootstrap/commit/3f307e4))\n* **paging:** factor out common controller code ([f2f8c4e](https://github.com/angular-ui/bootstrap/commit/f2f8c4e)), closes [#4803](https://github.com/angular-ui/bootstrap/issues/4803) [#4968](https://github.com/angular-ui/bootstrap/issues/4968)\n* **position:** implement auto positioning ([d265113](https://github.com/angular-ui/bootstrap/commit/d265113))\n* **position:** remove deprecated code ([42fa28f](https://github.com/angular-ui/bootstrap/commit/42fa28f)), closes [#4721](https://github.com/angular-ui/bootstrap/issues/4721)\n* **progressbar:** remove deprecated code ([0669b06](https://github.com/angular-ui/bootstrap/commit/0669b06)), closes [#4722](https://github.com/angular-ui/bootstrap/issues/4722)\n* **rating:** remove deprecated code ([d844623](https://github.com/angular-ui/bootstrap/commit/d844623)), closes [#4723](https://github.com/angular-ui/bootstrap/issues/4723)\n* **tabs:** add controllerAs support ([a5cac90](https://github.com/angular-ui/bootstrap/commit/a5cac90)), closes [#5019](https://github.com/angular-ui/bootstrap/issues/5019) [#5020](https://github.com/angular-ui/bootstrap/issues/5020)\n* **tabs:** remove deprecated code ([1b75164](https://github.com/angular-ui/bootstrap/commit/1b75164)), closes [#4710](https://github.com/angular-ui/bootstrap/issues/4710)\n* **timepicker:** add model state support ([fe69386](https://github.com/angular-ui/bootstrap/commit/fe69386)), closes [#3527](https://github.com/angular-ui/bootstrap/issues/3527) [#4835](https://github.com/angular-ui/bootstrap/issues/4835)\n* **timepicker:** add ngDisabled support ([4651191](https://github.com/angular-ui/bootstrap/commit/4651191)), closes [#2219](https://github.com/angular-ui/bootstrap/issues/2219) [#4811](https://github.com/angular-ui/bootstrap/issues/4811)\n* **timepicker:** add semantic classes ([1a822a1](https://github.com/angular-ui/bootstrap/commit/1a822a1)), closes [#4764](https://github.com/angular-ui/bootstrap/issues/4764) [#4971](https://github.com/angular-ui/bootstrap/issues/4971)\n* **timepicker:** add support for seconds ([c7fa845](https://github.com/angular-ui/bootstrap/commit/c7fa845)), closes [#4768](https://github.com/angular-ui/bootstrap/issues/4768)\n* **timepicker:** added ability to handle empty model ([8ffdaeb](https://github.com/angular-ui/bootstrap/commit/8ffdaeb)), closes [#1114](https://github.com/angular-ui/bootstrap/issues/1114) [#4203](https://github.com/angular-ui/bootstrap/issues/4203) [#4617](https://github.com/angular-ui/bootstrap/issues/4617)\n* **timepicker:** remove deprecated code ([feb2b73](https://github.com/angular-ui/bootstrap/commit/feb2b73)), closes [#4712](https://github.com/angular-ui/bootstrap/issues/4712)\n* **tooltip:** add appendToBody only attribute support ([2a1aaf2](https://github.com/angular-ui/bootstrap/commit/2a1aaf2)), closes [#4945](https://github.com/angular-ui/bootstrap/issues/4945) [#5071](https://github.com/angular-ui/bootstrap/issues/5071)\n* **tooltip:** add outsideClick trigger ([8737303](https://github.com/angular-ui/bootstrap/commit/8737303)), closes [#4419](https://github.com/angular-ui/bootstrap/issues/4419) [#4871](https://github.com/angular-ui/bootstrap/issues/4871)\n* **tooltip:** change back to jqLite listeners ([a5ca78a](https://github.com/angular-ui/bootstrap/commit/a5ca78a)), closes [#4886](https://github.com/angular-ui/bootstrap/issues/4886) [#5157](https://github.com/angular-ui/bootstrap/issues/5157)\n* **tooltip:** remove deprecated code ([187f64c](https://github.com/angular-ui/bootstrap/commit/187f64c)), closes [#4713](https://github.com/angular-ui/bootstrap/issues/4713)\n* **typeahead:** change to `appendTo` ([8637afc](https://github.com/angular-ui/bootstrap/commit/8637afc)), closes [#4797](https://github.com/angular-ui/bootstrap/issues/4797)\n* add npm support in main repository ([a9e476f](https://github.com/angular-ui/bootstrap/commit/a9e476f)), closes [#4739](https://github.com/angular-ui/bootstrap/issues/4739) [#5129](https://github.com/angular-ui/bootstrap/issues/5129)\n* prefix virtual templates with `uib/` ([342c576](https://github.com/angular-ui/bootstrap/commit/342c576)), closes [#1205](https://github.com/angular-ui/bootstrap/issues/1205) [#4839](https://github.com/angular-ui/bootstrap/issues/4839)\n* **typeahead:** add 'is-open' support ([167cfad](https://github.com/angular-ui/bootstrap/commit/167cfad)), closes [#4760](https://github.com/angular-ui/bootstrap/issues/4760) [#4779](https://github.com/angular-ui/bootstrap/issues/4779)\n* **typeahead:** add ability to scroll with matches ([a1355e7](https://github.com/angular-ui/bootstrap/commit/a1355e7)), closes [#4463](https://github.com/angular-ui/bootstrap/issues/4463)\n* **typeahead:** add dynamic toggling of Editable ([a5bafe6](https://github.com/angular-ui/bootstrap/commit/a5bafe6)), closes [#2638](https://github.com/angular-ui/bootstrap/issues/2638) [#4820](https://github.com/angular-ui/bootstrap/issues/4820)\n* **typeahead:** add event object to onSelect ([3e876b8](https://github.com/angular-ui/bootstrap/commit/3e876b8)), closes [#5165](https://github.com/angular-ui/bootstrap/issues/5165)\n* **typeahead:** add min-length === 0 support ([d859f42](https://github.com/angular-ui/bootstrap/commit/d859f42)), closes [#764](https://github.com/angular-ui/bootstrap/issues/764) [#2324](https://github.com/angular-ui/bootstrap/issues/2324) [#4789](https://github.com/angular-ui/bootstrap/issues/4789)\n* **typeahead:** add ng-model-options debounce support ([bd47f6c](https://github.com/angular-ui/bootstrap/commit/bd47f6c)), closes [#4982](https://github.com/angular-ui/bootstrap/issues/4982)\n* **typeahead:** add show-hint option ([ef82ad1](https://github.com/angular-ui/bootstrap/commit/ef82ad1)), closes [#2570](https://github.com/angular-ui/bootstrap/issues/2570) [#4784](https://github.com/angular-ui/bootstrap/issues/4784)\n* **typeahead:** remove deprecated code ([606d419](https://github.com/angular-ui/bootstrap/commit/606d419)), closes [#4711](https://github.com/angular-ui/bootstrap/issues/4711)\n\n## Reverts\n\n* **dateparser:** change template literal to ' ([f40066a](https://github.com/angular-ui/bootstrap/commit/f40066a)), closes [#5091](https://github.com/angular-ui/bootstrap/issues/5091)\n* **progressbar:** remove min-width ([ed7e460](https://github.com/angular-ui/bootstrap/commit/ed7e460)), closes [#5141](https://github.com/angular-ui/bootstrap/issues/5141)\n\n\n## BREAKING CHANGES\n\n* all: All of the deprecated services/directives/etc. are removed - one must use all prefixed versions instead\n* pager: As part of the split of the pager component from the\npagination component, this changes the controllerAs use to `pager` from\n`pagination`\n* dropdown: `keyboard-nav` for the dropdown is no longer a directive and to use it you have to use `keyboard-nav` instead of `uib-keyboard-nav`.\n* dropdown: remove class support for `uib-dropdown-menu` directive.\n* All virtual templates in UI Bootstrap now are prefixed\nwith `uib/` - if one is overriding the templates via `$templateCache` path\nor manually building the templates from the UI Bootstrap repository, one\nneeds to change the string used in the `$templateCache` representation\nto have the new prefix\n* typeahead: Usage before\n```html\n<div id=\"typeahead-container\"></div>\n<input typeahead=\"state for state in states | filter: $viewValue | limitTo: 8\" typeahead-append-to-element-id=\"typeahead-container\">\n```\nAfter\n```html\n<div id=\"typeahead-container\"></div>\n<input typeahead=\"state for state in states | filter: $viewValue | limitTo: 8\" typeahead-append-to=\"typeaheadContainer\">\n```\n```js\n$scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadContainer'));\n```\n* tab: This causes the cursor style to be removed from the tab - implement CSS on the `.uib-tab > div` selector, or similar, accordingly\n* accordion: This causes the cursor style to be removed from the heading - implement CSS on the `accordion-toggle` class accordingly\n* datepicker: yearRange is replaced by yearRows and yearColumns for manually specifying the dimensions of the yearpicker. If one wants the prior behavior with yearRange with a different number of rows, just set yearRows\n\n\n\n<a name=\"0.14.3\"></a>\n# [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...0.14.3) (2015-10-23)\n\n\n## Bug Fixes\n\n* **alert:** allow interpolations with dismiss-on-timeout ([de24f46](https://github.com/angular-ui/bootstrap/commit/de24f46)), closes [#4665](https://github.com/angular-ui/bootstrap/issues/4665) [#4666](https://github.com/angular-ui/bootstrap/issues/4666)\n* **buttons:** double toggle on spacebar ([e8808d3](https://github.com/angular-ui/bootstrap/commit/e8808d3)), closes [#4474](https://github.com/angular-ui/bootstrap/issues/4474) [#4630](https://github.com/angular-ui/bootstrap/issues/4630)\n* **collapse:** fix collapse animation timing ([6d1cd0f](https://github.com/angular-ui/bootstrap/commit/6d1cd0f)), closes [#4493](https://github.com/angular-ui/bootstrap/issues/4493)\n* **collapse:** trigger digest after ([3144633](https://github.com/angular-ui/bootstrap/commit/3144633)), closes [#4647](https://github.com/angular-ui/bootstrap/issues/4647) [#4628](https://github.com/angular-ui/bootstrap/issues/4628) [#4561](https://github.com/angular-ui/bootstrap/issues/4561) [#4651](https://github.com/angular-ui/bootstrap/issues/4651)\n* **datepicker:** datepicker-popup nest in dropdown ([134086a](https://github.com/angular-ui/bootstrap/commit/134086a)), closes [#4197](https://github.com/angular-ui/bootstrap/issues/4197) [#4693](https://github.com/angular-ui/bootstrap/issues/4693)\n* **datepicker:** fix support for literal format on popup ([7c3c631](https://github.com/angular-ui/bootstrap/commit/7c3c631)), closes [#4635](https://github.com/angular-ui/bootstrap/issues/4635) [#4616](https://github.com/angular-ui/bootstrap/issues/4616)\n* **tooltip:** delay timeouts ([02425b8](https://github.com/angular-ui/bootstrap/commit/02425b8)), closes [#4621](https://github.com/angular-ui/bootstrap/issues/4621) [#4618](https://github.com/angular-ui/bootstrap/issues/4618)\n* **tooltip:** null scope check in isOpen watch ([1f94104](https://github.com/angular-ui/bootstrap/commit/1f94104)), closes [#4697](https://github.com/angular-ui/bootstrap/issues/4697) [#4683](https://github.com/angular-ui/bootstrap/issues/4683)\n* **tooltip:** scrollbar flashing ([6c82b2b](https://github.com/angular-ui/bootstrap/commit/6c82b2b)), closes [#4550](https://github.com/angular-ui/bootstrap/issues/4550) [#4623](https://github.com/angular-ui/bootstrap/issues/4623) [#4458](https://github.com/angular-ui/bootstrap/issues/4458)\n* **typeahead:** dangling event listeners ([94fb282](https://github.com/angular-ui/bootstrap/commit/94fb282)), closes [#4632](https://github.com/angular-ui/bootstrap/issues/4632) [#4636](https://github.com/angular-ui/bootstrap/issues/4636)\n\n## Features\n\n* **datepicker:** add templateUrl support for pickers ([1f65d87](https://github.com/angular-ui/bootstrap/commit/1f65d87)), closes [#4432](https://github.com/angular-ui/bootstrap/issues/4432)\n* **datepicker:** preserve timezone with model ([0d64aad](https://github.com/angular-ui/bootstrap/commit/0d64aad)), closes [#4676](https://github.com/angular-ui/bootstrap/issues/4676)\n* **modal:** support $uibModalInstance ([97fd37e](https://github.com/angular-ui/bootstrap/commit/97fd37e)), closes [#4638](https://github.com/angular-ui/bootstrap/issues/4638) [#4661](https://github.com/angular-ui/bootstrap/issues/4661)\n\n\n\n<a name=\"0.14.2\"></a>\n# [0.14.2](https://github.com/angular-ui/bootstrap/compare/0.14.1...0.14.2) (2015-10-14)\n\n\n## Bug Fixes\n\n* **progressbar:** fix percentage calculation ([feb689c](https://github.com/angular-ui/bootstrap/commit/feb689c)), closes [#4471](https://github.com/angular-ui/bootstrap/issues/4471) [#4588](https://github.com/angular-ui/bootstrap/issues/4588) [#4452](https://github.com/angular-ui/bootstrap/issues/4452)\n* **tooltip:** clean up stackedMap on scope destroy ([ebb5e18](https://github.com/angular-ui/bootstrap/commit/ebb5e18)), closes [#4610](https://github.com/angular-ui/bootstrap/issues/4610) [#4604](https://github.com/angular-ui/bootstrap/issues/4604)\n* **tooltip:** popup close delay not respected ([6daf871](https://github.com/angular-ui/bootstrap/commit/6daf871)), closes [#4597](https://github.com/angular-ui/bootstrap/issues/4597) [#4567](https://github.com/angular-ui/bootstrap/issues/4567)\n\n\n\n<a name=\"0.14.1\"></a>\n# [0.14.1](https://github.com/angular-ui/bootstrap/compare/0.14.0...0.14.1) (2015-10-11)\n\n\n## Bug Fixes\n\n* **accordion:** make deprecated controller work with 1.3.x ([c5e6042](https://github.com/angular-ui/bootstrap/commit/c5e6042)), closes [#4574](https://github.com/angular-ui/bootstrap/issues/4574)\n* **alert:** make deprecated controller work with 1.3.x ([e8c8ee6](https://github.com/angular-ui/bootstrap/commit/e8c8ee6)), closes [#4576](https://github.com/angular-ui/bootstrap/issues/4576)\n* **buttons:** make deprecated controller work with 1.3.x ([1e3cbd8](https://github.com/angular-ui/bootstrap/commit/1e3cbd8)), closes [#4577](https://github.com/angular-ui/bootstrap/issues/4577)\n* **carousel:** make deprecated controller work with 1.3.x ([f6c7931](https://github.com/angular-ui/bootstrap/commit/f6c7931)), closes [#4578](https://github.com/angular-ui/bootstrap/issues/4578)\n* **datepicker:** make deprecated controller work with 1.3.x ([18371ab](https://github.com/angular-ui/bootstrap/commit/18371ab)), closes [#4586](https://github.com/angular-ui/bootstrap/issues/4586)\n* **dropdown:** make deprecated controller work with 1.3.x ([ae1a87c](https://github.com/angular-ui/bootstrap/commit/ae1a87c)), closes [#4585](https://github.com/angular-ui/bootstrap/issues/4585)\n* **pagination:** make deprecated controller work with 1.3.x ([d50e8d2](https://github.com/angular-ui/bootstrap/commit/d50e8d2)), closes [#4580](https://github.com/angular-ui/bootstrap/issues/4580)\n* **progressbar:** make deprecated controller work with 1.3.x ([1c5e479](https://github.com/angular-ui/bootstrap/commit/1c5e479)), closes [#4581](https://github.com/angular-ui/bootstrap/issues/4581)\n* **rating:** make deprecated controller work with 1.3.x ([ce1114a](https://github.com/angular-ui/bootstrap/commit/ce1114a)), closes [#4582](https://github.com/angular-ui/bootstrap/issues/4582)\n* **tabs:** make deprecated controller work with 1.3.x ([685bd6a](https://github.com/angular-ui/bootstrap/commit/685bd6a)), closes [#4583](https://github.com/angular-ui/bootstrap/issues/4583)\n* **timepicker:** make deprecated controller work with 1.3.x ([00f60ee](https://github.com/angular-ui/bootstrap/commit/00f60ee)), closes [#4584](https://github.com/angular-ui/bootstrap/issues/4584)\n\n## Features\n\n* **timepicker:** add accessibility improvements ([4ebecbc](https://github.com/angular-ui/bootstrap/commit/4ebecbc)), closes [#4569](https://github.com/angular-ui/bootstrap/issues/4569) [#4573](https://github.com/angular-ui/bootstrap/issues/4573)\n\n\n\n<a name=\"0.14.0\"></a>\n# [0.14.0](https://github.com/angular-ui/bootstrap/compare/0.13.4...0.14.0) (2015-10-09)\n\n\n## Bug Fixes\n\n* **accordion:** coerce to boolean ([b864aa9](https://github.com/angular-ui/bootstrap/commit/b864aa9)), closes [#4385](https://github.com/angular-ui/bootstrap/issues/4385)\n* **accordion:** re-expose AccordionController ([5382226](https://github.com/angular-ui/bootstrap/commit/5382226)), closes [#4524](https://github.com/angular-ui/bootstrap/issues/4524)\n* **alert:** properly pass $event as local ([eb2366f](https://github.com/angular-ui/bootstrap/commit/eb2366f)), closes [#4386](https://github.com/angular-ui/bootstrap/issues/4386) [#4387](https://github.com/angular-ui/bootstrap/issues/4387)\n* **alert:** re-expose AlertController ([f561aa9](https://github.com/angular-ui/bootstrap/commit/f561aa9)), closes [#4525](https://github.com/angular-ui/bootstrap/issues/4525)\n* **buttons:** re-expose ButtonsController ([c0dbf79](https://github.com/angular-ui/bootstrap/commit/c0dbf79)), closes [#4526](https://github.com/angular-ui/bootstrap/issues/4526)\n* **carousel:** fix reading of `noTransition` ([2e26815](https://github.com/angular-ui/bootstrap/commit/2e26815)), closes [#4325](https://github.com/angular-ui/bootstrap/issues/4325)\n* **carousel:** improve accessibility ([da71159](https://github.com/angular-ui/bootstrap/commit/da71159)), closes [#4478](https://github.com/angular-ui/bootstrap/issues/4478) [#4479](https://github.com/angular-ui/bootstrap/issues/4479)\n* **carousel:** re-enable deprecated directives ([30e8aa7](https://github.com/angular-ui/bootstrap/commit/30e8aa7)), closes [#4527](https://github.com/angular-ui/bootstrap/issues/4527)\n* **carousel:** reset $currentTransition when no slides ([0b3d5bd](https://github.com/angular-ui/bootstrap/commit/0b3d5bd)), closes [#4532](https://github.com/angular-ui/bootstrap/issues/4532) [#4390](https://github.com/angular-ui/bootstrap/issues/4390)\n* **datepicker:** add check for `contains` ([868c0e2](https://github.com/angular-ui/bootstrap/commit/868c0e2)), closes [#4423](https://github.com/angular-ui/bootstrap/issues/4423) [#4411](https://github.com/angular-ui/bootstrap/issues/4411)\n* **datepicker:** add custom class to year picker ([0ad7cb9](https://github.com/angular-ui/bootstrap/commit/0ad7cb9)), closes [#4558](https://github.com/angular-ui/bootstrap/issues/4558) [#4546](https://github.com/angular-ui/bootstrap/issues/4546)\n* **datepicker:** change to `$popup` ([65814f1](https://github.com/angular-ui/bootstrap/commit/65814f1))\n* **datepicker:** datepicker-popup nest in dropdown ([6b4267b](https://github.com/angular-ui/bootstrap/commit/6b4267b)), closes [#4489](https://github.com/angular-ui/bootstrap/issues/4489) [#4197](https://github.com/angular-ui/bootstrap/issues/4197)\n* **datepicker:** remove focus management on date selection by keyboard ([36ecf60](https://github.com/angular-ui/bootstrap/commit/36ecf60)), closes [#4409](https://github.com/angular-ui/bootstrap/issues/4409)\n* **dropdown:** ensure class is present in dropdown-menu ([92ab48e](https://github.com/angular-ui/bootstrap/commit/92ab48e)), closes [#4523](https://github.com/angular-ui/bootstrap/issues/4523) [#4442](https://github.com/angular-ui/bootstrap/issues/4442)\n* **dropdown:** restore deprecated directives ([e7c5879](https://github.com/angular-ui/bootstrap/commit/e7c5879)), closes [#4514](https://github.com/angular-ui/bootstrap/issues/4514)\n* **modal:** fix for conflicts with ngTouch module on mobile devices ([508aceb](https://github.com/angular-ui/bootstrap/commit/508aceb)), closes [#2280](https://github.com/angular-ui/bootstrap/issues/2280) [#4357](https://github.com/angular-ui/bootstrap/issues/4357)\n* **progressbar:** re-expose ProgressController ([5604e59](https://github.com/angular-ui/bootstrap/commit/5604e59)), closes [#4528](https://github.com/angular-ui/bootstrap/issues/4528)\n* **rating:** re-expose RatingController ([aede646](https://github.com/angular-ui/bootstrap/commit/aede646)), closes [#4529](https://github.com/angular-ui/bootstrap/issues/4529)\n* **tabs:** re-expose TabsetController ([435924f](https://github.com/angular-ui/bootstrap/commit/435924f)), closes [#4530](https://github.com/angular-ui/bootstrap/issues/4530)\n* **timepicker:** re-expose TimepickerController ([3aa9841](https://github.com/angular-ui/bootstrap/commit/3aa9841)), closes [#4531](https://github.com/angular-ui/bootstrap/issues/4531)\n* **tooltip:** add display block to style ([b413a22](https://github.com/angular-ui/bootstrap/commit/b413a22)), closes [#4363](https://github.com/angular-ui/bootstrap/issues/4363) [#4379](https://github.com/angular-ui/bootstrap/issues/4379)\n* **tooltip:** check for ttScope in $$postDigest ([01b9624](https://github.com/angular-ui/bootstrap/commit/01b9624)), closes [#4555](https://github.com/angular-ui/bootstrap/issues/4555) [#4552](https://github.com/angular-ui/bootstrap/issues/4552)\n* **tooltip:** correct flash of reposition ([8fee75d](https://github.com/angular-ui/bootstrap/commit/8fee75d)), closes [#4363](https://github.com/angular-ui/bootstrap/issues/4363) [#4195](https://github.com/angular-ui/bootstrap/issues/4195)\n* **tooltip:** do nothing if `$scope` doesn't exist ([1e039e8](https://github.com/angular-ui/bootstrap/commit/1e039e8)), closes [#4346](https://github.com/angular-ui/bootstrap/issues/4346) [#3347](https://github.com/angular-ui/bootstrap/issues/3347)\n* **tooltip:** fix binding to multiple triggers ([d6cda93](https://github.com/angular-ui/bootstrap/commit/d6cda93)), closes [#4371](https://github.com/angular-ui/bootstrap/issues/4371) [#4384](https://github.com/angular-ui/bootstrap/issues/4384)\n* **tooltip:** isOpen to work with expressions ([5f68280](https://github.com/angular-ui/bootstrap/commit/5f68280)), closes [#4380](https://github.com/angular-ui/bootstrap/issues/4380) [#4362](https://github.com/angular-ui/bootstrap/issues/4362)\n* **tooltip:** properly gc popupTimeout ([ff52f52](https://github.com/angular-ui/bootstrap/commit/ff52f52)), closes [#2786](https://github.com/angular-ui/bootstrap/issues/2786)\n* **tooltip:** set `visibility: hidden` to avoid flicker ([f7cb8bc](https://github.com/angular-ui/bootstrap/commit/f7cb8bc)), closes [#4342](https://github.com/angular-ui/bootstrap/issues/4342)\n\n## Features\n\n* **accordion:** use uib- prefix ([0328a76](https://github.com/angular-ui/bootstrap/commit/0328a76)), closes [#4389](https://github.com/angular-ui/bootstrap/issues/4389)\n* **accordion:** use uib- prefix ([298ec8c](https://github.com/angular-ui/bootstrap/commit/298ec8c)), closes [#4503](https://github.com/angular-ui/bootstrap/issues/4503)\n* **alert:** use uib- prefix ([5e3a87a](https://github.com/angular-ui/bootstrap/commit/5e3a87a)), closes [#4406](https://github.com/angular-ui/bootstrap/issues/4406)\n* **buttons:** use uib- prefix ([5a1c2c9](https://github.com/angular-ui/bootstrap/commit/5a1c2c9)), closes [#4445](https://github.com/angular-ui/bootstrap/issues/4445)\n* **carousel:** use uib- prefix ([2e5bfac](https://github.com/angular-ui/bootstrap/commit/2e5bfac)), closes [#4501](https://github.com/angular-ui/bootstrap/issues/4501)\n* **collapse:** convert to use `$animateCss` ([533a9f0](https://github.com/angular-ui/bootstrap/commit/533a9f0)), closes [#4257](https://github.com/angular-ui/bootstrap/issues/4257)\n* **collapse:** use uib- prefix ([9bdb32e](https://github.com/angular-ui/bootstrap/commit/9bdb32e)), closes [#4370](https://github.com/angular-ui/bootstrap/issues/4370)\n* **dateparser:** reset parsers when $locale.id changes ([d9a521a](https://github.com/angular-ui/bootstrap/commit/d9a521a)), closes [#4286](https://github.com/angular-ui/bootstrap/issues/4286) [#4425](https://github.com/angular-ui/bootstrap/issues/4425)\n* **dateparser:** use uib- prefix ([0fa851f](https://github.com/angular-ui/bootstrap/commit/0fa851f)), closes [#4504](https://github.com/angular-ui/bootstrap/issues/4504)\n* **datepicker:** add uib- prefix ([44354f6](https://github.com/angular-ui/bootstrap/commit/44354f6)), closes [#4509](https://github.com/angular-ui/bootstrap/issues/4509)\n* **dropdown:** uib- prefix ([5bc0851](https://github.com/angular-ui/bootstrap/commit/5bc0851)), closes [#4510](https://github.com/angular-ui/bootstrap/issues/4510)\n* **modal:** Added ability to add CSS class to top window ([bd38e8f](https://github.com/angular-ui/bootstrap/commit/bd38e8f)), closes [#2524](https://github.com/angular-ui/bootstrap/issues/2524)\n* **modal:** add uib- prefix ([8c7b9e4](https://github.com/angular-ui/bootstrap/commit/8c7b9e4)), closes [#4511](https://github.com/angular-ui/bootstrap/issues/4511)\n* **pagination:** add uib- prefix ([9aea856](https://github.com/angular-ui/bootstrap/commit/9aea856)), closes [#4536](https://github.com/angular-ui/bootstrap/issues/4536)\n* **position:** add uib- prefix ([6158091](https://github.com/angular-ui/bootstrap/commit/6158091)), closes [#4507](https://github.com/angular-ui/bootstrap/issues/4507)\n* **progressbar:** add `aria-labelledby` support ([e6f3b87](https://github.com/angular-ui/bootstrap/commit/e6f3b87)), closes [#4350](https://github.com/angular-ui/bootstrap/issues/4350) [#4347](https://github.com/angular-ui/bootstrap/issues/4347)\n* **rating:** add `aria-valuetext` attribute ([72de2d8](https://github.com/angular-ui/bootstrap/commit/72de2d8)), closes [#4349](https://github.com/angular-ui/bootstrap/issues/4349) [#4347](https://github.com/angular-ui/bootstrap/issues/4347)\n* **rating:** user uib- prefix ([377b4b7](https://github.com/angular-ui/bootstrap/commit/377b4b7)), closes [#4502](https://github.com/angular-ui/bootstrap/issues/4502)\n* **tabs:** use uib- prefix ([d25a8c2](https://github.com/angular-ui/bootstrap/commit/d25a8c2)), closes [#4449](https://github.com/angular-ui/bootstrap/issues/4449)\n* **timepicker:** use uib- prefix ([504e653](https://github.com/angular-ui/bootstrap/commit/504e653)), closes [#4505](https://github.com/angular-ui/bootstrap/issues/4505)\n* **tooltip:** add uib- prefix ([f8bc038](https://github.com/angular-ui/bootstrap/commit/f8bc038)), closes [#4515](https://github.com/angular-ui/bootstrap/issues/4515)\n* **tooltip:** allow custom closing delay ([5f7051b](https://github.com/angular-ui/bootstrap/commit/5f7051b)), closes [#3576](https://github.com/angular-ui/bootstrap/issues/3576)\n* **tooltip:** hide tooltip when `esc` is hit ([c08509a](https://github.com/angular-ui/bootstrap/commit/c08509a)), closes [#4367](https://github.com/angular-ui/bootstrap/issues/4367) [#4248](https://github.com/angular-ui/bootstrap/issues/4248)\n* **typeahead:** add `appendElementToId` ([fdf53e6](https://github.com/angular-ui/bootstrap/commit/fdf53e6)), closes [#4231](https://github.com/angular-ui/bootstrap/issues/4231) [#4497](https://github.com/angular-ui/bootstrap/issues/4497)\n* **typeahead:** add customClass support for dropdown ([fa1cdfc](https://github.com/angular-ui/bootstrap/commit/fa1cdfc)), closes [#4332](https://github.com/angular-ui/bootstrap/issues/4332) [#4410](https://github.com/angular-ui/bootstrap/issues/4410)\n* **typeahead:** add uib- prefix ([9e5e1a2](https://github.com/angular-ui/bootstrap/commit/9e5e1a2)), closes [#4542](https://github.com/angular-ui/bootstrap/issues/4542)\n\n## Reverts\n\n* **dropdown:** undo adding of `open` class on body ([6f9f1fc](https://github.com/angular-ui/bootstrap/commit/6f9f1fc))\n\n\n## BREAKING CHANGES\n\n* Removes focus on datepicker on selection of a date via keyboard for accessibility reasons\n\n\n<a name=\"0.13.4\"></a>\n# [0.13.4](https://github.com/angular-ui/bootstrap/compare/0.13.3...0.13.4) (2015-09-03)\n\n\n## Bug Fixes\n\n* **accordion:** add custom open class support ([575eb85](https://github.com/angular-ui/bootstrap/commit/575eb85)), closes [#4198](https://github.com/angular-ui/bootstrap/issues/4198)\n* **datepicker:** ensure the original target is not in popup ([9b2f7ac](https://github.com/angular-ui/bootstrap/commit/9b2f7ac)), closes [#4316](https://github.com/angular-ui/bootstrap/issues/4316) [#4314](https://github.com/angular-ui/bootstrap/issues/4314)\n* **dropdown:** fix display when using with append-to-body ([bf63cef](https://github.com/angular-ui/bootstrap/commit/bf63cef)), closes [#4305](https://github.com/angular-ui/bootstrap/issues/4305) [#4240](https://github.com/angular-ui/bootstrap/issues/4240)\n* **dropdown:** fix up arrow nav support ([defcbbb](https://github.com/angular-ui/bootstrap/commit/defcbbb)), closes [#4330](https://github.com/angular-ui/bootstrap/issues/4330) [#4327](https://github.com/angular-ui/bootstrap/issues/4327)\n* **modal:** Wait for animation before focus. ([937a1f3](https://github.com/angular-ui/bootstrap/commit/937a1f3)), closes [#4300](https://github.com/angular-ui/bootstrap/issues/4300) [#4274](https://github.com/angular-ui/bootstrap/issues/4274)\n* **modal:** correctly remove custom class ([ba2ce24](https://github.com/angular-ui/bootstrap/commit/ba2ce24)), closes [#4175](https://github.com/angular-ui/bootstrap/issues/4175) [#4171](https://github.com/angular-ui/bootstrap/issues/4171)\n* **modal:** fix allowing promises to be resolved ([b1e98b1](https://github.com/angular-ui/bootstrap/commit/b1e98b1)), closes [#4310](https://github.com/angular-ui/bootstrap/issues/4310) [#4309](https://github.com/angular-ui/bootstrap/issues/4309)\n* **progress:** rename to avoid conflict ([07a938d](https://github.com/angular-ui/bootstrap/commit/07a938d)), closes [#4255](https://github.com/angular-ui/bootstrap/issues/4255)\n* **tabs:** ensure tab selection only occurs once ([7d3ba1e](https://github.com/angular-ui/bootstrap/commit/7d3ba1e)), closes [#3060](https://github.com/angular-ui/bootstrap/issues/3060) [#4230](https://github.com/angular-ui/bootstrap/issues/4230) [#2883](https://github.com/angular-ui/bootstrap/issues/2883)\n* **timepicker:** leave view alone if either input is invalid ([818f7e5](https://github.com/angular-ui/bootstrap/commit/818f7e5)), closes [#4160](https://github.com/angular-ui/bootstrap/issues/4160) [#3825](https://github.com/angular-ui/bootstrap/issues/3825)\n* **tooltip:** correctly position tooltip ([457f10c](https://github.com/angular-ui/bootstrap/commit/457f10c)), closes [#4311](https://github.com/angular-ui/bootstrap/issues/4311) [#4195](https://github.com/angular-ui/bootstrap/issues/4195)\n* **tooltip:** fix jshint error ([17cc39f](https://github.com/angular-ui/bootstrap/commit/17cc39f))\n* **tooltip:** properly gc timeout on toggle of disabled ([f8eab55](https://github.com/angular-ui/bootstrap/commit/f8eab55)), closes [#4210](https://github.com/angular-ui/bootstrap/issues/4210) [#4204](https://github.com/angular-ui/bootstrap/issues/4204)\n* **tooltip:** switch to use raw DOM event bindings ([7556bed](https://github.com/angular-ui/bootstrap/commit/7556bed)), closes [#4322](https://github.com/angular-ui/bootstrap/issues/4322) [#4060](https://github.com/angular-ui/bootstrap/issues/4060)\n* **typeahead:** add support for ngModelOptions getterSetter ([ccaa627](https://github.com/angular-ui/bootstrap/commit/ccaa627)), closes [#3865](https://github.com/angular-ui/bootstrap/issues/3865) [#3823](https://github.com/angular-ui/bootstrap/issues/3823)\n* **typeahead:** release references on destruction ([695db9d](https://github.com/angular-ui/bootstrap/commit/695db9d)), closes [#4299](https://github.com/angular-ui/bootstrap/issues/4299) [#4298](https://github.com/angular-ui/bootstrap/issues/4298)\n* **typeahead:** use ng-bind-html ([bb9fa1a](https://github.com/angular-ui/bootstrap/commit/bb9fa1a)), closes [#3463](https://github.com/angular-ui/bootstrap/issues/3463) [#4073](https://github.com/angular-ui/bootstrap/issues/4073)\n\n## Features\n\n* **accordion:** allow custom panel class ([5ee23a4](https://github.com/angular-ui/bootstrap/commit/5ee23a4)), closes [#4242](https://github.com/angular-ui/bootstrap/issues/4242) [#3968](https://github.com/angular-ui/bootstrap/issues/3968)\n* **accordion:** support spacebar to toggle group ([aa5a991](https://github.com/angular-ui/bootstrap/commit/aa5a991)), closes [#4319](https://github.com/angular-ui/bootstrap/issues/4319) [#4249](https://github.com/angular-ui/bootstrap/issues/4249)\n* **buttons:** allow toggling via spacebar when focused ([bdfb289](https://github.com/angular-ui/bootstrap/commit/bdfb289)), closes [#4252](https://github.com/angular-ui/bootstrap/issues/4252) [#4259](https://github.com/angular-ui/bootstrap/issues/4259)\n* **buttons:** hide nested inputs ([a06afe6](https://github.com/angular-ui/bootstrap/commit/a06afe6)), closes [#4282](https://github.com/angular-ui/bootstrap/issues/4282)\n* **carousel:** add model binding support to slide ([dac087e](https://github.com/angular-ui/bootstrap/commit/dac087e)), closes [#4202](https://github.com/angular-ui/bootstrap/issues/4202) [#4201](https://github.com/angular-ui/bootstrap/issues/4201)\n* **dateparser:** add support for the `h` format ([550fe20](https://github.com/angular-ui/bootstrap/commit/550fe20)), closes [#4220](https://github.com/angular-ui/bootstrap/issues/4220)\n* **datepicker:** disable today button if invalid ([71e0b8a](https://github.com/angular-ui/bootstrap/commit/71e0b8a)), closes [#4199](https://github.com/angular-ui/bootstrap/issues/4199) [#3988](https://github.com/angular-ui/bootstrap/issues/3988)\n* **modal:** complete modal open resolution in order ([1bba8b4](https://github.com/angular-ui/bootstrap/commit/1bba8b4)), closes [#2443](https://github.com/angular-ui/bootstrap/issues/2443) [#4302](https://github.com/angular-ui/bootstrap/issues/4302) [#2404](https://github.com/angular-ui/bootstrap/issues/2404)\n* **modal:** support multiple open classes ([3d01c59](https://github.com/angular-ui/bootstrap/commit/3d01c59)), closes [#4226](https://github.com/angular-ui/bootstrap/issues/4226) [#4184](https://github.com/angular-ui/bootstrap/issues/4184)\n* **pagination:** add `ngDisabled` support for the pager ([ba734b4](https://github.com/angular-ui/bootstrap/commit/ba734b4)), closes [#4217](https://github.com/angular-ui/bootstrap/issues/4217) [#2856](https://github.com/angular-ui/bootstrap/issues/2856)\n* **pagination:** add `templateUrl` support ([64b5289](https://github.com/angular-ui/bootstrap/commit/64b5289)), closes [#4162](https://github.com/angular-ui/bootstrap/issues/4162)\n* **tabs:** add support for `x-tab-heading` ([1abfd05](https://github.com/angular-ui/bootstrap/commit/1abfd05)), closes [#4166](https://github.com/angular-ui/bootstrap/issues/4166) [#1893](https://github.com/angular-ui/bootstrap/issues/1893)\n* **timepicker:** add `templateUrl` and `controllerAs` support ([639d511](https://github.com/angular-ui/bootstrap/commit/639d511)), closes [#4275](https://github.com/angular-ui/bootstrap/issues/4275) [#4284](https://github.com/angular-ui/bootstrap/issues/4284)\n* **tooltip:** expose isOpen property ([99b87cc](https://github.com/angular-ui/bootstrap/commit/99b87cc)), closes [#4179](https://github.com/angular-ui/bootstrap/issues/4179) [#2148](https://github.com/angular-ui/bootstrap/issues/2148) [#590](https://github.com/angular-ui/bootstrap/issues/590)\n* **typeahead:** add `typeaheadFocusOnSelect` ([b5ecda3](https://github.com/angular-ui/bootstrap/commit/b5ecda3)), closes [#4212](https://github.com/angular-ui/bootstrap/issues/4212) [#4211](https://github.com/angular-ui/bootstrap/issues/4211) [#4206](https://github.com/angular-ui/bootstrap/issues/4206)\n* **typeahead:** add custom popup template support ([4b02648](https://github.com/angular-ui/bootstrap/commit/4b02648)), closes [#4320](https://github.com/angular-ui/bootstrap/issues/4320) [#3774](https://github.com/angular-ui/bootstrap/issues/3774)\n\n\n## Breaking Changes\n\n* **buttons**\n  * hide nested `<input>` elements on `btn-radio` and `btn-checkbox` directives.\n\n  Fixes #3264\n  Closes #4282\n\n   ([a06afe6](https://github.com/angular-ui/bootstrap/commit/a06afe6))\n\n* **dropdown**\n  * when using `append-to-body`, both the `dropdown` and `open` classes are added to the `<body>` element.\n  * this differs from the existing behavior in that it will no longer toggle based on the existing `dropdown` directive element, but on the `body` element instead.\n\n  Fixes #4240\n  Closes #4305\n\n   ([bf63cef](https://github.com/angular-ui/bootstrap/commit/bf63cef))\n\n* **tooltip**\n  * Switch to use `addEventListener` and `removeEventListener` to prevent jqLite/jQuery bug where the events are swallowed on disabled elements\n  * this affects custom events, which must now be dispatched with `element[0].dispatchEvent(new Event('customEvent'))`, as opposed to `element.trigger('customEvent')`\n\n  Fixes #4060\n  Closes #4322\n\n   ([7556bed](https://github.com/angular-ui/bootstrap/commit/7556beda486f26b40fb860448316e8a32457e9e9))\n\n* **typeahead**\n  * for security reasons, only whitelisted HTML should be added.\n  * the typeahead match template now uses `ng-bind-html` instead of `bind-html-unsafe`.\n  * typeahead now uses the `$sce` service when `ngSanitize` is present and logs a warning if it is not.\n\n  Fixes #2884\n  Closes #3463\n  Closes #4073\n\n   ([bb9fa1a](https://github.com/angular-ui/bootstrap/commit/bb9fa1a))\n\n\n<a name\"0.13.3\"></a>\n# [0.13.3](https://github.com/angular-ui/bootstrap/compare/0.13.2...0.13.3) (2015-08-09)\n\n\n## Bug Fixes\n\n* **accordion:**\n  * add `open` class when expanded ([ead15e37](https://github.com/angular-ui/bootstrap/commit/ead15e37), closes [#4152](https://github.com/angular-ui/bootstrap/issues/4152), [#3419](https://github.com/angular-ui/bootstrap/issues/3419))\n  * revert to empty href ([b18dc8f9](https://github.com/angular-ui/bootstrap/commit/b18dc8f9), closes [#4104](https://github.com/angular-ui/bootstrap/issues/4104))\n* **buttons:**\n  * change to use `attrs.disabled` ([c9b0d0b0](https://github.com/angular-ui/bootstrap/commit/c9b0d0b0), closes [#4088](https://github.com/angular-ui/bootstrap/issues/4088))\n  * allow selection of undisabled button ([707fbf55](https://github.com/angular-ui/bootstrap/commit/707fbf55), closes [#4088](https://github.com/angular-ui/bootstrap/issues/4088))\n* **carousel:**\n  * fix animation direction ([8359d73f](https://github.com/angular-ui/bootstrap/commit/8359d73f), closes [#4092](https://github.com/angular-ui/bootstrap/issues/4092), [#4087](https://github.com/angular-ui/bootstrap/issues/4087))\n  * fix sorting of indicators ([8056368e](https://github.com/angular-ui/bootstrap/commit/8056368e), closes [#4071](https://github.com/angular-ui/bootstrap/issues/4071), [#3764](https://github.com/angular-ui/bootstrap/issues/3764))\n* **dateparser:** Support 12-hour format and AM/PM ([1ecd82ce](https://github.com/angular-ui/bootstrap/commit/1ecd82ce), closes [#4117](https://github.com/angular-ui/bootstrap/issues/4117))\n* **datepicker:**\n  * commit safe apply on destruction ([74a8be4c](https://github.com/angular-ui/bootstrap/commit/74a8be4c), closes [#4079](https://github.com/angular-ui/bootstrap/issues/4079), [#4076](https://github.com/angular-ui/bootstrap/issues/4076))\n  * change to `dateDisabled` ([5245ccad](https://github.com/angular-ui/bootstrap/commit/5245ccad), closes [#2773](https://github.com/angular-ui/bootstrap/issues/2773), [#4080](https://github.com/angular-ui/bootstrap/issues/4080))\n* **dropdown:** handle `keyboard-nav` correctly ([0b37f088](https://github.com/angular-ui/bootstrap/commit/0b37f088), closes [#4110](https://github.com/angular-ui/bootstrap/issues/4110), [#4091](https://github.com/angular-ui/bootstrap/issues/4091))\n* **modal:**\n  * skipping ESC handling for form inputs ([a05b9c1a](https://github.com/angular-ui/bootstrap/commit/a05b9c1a), closes [#3551](https://github.com/angular-ui/bootstrap/issues/3551), [#2544](https://github.com/angular-ui/bootstrap/issues/2544))\n  * add `$animateCss` support ([c7f19d58](https://github.com/angular-ui/bootstrap/commit/c7f19d58), closes [#4121](https://github.com/angular-ui/bootstrap/issues/4121), [#4119](https://github.com/angular-ui/bootstrap/issues/4119))\n  * fix test ([e60c3ff6](https://github.com/angular-ui/bootstrap/commit/e60c3ff6))\n  * dismiss modal on unschedule destruction ([3584061f](https://github.com/angular-ui/bootstrap/commit/3584061f), closes [#4097](https://github.com/angular-ui/bootstrap/issues/4097), [#3694](https://github.com/angular-ui/bootstrap/issues/3694))\n* **progressbar:** fix `min-width` for Bootstrap 3.2 ([8dc13be9](https://github.com/angular-ui/bootstrap/commit/8dc13be9), closes [#4081](https://github.com/angular-ui/bootstrap/issues/4081), [#2511](https://github.com/angular-ui/bootstrap/issues/2511))\n* **tooltip:**\n  * add safety to `$apply` ([22b16f01](https://github.com/angular-ui/bootstrap/commit/22b16f01), closes [#3943](https://github.com/angular-ui/bootstrap/issues/3943), [#4150](https://github.com/angular-ui/bootstrap/issues/4150), [#516](https://github.com/angular-ui/bootstrap/issues/516))\n  * tooltip w/ template position ([895a2281](https://github.com/angular-ui/bootstrap/commit/895a2281))\n  * prevent opening when `tooltipPopupDelay` is present ([12c527af](https://github.com/angular-ui/bootstrap/commit/12c527af), closes [#4098](https://github.com/angular-ui/bootstrap/issues/4098), [#3611](https://github.com/angular-ui/bootstrap/issues/3611))\n* **typeahead:** return `null` if empty ([c7d3a660](https://github.com/angular-ui/bootstrap/commit/c7d3a660), closes [#4078](https://github.com/angular-ui/bootstrap/issues/4078), [#3176](https://github.com/angular-ui/bootstrap/issues/3176))\n\n\n## Features\n\n* **accordion:**\n  * add `controllerAs` support ([9865ee8e](https://github.com/angular-ui/bootstrap/commit/9865ee8e), closes [#4138](https://github.com/angular-ui/bootstrap/issues/4138))\n  * add `templateUrl` support ([f777c320](https://github.com/angular-ui/bootstrap/commit/f777c320), closes [#4084](https://github.com/angular-ui/bootstrap/issues/4084))\n* **alert:** add `templateUrl` support ([88a885ca](https://github.com/angular-ui/bootstrap/commit/88a885ca), closes [#4139](https://github.com/angular-ui/bootstrap/issues/4139))\n* **buttons:** add `controllerAs` support ([02872dc1](https://github.com/angular-ui/bootstrap/commit/02872dc1), closes [#4140](https://github.com/angular-ui/bootstrap/issues/4140))\n* **carousel:**\n  * add `templateUrl` support ([a29c8f20](https://github.com/angular-ui/bootstrap/commit/a29c8f20), closes [#4141](https://github.com/angular-ui/bootstrap/issues/4141))\n  * expose carousel controller via `controllerAs` ([bfec07e4](https://github.com/angular-ui/bootstrap/commit/bfec07e4), closes [#4131](https://github.com/angular-ui/bootstrap/issues/4131))\n* **datepicker:**\n  * allow custom templates ([e04b06d7](https://github.com/angular-ui/bootstrap/commit/e04b06d7), closes [#4157](https://github.com/angular-ui/bootstrap/issues/4157), [#1913](https://github.com/angular-ui/bootstrap/issues/1913))\n  * add `onOpenFocus` support ([68afc4c6](https://github.com/angular-ui/bootstrap/commit/68afc4c6), closes [#2303](https://github.com/angular-ui/bootstrap/issues/2303), [#2546](https://github.com/angular-ui/bootstrap/issues/2546), [#4146](https://github.com/angular-ui/bootstrap/issues/4146))\n  * add support for dynamic `min-mode` and `max-mode` ([f3d263e1](https://github.com/angular-ui/bootstrap/commit/f3d263e1), closes [#3843](https://github.com/angular-ui/bootstrap/issues/3843), [#2618](https://github.com/angular-ui/bootstrap/issues/2618))\n  * allow suppression of log error ([bab1d375](https://github.com/angular-ui/bootstrap/commit/bab1d375), closes [#3836](https://github.com/angular-ui/bootstrap/issues/3836), [#4115](https://github.com/angular-ui/bootstrap/issues/4115))\n* **docs:**\n  * add explanation of eye icon ([265d429b](https://github.com/angular-ui/bootstrap/commit/265d429b), closes [#4120](https://github.com/angular-ui/bootstrap/issues/4120))\n  * add ngAnimate Plunker support ([a8a22cff](https://github.com/angular-ui/bootstrap/commit/a8a22cff), closes [#3648](https://github.com/angular-ui/bootstrap/issues/3648), [#4072](https://github.com/angular-ui/bootstrap/issues/4072))\n* **modal:**\n  * add ability to change class on body ([5a28ff76](https://github.com/angular-ui/bootstrap/commit/5a28ff76), closes [#2633](https://github.com/angular-ui/bootstrap/issues/2633), [#4132](https://github.com/angular-ui/bootstrap/issues/4132))\n  * allow users to resolve with strings ([89368856](https://github.com/angular-ui/bootstrap/commit/89368856), closes [#2676](https://github.com/angular-ui/bootstrap/issues/2676), [#4124](https://github.com/angular-ui/bootstrap/issues/4124))\n* **pagination:**\n  * add classes to assist with styling ([b21c9abd](https://github.com/angular-ui/bootstrap/commit/b21c9abd), closes [#4130](https://github.com/angular-ui/bootstrap/issues/4130), [#4142](https://github.com/angular-ui/bootstrap/issues/4142))\n  * add `templateUrl` support ([a0e1c91c](https://github.com/angular-ui/bootstrap/commit/a0e1c91c), closes [#4137](https://github.com/angular-ui/bootstrap/issues/4137))\n* **timepicker:**\n  * add documentation for max/min ([87fc242d](https://github.com/angular-ui/bootstrap/commit/87fc242d))\n  * Added min/max attributes for timepicker. ([6c0010be](https://github.com/angular-ui/bootstrap/commit/6c0010be), closes [#4019](https://github.com/angular-ui/bootstrap/issues/4019))\n* **tooltip:**\n  * remove unnecessary `$digest` ([901a7c66](https://github.com/angular-ui/bootstrap/commit/901a7c66), closes [#4151](https://github.com/angular-ui/bootstrap/issues/4151))\n  * add multiple trigger support ([ca9196fa](https://github.com/angular-ui/bootstrap/commit/ca9196fa), closes [#3987](https://github.com/angular-ui/bootstrap/issues/3987), [#4077](https://github.com/angular-ui/bootstrap/issues/4077))\n\n\n## Breaking Changes\n\n* add `open` class to accordion group when expanded\n\nCloses #4152\nCloses #3419\n\n ([ead15e37](https://github.com/angular-ui/bootstrap/commit/ead15e37))\n* Allow the user to hit `esc` inside an element in a modal and not exit the modal if the event has been `defaultPrevented`\n\nCloses #3551\nFixes #2544\n\n ([a05b9c1a](https://github.com/angular-ui/bootstrap/commit/a05b9c1a))\n* Change validation key to `dateDisabled` to align better with Angular's convention\n\nCloses #2773\nCloses #4080\n\n ([5245ccad](https://github.com/angular-ui/bootstrap/commit/5245ccad))\n\n\n<a name\"0.13.2\"></a>\n# [0.13.2](https://github.com/angular-ui/bootstrap/compare/0.13.1...0.13.2) (2015-08-02)\n\n\n## Bug Fixes\n\n* **accordion:** apply disabled style to accordion-header ([0643fd3e](https://github.com/angular-ui/bootstrap/commit/0643fd3e), closes [#3599](https://github.com/angular-ui/bootstrap/issues/3599), [#3233](https://github.com/angular-ui/bootstrap/issues/3233))\n* **buttons:** respect disabled attribute ([42e1af5c](https://github.com/angular-ui/bootstrap/commit/42e1af5c), closes [#4026](https://github.com/angular-ui/bootstrap/issues/4026), [#4013](https://github.com/angular-ui/bootstrap/issues/4013))\n* **carousel:**\n  * fix animations with 1.4 ([f45b4a4c](https://github.com/angular-ui/bootstrap/commit/f45b4a4c), closes [#3946](https://github.com/angular-ui/bootstrap/issues/3946), [#4041](https://github.com/angular-ui/bootstrap/issues/4041), [#3811](https://github.com/angular-ui/bootstrap/issues/3811))\n  * clear `currentSlide` when there are no slides ([0c78026b](https://github.com/angular-ui/bootstrap/commit/0c78026b), closes [#4021](https://github.com/angular-ui/bootstrap/issues/4021))\n* **dateparser:** add type and validity check ([4f1e03f1](https://github.com/angular-ui/bootstrap/commit/4f1e03f1), closes [#3701](https://github.com/angular-ui/bootstrap/issues/3701), [#3759](https://github.com/angular-ui/bootstrap/issues/3759), [#3933](https://github.com/angular-ui/bootstrap/issues/3933), [#3609](https://github.com/angular-ui/bootstrap/issues/3609), [#3713](https://github.com/angular-ui/bootstrap/issues/3713), [#3736](https://github.com/angular-ui/bootstrap/issues/3736), [#3875](https://github.com/angular-ui/bootstrap/issues/3875), [#3937](https://github.com/angular-ui/bootstrap/issues/3937), [#3976](https://github.com/angular-ui/bootstrap/issues/3976))\n* **datepicker:**\n  * change to contains ([9f73d240](https://github.com/angular-ui/bootstrap/commit/9f73d240), closes [#4066](https://github.com/angular-ui/bootstrap/issues/4066), [#3076](https://github.com/angular-ui/bootstrap/issues/3076))\n  * *BREAKING CHANGE* remove `new Date` fallback ([ab4580fd](https://github.com/angular-ui/bootstrap/commit/ab4580fd), closes [#2513](https://github.com/angular-ui/bootstrap/issues/2513), [#3294](https://github.com/angular-ui/bootstrap/issues/3294), [#3344](https://github.com/angular-ui/bootstrap/issues/3344), [#3682](https://github.com/angular-ui/bootstrap/issues/3682), [#4092](https://github.com/angular-ui/bootstrap/issues/4092), [#1289](https://github.com/angular-ui/bootstrap/issues/1289), [#2446](https://github.com/angular-ui/bootstrap/issues/2446), [#3037](https://github.com/angular-ui/bootstrap/issues/3037), [#3104](https://github.com/angular-ui/bootstrap/issues/3104), [#3196](https://github.com/angular-ui/bootstrap/issues/3196), [#3206](https://github.com/angular-ui/bootstrap/issues/3206), [#3342](https://github.com/angular-ui/bootstrap/issues/3342), [#3617](https://github.com/angular-ui/bootstrap/issues/3617), [#3644](https://github.com/angular-ui/bootstrap/issues/3644))\n  * ensure `initDate` is on an object ([577b2a2a](https://github.com/angular-ui/bootstrap/commit/577b2a2a), closes [#3625](https://github.com/angular-ui/bootstrap/issues/3625))\n  * change to higher max date ([32e73280](https://github.com/angular-ui/bootstrap/commit/32e73280), closes [#4042](https://github.com/angular-ui/bootstrap/issues/4042))\n  * fix validation with `ngRequired` ([fe0d954a](https://github.com/angular-ui/bootstrap/commit/fe0d954a), closes [#4002](https://github.com/angular-ui/bootstrap/issues/4002), [#3862](https://github.com/angular-ui/bootstrap/issues/3862))\n  * set to `null` if not present ([a65a5fa1](https://github.com/angular-ui/bootstrap/commit/a65a5fa1), closes [#4014](https://github.com/angular-ui/bootstrap/issues/4014))\n* **dropdown:** add safety check for setIsOpen ([60e43160](https://github.com/angular-ui/bootstrap/commit/60e43160), closes [#4030](https://github.com/angular-ui/bootstrap/issues/4030))\n* **modal:**\n  * properly garbage collect DOM node ([1e8297be](https://github.com/angular-ui/bootstrap/commit/1e8297be), closes [#2875](https://github.com/angular-ui/bootstrap/issues/2875))\n  * fix `bindToController` implementation ([811bf96e](https://github.com/angular-ui/bootstrap/commit/811bf96e), closes [#4054](https://github.com/angular-ui/bootstrap/issues/4054), [#4051](https://github.com/angular-ui/bootstrap/issues/4051))\n  * animate backdrop concurrently with window ([c55ee4f5](https://github.com/angular-ui/bootstrap/commit/c55ee4f5), closes [#4039](https://github.com/angular-ui/bootstrap/issues/4039), [#4036](https://github.com/angular-ui/bootstrap/issues/4036))\n* **progressbar:**\n  * use more visible color ([1afc5d1d](https://github.com/angular-ui/bootstrap/commit/1afc5d1d), closes [#4044](https://github.com/angular-ui/bootstrap/issues/4044))\n  * allow max width of 100% ([2e9177e5](https://github.com/angular-ui/bootstrap/commit/2e9177e5), closes [#4027](https://github.com/angular-ui/bootstrap/issues/4027), [#4018](https://github.com/angular-ui/bootstrap/issues/4018))\n* **tooltip:**\n  * update tooltip placement dynamically ([13df1c93](https://github.com/angular-ui/bootstrap/commit/13df1c93), closes [#3980](https://github.com/angular-ui/bootstrap/issues/3980), [#3978](https://github.com/angular-ui/bootstrap/issues/3978))\n  * prevent 1px shift in Webkit/Blink ([632aa820](https://github.com/angular-ui/bootstrap/commit/632aa820), closes [#3964](https://github.com/angular-ui/bootstrap/issues/3964))\n* **typeahead:**\n  * reset matches if enter is hit ([25704838](https://github.com/angular-ui/bootstrap/commit/25704838), closes [#4063](https://github.com/angular-ui/bootstrap/issues/4063), [#3545](https://github.com/angular-ui/bootstrap/issues/3545))\n  * only reset matches if matches are present ([97e077e1](https://github.com/angular-ui/bootstrap/commit/97e077e1), closes [#3119](https://github.com/angular-ui/bootstrap/issues/3119))\n\n\n## Features\n\n* **build:** add support for npm publishing ([27f7ca26](https://github.com/angular-ui/bootstrap/commit/27f7ca26), closes [#3108](https://github.com/angular-ui/bootstrap/issues/3108))\n* **modal:** trap focus in modal for tabbing ([a028d2aa](https://github.com/angular-ui/bootstrap/commit/a028d2aa), closes [#3689](https://github.com/angular-ui/bootstrap/issues/3689), [#4004](https://github.com/angular-ui/bootstrap/issues/4004), [#738](https://github.com/angular-ui/bootstrap/issues/738))\n* **popover:** add custom template support ([a9d3d253](https://github.com/angular-ui/bootstrap/commit/a9d3d253), closes [#4056](https://github.com/angular-ui/bootstrap/issues/4056), [#4057](https://github.com/angular-ui/bootstrap/issues/4057))\n* **rating:** add title support for stars ([713c8487](https://github.com/angular-ui/bootstrap/commit/713c8487), closes [#3621](https://github.com/angular-ui/bootstrap/issues/3621))\n* **typeahead:**\n  * add `noResults` indicator binding ([647cdd93](https://github.com/angular-ui/bootstrap/commit/647cdd93), closes [#2016](https://github.com/angular-ui/bootstrap/issues/2016), [#2792](https://github.com/angular-ui/bootstrap/issues/2792), [#4068](https://github.com/angular-ui/bootstrap/issues/4068))\n  * add `typeaheadSelectOnExact` support ([277b30ca](https://github.com/angular-ui/bootstrap/commit/277b30ca), closes [#3365](https://github.com/angular-ui/bootstrap/issues/3365), [#3310](https://github.com/angular-ui/bootstrap/issues/3310))\n\n\n<a name\"0.13.1\"></a>\n# [0.13.1](https://github.com/angular-ui/bootstrap/compare/0.13.0...0.13.1) (2015-07-23)\n\n\n## Bug Fixes\n\n* **accordion:** add CSP compliance for accordion & typeahead ([fb302c60](https://github.com/angular-ui/bootstrap/commit/fb302c60), closes [#3909](https://github.com/angular-ui/bootstrap/issues/3909), [#3904](https://github.com/angular-ui/bootstrap/issues/3904))\n* **alert:**\n  * adjust check for close attribute ([13a0354f](https://github.com/angular-ui/bootstrap/commit/13a0354f), closes [#3864](https://github.com/angular-ui/bootstrap/issues/3864), [#3890](https://github.com/angular-ui/bootstrap/issues/3890), [#3848](https://github.com/angular-ui/bootstrap/issues/3848))\n  * rename alert-dismissable to alert-dismissible ([d631af5a](https://github.com/angular-ui/bootstrap/commit/d631af5a))\n* **carousel:**\n  * change to avoid references to debug info ([ca07ad7c](https://github.com/angular-ui/bootstrap/commit/ca07ad7c), closes [#3795](https://github.com/angular-ui/bootstrap/issues/3795), [#3794](https://github.com/angular-ui/bootstrap/issues/3794))\n  * ensure there are slides present ([115d490a](https://github.com/angular-ui/bootstrap/commit/115d490a), closes [#3755](https://github.com/angular-ui/bootstrap/issues/3755))\n  * disable transition until animation completes ([ef45ecf8](https://github.com/angular-ui/bootstrap/commit/ef45ecf8), closes [#3729](https://github.com/angular-ui/bootstrap/issues/3729), [#3757](https://github.com/angular-ui/bootstrap/issues/3757))\n* **collapse:** fix occasional flickering ([ede9ea46](https://github.com/angular-ui/bootstrap/commit/ede9ea46), closes [#3804](https://github.com/angular-ui/bootstrap/issues/3804), [#3801](https://github.com/angular-ui/bootstrap/issues/3801))\n* **datepicker:**\n  * change to min width cells ([5567c432](https://github.com/angular-ui/bootstrap/commit/5567c432), closes [#4000](https://github.com/angular-ui/bootstrap/issues/4000), [#3941](https://github.com/angular-ui/bootstrap/issues/3941))\n  * fix OS dependent time zone issue ([f1412014](https://github.com/angular-ui/bootstrap/commit/f1412014), closes [#3079](https://github.com/angular-ui/bootstrap/issues/3079))\n  * Apply custom class to month ([eb3b32ec](https://github.com/angular-ui/bootstrap/commit/eb3b32ec), closes [#3863](https://github.com/angular-ui/bootstrap/issues/3863))\n  * check if getter.assign is function ([ed10899d](https://github.com/angular-ui/bootstrap/commit/ed10899d), closes [#3155](https://github.com/angular-ui/bootstrap/issues/3155), [#3345](https://github.com/angular-ui/bootstrap/issues/3345), [#3719](https://github.com/angular-ui/bootstrap/issues/3719))\n* **dropdown:**\n  * do not autoclose with outsideClick and append to body ([cc66a068](https://github.com/angular-ui/bootstrap/commit/cc66a068), closes [#3792](https://github.com/angular-ui/bootstrap/issues/3792), [#3645](https://github.com/angular-ui/bootstrap/issues/3645))\n  * avoid matching 138 & 140 ([41ebd984](https://github.com/angular-ui/bootstrap/commit/41ebd984))\n  * align when using dropdown-menu-body ([2332f14d](https://github.com/angular-ui/bootstrap/commit/2332f14d), closes [#3913](https://github.com/angular-ui/bootstrap/issues/3913), [#3820](https://github.com/angular-ui/bootstrap/issues/3820))\n  * call toggle after animation ([054341b7](https://github.com/angular-ui/bootstrap/commit/054341b7), closes [#3513](https://github.com/angular-ui/bootstrap/issues/3513), [#3655](https://github.com/angular-ui/bootstrap/issues/3655), [#3511](https://github.com/angular-ui/bootstrap/issues/3511))\n  * do not close on $locationChangeSuccess ([e5a1e88f](https://github.com/angular-ui/bootstrap/commit/e5a1e88f), closes [#3683](https://github.com/angular-ui/bootstrap/issues/3683), [#3704](https://github.com/angular-ui/bootstrap/issues/3704))\n* **modal:**\n  * backdrop animation on AngularJS 1.4 ([158d2676](https://github.com/angular-ui/bootstrap/commit/158d2676), closes [#3896](https://github.com/angular-ui/bootstrap/issues/3896))\n  * closing breaks on missing scope, 1.4 ([0286828b](https://github.com/angular-ui/bootstrap/commit/0286828b), closes [#3787](https://github.com/angular-ui/bootstrap/issues/3787), [#3806](https://github.com/angular-ui/bootstrap/issues/3806), [#3873](https://github.com/angular-ui/bootstrap/issues/3873), [#3888](https://github.com/angular-ui/bootstrap/issues/3888))\n  * remove illegal character ([dd4f3cc8](https://github.com/angular-ui/bootstrap/commit/dd4f3cc8), closes [#3893](https://github.com/angular-ui/bootstrap/issues/3893), [#3892](https://github.com/angular-ui/bootstrap/issues/3892))\n  * focus on body if element disappears ([988336cc](https://github.com/angular-ui/bootstrap/commit/988336cc), closes [#3653](https://github.com/angular-ui/bootstrap/issues/3653), [#3639](https://github.com/angular-ui/bootstrap/issues/3639))\n* **progressbar:** use max value on stacked progress bar ([36e0f0ea](https://github.com/angular-ui/bootstrap/commit/36e0f0ea), closes [#3830](https://github.com/angular-ui/bootstrap/issues/3830), [#3618](https://github.com/angular-ui/bootstrap/issues/3618))\n* **rating:** Set rating to 0 when same value is selected ([dbceec76](https://github.com/angular-ui/bootstrap/commit/dbceec76), closes [#3963](https://github.com/angular-ui/bootstrap/issues/3963), [#3246](https://github.com/angular-ui/bootstrap/issues/3246))\n* **tabs:** fix empty href ([2b27dcbf](https://github.com/angular-ui/bootstrap/commit/2b27dcbf), closes [#3799](https://github.com/angular-ui/bootstrap/issues/3799))\n* **typeahead:**\n  * select match on tab for iOS webview ([5b37bb8b](https://github.com/angular-ui/bootstrap/commit/5b37bb8b), closes [#3762](https://github.com/angular-ui/bootstrap/issues/3762), [#3699](https://github.com/angular-ui/bootstrap/issues/3699))\n  * don't close popup on right click ([7d1c4600](https://github.com/angular-ui/bootstrap/commit/7d1c4600), closes [#3975](https://github.com/angular-ui/bootstrap/issues/3975), [#3973](https://github.com/angular-ui/bootstrap/issues/3973))\n  * close dropdown on tab with no selection ([493510d0](https://github.com/angular-ui/bootstrap/commit/493510d0), closes [#3340](https://github.com/angular-ui/bootstrap/issues/3340))\n  * do not execute unnecessary $digest ([0d96221f](https://github.com/angular-ui/bootstrap/commit/0d96221f), closes [#2652](https://github.com/angular-ui/bootstrap/issues/2652), [#3791](https://github.com/angular-ui/bootstrap/issues/3791))\n  * add href to show cursor as pointer ([195e520e](https://github.com/angular-ui/bootstrap/commit/195e520e), closes [#3649](https://github.com/angular-ui/bootstrap/issues/3649))\n\n\n## Features\n\n* **alert:** pass $event to close() ([44e06425](https://github.com/angular-ui/bootstrap/commit/44e06425), closes [#3828](https://github.com/angular-ui/bootstrap/issues/3828), [#3827](https://github.com/angular-ui/bootstrap/issues/3827))\n* **carousel:** add `noWrap` option to prevent re-cycling of slides ([7fb3840f](https://github.com/angular-ui/bootstrap/commit/7fb3840f), closes [#3462](https://github.com/angular-ui/bootstrap/issues/3462), [#3397](https://github.com/angular-ui/bootstrap/issues/3397))\n* **collapse:** add accessibility support ([92551342](https://github.com/angular-ui/bootstrap/commit/92551342), closes [#3920](https://github.com/angular-ui/bootstrap/issues/3920))\n* **dropdown:**\n  * add dropdown classes dynamically ([4af83ade](https://github.com/angular-ui/bootstrap/commit/4af83ade), closes [#3984](https://github.com/angular-ui/bootstrap/issues/3984), [#3986](https://github.com/angular-ui/bootstrap/issues/3986))\n  * add accessibility attributes ([14689e05](https://github.com/angular-ui/bootstrap/commit/14689e05), closes [#3951](https://github.com/angular-ui/bootstrap/issues/3951))\n  * add keynav support to dropdown ([62359370](https://github.com/angular-ui/bootstrap/commit/62359370), closes [#3685](https://github.com/angular-ui/bootstrap/issues/3685), [#3212](https://github.com/angular-ui/bootstrap/issues/3212), [#1228](https://github.com/angular-ui/bootstrap/issues/1228))\n  * support optional templates for dropdown menus ([83c4266c](https://github.com/angular-ui/bootstrap/commit/83c4266c))\n* **modal:** add support for bindToController ([8adfc833](https://github.com/angular-ui/bootstrap/commit/8adfc833), closes [#3965](https://github.com/angular-ui/bootstrap/issues/3965), [#3404](https://github.com/angular-ui/bootstrap/issues/3404))\n* **pagination:** add support for `ng-disabled` ([f6edfa5d](https://github.com/angular-ui/bootstrap/commit/f6edfa5d), closes [#3956](https://github.com/angular-ui/bootstrap/issues/3956))\n* **timepicker:** add `showSpinner` flag ([1f760eb3](https://github.com/angular-ui/bootstrap/commit/1f760eb3))\n* **typeahead:**\n  * add 'select on blur' option. ([68cac59a](https://github.com/angular-ui/bootstrap/commit/68cac59a), closes [#3445](https://github.com/angular-ui/bootstrap/issues/3445))\n  * popup position ([86bfec19](https://github.com/angular-ui/bootstrap/commit/86bfec19), closes [#3874](https://github.com/angular-ui/bootstrap/issues/3874))\n  * handles min-length of 0 ([a5a25141](https://github.com/angular-ui/bootstrap/commit/a5a25141), closes [#3600](https://github.com/angular-ui/bootstrap/issues/3600))\n\n\n<a name=\"0.13.0\"></a>\n# [0.13.0](https://github.com/angular-ui/bootstrap/compare/0.12.1...0.13.0) (2015-05-02)\n\n\n## Bug Fixes\n\n* **accordion:**\n  * Made accordion heading tab-able for IE9-10 ([6abad509](https://github.com/angular-ui/bootstrap/commit/6abad509cd4d44c3ca432f2f21c9ecea0a206b53))\n  * noop for href in header to prevent page refresh with nested buttons canceling ev ([9ca4ec39](https://github.com/angular-ui/bootstrap/commit/9ca4ec399be0e0e8f6c3fe6fd924a7d94ce669b5))\n* **buttons:** add unit tests for buttons ([9468d723](https://github.com/angular-ui/bootstrap/commit/9468d7239dd6eed4a3a2945f6761f7a2fa97222b), closes [#3030](https://github.com/angular-ui/bootstrap/issues/3030))\n* **carousel:** respect the order of the slides ([b5f220fa](https://github.com/angular-ui/bootstrap/commit/b5f220fa8483f5743ba6ab3610f5064bf5c71be7), closes [#488](https://github.com/angular-ui/bootstrap/issues/488))\n* **changelog:** add comment on breaking change ([f02c1bbb](https://github.com/angular-ui/bootstrap/commit/f02c1bbbf7777bee9cbb4a038730fb475337a2c5), closes [#2675](https://github.com/angular-ui/bootstrap/issues/2675))\n* **dateparser:** add extra validation constraint to days ([c19b8879](https://github.com/angular-ui/bootstrap/commit/c19b8879e902e8753b4077e7983ec629242436fc))\n* **datepicker:**\n  * week count issues ([39e5fd3e](https://github.com/angular-ui/bootstrap/commit/39e5fd3e4981f311d237d21c37a89cde9f42f0d6), closes [#2506](https://github.com/angular-ui/bootstrap/issues/2506), [#3120](https://github.com/angular-ui/bootstrap/issues/3120), [#2306](https://github.com/angular-ui/bootstrap/issues/2306))\n  * make 'show-weeks' work on datepickerPopup ([d0cc7284](https://github.com/angular-ui/bootstrap/commit/d0cc72841b9641b768cc5eb184de2dbbaa804e2d), closes [#3143](https://github.com/angular-ui/bootstrap/issues/3143), [#3149](https://github.com/angular-ui/bootstrap/issues/3149))\n  * datepicker-popup compatibility with ngModelOptions ([d024dd77](https://github.com/angular-ui/bootstrap/commit/d024dd77ed6a20983bea5b90b75481c93f17980f), closes [#3349](https://github.com/angular-ui/bootstrap/issues/3349))\n  * disable title button when in max mode ([35b8512a](https://github.com/angular-ui/bootstrap/commit/35b8512ac7949eba7d432760f83748bb2bb893c1), closes [#3012](https://github.com/angular-ui/bootstrap/issues/3012))\n  * add shortcutPropagation to datepickerPopup ([13bd516c](https://github.com/angular-ui/bootstrap/commit/13bd516cfcc1b2ee940112675830f7ce3d93ea4f))\n  * fixed shortcut event kill by adding option ([89ab4580](https://github.com/angular-ui/bootstrap/commit/89ab4580cd3d0bbdf63e02fbebe2cb3a9f2616dd))\n  * fix initDate implementation in datepicker ([98e2bdfc](https://github.com/angular-ui/bootstrap/commit/98e2bdfc8b4dd108acdf3fac75a795675889bbc7))\n  * Fix init-date not applying on datepicker-popup ([c5b63ded](https://github.com/angular-ui/bootstrap/commit/c5b63ded0aebd7ae80f238bd0a33f1d9c0746dba))\n  * Make datepicker respect dateFormat inside ng-if ([2c2dba6d](https://github.com/angular-ui/bootstrap/commit/2c2dba6d145c95500efe7f6be6248d56ae1aebee))\n  * `ng-model` value can be a timestamp ([d253208b](https://github.com/angular-ui/bootstrap/commit/d253208bd7ebbd6209c6a25f70e010123198fcd7), closes [#2345](https://github.com/angular-ui/bootstrap/issues/2345))\n  * Parse date from $viewValue instead of $modelValue ([0ecf7faa](https://github.com/angular-ui/bootstrap/commit/0ecf7faad3b340e171e5c8ca17a17597fa6e6596))\n  * don't stop ESC propagation unless dropdown is open ([c2e5b284](https://github.com/angular-ui/bootstrap/commit/c2e5b284a31fc992b8230b89dc98ed757d155493), closes [#3096](https://github.com/angular-ui/bootstrap/issues/3096), [#3179](https://github.com/angular-ui/bootstrap/issues/3179))\n  * date formatting when using angular 1.3 fixes #2659 ([23936f9f](https://github.com/angular-ui/bootstrap/commit/23936f9f6ec7170b56c7f13345ae022c74fa3034), closes [#3293](https://github.com/angular-ui/bootstrap/issues/3293), [#3279](https://github.com/angular-ui/bootstrap/issues/3279), [#2440](https://github.com/angular-ui/bootstrap/issues/2440), [#2932](https://github.com/angular-ui/bootstrap/issues/2932), [#3074](https://github.com/angular-ui/bootstrap/issues/3074), [#2943](https://github.com/angular-ui/bootstrap/issues/2943), [#2733](https://github.com/angular-ui/bootstrap/issues/2733), [#3047](https://github.com/angular-ui/bootstrap/issues/3047), [#2659](https://github.com/angular-ui/bootstrap/issues/2659), [#2681](https://github.com/angular-ui/bootstrap/issues/2681))\n  * date formatting when using angular 1.3 fixes #2659 ([5f9afe5a](https://github.com/angular-ui/bootstrap/commit/5f9afe5a86af0e207eaacd6a9b1f202fd78d7009))\n* **demo:** Modify the demo app to play nice with Angular 1.3 ([aa0b6392](https://github.com/angular-ui/bootstrap/commit/aa0b6392db57d436d7c203cf05555d708c945322), closes [#3098](https://github.com/angular-ui/bootstrap/issues/3098))\n* **dropdown:** Fix $digest:inprog on dropdown dismissal ([4a06adba](https://github.com/angular-ui/bootstrap/commit/4a06adbac6c1c1fa6b0182ca9bd7335eda89c43f), closes [#3274](https://github.com/angular-ui/bootstrap/issues/3274))\n* **grunt:** fix typo in gruntfile ([f0cadb1f](https://github.com/angular-ui/bootstrap/commit/f0cadb1f1a6b8d994c2df3313a2121e92a36bddb), closes [#3589](https://github.com/angular-ui/bootstrap/issues/3589))\n* **modal:**\n  * Use attribute observe and add a render promise. ([99af5f8a](https://github.com/angular-ui/bootstrap/commit/99af5f8a369b13b018ec1e592d82a1a140cbb6bb))\n  * fix minor grammar error ([22a21448](https://github.com/angular-ui/bootstrap/commit/22a21448cbad1448bd4541bd0ce3fc8d3515943d), closes [#3519](https://github.com/angular-ui/bootstrap/issues/3519))\n  * Fix focus when the dialog is close or cancelled ([e6b105ae](https://github.com/angular-ui/bootstrap/commit/e6b105ae39bcbcf468c3e32057c6acb5ab50d4ce), closes [#2888](https://github.com/angular-ui/bootstrap/issues/2888))\n  * allow for custom user modal sizes ([85eeb954](https://github.com/angular-ui/bootstrap/commit/85eeb95428e0dd367cf3e251f2d01fc8dc899dd4), closes [#3429](https://github.com/angular-ui/bootstrap/issues/3429), [#3431](https://github.com/angular-ui/bootstrap/issues/3431))\n  * Autofocus corrects the second time that the modal is open ([e5f5f75b](https://github.com/angular-ui/bootstrap/commit/e5f5f75b370b6d4806da87bec575b7084e30c520), closes [#2802](https://github.com/angular-ui/bootstrap/issues/2802))\n  * fix messages on modal test failed ([ab919f9f](https://github.com/angular-ui/bootstrap/commit/ab919f9f89aeaaa336b5d3fe1aed1e0617b5a482))\n* **pagination:**\n  * remove focus from prior clicked elements ([33269bb6](https://github.com/angular-ui/bootstrap/commit/33269bb6b444cdca18e08063814d39c5ef502589), closes [#3488](https://github.com/angular-ui/bootstrap/issues/3488), [#3486](https://github.com/angular-ui/bootstrap/issues/3486))\n  * fixes issue when init called after watch triggered ([26b40903](https://github.com/angular-ui/bootstrap/commit/26b40903abf012b5d3d35154d6210119ac9a76d4), closes [#2257](https://github.com/angular-ui/bootstrap/issues/2257), [#2227](https://github.com/angular-ui/bootstrap/issues/2227))\n* **popover:**\n  * prevent wrong positioning from title ([c8156c7e](https://github.com/angular-ui/bootstrap/commit/c8156c7e21d0f3a61fc19d59644ae24c423eb5c6), closes [#3518](https://github.com/angular-ui/bootstrap/issues/3518))\n  * animations with ngAnimate ([c2ace472](https://github.com/angular-ui/bootstrap/commit/c2ace47225ecb337328a16d2c95744e0d37ce80f), closes [#3509](https://github.com/angular-ui/bootstrap/issues/3509), [#3375](https://github.com/angular-ui/bootstrap/issues/3375), [#3506](https://github.com/angular-ui/bootstrap/issues/3506))\n  * make it work with ngAnimate ([461087b5](https://github.com/angular-ui/bootstrap/commit/461087b5ff360e1d06d6724375ab29169a89fcf2), closes [#3482](https://github.com/angular-ui/bootstrap/issues/3482), [#3375](https://github.com/angular-ui/bootstrap/issues/3375))\n* **progressbar:** limit max width to 100% ([489961e1](https://github.com/angular-ui/bootstrap/commit/489961e1a0036ddab4e719a772382209a595f163), closes [#3005](https://github.com/angular-ui/bootstrap/issues/3005))\n* **tab:** change to `disable` attribute ([4bfae223](https://github.com/angular-ui/bootstrap/commit/4bfae2238d08d7edea8e6dfe99051192a16d6587), closes [#2677](https://github.com/angular-ui/bootstrap/issues/2677))\n* **timepicker:**\n  * move render logic to formatter ([b4bbc019](https://github.com/angular-ui/bootstrap/commit/b4bbc0198284d90085bd85840dffd030345d5211), closes [#3160](https://github.com/angular-ui/bootstrap/issues/3160), [#3427](https://github.com/angular-ui/bootstrap/issues/3427))\n  * remove ng-mousewheel binding ([a726b7cd](https://github.com/angular-ui/bootstrap/commit/a726b7cd47d417ed656aef3af012e7b6c7b00bf4), closes [#3442](https://github.com/angular-ui/bootstrap/issues/3442))\n  * fix widths of inputs when inside form-inline ([8e89440b](https://github.com/angular-ui/bootstrap/commit/8e89440ba86d26d74ed799843cde7f9f2bf26c0b))\n  * Stringify pad return when value >= 10 ([405dab65](https://github.com/angular-ui/bootstrap/commit/405dab65f72f5bed488eb8f5dfa5a1a848c4a606))\n* **tooltip:**\n  * template type should respect popup class ([6af627a8](https://github.com/angular-ui/bootstrap/commit/6af627a8edcbce0370ca2d90f9a575cd6770f6c1), closes [#3569](https://github.com/angular-ui/bootstrap/issues/3569))\n  * tooltip-html should not open if empty ([34044a77](https://github.com/angular-ui/bootstrap/commit/34044a77070bc809220e2ed6b628393889c40b86), closes [#3563](https://github.com/angular-ui/bootstrap/issues/3563))\n  * use correct prefix for -template ([9ca9d7f5](https://github.com/angular-ui/bootstrap/commit/9ca9d7f5263d8d5e0a9de18c2b75f64aca7c5255), closes [#3498](https://github.com/angular-ui/bootstrap/issues/3498), [#3473](https://github.com/angular-ui/bootstrap/issues/3473))\n  * Fix for issue #3167 ([87a36076](https://github.com/angular-ui/bootstrap/commit/87a3607632cae03ead57b377bb91ca27610e7730))\n* **typeahead:**\n  * Fix for memory-leak in typeahead ([b5a80c08](https://github.com/angular-ui/bootstrap/commit/b5a80c08f2364db2170766366cc7433661e737ae))\n  * reset 'parse' validation key ([c0a9c707](https://github.com/angular-ui/bootstrap/commit/c0a9c707903fa3dfc662887cd348ee2b0bf13f85), closes [#3166](https://github.com/angular-ui/bootstrap/issues/3166))\n  * resolve property length of undefined error ([950c22cd](https://github.com/angular-ui/bootstrap/commit/950c22cdab3d0b3479a0d0515ed64e57c9ff046b), closes [#2999](https://github.com/angular-ui/bootstrap/issues/2999), [#3178](https://github.com/angular-ui/bootstrap/issues/3178))\n  * set validity if model is set manually ([b0044433](https://github.com/angular-ui/bootstrap/commit/b004443371d75bb61e9a17a00a58b485c1279aee), closes [#3318](https://github.com/angular-ui/bootstrap/issues/3318))\n  * $compile match template after adding to DOM ([03446c56](https://github.com/angular-ui/bootstrap/commit/03446c56335d5ee0970f9730af215fea1dd53f48))\n\n\n## Features\n\n* **dateparser:**\n  * Add support for HH, H, mm, m, ss, s formats ([971a1b57](https://github.com/angular-ui/bootstrap/commit/971a1b57394c1e1088564e0809c1341620586aa0), closes [#2509](https://github.com/angular-ui/bootstrap/issues/2509), [#3159](https://github.com/angular-ui/bootstrap/issues/3159), [#3417](https://github.com/angular-ui/bootstrap/issues/3417))\n  * add support for milliseconds ([82cb637d](https://github.com/angular-ui/bootstrap/commit/82cb637dd12288d9fa0d42783f672fc71b94ffda), closes [#3537](https://github.com/angular-ui/bootstrap/issues/3537))\n* **datepicker:**\n  * support HTML5 month input type ([aef8953c](https://github.com/angular-ui/bootstrap/commit/aef8953c792066c008d8ce97918153f756a39112), closes [#3499](https://github.com/angular-ui/bootstrap/issues/3499))\n  * support HTML5 date input type ([1a9e88fe](https://github.com/angular-ui/bootstrap/commit/1a9e88fe9e6a078943273012429788e742f8ebbd), closes [#3499](https://github.com/angular-ui/bootstrap/issues/3499))\n  * Add custom class to specific days via outside logic ([0bcd30c4](https://github.com/angular-ui/bootstrap/commit/0bcd30c4a6d65763e1c4d1b6820a4537a11e95ee))\n* **dropdown:**\n  * Dropdown append-to-body ([dfe9854b](https://github.com/angular-ui/bootstrap/commit/dfe9854be3d0ae82f361cefec19a6f692aa41591), closes [#3411](https://github.com/angular-ui/bootstrap/issues/3411), [#1030](https://github.com/angular-ui/bootstrap/issues/1030))\n  * Make Auto-Close Dropdowns optional. ([a50f1120](https://github.com/angular-ui/bootstrap/commit/a50f11201eeb18569dca0ac3bb157e43cb9d9076), closes [#2218](https://github.com/angular-ui/bootstrap/issues/2218), [#3045](https://github.com/angular-ui/bootstrap/issues/3045))\n* **modal:**\n  * Add a vetoable modal.closing event ([a5a82d9b](https://github.com/angular-ui/bootstrap/commit/a5a82d9be7dc0bd1cfd510bacf9aa411c6efe1bc))\n  * pass reason when opened promise rejected ([0ad208d6](https://github.com/angular-ui/bootstrap/commit/0ad208d6a0164517b85d8e8181e4a0fc98f4f5ec), closes [#2978](https://github.com/angular-ui/bootstrap/issues/2978))\n  * add option to disable animations ([5e661d47](https://github.com/angular-ui/bootstrap/commit/5e661d47d6698fdfebb52402eb1687e5697c0040), closes [#1007](https://github.com/angular-ui/bootstrap/issues/1007), [#2725](https://github.com/angular-ui/bootstrap/issues/2725))\n* **popover:**\n  * respect popover-class option ([88a41dce](https://github.com/angular-ui/bootstrap/commit/88a41dce86c33bf1e0665d880ca68d3535aed553), closes [#3569](https://github.com/angular-ui/bootstrap/issues/3569))\n  * use expression to fix usage with $sce ([422c8234](https://github.com/angular-ui/bootstrap/commit/422c823460663151e3482275d9c6749bb714a415), closes [#3558](https://github.com/angular-ui/bootstrap/issues/3558))\n  * add popover-template directive ([7e3179ab](https://github.com/angular-ui/bootstrap/commit/7e3179ab9fdbba6c00ff1c30f97b432b58f9eafb))\n* **progressbar:** allow dynamic update to max ([7ccff028](https://github.com/angular-ui/bootstrap/commit/7ccff028ff5ed19af63d9cf1f5e078223924f3a0))\n* **rating:** add rounding logic to rating value ([b076483c](https://github.com/angular-ui/bootstrap/commit/b076483caa9b672068ae8d0cbdfeaed68f530861), closes [#3413](https://github.com/angular-ui/bootstrap/issues/3413), [#3415](https://github.com/angular-ui/bootstrap/issues/3415))\n* **tabs:** it should not select first not active tab as selected ([91b5fb62](https://github.com/angular-ui/bootstrap/commit/91b5fb62eedbb600d6a6abe32376846f327a903d))\n* **timepicker:**\n  * always pad minutes ([6324486d](https://github.com/angular-ui/bootstrap/commit/6324486d70edc01fb28f61f0a68a6a490378d602), closes [#1598](https://github.com/angular-ui/bootstrap/issues/1598), [#3533](https://github.com/angular-ui/bootstrap/issues/3533))\n  * have up/down arrow keys control time selection ([22961157](https://github.com/angular-ui/bootstrap/commit/22961157ad4636db12336abba54cc893554b99d6))\n* **tooltip:**\n  * use expression to fix usage with $sce ([d867f830](https://github.com/angular-ui/bootstrap/commit/d867f8302d4632a34c81d151923b497f9af3fcfd), closes [#3558](https://github.com/angular-ui/bootstrap/issues/3558))\n  * add tooltip-html directive ([e31fcf0f](https://github.com/angular-ui/bootstrap/commit/e31fcf0fcb06580064d1e6375dbedb69f1c95f25), closes [#3496](https://github.com/angular-ui/bootstrap/issues/3496))\n  * add tooltip-template directive ([a1695114](https://github.com/angular-ui/bootstrap/commit/a1695114a245312878d315dfc9e369f98d573eae), closes [#220](https://github.com/angular-ui/bootstrap/issues/220))\n  * update position dynamically ([853fa457](https://github.com/angular-ui/bootstrap/commit/853fa4578a1f127fee9283e725d6e19789882121), closes [#96](https://github.com/angular-ui/bootstrap/issues/96), [#1109](https://github.com/angular-ui/bootstrap/issues/1109), [#2816](https://github.com/angular-ui/bootstrap/issues/2816), [#3435](https://github.com/angular-ui/bootstrap/issues/3435))\n  * Support for tooltip-class configuration ([d784354a](https://github.com/angular-ui/bootstrap/commit/d784354a53fa40597cb8c124405ea9a90b6f0b8e), closes [#3126](https://github.com/angular-ui/bootstrap/issues/3126))\n* **transition:** deprecate transition module ([8a552443](https://github.com/angular-ui/bootstrap/commit/8a552443741d1e5b4b29d9da9c7e9990fa37886c), closes [#3497](https://github.com/angular-ui/bootstrap/issues/3497))\n\n\n<a name=\"0.12.1\"></a>\n# [0.12.1](https://github.com/angular-ui/bootstrap/compare/0.12.0...0.12.1) (2015-02-20)\n\n## Bug Fixes\n\n- **tooltip:**\n  - incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef))\n\n<a name=\"0.12.0\"></a>\n# [0.12.0](https://github.com/angular-ui/bootstrap/compare/0.11.2...0.12.0) (2014-11-16)\n\n\n## Bug Fixes\n\n* **accordion:** make header links keyboard accessible ([992b2329](http://github.com/angular-ui/bootstrap/commit/992b23297cd100ab4e236fba469e3a70566a4163), closes [#2869](http://github.com/angular-ui/bootstrap/issues/2869))\n* **build:** make custom builds on demo site work ([390f2bf6](http://github.com/angular-ui/bootstrap/commit/390f2bf6b0846ee640e86ad87bbae8c449e53026), closes [#2960](http://github.com/angular-ui/bootstrap/issues/2960), [#2847](http://github.com/angular-ui/bootstrap/issues/2847), [#2625](http://github.com/angular-ui/bootstrap/issues/2625), [#2489](http://github.com/angular-ui/bootstrap/issues/2489), [#2357](http://github.com/angular-ui/bootstrap/issues/2357), [#2176](http://github.com/angular-ui/bootstrap/issues/2176), [#2892](http://github.com/angular-ui/bootstrap/issues/2892))\n* **carousel:** replaced $timeout with $interval when it was wrong ([392c0ad1](http://github.com/angular-ui/bootstrap/commit/392c0ad13ca9b65be5e77ac0c68de24ead8ea2ce), closes [#1308](http://github.com/angular-ui/bootstrap/issues/1308), [#2454](http://github.com/angular-ui/bootstrap/issues/2454), [#2776](http://github.com/angular-ui/bootstrap/issues/2776))\n* **datepicker:** correct button alignment when using bootstrap v3.2.0 ([460fbec7](http://github.com/angular-ui/bootstrap/commit/460fbec776c6d08d0e7db40aedd29d10ac48d7e9), closes [#2728](http://github.com/angular-ui/bootstrap/issues/2728))\n* **demo:** initial load of fragment URLs ([eab6daf6](http://github.com/angular-ui/bootstrap/commit/eab6daf64b3c963d8e285e254c75af5f97c24ec1), closes [#2762](http://github.com/angular-ui/bootstrap/issues/2762))\n* **dropdown:**\n  * compatibility with `$location` url rewriting ([ef095170](http://github.com/angular-ui/bootstrap/commit/ef09517061b0b4c0c9e9f85086635af33207ec54), closes [#2343](http://github.com/angular-ui/bootstrap/issues/2343))\n  * remove `C` restrictions to avoid conflicts ([192768e1](http://github.com/angular-ui/bootstrap/commit/192768e109b5c4a50c7dcd320e09d05fedd4298a), closes [#2156](http://github.com/angular-ui/bootstrap/issues/2156), [#2170](http://github.com/angular-ui/bootstrap/issues/2170))\n* **tabs:**\n  * make tab links keyboard accessible ([5df524b7](http://github.com/angular-ui/bootstrap/commit/5df524b77114bccdc9a49540e1eb52a564ee5dfd), closes [#2226](http://github.com/angular-ui/bootstrap/issues/2226), [#2290](http://github.com/angular-ui/bootstrap/issues/2290), [#2870](http://github.com/angular-ui/bootstrap/issues/2870), [#2304](http://github.com/angular-ui/bootstrap/issues/2304))\n  * don't select tabs on destroy ([9939867a](http://github.com/angular-ui/bootstrap/commit/9939867aba0b7b763588b18829b557c052ea69ba), closes [#2155](http://github.com/angular-ui/bootstrap/issues/2155), [#2596](http://github.com/angular-ui/bootstrap/issues/2596))\n* **tests:** usage of undefined variables ([34273ff0](http://github.com/angular-ui/bootstrap/commit/34273ff0107ecfa28438a7389d94ca619b8704e5))\n* **tooltip:**\n  * remove extra digest causing incompatibility ([32c4704b](http://github.com/angular-ui/bootstrap/commit/32c4704b748cecf2de4c651f2e5157c1ef6c88b1), closes [#2951](http://github.com/angular-ui/bootstrap/issues/2951), [#2959](http://github.com/angular-ui/bootstrap/issues/2959))\n  * show correct tooltip on `ng-repeat` ([b4832c4b](http://github.com/angular-ui/bootstrap/commit/b4832c4b551af7e580ed65d9e5aaee1ef9e6c53e), closes [#2935](http://github.com/angular-ui/bootstrap/issues/2935))\n  * memory leak on show/hide ([faf38d20](http://github.com/angular-ui/bootstrap/commit/faf38d20a49176f2016f7f7d4fa49a5c438a986e), closes [#2709](http://github.com/angular-ui/bootstrap/issues/2709), [#2919](http://github.com/angular-ui/bootstrap/issues/2919))\n  * remove child scope requirement ([8204c808](http://github.com/angular-ui/bootstrap/commit/8204c8088139165ac9b2ad3977a2c20570e434cb), closes [#1269](http://github.com/angular-ui/bootstrap/issues/1269), [#2320](http://github.com/angular-ui/bootstrap/issues/2320), [#2203](http://github.com/angular-ui/bootstrap/issues/2203))\n  * evaluate appendToBody on init ([e10d561f](http://github.com/angular-ui/bootstrap/commit/e10d561f92c2927be0ec429761fa229520fb9a51), closes [#2921](http://github.com/angular-ui/bootstrap/issues/2921))\n  * don't use an empty transclusion fn ([689c4d01](http://github.com/angular-ui/bootstrap/commit/689c4d017d303b6d758164ee12837a172bb01139), closes [#2825](http://github.com/angular-ui/bootstrap/issues/2825))\n* **typeahead:** don't leak DOM nodes ([1f6c3c92](http://github.com/angular-ui/bootstrap/commit/1f6c3c92af0e343c7e34b85ea6d270ac79bf6755))\n\n\n## Features\n\n* **alert:** allow alerts to be closed from a controller ([ca6fad67](http://github.com/angular-ui/bootstrap/commit/ca6fad675bf2aa793896bf3e086330667a5d9051), closes [#2399](http://github.com/angular-ui/bootstrap/issues/2399), [#2854](http://github.com/angular-ui/bootstrap/issues/2854))\n* **typeahead:** add focus-first option ([35d0cc1d](http://github.com/angular-ui/bootstrap/commit/35d0cc1d57302883840f7ad54a03918ae2df001d), closes [#908](http://github.com/angular-ui/bootstrap/issues/908), [#2916](http://github.com/angular-ui/bootstrap/issues/2916))\n\n\n## Breaking Changes\n\n* `tooltip-trigger` and `popover-trigger` are no longer watched\nattributes.\n([a65bea95](http://github.com/angular-ui/bootstrap/commit/a65bea95338802b026fd213805b095b5a0b5b393))\nThis affects both popovers and tooltips. The *triggers are now set up\nonce* and can no longer be changed after initialization.\n\n* `dropdown` and `dropdown-toggle` are attribute-only directives. ([192768e1](http://github.com/angular-ui/bootstrap/commit/192768e109b5c4a50c7dcd320e09d05fedd4298a))\n\n  Before:\n    ```html\n    <button class=\"dropdown-toggle\" ...>\n    ```\n    After:\n    ```html\n    <button class=\"dropdown-toggle\" dropdown-toggle ...>\n    ```\n\n\n<a name=\"0.11.2\"></a>\n# [0.11.2](https://github.com/angular-ui/bootstrap/compare/0.11.1...0.11.2) (2014-09-26)\n\nRevert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/bootstrap/commit/1a998c4))\n\n<a name=\"0.11.1\"></a>\n# [0.11.1](https://github.com/angular-ui/bootstrap/compare/0.11.0...0.11.1) (2014-09-26)\n\n## Features\n\n- **modal:**\n  - add backdropClass option, similar to windowClass option ([353e6127](http://github.com/angular-ui/bootstrap/commit/353e6127))\n  - support alternative controllerAs syntax ([8d7c2a26](http://github.com/angular-ui/bootstrap/commit/8d7c2a26))\n  - allow templateUrl to be a function ([990015fb](http://github.com/angular-ui/bootstrap/commit/990015fb))\n\n## Bug Fixes\n\n- **alert:**\n  - correct binding of alert type class ([aa188aec](http://github.com/angular-ui/bootstrap/commit/aa188aec))\n- **dateparser:**\n  - do not parse if no format specified ([42cc3f26](http://github.com/angular-ui/bootstrap/commit/42cc3f26))\n- **datepicker:**\n  - correct `datepicker-mode` binding for popup ([63ae06c9](http://github.com/angular-ui/bootstrap/commit/63ae06c9))\n  - memory leak fix for datepicker ([08c150e1](http://github.com/angular-ui/bootstrap/commit/08c150e1))\n- **dropdown:**\n  - close after selecting an item ([3ac3b487](http://github.com/angular-ui/bootstrap/commit/3ac3b487))\n  - remove `C` restrictions to avoid conflicts ([7512b93f](http://github.com/angular-ui/bootstrap/commit/7512b93f))\n- **modal:**\n  - allow modal.{dismiss,close} to be called again ([1590920c](http://github.com/angular-ui/bootstrap/commit/1590920c))\n  - add a work-around for transclusion scope ([0b31e865](http://github.com/angular-ui/bootstrap/commit/0b31e865))\n  - allow in-lined controller-as controllers ([79105368](http://github.com/angular-ui/bootstrap/commit/79105368))\n  - respect autofocus on child elements ([e62ab94a](http://github.com/angular-ui/bootstrap/commit/e62ab94a))\n  - controllerAs not checked ([7b7cdf84](http://github.com/angular-ui/bootstrap/commit/7b7cdf84))\n- **tabs:**\n  - remove leading newline from a template ([a708fe6d](http://github.com/angular-ui/bootstrap/commit/a708fe6d))\n- **typeahead:**\n  - timeout cancellation when deleting characters ([5dc57927](http://github.com/angular-ui/bootstrap/commit/5dc57927))\n  - allow multiple line expression ([c7db0df4](http://github.com/angular-ui/bootstrap/commit/c7db0df4))\n  - replace ng-if with ng-show in matches popup ([a0be450d](http://github.com/angular-ui/bootstrap/commit/a0be450d))\n\n<a name=\"0.11.0\"></a>\n# [0.11.0](https://github.com/angular-ui/bootstrap/compare/0.10.0...0.11.0) (2014-05-01)\n\n## Features\n\n- **accordion:**\n  - support `is-disabled` state ([9c43ae7c](http://github.com/angular-ui/bootstrap/commit/9c43ae7c))\n- **alert:**\n  - add WAI-ARIA markup ([9a2638bf](http://github.com/angular-ui/bootstrap/commit/9a2638bf))\n- **button:**\n  - allow uncheckable radio button ([82df4fb1](http://github.com/angular-ui/bootstrap/commit/82df4fb1))\n- **carousel:**\n  - Support swipe for touchscreen devices ([85140f84](http://github.com/angular-ui/bootstrap/commit/85140f84))\n- **dateParser:**\n  - add `dateParser` service ([bd2ae0ee](http://github.com/angular-ui/bootstrap/commit/bd2ae0ee))\n- **datepicker:**\n  - add `datepicker-mode`, `init-date` & today hint ([7f4b40eb](http://github.com/angular-ui/bootstrap/commit/7f4b40eb))\n  - make widget accessible ([2423f6d4](http://github.com/angular-ui/bootstrap/commit/2423f6d4))\n  - full six-week calendar ([b0b14343](http://github.com/angular-ui/bootstrap/commit/b0b14343))\n- **dropdown:**\n  - add WAI-ARIA attributes ([22ebd230](http://github.com/angular-ui/bootstrap/commit/22ebd230))\n  - focus toggle element when opening or closing with Esc` ([f715d052](http://github.com/angular-ui/bootstrap/commit/f715d052))\n- **dropdownToggle:**\n  - support programmatic trigger & toggle callback ([ae31079c](http://github.com/angular-ui/bootstrap/commit/ae31079c))\n  - add support for `escape` key ([1417c548](http://github.com/angular-ui/bootstrap/commit/1417c548))\n- **modal:**\n  - support custom template for modal window ([96def3d6](http://github.com/angular-ui/bootstrap/commit/96def3d6))\n  - support modal window sizes ([976f6083](http://github.com/angular-ui/bootstrap/commit/976f6083))\n  - improve accessibility - add role='dialog' ([60cee9dc](http://github.com/angular-ui/bootstrap/commit/60cee9dc))\n- **pagination:**\n  - plug into `ngModel` controller ([d65901cf](http://github.com/angular-ui/bootstrap/commit/d65901cf))\n- **progressbar:**\n  - make widget accessible ([9dfe3157](http://github.com/angular-ui/bootstrap/commit/9dfe3157))\n- **rating:**\n  - plug into `ngModel` controller ([47e227f6](http://github.com/angular-ui/bootstrap/commit/47e227f6))\n  - make widget accessible ([4f56e60e](http://github.com/angular-ui/bootstrap/commit/4f56e60e))\n- **tooltip:**\n  - support more positioning options ([3704db9a](http://github.com/angular-ui/bootstrap/commit/3704db9a))\n- **typeahead:**\n  - add WAI-ARIA markup ([5ca23e97](http://github.com/angular-ui/bootstrap/commit/5ca23e97))\n  - add `aria-owns` & `aria-activedescendant` roles ([4c76a858](http://github.com/angular-ui/bootstrap/commit/4c76a858))\n\n## Bug Fixes\n\n- **alert:**\n  - use interpolation for type attribute ([f0a129ad](http://github.com/angular-ui/bootstrap/commit/f0a129ad))\n  - add `alert-dismissable` class ([794954af](http://github.com/angular-ui/bootstrap/commit/794954af))\n- **carousel:**\n  - correct glyphicon ([3b6ab25b](http://github.com/angular-ui/bootstrap/commit/3b6ab25b))\n- **datepicker:**\n  - remove unneeded date creation ([68cb2e5a](http://github.com/angular-ui/bootstrap/commit/68cb2e5a))\n  - `Today` button should not set time ([e1993491](http://github.com/angular-ui/bootstrap/commit/e1993491))\n  - mark input field as invalid if the date is invalid ([467dd159](http://github.com/angular-ui/bootstrap/commit/467dd159))\n  - rename `dateFormat` to `datepickerPopup` in datepickerPopupConfig ([93da30d5](http://github.com/angular-ui/bootstrap/commit/93da30d5))\n  - parse input using dateParser ([e0eb1bce](http://github.com/angular-ui/bootstrap/commit/e0eb1bce))\n- **dropdown:**\n  - use $animate for adding and removing classes ([e8d5fefc](http://github.com/angular-ui/bootstrap/commit/e8d5fefc))\n  - unbind toggle element event on scope destroy ([890e2d37](http://github.com/angular-ui/bootstrap/commit/890e2d37))\n  - do not call `on-toggle` initially ([004dd1de](http://github.com/angular-ui/bootstrap/commit/004dd1de))\n  - ensure `on-toggle` works when `is-open` is not used ([06ad3bd5](http://github.com/angular-ui/bootstrap/commit/06ad3bd5))\n- **modal:**\n  - destroy modal scope after animation end ([dfc36fd9](http://github.com/angular-ui/bootstrap/commit/dfc36fd9))\n  - backdrop z-index when stacking modals ([94a7f593](http://github.com/angular-ui/bootstrap/commit/94a7f593))\n  - give a reason of rejection when escape key pressed ([cb31b875](http://github.com/angular-ui/bootstrap/commit/cb31b875))\n  - prevent default event when closing via escape key ([da951222](http://github.com/angular-ui/bootstrap/commit/da951222))\n  - toggle 'modal-open' class after animation ([4d641ca7](http://github.com/angular-ui/bootstrap/commit/4d641ca7))\n- **pagination:**\n  - take maxSize defaults into account ([a294c87f](http://github.com/angular-ui/bootstrap/commit/a294c87f))\n- **position:**\n  - remove deprecated body scrollTop and scrollLeft ([1ba07c1b](http://github.com/angular-ui/bootstrap/commit/1ba07c1b))\n- **progressbar:**\n  - allow fractional values for bar width ([0daa7a74](http://github.com/angular-ui/bootstrap/commit/0daa7a74))\n  - number filter in bar template and only for percent ([378a9337](http://github.com/angular-ui/bootstrap/commit/378a9337))\n- **tabs:**\n  - fire deselect before select callback ([7474c47b](http://github.com/angular-ui/bootstrap/commit/7474c47b))\n  - use interpolation for type attribute ([83ceb78a](http://github.com/angular-ui/bootstrap/commit/83ceb78a))\n  - remove `tabbable` class required for left/right tabs ([19468331](http://github.com/angular-ui/bootstrap/commit/19468331))\n- **timepicker:**\n  - evaluate correctly the `readonly-input` attribute ([f9b6c496](http://github.com/angular-ui/bootstrap/commit/f9b6c496))\n- **tooltip:**\n  - animation causes tooltip to hide on show ([2b429f5d](http://github.com/angular-ui/bootstrap/commit/2b429f5d))\n- **typeahead:**\n  - correctly handle append to body attribute ([10785736](http://github.com/angular-ui/bootstrap/commit/10785736))\n  - correctly higlight numeric matches ([09678b12](http://github.com/angular-ui/bootstrap/commit/09678b12))\n  - loading callback updates after blur ([6a830116](http://github.com/angular-ui/bootstrap/commit/6a830116))\n  - incompatibility with ng-focus ([d0024931](http://github.com/angular-ui/bootstrap/commit/d0024931))\n\n## Breaking Changes\n\n- **alert:**\n Use interpolation for type attribute.\n\n  Before:\n\n  ```html\n  <alert type=\"'info'\" ...></alert >\n  ```\n  or\n  ```html\n  <alert type=\"alert.type\" ...></alert >\n  ```\n\n  After:\n\n  ```html\n  <alert type=\"info\" ...></alert >\n  ```\n  or\n  ```html\n  <alert type=\"{{alert.type}}\" ...></alert >\n  ```\n\n- **datepicker:**\n\n`show-weeks` is no longer a watched attribute\n`*-format` attributes have been renamed to `format-*`\n`min` attribute has been renamed to `min-date`\n`max` attribute has been renamed to `max-date`\n`Open on focus` has been removed. Read more on this ([comment](https://github.com/angular-ui/bootstrap/pull/1922#issuecomment-40491716)).\n`dateFormat` renamed to `datepickerPopup` in datepickerPopupConfig\n\n- **dropdown:**\n\n Elements with the `dropdown-toggle` directive must have a parent element with the `dropdown` directive.\n\n- **pagination:**\n\n Both `pagination` and `pager` are now integrated with `ngModelController`.\n * `page` is replaced from `ng-model`.\n * `on-select-page` is removed since `ng-change` can now be used.\n\n  Before:\n\n  ```html\n  <pagination page=\"current\" on-select-page=\"changed(page)\" ...></pagination>\n  ```\n\n  After:\n\n  ```html\n  <pagination ng-model=\"current\" ng-change=\"changed()\" ...></pagination>\n  ```\n\n- **rating:**\n `rating` is now integrated with `ngModelController`.\n * `value` is replaced from `ng-model`.\n\n  Before:\n\n  ```html\n  <rating value=\"rate\" ...></rating>\n  ```\n\n  After:\n\n  ```html\n  <rating ng-model=\"rate\" ...></rating>\n  ```\n\n- **tabs:**\n\n Use interpolation for type attribute.\n\n  Before:\n\n  ```html\n  <tabset type=\"'pills'\" ...></tabset >\n  <!-- or -->\n  <tabset type=\"navtype\" ...></tabset>\n  ```\n\n  After:\n\n  ```html\n  <tabset type=\"pills\" ...></tabset>\n  <!-- or -->\n  <tabset type=\"{{navtype}}\" ...></tabset>\n  ```\n\n<a name=\"0.10.0\"></a>\n# [0.10.0](https://github.com/angular-ui/bootstrap/compare/0.9.0...0.10.0) (2014-01-13)\n\n_This release adds AngularJS 1.2 support_\n\n## Features\n\n- **modal:**\n  - expose dismissAll on $modalStack ([bc8d21c1](http://github.com/angular-ui/bootstrap/commit/bc8d21c1))\n\n## Bug Fixes\n\n- **datepicker:**\n  - evaluate `show-weeks` from `datepicker-options` ([92c1715f](http://github.com/angular-ui/bootstrap/commit/92c1715f))\n- **modal:**\n  - leaking watchers due to scope re-use ([0754ad7b](http://github.com/angular-ui/bootstrap/commit/0754ad7b))\n  - support close animation ([1933488c](http://github.com/angular-ui/bootstrap/commit/1933488c))\n- **timepicker:**\n  - add correct type for meridian button ([bcf39efe](http://github.com/angular-ui/bootstrap/commit/bcf39efe))\n- **tooltip:**\n  - performance and scope fixes ([c0df3201](http://github.com/angular-ui/bootstrap/commit/c0df3201))\n\n<a name=\"0.9.0\"></a>\n# [0.9.0](https://github.com/angular-ui/bootstrap/compare/0.8.0...0.9.0) (2013-12-28)\n\n_This release adds Bootstrap3 support_\n\n## Features\n\n- **accordion:**\n  - convert to bootstrap3 panel styling ([458a9bd3](http://github.com/angular-ui/bootstrap/commit/458a9bd3))\n- **carousel:**\n  - some changes for Bootstrap3 ([1f632b65](http://github.com/angular-ui/bootstrap/commit/1f632b65))\n- **collapse:**\n  - make collapse work with bootstrap3 ([517dff6e](http://github.com/angular-ui/bootstrap/commit/517dff6e))\n- **datepicker:**\n  - update to Bootstrap 3 ([37684330](http://github.com/angular-ui/bootstrap/commit/37684330))\n- **modal:**\n  - added bootstrap3 support ([444c488d](http://github.com/angular-ui/bootstrap/commit/444c488d))\n- **pagination:**\n  - support bootstrap3 ([3db699d7](http://github.com/angular-ui/bootstrap/commit/3db699d7))\n- **progressbar:**\n  - update to bootstrap3 ([5bcff623](http://github.com/angular-ui/bootstrap/commit/5bcff623))\n- **rating:**\n  - update rating to bootstrap3 ([7e60284e](http://github.com/angular-ui/bootstrap/commit/7e60284e))\n- **tabs:**\n  - add nav-justified ([3199dd88](http://github.com/angular-ui/bootstrap/commit/3199dd88))\n- **timepicker:**\n  - restyled for bootstrap 3 ([6724a721](http://github.com/angular-ui/bootstrap/commit/6724a721))\n- **typeahead:**\n  - update to Bootstrap 3 ([eadf934a](http://github.com/angular-ui/bootstrap/commit/eadf934a))\n\n## Bug Fixes\n\n- **alert:**\n  - update template to Bootstrap 3 ([dfc3b0bd](http://github.com/angular-ui/bootstrap/commit/dfc3b0bd))\n- **collapse:**\n  - Prevent consecutive transitions & tidy up code ([b0032d68](http://github.com/angular-ui/bootstrap/commit/b0032d68))\n  - fixes after rebase ([dc02ad1d](http://github.com/angular-ui/bootstrap/commit/dc02ad1d))\n- **rating:**\n  - user glyhicon classes ([d221d517](http://github.com/angular-ui/bootstrap/commit/d221d517))\n- **timepicker:**\n  - fix look with bootstrap3 ([9613b61b](http://github.com/angular-ui/bootstrap/commit/9613b61b))\n- **tooltip:**\n  - re-position tooltip after draw ([a99b3608](http://github.com/angular-ui/bootstrap/commit/a99b3608))\n\n<a name=\"0.8.0\"></a>\n# [0.8.0](https://github.com/angular-ui/bootstrap/compare/0.7.0...0.8.0) (2013-12-28)\n\n## Features\n\n- **datepicker:**\n  - option whether to display button bar in popup ([4d158e0d](http://github.com/angular-ui/bootstrap/commit/4d158e0d))\n- **modal:**\n  - add modal-open class to body on modal open ([e76512fa](http://github.com/angular-ui/bootstrap/commit/e76512fa))\n- **progressbar:**\n  - add `max` attribute & support transclusion ([365573ab](http://github.com/angular-ui/bootstrap/commit/365573ab))\n- **timepicker:**\n  - default meridian labels based on locale ([8b1ab79a](http://github.com/angular-ui/bootstrap/commit/8b1ab79a))\n- **typeahead:**\n  - add typeahead-append-to-body option ([dd8eac22](http://github.com/angular-ui/bootstrap/commit/dd8eac22))\n\n## Bug Fixes\n\n- **accordion:**\n  - correct `is-open` handling for dynamic groups ([9ec21286](http://github.com/angular-ui/bootstrap/commit/9ec21286))\n- **carousel:**\n  - cancel timer on scope destruction ([5b9d929c](http://github.com/angular-ui/bootstrap/commit/5b9d929c))\n  - cancel goNext on scope destruction ([7515df45](http://github.com/angular-ui/bootstrap/commit/7515df45))\n- **collapse:**\n  - dont animate height changes from 0 to 0 ([81e014a8](http://github.com/angular-ui/bootstrap/commit/81e014a8))\n- **datepicker:**\n  - set default zero time after no date selected ([93cd0df8](http://github.com/angular-ui/bootstrap/commit/93cd0df8))\n  - fire `ngChange` on today/clear button press ([6b1c68fb](http://github.com/angular-ui/bootstrap/commit/6b1c68fb))\n  - remove datepicker's popup on scope destroy ([48955d69](http://github.com/angular-ui/bootstrap/commit/48955d69))\n  - remove edge case position updates ([1fbcb5d6](http://github.com/angular-ui/bootstrap/commit/1fbcb5d6))\n- **modal:**\n  - put backdrop in before window ([d64f4a97](http://github.com/angular-ui/bootstrap/commit/d64f4a97))\n  - grab reference to body when it is needed in lieu of when the factory is created ([dd415a98](http://github.com/angular-ui/bootstrap/commit/dd415a98))\n  - focus freshly opened modal ([709e679c](http://github.com/angular-ui/bootstrap/commit/709e679c))\n  - properly animate backdrops on each modal opening ([672a557a](http://github.com/angular-ui/bootstrap/commit/672a557a))\n- **tabs:**\n  - make nested tabs work ([c9acebbe](http://github.com/angular-ui/bootstrap/commit/c9acebbe))\n- **tooltip:**\n  - update tooltip content when empty ([60515ae1](http://github.com/angular-ui/bootstrap/commit/60515ae1))\n  - support IE8 ([5dd98238](http://github.com/angular-ui/bootstrap/commit/5dd98238))\n  - unbind element events on scope destroy ([3fe7aa8c](http://github.com/angular-ui/bootstrap/commit/3fe7aa8c))\n  - respect animate attribute ([54e614a8](http://github.com/angular-ui/bootstrap/commit/54e614a8))\n\n## Breaking Changes\n\n- **progressbar:**\n The onFull/onEmpty handlers & auto/stacked types have been removed.\n\n  To migrate your code change your markup like below.\n\n  Before:\n\n```html\n  <progress percent=\"var\" class=\"progress-warning\"></progress>\n```\n\n  After:\n\n```html\n  <progressbar value=\"var\" type=\"warning\"></progressbar>\n```\n\n  and for stacked instead of passing array/objects you can do:\n\n```html\n  <progress><bar ng-repeat=\"obj in objs\" value=\"obj.var\" type=\"{{obj.type}}\"></bar></progress>\n```\n\n<a name=\"0.7.0\"></a>\n# [0.7.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-11-22)\n\n## Features\n\n- **datepicker:**\n  - add i18n support for bar buttons in popup ([c6ba8d7f](http://github.com/angular-ui/bootstrap/commit/c6ba8d7f))\n  - dynamic date format for popup ([aa3eaa91](http://github.com/angular-ui/bootstrap/commit/aa3eaa91))\n  - datepicker-append-to-body attribute ([0cdc4609](http://github.com/angular-ui/bootstrap/commit/0cdc4609))\n- **dropdownToggle:**\n  - disable dropdown when it has the disabled class ([104bdd1b](http://github.com/angular-ui/bootstrap/commit/104bdd1b))\n- **tooltip:**\n  - add ability to enable / disable tooltip ([5d9bd058](http://github.com/angular-ui/bootstrap/commit/5d9bd058))\n\n## Bug Fixes\n\n- **accordion:**\n  - assign `is-open` to correct scope ([157f614a](http://github.com/angular-ui/bootstrap/commit/157f614a))\n- **collapse:**\n  - remove element height watching ([a72c635c](http://github.com/angular-ui/bootstrap/commit/a72c635c))\n  - add the \"in\" class for expanded panels ([9eca35a8](http://github.com/angular-ui/bootstrap/commit/9eca35a8))\n- **datepicker:**\n  - some IE8 compatibility improvements ([4540476f](http://github.com/angular-ui/bootstrap/commit/4540476f))\n  - set popup initial position in append-to-body case ([78a1e9d7](http://github.com/angular-ui/bootstrap/commit/78a1e9d7))\n  - properly handle showWeeks config option ([570dba90](http://github.com/angular-ui/bootstrap/commit/570dba90))\n- **modal:**\n  - correctly close modals with no backdrop ([e55c2de3](http://github.com/angular-ui/bootstrap/commit/e55c2de3))\n- **pagination:**\n  - fix altering of current page caused by totals change ([81164dae](http://github.com/angular-ui/bootstrap/commit/81164dae))\n  - handle extreme values for `total-items` ([8ecf93ed](http://github.com/angular-ui/bootstrap/commit/8ecf93ed))\n- **position:**\n  - correct positioning for SVG elements ([968e5407](http://github.com/angular-ui/bootstrap/commit/968e5407))\n- **tabs:**\n  - initial tab selection ([a08173ec](http://github.com/angular-ui/bootstrap/commit/a08173ec))\n- **timepicker:**\n  - use html5 for input elements ([53709f0f](http://github.com/angular-ui/bootstrap/commit/53709f0f))\n- **tooltip:**\n  - restore html-unsafe compatibility with AngularJS 1.2 ([08d8b21d](http://github.com/angular-ui/bootstrap/commit/08d8b21d))\n  - hide tooltips when content becomes empty ([cf5c27ae](http://github.com/angular-ui/bootstrap/commit/cf5c27ae))\n  - tackle DOM node and event handlers leak ([0d810acd](http://github.com/angular-ui/bootstrap/commit/0d810acd))\n- **typeahead:**\n  - do not set editable error when input is empty ([006986db](http://github.com/angular-ui/bootstrap/commit/006986db))\n  - remove popup flickering ([dde804b6](http://github.com/angular-ui/bootstrap/commit/dde804b6))\n  - don't show matches if an element is not focused ([d1f94530](http://github.com/angular-ui/bootstrap/commit/d1f94530))\n  - fix loading callback when deleting characters ([0149eff6](http://github.com/angular-ui/bootstrap/commit/0149eff6))\n  - prevent accidental form submission on ENTER ([253c49ff](http://github.com/angular-ui/bootstrap/commit/253c49ff))\n  - evaluate matches source against a correct scope ([fd21214d](http://github.com/angular-ui/bootstrap/commit/fd21214d))\n  - support IE8 ([0e9f9980](http://github.com/angular-ui/bootstrap/commit/0e9f9980))\n\n<a name=\"0.6.0\"></a>\n# [0.6.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-09-08)\n\n## Features\n\n- **modal:**\n  - rewrite $dialog as $modal ([d7a48523](http://github.com/angular-ui/bootstrap/commit/d7a48523))\n  - add support for custom window settings ([015625d1](http://github.com/angular-ui/bootstrap/commit/015625d1))\n  - expose $close and $dismiss options on modal's scope ([8d153acb](http://github.com/angular-ui/bootstrap/commit/8d153acb))\n- **pagination:**\n  - `total-items` & optional `items-per-page` API ([e55d9063](http://github.com/angular-ui/bootstrap/commit/e55d9063))\n- **rating:**\n  - add support for custom icons per instance ([20ab01ad](http://github.com/angular-ui/bootstrap/commit/20ab01ad))\n- **timepicker:**\n  - plug into `ngModel` controller ([b08e993f](http://github.com/angular-ui/bootstrap/commit/b08e993f))\n\n## Bug Fixes\n\n- **carousel:**\n  - correct reflow triggering on FFox and Safari ([d34f2de1](http://github.com/angular-ui/bootstrap/commit/d34f2de1))\n- **datepicker:**\n  - correctly manage focus without jQuery present ([d474824b](http://github.com/angular-ui/bootstrap/commit/d474824b))\n  - compatibility with angular 1.1.5 and no jquery ([bf30898d](http://github.com/angular-ui/bootstrap/commit/bf30898d))\n  - use $setViewValue for inner changes ([dd99f35d](http://github.com/angular-ui/bootstrap/commit/dd99f35d))\n- **modal:**\n  - insert backdrop before modal window ([d870f212](http://github.com/angular-ui/bootstrap/commit/d870f212))\n  - ie8 fix after $modal rewrite ([ff9d969e](http://github.com/angular-ui/bootstrap/commit/ff9d969e))\n  - opening a modal should not change default options ([82532d1b](http://github.com/angular-ui/bootstrap/commit/82532d1b))\n  - backdrop should cover previously opened modals ([7fce2fe8](http://github.com/angular-ui/bootstrap/commit/7fce2fe8))\n  - allow replacing object with default options ([8e7fbf06](http://github.com/angular-ui/bootstrap/commit/8e7fbf06))\n- **position:**\n  - fallback for IE8's scrollTop/Left for offset ([9aecd4ed](http://github.com/angular-ui/bootstrap/commit/9aecd4ed))\n- **tabs:**\n  - add DI array-style annotations ([aac4a0dd](http://github.com/angular-ui/bootstrap/commit/aac4a0dd))\n  - evaluate `vertical` on parent scope ([9af6f96e](http://github.com/angular-ui/bootstrap/commit/9af6f96e))\n- **timepicker:**\n  - add type attribute for meridian button ([1f89fd4b](http://github.com/angular-ui/bootstrap/commit/1f89fd4b))\n- **tooltip:**\n  - remove placement='mouse' option ([17163c22](http://github.com/angular-ui/bootstrap/commit/17163c22))\n- **typeahead:**\n  - fix label rendering for equal model and items names ([5de71216](http://github.com/angular-ui/bootstrap/commit/5de71216))\n  - set validity flag for non-editable inputs ([366e0c8a](http://github.com/angular-ui/bootstrap/commit/366e0c8a))\n  - plug in front of existing parsers ([80cef614](http://github.com/angular-ui/bootstrap/commit/80cef614))\n  - highlight return match if no query ([45dd9be1](http://github.com/angular-ui/bootstrap/commit/45dd9be1))\n  - keep pop-up on clicking input ([5f9e270d](http://github.com/angular-ui/bootstrap/commit/5f9e270d))\n  - remove dependency on ng-bind-html-unsafe ([75893393](http://github.com/angular-ui/bootstrap/commit/75893393))\n\n## Breaking Changes\n\n- **modal:**\n\n* `$dialog` service was refactored into `$modal`\n* `modal` directive was removed - use the `$modal` service instead\n\nCheck the documentation for the `$modal` service to migrate from `$dialog`\n\n- **pagination:**\n API has undergone some changes in order to be easier to use.\n * `current-page` is replaced from `page`.\n * Number of pages is not defined by `num-pages`, but from `total-items` &\n  `items-per-page` instead. If `items-per-page` is missing, default is 10.\n * `num-pages` still exists but is just readonly.\n\n  Before:\n\n```html\n  <pagination num-pages=\"10\" ...></pagination>\n```\n\n  After:\n\n```html\n  <pagination total-items=\"100\" ...></pagination>\n```\n\n- **tooltip:**\n\n\nThe placment='mouse' is gone with no equivalent\n\n<a name=\"0.5.0\"></a>\n# [0.5.0](https://github.com/angular-ui/bootstrap/compare/0.4.0...0.5.0) (2013-08-04)\n\n## Features\n\n- **buttons:**\n  - support dynamic true / false values in btn-checkbox ([3e30cd94](http://github.com/angular-ui/bootstrap/commit/3e30cd94))\n- **datepicker:**\n  - `ngModelController` plug & new `datepickerPopup` ([dab18336](http://github.com/angular-ui/bootstrap/commit/dab18336))\n- **rating:**\n  - added onHover and onLeave. ([5b1115e3](http://github.com/angular-ui/bootstrap/commit/5b1115e3))\n- **tabs:**\n  - added onDeselect callback, used similarly as onSelect ([fe47c9bb](http://github.com/angular-ui/bootstrap/commit/fe47c9bb))\n  - add the ability to set the direction of the tabs ([220e7b60](http://github.com/angular-ui/bootstrap/commit/220e7b60))\n- **typeahead:**\n  - support custom templates for matched items ([e2238174](http://github.com/angular-ui/bootstrap/commit/e2238174))\n  - expose index to custom templates ([5ffae83d](http://github.com/angular-ui/bootstrap/commit/5ffae83d))\n\n## Bug Fixes\n\n- **datepicker:**\n  - handle correctly `min`/`max` when cleared ([566bdd16](http://github.com/angular-ui/bootstrap/commit/566bdd16))\n  - add type attribute for buttons ([25caf5fb](http://github.com/angular-ui/bootstrap/commit/25caf5fb))\n- **pagination:**\n  - handle `currentPage` number as string ([b1fa7bb8](http://github.com/angular-ui/bootstrap/commit/b1fa7bb8))\n  - use interpolation for text attributes ([f45815cb](http://github.com/angular-ui/bootstrap/commit/f45815cb))\n- **popover:**\n  - don't unbind event handlers created by other directives ([56f624a2](http://github.com/angular-ui/bootstrap/commit/56f624a2))\n  - correctly position popovers appended to body ([93a82af0](http://github.com/angular-ui/bootstrap/commit/93a82af0))\n- **rating:**\n  - evaluate `max` attribute on parent scope ([60619d51](http://github.com/angular-ui/bootstrap/commit/60619d51))\n- **tabs:**\n  - make tab contents be correctly connected to parent (#524) ([be7ecff0](http://github.com/angular-ui/bootstrap/commit/be7ecff0))\n  - Make tabset template correctly use tabset attributes (#584) ([8868f236](http://github.com/angular-ui/bootstrap/commit/8868f236))\n  - fix tab content compiling wrong (Closes #599, #631, #574) ([224bc2f5](http://github.com/angular-ui/bootstrap/commit/224bc2f5))\n  - make tabs added with active=true be selected ([360cd5ca](http://github.com/angular-ui/bootstrap/commit/360cd5ca))\n  - if tab is active at start, always select it ([ba1f741d](http://github.com/angular-ui/bootstrap/commit/ba1f741d))\n- **timepicker:**\n  - prevent date change ([ee741707](http://github.com/angular-ui/bootstrap/commit/ee741707))\n  - added wheel event to enable mousewheel on Firefox ([8dc92afa](http://github.com/angular-ui/bootstrap/commit/8dc92afa))\n- **tooltip:**\n  - fix positioning inside scrolling element ([63ae7e12](http://github.com/angular-ui/bootstrap/commit/63ae7e12))\n  - triggers should be local to tooltip instances ([58e8ef4f](http://github.com/angular-ui/bootstrap/commit/58e8ef4f))\n  - correctly handle initial events unbinding ([4fd5bf43](http://github.com/angular-ui/bootstrap/commit/4fd5bf43))\n  - bind correct 'hide' event handler ([d50b0547](http://github.com/angular-ui/bootstrap/commit/d50b0547))\n- **typeahead:**\n  - play nicelly with existing formatters ([d2df0b35](http://github.com/angular-ui/bootstrap/commit/d2df0b35))\n  - properly render initial input value ([c4e169cb](http://github.com/angular-ui/bootstrap/commit/c4e169cb))\n  - separate text field rendering and drop down rendering ([ea1e858a](http://github.com/angular-ui/bootstrap/commit/ea1e858a))\n  - fixed waitTime functionality ([90a8aa79](http://github.com/angular-ui/bootstrap/commit/90a8aa79))\n  - correctly close popup on match selection ([624fd5f5](http://github.com/angular-ui/bootstrap/commit/624fd5f5))\n\n## Breaking Changes\n\n- **pagination:**\n The 'first-text', 'previous-text', 'next-text' and 'last-text'\n  attributes are now interpolated.\n\n  To migrate your code, remove quotes for constant attributes and/or\n  interpolate scope variables.\n\n  Before:\n\n```html\n  <pagination first-text=\"'<<'\" ...></pagination>\n```\n  and/or\n\n```html\n  $scope.var1 = '<<';\n  <pagination first-text=\"var1\" ...></pagination>\n```\n  After:\n\n```html\n  <pagination first-text=\"<<\" ...></pagination>\n```\n  and/or\n\n```html\n  $scope.var1 = '<<';\n  <pagination first-text=\"{{var1}}\" ...></pagination>\n```\n\n<a name=\"0.4.0\"></a>\n# [0.4.0](https://github.com/angular-ui/bootstrap/compare/0.3.0...0.4.0) (2013-06-24)\n\n## Features\n\n- **buttons:**\n  - support dynamic values in btn-radio ([e8c5b548](http://github.com/angular-ui/bootstrap/commit/e8c5b548))\n- **carousel:**\n  - add option to prevent pause ([5f895c13](http://github.com/angular-ui/bootstrap/commit/5f895c13))\n- **datepicker:**\n  - add datepicker directive ([30a00a07](http://github.com/angular-ui/bootstrap/commit/30a00a07))\n- **pagination:**\n  - option for different mode when maxSize ([a023d082](http://github.com/angular-ui/bootstrap/commit/a023d082))\n  - add pager directive ([d9526475](http://github.com/angular-ui/bootstrap/commit/d9526475))\n- **tabs:**\n  - Change directive name, add features ([c5326595](http://github.com/angular-ui/bootstrap/commit/c5326595))\n  - support disabled state ([2b78dd16](http://github.com/angular-ui/bootstrap/commit/2b78dd16))\n  - add support for vertical option ([88d17a75](http://github.com/angular-ui/bootstrap/commit/88d17a75))\n  - add support for other navigation types, like 'pills' ([53e0a39f](http://github.com/angular-ui/bootstrap/commit/53e0a39f))\n- **timepicker:**\n  - add timepicker directive ([9bc5207b](http://github.com/angular-ui/bootstrap/commit/9bc5207b))\n- **tooltip:**\n  - add mouse placement option ([ace7bc60](http://github.com/angular-ui/bootstrap/commit/ace7bc60))\n  - add *-append-to-body attribute ([d0896263](http://github.com/angular-ui/bootstrap/commit/d0896263))\n  - add custom trigger support ([dfa53155](http://github.com/angular-ui/bootstrap/commit/dfa53155))\n- **typeahead:**\n  - support typeahead-on-select callback ([91ac17c9](http://github.com/angular-ui/bootstrap/commit/91ac17c9))\n  - support wait-ms option ([7f35a3f2](http://github.com/angular-ui/bootstrap/commit/7f35a3f2))\n\n## Bug Fixes\n\n- **accordion:**\n  - allow accordion heading directives as attributes. ([25f6e55c](http://github.com/angular-ui/bootstrap/commit/25f6e55c))\n- **carousel:**\n  - do not allow user to change slide if transitioning ([1d19663f](http://github.com/angular-ui/bootstrap/commit/1d19663f))\n  - make slide 'active' binding optional ([17d6c3b5](http://github.com/angular-ui/bootstrap/commit/17d6c3b5))\n  - fix error with deleting multiple slides at once ([3fcb70f0](http://github.com/angular-ui/bootstrap/commit/3fcb70f0))\n- **dialog:**\n  - remove dialogOpenClass to get in line with v2.3 ([f009b23f](http://github.com/angular-ui/bootstrap/commit/f009b23f))\n- **pagination:**\n  - bind *-text attributes ([e1bff6b7](http://github.com/angular-ui/bootstrap/commit/e1bff6b7))\n- **progressbar:**\n  - user `percent` attribute instead of `value`. ([58efec80](http://github.com/angular-ui/bootstrap/commit/58efec80))\n- **tooltip:**\n  - fix positioning error when appendToBody is set to true ([76fee1f9](http://github.com/angular-ui/bootstrap/commit/76fee1f9))\n  - close tooltips appended to body on location change ([041261b5](http://github.com/angular-ui/bootstrap/commit/041261b5))\n  - tooltips will hide on scope.$destroy ([3e5a58e5](http://github.com/angular-ui/bootstrap/commit/3e5a58e5))\n  - support of custom $interpolate.startSymbol ([88c94ee6](http://github.com/angular-ui/bootstrap/commit/88c94ee6))\n  - make sure tooltip scope is evicted from cache ([9246905a](http://github.com/angular-ui/bootstrap/commit/9246905a))\n- **typeahead:**\n  - return focus to the input after selecting a suggestion ([04a21e33](http://github.com/angular-ui/bootstrap/commit/04a21e33))\n\n## Breaking Changes\n\n- **pagination:**\n The 'first-text', 'previous-text', 'next-text' and 'last-text'\n  attributes are now binded to parent scope.\n\n  To migrate your code, surround the text of these attributes with quotes.\n\n  Before:\n\n    ```html\n    <pagination first-text=\"<<\"></pagination>\n    ```\n\n  After:\n\n    ```html\n    <pagination first-text=\"'<<'\"></pagination>\n    ```\n\n- **progressbar:**\n The 'value' is replaced by 'percent'.\n\n  Before:\n\n    ```html\n    <progress value=\"...\"></progress>\n    ```\n\n  After:\n\n    ```html\n    <progress percent=\"...\"></progress>\n    ```\n\n- **tabs:**\n The 'tabs' directive has been renamed to 'tabset', and\n the 'pane' directive has been renamed to 'tab'.\n\n    To migrate your code, follow the example below.\n\n  Before:\n\n    ```html\n    <tabs>\n      <pane heading=\"one\">\n        First Content\n      </pane>\n      <pane ng-repeat=\"apple in basket\" heading=\"{{apple.heading}}\">\n        {{apple.content}}\n      </pane>\n    </tabs>\n    ```\n\n  After:\n\n    ```html\n    <tabset>\n      <tab heading=\"one\">\n        First Content\n      </tab>\n      <tab ng-repeat=\"apple in basket\" heading=\"{{apple.heading}}\">\n        {{apple.content}}\n      </tab>\n    </tabset>\n    ```\n\n<a name=\"0.3.0\"></a>\n# [0.3.0](https://github.com/angular-ui/bootstrap/compare/0.2.0...0.3.0) (2013-04-30)\n\n## Features\n\n- **progressbar:**\n  - add progressbar directive ([261f2072](https://github.com/angular-ui/bootstrap/commit/261f2072))\n- **rating:**\n  - add rating directive ([6b5e6369](https://github.com/angular-ui/bootstrap/commit/6b5e6369))\n- **typeahead:**\n  - support the editable property ([a40c3fbe](https://github.com/angular-ui/bootstrap/commit/a40c3fbe))\n  - support typeahead-loading bindable expression ([b58c9c88](https://github.com/angular-ui/bootstrap/commit/b58c9c88))\n- **tooltip:**\n  - added popup-delay option ([a79a2ba8](https://github.com/angular-ui/bootstrap/commit/a79a2ba8))\n  - added appendToBody to $tooltip ([1ee467f8](https://github.com/angular-ui/bootstrap/commit/1ee467f8))\n  - added tooltip-html-unsafe directive ([45ed2805](https://github.com/angular-ui/bootstrap/commit/45ed2805))\n  - support for custom triggers ([b1ba821b](https://github.com/angular-ui/bootstrap/commit/b1ba821b))\n\n## Bug Fixes\n\n- **alert:**\n  - don't show close button if no close callback specified ([c2645f4a](https://github.com/angular-ui/bootstrap/commit/c2645f4a))\n- **carousel:**\n  - Hide navigation indicators if only one slide ([aedc0565](https://github.com/angular-ui/bootstrap/commit/aedc0565))\n- **collapse:**\n  - remove reference to msTransition for IE10 ([55437b16](https://github.com/angular-ui/bootstrap/commit/55437b16))\n- **dialog:**\n  - set _open to false on init ([dcc9ef31](https://github.com/angular-ui/bootstrap/commit/dcc9ef31))\n  - close dialog on location change ([474ce52e](https://github.com/angular-ui/bootstrap/commit/474ce52e))\n  - IE8 fix to not set data() against text nodes ([a6c540e5](https://github.com/angular-ui/bootstrap/commit/a6c540e5))\n  - fix $apply in progres on $location change ([77e6acb9](https://github.com/angular-ui/bootstrap/commit/77e6acb9))\n- **tabs:**\n  - remove superfluous href from tabs template ([38c1badd](https://github.com/angular-ui/bootstrap/commit/38c1badd))\n- **tooltip:**\n  - fix positioning issues in tooltips and popovers ([6458f487](https://github.com/angular-ui/bootstrap/commit/6458f487))\n- **typeahead:**\n  - close matches popup on click outside typeahead ([acca7dcd](https://github.com/angular-ui/bootstrap/commit/acca7dcd))\n  - stop keydown event propagation when ESC pressed to discard matches ([22a00cd0](https://github.com/angular-ui/bootstrap/commit/22a00cd0))\n  - correctly render initial model value ([929a46fa](https://github.com/angular-ui/bootstrap/commit/929a46fa))\n  - correctly higlight matches if query contains regexp-special chars ([467afcd6](https://github.com/angular-ui/bootstrap/commit/467afcd6))\n  - fix matches pop-up positioning issues ([74beecdb](https://github.com/angular-ui/bootstrap/commit/74beecdb))\n\n<a name=\"0.2.0\"></a>\n# [0.2.0](https://github.com/angular-ui/bootstrap/compare/0.1.0...0.2.0) (2013-03-03)\n\n## Features\n\n- **dialog:**\n  - Make $dialog 'resolve' property to work the same way of $routeProvider.when ([739f86f](https://github.com/angular-ui/bootstrap/commit/739f86f))\n- **modal:**\n  - allow global override of modal options ([acaf72b](https://github.com/angular-ui/bootstrap/commit/acaf72b))\n- **buttons:**\n  - add checkbox and radio buttons ([571ccf4](https://github.com/angular-ui/bootstrap/commit/571ccf4))\n- **carousel:**\n  - add slide indicators ([3b677ee](https://github.com/angular-ui/bootstrap/commit/3b677ee))\n- **typeahead:**\n  - add typeahead directive ([6a97da2](https://github.com/angular-ui/bootstrap/commit/6a97da2))\n- **accordion:**\n  - enable HTML in accordion headings ([3afcaa4](https://github.com/angular-ui/bootstrap/commit/3afcaa4))\n- **pagination:**\n  - add first/last link & constant congif options ([0ff0454](https://github.com/angular-ui/bootstrap/commit/0ff0454))\n\n## Bug fixes\n\n- **dialog:**\n  - update resolve section to new syntax ([1f87486](https://github.com/angular-ui/bootstrap/commit/1f87486))\n  - $compile entire modal ([7575b3c](https://github.com/angular-ui/bootstrap/commit/7575b3c))\n- **tooltip:**\n  - don't show tooltips if there is no content to show ([030901e](https://github.com/angular-ui/bootstrap/commit/030901e))\n  - fix placement issues ([a2bbf4d](https://github.com/angular-ui/bootstrap/commit/a2bbf4d))\n- **collapse:**\n  - Avoids fixed height on collapse ([ff5d119](https://github.com/angular-ui/bootstrap/commit/ff5d119))\n- **accordion:**\n  - fix minification issues ([f4da4d6](https://github.com/angular-ui/bootstrap/commit/f4da4d6))\n- **typeahead:**\n  -  update inputs value on mapping where label is not derived from the model ([a5f64de](https://github.com/angular-ui/bootstrap/commit/a5f64de))\n\n<a name=\"0.1.0\"></a>\n# 0.1.0 (2013-02-02)\n\n_Very first, initial release_.\n\n## Features\n\nVersion `0.1.0` was released with the following directives:\n\n* accordion\n* alert\n* carousel\n* dialog\n* dropdownToggle\n* modal\n* pagination\n* popover\n* tabs\n* tooltip\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery\n* Personal attacks\n* Trolling or insulting/derogatory comments\n* Public or private harassment\n* Publishing other's private information, such as physical or electronic addresses, without explicit permission\n* Other unethical or unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.\n\nThis code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Got a question or problem?\n\nFirstly, please go over our FAQ: https://github.com/angular-ui/bootstrap/wiki/FAQ\n\nPlease, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) where maintainers are looking at questions tagged with `angular-ui-bootstrap`.\n\nStackOverflow is a much better place to ask questions since:\n* there are hundreds of people willing to help on StackOverflow\n* questions and answers stay available for public viewing so your question / answer might help someone else\n* SO voting system assures that the best answers are prominently visible.\n\nTo save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.\n\n## You think you've found a bug?\n\nOh, we are ashamed and want to fix it asap! But before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a _minimal_ reproduce scenario using http://plnkr.co/. Having a live reproduce scenario gives us wealth of important information without going back & forth to you with additional questions like:\n* version of AngularJS used\n* version of this library that you are using\n* 3rd-party libraries used, if any\n* and most importantly - a use-case that fails\n\nA minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem.\n\nWe will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.\n\nThe best part is that you don't need to create plunks from scratch - you can use one from our [demo page](http://angular-ui.github.io/bootstrap/).\n\nUnfortunately we are not able to investigate / fix bugs without a minimal reproduce scenario using http://plnkr.co/, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.\n\n\n## You want to contribute some code?\n\nWe are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules:\n\n* Please make sure that your contribution fits well in the project's context:\n  * we are aiming at rebuilding bootstrap directives in pure AngularJS, without any dependencies on any external JavaScript library;\n  * the only dependency should be bootstrap CSS and its markup structure;\n  * directives should be html-agnostic as much as possible which in practice means:\n        * templates should be referred to using the `templateUrl` property\n        * it should be easy to change a default template to a custom one\n        * directives shouldn't manipulate DOM structure directly (when possible)\n* Please assure that you are submitting quality code, specifically make sure that:\n  * your directive has accompanying tests and all the tests are passing; don't hesitate to contact us (angular-ui@googlegroups.com) if you need any help with unit testing\n  * your PR doesn't break the build; check the Travis-CI build status after opening a PR and push corrective commits if anything goes wrong\n  * your commits conform to the conventions established [here](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "var marked = require('marked');\nvar fs = require('fs');\nvar _ = require('lodash');\n\nmodule.exports = function(grunt) {\n  require('load-grunt-tasks')(grunt);\n\n  // Project configuration.\n  grunt.util.linefeed = '\\n';\n\n  grunt.initConfig({\n    ngversion: '1.6.1',\n    bsversion: '3.3.7',\n    modules: [],//to be filled in by build task\n    pkg: grunt.file.readJSON('package.json'),\n    dist: 'dist',\n    filename: 'ui-bootstrap',\n    filenamecustom: '<%= filename %>-custom',\n    meta: {\n      modules: 'angular.module(\"ui.bootstrap\", [<%= srcModules %>]);',\n      tplmodules: 'angular.module(\"ui.bootstrap.tpls\", [<%= tplModules %>]);',\n      all: 'angular.module(\"ui.bootstrap\", [\"ui.bootstrap.tpls\", <%= srcModules %>]);',\n      cssInclude: '',\n      cssFileBanner: '/* Include this file in your html if you are using the CSP mode. */\\n\\n',\n      cssFileDest: '<%= dist %>/<%= filename %>-<%= pkg.version %>-csp.css',\n      banner: [\n        '/*',\n        ' * <%= pkg.name %>',\n        ' * <%= pkg.homepage %>\\n',\n        ' * Version: <%= pkg.version %> - <%= grunt.template.today(\"yyyy-mm-dd\") %>',\n        ' * License: <%= pkg.license %>',\n        ' */'\n      ].join('\\n')\n    },\n    delta: {\n      docs: {\n        files: ['misc/demo/index.html'],\n        tasks: ['after-test']\n      },\n      html: {\n        files: ['template/**/*.html'],\n        tasks: ['html2js', 'karma:watch:run']\n      },\n      js: {\n        files: ['src/**/*.js', '!src/**/index.js'],\n        tasks: ['karma:watch:run']\n      }\n    },\n    concat: {\n      dist: {\n        options: {\n          banner: '<%= meta.banner %><%= meta.modules %>\\n',\n          footer: '<%= meta.cssInclude %>'\n        },\n        src: [], //src filled in by build task\n        dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'\n      },\n      dist_tpls: {\n        options: {\n          banner: '<%= meta.banner %><%= meta.all %>\\n<%= meta.tplmodules %>\\n',\n          footer: '<%= meta.cssInclude %>'\n        },\n        src: [], //src filled in by build task\n        dest: '<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js'\n      }\n    },\n    copy: {\n      demohtml: {\n        options: {\n          //process html files with gruntfile config\n          processContent: grunt.template.process\n        },\n        files: [{\n          expand: true,\n          src: ['**/*.html'],\n          cwd: 'misc/demo/',\n          dest: 'dist/'\n        }]\n      },\n      demoassets: {\n        files: [{\n          expand: true,\n          //Don't re-copy html files, we process those\n          src: ['**/**/*', '!**/*.html'],\n          cwd: 'misc/demo',\n          dest: 'dist/'\n        }]\n      }\n    },\n    uglify: {\n      options: {\n        banner: '<%= meta.banner %>'\n      },\n      dist:{\n        src:['<%= concat.dist.dest %>'],\n        dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'\n      },\n      dist_tpls:{\n        src:['<%= concat.dist_tpls.dest %>'],\n        dest:'<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.min.js'\n      }\n    },\n    html2js: {\n      dist: {\n        options: {\n          module: null, // no bundle module for all the html2js templates\n          base: '.',\n          rename: function(moduleName) {\n            return `uib/${moduleName}`;\n          }\n        },\n        files: [{\n          expand: true,\n          src: ['template/**/*.html'],\n          ext: '.html.js'\n        }]\n      }\n    },\n    eslint: {\n      files: ['Gruntfile.js','src/**/*.js']\n    },\n    karma: {\n      options: {\n        configFile: 'karma.conf.js'\n      },\n      watch: {\n        background: true\n      },\n      continuous: {\n        singleRun: true\n      },\n      jenkins: {\n        singleRun: true,\n        autoWatch: false,\n        colors: false,\n        reporters: ['dots', 'junit'],\n        browsers: ['Chrome', 'ChromeCanary', 'Firefox', 'Opera', '/Users/jenkins/bin/safari.sh']\n      },\n      travis: {\n        singleRun: true,\n        autoWatch: false,\n        reporters: ['dots'],\n        browsers: ['Firefox']\n      },\n      coverage: {\n        preprocessors: {\n          'src/*/*.js': 'coverage'\n        },\n        reporters: ['progress', 'coverage']\n      }\n    },\n    conventionalChangelog: {\n      options: {\n        changelogOpts: {\n          preset: 'angular'\n        },\n        templateFile: 'misc/changelog.tpl.md'\n      },\n      release: {\n        src: 'CHANGELOG.md'\n      }\n    },\n    shell: {\n      //We use %version% and evaluate it at run-time, because <%= pkg.version %>\n      //is only evaluated once\n      'release-prepare': [\n        'grunt before-test after-test',\n        'grunt version', //remove \"-SNAPSHOT\"\n        'grunt conventionalChangelog'\n      ],\n      'release-complete': [\n        'git commit CHANGELOG.md package.json -m \"chore(release): v%version%\"',\n        'git tag %version%'\n      ],\n      'release-start': [\n        'grunt version:minor:\"SNAPSHOT\"',\n        'git commit package.json -m \"chore(release): Starting v%version%\"'\n      ]\n    },\n    'ddescribe-iit': {\n      files: [\n        'src/**/*.spec.js'\n      ]\n    }\n  });\n\n  //register before and after test tasks so we've don't have to change cli\n  //options on the google's CI server\n  grunt.registerTask('before-test', ['enforce', 'ddescribe-iit', 'eslint', 'html2js']);\n  grunt.registerTask('after-test', ['build', 'copy']);\n\n  //Rename our watch task to 'delta', then make actual 'watch'\n  //task build things, then start test server\n  grunt.renameTask('watch', 'delta');\n  grunt.registerTask('watch', ['before-test', 'after-test', 'karma:watch', 'delta']);\n\n  // Default task.\n  grunt.registerTask('default', ['before-test', 'test', 'after-test']);\n\n  grunt.registerTask('enforce', `Install commit message enforce script if it doesn't exist`, function() {\n    if (!grunt.file.exists('.git/hooks/commit-msg')) {\n      grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg');\n      require('fs').chmodSync('.git/hooks/commit-msg', '0755');\n    }\n  });\n\n  //Common ui.bootstrap module containing all modules for src and templates\n  //findModule: Adds a given module to config\n  var foundModules = {};\n  function findModule(name) {\n    if (foundModules[name]) { return; }\n    foundModules[name] = true;\n\n    function breakup(text, separator) {\n      return text.replace(/[A-Z]/g, function (match) {\n        return separator + match;\n      });\n    }\n    function ucwords(text) {\n      return text.replace(/^([a-z])|\\s+([a-z])/g, function ($1) {\n        return $1.toUpperCase();\n      });\n    }\n    function enquote(str) {\n      return `\"${str}\"`;\n    }\n    function enquoteUibDir(str) {\n      return enquote(`uib/${str}`);\n    }\n\n    var module = {\n      name: name,\n      moduleName: enquote(`ui.bootstrap.${name}`),\n      displayName: ucwords(breakup(name, ' ')),\n      srcFiles: grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`]),\n      cssFiles: grunt.file.expand(`src/${name}/*.css`),\n      tplFiles: grunt.file.expand(`template/${name}/*.html`),\n      tpljsFiles: grunt.file.expand(`template/${name}/*.html.js`),\n      tplModules: grunt.file.expand(`template/${name}/*.html`).map(enquoteUibDir),\n      dependencies: dependenciesForModule(name),\n      docs: {\n        md: grunt.file.expand(`src/${name}/docs/*.md`)\n          .map(grunt.file.read).map((str) => marked(str)).join('\\n'),\n        js: grunt.file.expand(`src/${name}/docs/*.js`)\n          .map(grunt.file.read).join('\\n'),\n        html: grunt.file.expand(`src/${name}/docs/*.html`)\n          .map(grunt.file.read).join('\\n')\n      }\n    };\n\n    var styles = {\n      css: [],\n      js: []\n    };\n    module.cssFiles.forEach(processCSS.bind(null, module.name, styles, true));\n    if (styles.css.length) {\n      module.css = styles.css.join('\\n');\n      module.cssJs = styles.js.join('\\n');\n    }\n\n    module.dependencies.forEach(findModule);\n    grunt.config('modules', grunt.config('modules').concat(module));\n  }\n\n  function dependenciesForModule(name) {\n    var deps = [];\n    grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`])\n    .map(grunt.file.read)\n    .forEach(function(contents) {\n      //Strategy: find where module is declared,\n      //and from there get everything inside the [] and split them by comma\n      var moduleDeclIndex = contents.indexOf('angular.module(');\n      var depArrayStart = contents.indexOf('[', moduleDeclIndex);\n      var depArrayEnd = contents.indexOf(']', depArrayStart);\n      var dependencies = contents.substring(depArrayStart + 1, depArrayEnd);\n      dependencies.split(',').forEach(function(dep) {\n        if (dep.indexOf('ui.bootstrap.') > -1) {\n          var depName = dep.trim().replace('ui.bootstrap.','').replace(/['\"]/g,'');\n          if (deps.indexOf(depName) < 0) {\n            deps.push(depName);\n            //Get dependencies for this new dependency\n            deps = deps.concat(dependenciesForModule(depName));\n          }\n        }\n      });\n    });\n    return deps;\n  }\n\n  grunt.registerTask('dist', 'Override dist directory', function() {\n    var dir = this.args[0];\n    if (dir) { grunt.config('dist', dir); }\n  });\n\n  grunt.registerTask('build', 'Create bootstrap build files', function() {\n    var _ = grunt.util._;\n\n    //If arguments define what modules to build, build those. Else, everything\n    if (this.args.length) {\n      this.args.forEach(findModule);\n      grunt.config('filename', grunt.config('filenamecustom'));\n    } else {\n      grunt.file.expand({\n        filter: 'isDirectory', cwd: '.'\n      }, 'src/*').forEach((dir) => {\n        findModule(dir.split('/')[1]);\n      });\n    }\n\n    var modules = grunt.config('modules');\n    grunt.config('srcModules', _.pluck(modules, 'moduleName'));\n    grunt.config('tplModules', _.pluck(modules, 'tplModules').filter((tpls) => tpls.length > 0));\n    grunt.config('demoModules', modules\n      .filter((module) => module.docs.md && module.docs.js && module.docs.html)\n      .sort((a, b) => {\n        if (a.name < b.name) { return -1; }\n        if (a.name > b.name) { return 1; }\n        return 0;\n      })\n    );\n\n    var cssStrings = _.flatten(_.compact(_.pluck(modules, 'css')));\n    var cssJsStrings = _.flatten(_.compact(_.pluck(modules, 'cssJs')));\n    if (cssStrings.length) {\n      grunt.config('meta.cssInclude', cssJsStrings.join('\\n'));\n\n      grunt.file.write(grunt.config('meta.cssFileDest'), grunt.config('meta.cssFileBanner') +\n                       cssStrings.join('\\n'));\n\n      grunt.log.writeln('File ' + grunt.config('meta.cssFileDest') + ' created');\n    }\n\n    var moduleFileMapping = _.clone(modules, true);\n    moduleFileMapping.forEach((module) => delete module.docs);\n\n    grunt.config('moduleFileMapping', moduleFileMapping);\n\n    var srcFiles = _.pluck(modules, 'srcFiles');\n    var tpljsFiles = _.pluck(modules, 'tpljsFiles');\n    //Set the concat task to concatenate the given src modules\n    grunt.config('concat.dist.src', grunt.config('concat.dist.src')\n                 .concat(srcFiles));\n    //Set the concat-with-templates task to concat the given src & tpl modules\n    grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')\n                 .concat(srcFiles).concat(tpljsFiles));\n\n    grunt.task.run(['concat', 'uglify', 'makeModuleMappingFile', 'makeRawFilesJs', 'makeVersionsMappingFile']);\n  });\n\n  grunt.registerTask('test', 'Run tests on singleRun karma server', function() {\n    //this task can be executed in 3 different environments: local, Travis-CI and Jenkins-CI\n    //we need to take settings for each one into account\n    if (process.env.TRAVIS) {\n      grunt.task.run('karma:travis');\n    } else {\n      var isToRunJenkinsTask = !!this.args.length;\n      if (grunt.option('coverage')) {\n        var karmaOptions = grunt.config.get('karma.options'),\n          coverageOpts = grunt.config.get('karma.coverage');\n        grunt.util._.extend(karmaOptions, coverageOpts);\n        grunt.config.set('karma.options', karmaOptions);\n      }\n      grunt.task.run(this.args.length ? 'karma:jenkins' : 'karma:continuous');\n    }\n  });\n\n  grunt.registerTask('makeModuleMappingFile', function() {\n    var _ = grunt.util._;\n    var moduleMappingJs = 'dist/assets/module-mapping.json';\n    var moduleMappings = grunt.config('moduleFileMapping');\n    var moduleMappingsMap = _.object(_.pluck(moduleMappings, 'name'), moduleMappings);\n    var jsContent = JSON.stringify(moduleMappingsMap);\n    grunt.file.write(moduleMappingJs, jsContent);\n    grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.');\n  });\n\n  grunt.registerTask('makeRawFilesJs', function() {\n    var _ = grunt.util._;\n    var jsFilename = 'dist/assets/raw-files.json';\n    var genRawFilesJs = require('./misc/raw-files-generator');\n\n    genRawFilesJs(grunt, jsFilename, _.flatten(grunt.config('concat.dist_tpls.src')),\n                  grunt.config('meta.banner'), grunt.config('meta.cssFileBanner'));\n  });\n\n  grunt.registerTask('makeVersionsMappingFile', function() {\n    var done = this.async();\n\n    var exec = require('child_process').exec;\n\n    var versionsMappingFile = 'dist/versions-mapping.json';\n\n    exec('git tag --sort -version:refname', function(error, stdout, stderr) {\n      // Let's remove the oldest 14 versions.\n      var versions = stdout.split('\\n').slice(0, -14);\n      var jsContent = versions.map(function(version) {\n        version = version.replace(/^v/, '');\n        return {\n          version: version,\n          url: `/bootstrap/versioned-docs/${version}`\n        };\n      });\n      jsContent = _.sortBy(jsContent, 'version').reverse();\n      jsContent.unshift({\n        version: 'Current',\n        url: '/bootstrap'\n      });\n      grunt.file.write(versionsMappingFile, JSON.stringify(jsContent));\n      grunt.log.writeln(`File ${versionsMappingFile.cyan} created.`);\n      done();\n    });\n\n  });\n\n  /**\n   * Logic from AngularJS\n   * https://github.com/angular/angular.js/blob/36831eccd1da37c089f2141a2c073a6db69f3e1d/lib/grunt/utils.js#L121-L145\n   */\n  function processCSS(moduleName, state, minify, file) {\n    var css = fs.readFileSync(file).toString(),\n      js;\n    state.css.push(css);\n\n    if (minify) {\n      css = css\n        .replace(/\\r?\\n/g, '')\n        .replace(/\\/\\*.*?\\*\\//g, '')\n        .replace(/:\\s+/g, ':')\n        .replace(/\\s*\\{\\s*/g, '{')\n        .replace(/\\s*\\}\\s*/g, '}')\n        .replace(/\\s*\\,\\s*/g, ',')\n        .replace(/\\s*\\;\\s*/g, ';');\n    }\n    //escape for js\n    css = css\n      .replace(/\\\\/g, '\\\\\\\\')\n      .replace(/'/g, \"\\\\'\")\n      .replace(/\\r?\\n/g, '\\\\n');\n    js = `angular.module('ui.bootstrap.${moduleName}').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uib${_.capitalize(moduleName)}Css && angular.element(document).find('head').prepend('<style type=\"text/css\">${css}</style>'); angular.$$uib${_.capitalize(moduleName)}Css = true; });`;\n    state.js.push(js);\n\n    return state;\n  }\n\n  function setVersion(type, suffix) {\n    var file = 'package.json';\n    var VERSION_REGEX = /([\\'|\\\"]version[\\'|\\\"][ ]*:[ ]*[\\'|\\\"])([\\d|.]*)(-\\w+)*([\\'|\\\"])/;\n    var contents = grunt.file.read(file);\n    var version;\n    contents = contents.replace(VERSION_REGEX, function(match, left, center) {\n      version = center;\n      if (type) {\n        version = require('semver').inc(version, type);\n      }\n      //semver.inc strips our suffix if it existed\n      if (suffix) {\n        version += '-' + suffix;\n      }\n      return left + version + '\"';\n    });\n    grunt.log.ok('Version set to ' + version.cyan);\n    grunt.file.write(file, contents);\n    return version;\n  }\n\n  grunt.registerTask('version', 'Set version. If no arguments, it just takes off suffix', function() {\n    setVersion(this.args[0], this.args[1]);\n  });\n\n  grunt.registerMultiTask('shell', 'run shell commands', function() {\n    var self = this;\n    var sh = require('shelljs');\n    self.data.forEach(function(cmd) {\n      cmd = cmd.replace('%version%', grunt.file.readJSON('package.json').version);\n      grunt.log.ok(cmd);\n      var result = sh.exec(cmd,{silent:true});\n      if (result.code !== 0) {\n        grunt.fatal(result.output);\n      }\n    });\n  });\n\n  return grunt;\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\r\n\r\nCopyright (c) 2012-2017 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.\r\n\r\n"
  },
  {
    "path": "README.md",
    "content": "# Project Status (please read)\nDue to [Angular](https://angular.io)'s continued adoption, our creation of [the Angular version of this library](https://ng-bootstrap.github.io), and the the project maintainers' moving on to other things, this project is considered feature-complete and is no longer being maintained.\n\nWe thank you for all your contributions over the years and hope you've enjoyed using this library as much as we've had developing and maintaining it.  It would not have been successful without them.\n\n---\n\n### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com)\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.svg)](http://travis-ci.org/angular-ui/bootstrap)\n[![devDependency Status](https://david-dm.org/angular-ui/bootstrap/dev-status.svg?branch=master)](https://david-dm.org/angular-ui/bootstrap#info=devDependencies)\n[![CDNJS](https://img.shields.io/cdnjs/v/angular-ui-bootstrap.svg)](https://cdnjs.com/libraries/angular-ui-bootstrap/)\n\n### Quick links\n- [Demo](#demo)\n- [Angular 2](#angular-2)\n- [Installation](#installation)\n    - [NPM](#install-with-npm)\n    - [Bower](#install-with-bower)\n    - [NuGet](#install-with-nuget)\n    - [Custom](#custom-build)\n    - [Manual](#manual-download)\n- [Webpack / JSPM](#webpack--jspm)\n- [Support](#support)\n    - [FAQ](#faq)\n    - [Code of Conduct](#code-of-conduct)\n    - [PREFIX MIGRATION GUIDE](#prefix-migration-guide)\n    - [Supported browsers](#supported-browsers)\n    - [Need help?](#need-help)\n    - [Found a bug?](#found-a-bug)\n- [Contributing to the project](#contributing-to-the-project)\n- [Development, meeting minutes, roadmap and more.](#development-meeting-minutes-roadmap-and-more)\n\n\n# Demo\n\nDo you want to see directives in action? Visit https://angular-ui.github.io/bootstrap/!\n\n# Angular 2\n\nAre you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core).\n\n# Installation\n\nInstallation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required.\n*Notes:*\n* Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation.\n* UI Bootstrap depends on [ngTouch](https://docs.angularjs.org/api/ngTouch) for swipe actions. Include `ngTouch` in the module dependencies for your app in order to enable swiping.\n\n## Angular Requirements\n* UI Bootstrap 1.0 and higher _requires_ Angular 1.4.x or higher and it has been tested with Angular 1.4.8.\n* UI Bootstrap 0.14.3 is the _last_ version that supports Angular 1.3.x.\n* UI Bootstrap 0.12.0 is the _last_ version that supports Angular 1.2.x.\n\n## Bootstrap Requirements\n* UI Bootstrap requires Bootstrap CSS version 3.x or higher and it has been tested with Bootstrap CSS 3.3.6.\n* UI Bootstrap 0.8 is the _last_ version that supports Bootstrap CSS 2.3.x.\n\n#### Install with NPM\n\n```sh\n$ npm install angular-ui-bootstrap\n```\n\nThis will install AngularJS and Bootstrap NPM packages.\n\n#### Install with Bower\n```sh\n$ bower install angular-bootstrap\n```\n\nNote: do not install 'angular-ui-bootstrap'.  A separate repository - [bootstrap-bower](https://github.com/angular-ui/bootstrap-bower) - hosts the compiled javascript file and bower.json.\n\n#### Install with NuGet\nTo install AngularJS UI Bootstrap, run the following command in the Package Manager Console\n\n```sh\nPM> Install-Package Angular.UI.Bootstrap\n```\n\n#### Custom build\n\nHead over to https://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it.\n\n#### Manual download\n\nAfter downloading dependencies (or better yet, referencing them from your favorite CDN) you need to download build version of this project. All the files and their purposes are described here:\nhttps://github.com/angular-ui/bootstrap/tree/gh-pages#build-files\nDon't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`.\n\n### Adding dependency to your project\n\nWhen you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module:\n\n```js\nangular.module('myModule', ['ui.bootstrap']);\n```\n\n# Webpack / JSPM\n\nTo use this project with webpack, follow the [NPM](#install-with-npm) instructions.\nNow, if you want to use only the accordion, you can do:\n\n```js\nimport accordion from 'angular-ui-bootstrap/src/accordion';\n\nangular.module('myModule', [accordion]);\n```\n\nYou can import all the pieces you need in the same way:\n\n```js\nimport accordion from 'angular-ui-bootstrap/src/accordion';\nimport datepicker from 'angular-ui-bootstrap/src/datepicker';\n\nangular.module('myModule', [accordion, datepicker]);\n```\n\nThis will load all the dependencies (if any) and also the templates (if any).\n\nBe sure to have a loader able to process `css` files like `css-loader`.\n\nIf you would prefer not to load your css through your JavaScript file loader/bundler, you can choose to import the `index-nocss.js` file instead, which is available for the modules:\n* carousel\n* datepicker\n* datepickerPopup\n* dropdown\n* modal\n* popover\n* position\n* timepicker\n* tooltip\n* typeahead\n\nThe other modules, such as `accordion` in the example below, do not have CSS resources to load, so you should continue to import them as normal:\n\n```js\nimport accordion from 'angular-ui-bootstrap/src/accordion';\nimport typeahead from 'angular-ui-bootstrap/src/typeahead/index-nocss.js';\n\nangular.module('myModule', [accordion, typeahead]);\n```\n\n# Versioning\n\nPre-2.0.0 does not follow a particular versioning system. 2.0.0 and onwards follows [semantic versioning](http://semver.org/). All release changes can be viewed on our [changelog](CHANGELOG.md).\n\n# Support\n\n## FAQ\n\nhttps://github.com/angular-ui/bootstrap/wiki/FAQ\n\n# Code of Conduct\n\nTake a moment to read our [Code of Conduct](CODE_OF_CONDUCT.md)\n\n## PREFIX MIGRATION GUIDE\n\nIf you're updating your application to use prefixes, please check the [migration guide](https://github.com/angular-ui/bootstrap/wiki/Migration-guide-for-prefixes).\n\n## Supported browsers\n\nDirectives from this repository are automatically tested with the following browsers:\n* Chrome (stable and canary channel)\n* Firefox\n* IE 9 and 10\n* Opera\n* Safari\n\nModern mobile browsers should work without problems.\n\n## Need help?\nNeed help using UI Bootstrap?\n\n* Live help in the IRC (`#angularjs` channel at the `freenode` network). Use this [webchat](https://webchat.freenode.net/) or your own IRC client.\n* Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag.\n\n**Please do not create new issues in this repository to ask questions about using UI Bootstrap**\n\n## Found a bug?\nPlease take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug) and submit your issue [here](https://github.com/angular-ui/bootstrap/issues/new).\n\n\n----\n\n\n# Contributing to the project\n\nWe are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines.\n\n# Development, meeting minutes, roadmap and more.\n\nHead over to the [Wiki](https://github.com/angular-ui/bootstrap/wiki) for notes on development for UI Bootstrap, meeting minutes from the UI Bootstrap team, roadmap plans, project philosophy and more.\n"
  },
  {
    "path": "index.js",
    "content": "require('./dist/ui-bootstrap-tpls');\n\nmodule.exports = 'ui.bootstrap';\n"
  },
  {
    "path": "karma.conf.js",
    "content": "// Karma configuration\n// Generated on Sat Mar 28 2015 11:17:34 GMT-0700 (PDT)\n\nmodule.exports = function(config) {\n  config.set({\n\n    // base path that will be used to resolve all patterns (eg. files, exclude)\n    basePath: '',\n\n\n    // frameworks to use\n    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n    frameworks: ['jasmine'],\n\n\n    // list of files / patterns to load in the browser\n    files: [\n      'misc/test-lib/jquery-1.8.2.min.js',\n      'node_modules/angular/angular.js',\n      'node_modules/angular-mocks/angular-mocks.js',\n      'node_modules/angular-sanitize/angular-sanitize.js',\n      'misc/test-lib/helpers.js',\n      'src/**/*.js',\n      'template/**/*.js'\n    ],\n\n\n    // list of files to exclude\n    exclude: [\n      'src/**/index.js',\n      'src/**/index-nocss.js',\n      'src/**/docs/*'\n    ],\n\n\n    // preprocess matching files before serving them to the browser\n    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n    preprocessors: {\n      'src/*/{*.js,!(test)/**/*.js}': ['coverage']\n    },\n\n\n    // test results reporter to use\n    // possible values: 'dots', 'progress'\n    // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n    reporters: ['progress', 'coverage'],\n\n    coverageReporter: {\n      dir: '.coverage/',\n      type: 'html'\n    },\n\n    reportSlowerThan: 100,\n\n    // web server port\n    port: 9876,\n\n\n    // enable / disable colors in the output (reporters and logs)\n    colors: true,\n\n\n    // level of logging\n    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: true,\n\n\n    // start these browsers\n    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher\n    browsers: ['Chrome'],\n\n\n    // Continuous Integration mode\n    // if true, Karma captures browsers, runs the tests and exits\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "misc/changelog.tpl.md",
    "content": "# <%= version%> (<%= today%>)\n<% if (_(changelog.feat).size() > 0) { %>\n## Features\n<% _(changelog.feat).keys().sort().forEach(function(scope) { %>\n- **<%= scope%>:** <% changelog.feat[scope].forEach(function(change) { %>\n  - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)<% }); %><% }); %> <% } %>\n<% if (_(changelog.fix).size() > 0) { %>\n## Bug Fixes\n<% _(changelog.fix).keys().sort().forEach(function(scope) { %>\n- **<%= scope%>:** <% changelog.fix[scope].forEach(function(change) { %>\n  - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)<% }); %><% }); %> <% } %>\n<% if (_(changelog.breaking).size() > 0) { %>\n## Breaking Changes\n<% _(changelog.breaking).keys().sort().forEach(function(scope) { %>\n- **<%= scope%>:** <% changelog.breaking[scope].forEach(function(change) { %>\n<%= change.msg%><% }); %><% }); %> <% } %>\n"
  },
  {
    "path": "misc/demo/assets/app.js",
    "content": "/* global FastClick, smoothScroll */\nangular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAnimate', 'ngSanitize'], function($httpProvider){\n  if (!!window.FastClick) {\n    FastClick.attach(document.body);\n  }\n  delete $httpProvider.defaults.headers.common['X-Requested-With'];\n}).run(['$location', function($location){\n  //Allows us to navigate to the correct element on initialization\n  if ($location.path() !== '' && $location.path() !== '/') {\n    smoothScroll(document.getElementById($location.path().substring(1)), 500, function(el) {\n      location.replace('#' + el.id);\n    });\n  }\n}]).factory('buildFilesService', function ($http, $q) {\n\n  var moduleMap;\n  var rawFiles;\n\n  return {\n    getModuleMap: getModuleMap,\n    getRawFiles: getRawFiles,\n    get: function () {\n      return $q.all({\n        moduleMap: getModuleMap(),\n        rawFiles: getRawFiles()\n      });\n    }\n  };\n\n  function getModuleMap() {\n    return moduleMap ? $q.when(moduleMap) : $http.get('assets/module-mapping.json')\n      .then(function (result) {\n        moduleMap = result.data;\n        return moduleMap;\n      });\n  }\n\n  function getRawFiles() {\n    return rawFiles ? $q.when(rawFiles) : $http.get('assets/raw-files.json')\n      .then(function (result) {\n        rawFiles = result.data;\n        return rawFiles;\n      });\n  }\n\n})\n.controller('MainCtrl', MainCtrl)\n.controller('SelectModulesCtrl', SelectModulesCtrl)\n.controller('DownloadCtrl', DownloadCtrl);\n\nfunction MainCtrl($scope, $http, $document, $uibModal, orderByFilter) {\n  // Grab old version docs\n  $http.get('/bootstrap/versions-mapping.json')\n    .then(function(result) {\n      $scope.oldDocs = result.data;\n    });\n\n  $scope.showBuildModal = function() {\n    var modalInstance = $uibModal.open({\n      templateUrl: 'buildModal.html',\n      controller: 'SelectModulesCtrl',\n      resolve: {\n        modules: function(buildFilesService) {\n          return buildFilesService.getModuleMap()\n            .then(function (moduleMap) {\n              return Object.keys(moduleMap);\n            });\n        }\n      }\n    });\n  };\n\n  $scope.showDownloadModal = function() {\n    var modalInstance = $uibModal.open({\n      templateUrl: 'downloadModal.html',\n      controller: 'DownloadCtrl'\n    });\n  };\n}\n\nfunction SelectModulesCtrl($scope, $uibModalInstance, modules, buildFilesService) {\n  $scope.selectedModules = [];\n  $scope.modules = modules;\n\n  $scope.selectedChanged = function(module, selected) {\n    if (selected) {\n      $scope.selectedModules.push(module);\n    } else {\n      $scope.selectedModules.splice($scope.selectedModules.indexOf(module), 1);\n    }\n  };\n\n  $scope.downloadBuild = function () {\n    $uibModalInstance.close($scope.selectedModules);\n  };\n\n  $scope.cancel = function () {\n    $uibModalInstance.dismiss();\n  };\n\n  $scope.isOldBrowser = function () {\n    return isOldBrowser;\n  };\n\n  $scope.build = function (selectedModules, version) {\n    /* global JSZip, saveAs */\n    var moduleMap, rawFiles;\n\n    buildFilesService.get().then(function (buildFiles) {\n      moduleMap = buildFiles.moduleMap;\n      rawFiles = buildFiles.rawFiles;\n\n      generateBuild();\n    });\n\n    function generateBuild() {\n      var srcModuleNames = selectedModules\n      .map(function (module) {\n        return moduleMap[module];\n      })\n      .reduce(function (toBuild, module) {\n        addIfNotExists(toBuild, module.name);\n\n        module.dependencies.forEach(function (depName) {\n          addIfNotExists(toBuild, depName);\n        });\n        return toBuild;\n      }, []);\n\n      var srcModules = srcModuleNames\n      .map(function (moduleName) {\n        return moduleMap[moduleName];\n      });\n\n      var srcModuleFullNames = srcModules\n      .map(function (module) {\n        return module.moduleName;\n      });\n\n      var srcJsContent = srcModules\n      .reduce(function (buildFiles, module) {\n        return buildFiles.concat(module.srcFiles);\n      }, [])\n      .map(getFileContent)\n      .join('\\n')\n      ;\n\n      var jsFile = createNoTplFile(srcModuleFullNames, srcJsContent);\n\n      var tplModuleNames = srcModules\n      .reduce(function (tplModuleNames, module) {\n        return tplModuleNames.concat(module.tplModules);\n      }, []);\n\n      var tplJsContent = srcModules\n      .reduce(function (buildFiles, module) {\n        return buildFiles.concat(module.tpljsFiles);\n      }, [])\n      .map(getFileContent)\n      .join('\\n')\n      ;\n\n      var jsTplFile = createWithTplFile(srcModuleFullNames, srcJsContent, tplModuleNames, tplJsContent);\n\n      var cssContent = srcModules\n      .map(function (module) {\n        return module.css;\n      })\n      .filter(function (css) {\n        return css;\n      })\n      .join('\\n')\n      ;\n\n      var cssJsContent = srcModules\n      .map(function (module) {\n        return module.cssJs;\n      })\n      .filter(function (cssJs) {\n        return cssJs;\n      })\n      .join('\\n')\n      ;\n\n      var footer = cssJsContent;\n\n      var zip = new JSZip();\n      zip.file('ui-bootstrap-custom-' + version + '.js', rawFiles.banner + jsFile + footer);\n      zip.file('ui-bootstrap-custom-' + version + '.min.js', rawFiles.banner + uglify(jsFile + footer));\n      zip.file('ui-bootstrap-custom-tpls-' + version + '.js', rawFiles.banner + jsTplFile + footer);\n      zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile + footer));\n      zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile + footer));\n\n      if (cssContent) {\n        zip.file('ui-bootstrap-custom-' + version + '-csp.css', rawFiles.cssBanner + cssContent);\n      }\n\n      saveAs(zip.generate({type: 'blob'}), 'ui-bootstrap-custom-build.zip');\n    }\n\n    function createNoTplFile(srcModuleNames, srcJsContent) {\n      return 'angular.module(\"ui.bootstrap\", [' + srcModuleNames.join(',') + ']);\\n' +\n        srcJsContent;\n    }\n\n    function createWithTplFile(srcModuleNames, srcJsContent, tplModuleNames, tplJsContent) {\n      var depModuleNames = srcModuleNames.slice();\n      depModuleNames.unshift('\"ui.bootstrap.tpls\"');\n\n      return 'angular.module(\"ui.bootstrap\", [' + depModuleNames.join(',') + ']);\\n' +\n        'angular.module(\"ui.bootstrap.tpls\", [' + tplModuleNames.join(',') + ']);\\n' +\n        srcJsContent + '\\n' + tplJsContent;\n\n    }\n\n    function addIfNotExists(array, element) {\n      if (array.indexOf(element) == -1) {\n        array.push(element);\n      }\n    }\n\n    function getFileContent(fileName) {\n      return rawFiles.files[fileName];\n    }\n\n    function uglify(js) {\n      /* global UglifyJS */\n\n      var ast = UglifyJS.parse(js);\n      ast.figure_out_scope();\n\n      var compressor = UglifyJS.Compressor();\n      var compressedAst = ast.transform(compressor);\n\n      compressedAst.figure_out_scope();\n      compressedAst.compute_char_frequency();\n      compressedAst.mangle_names();\n\n      var stream = UglifyJS.OutputStream();\n      compressedAst.print(stream);\n\n      return stream.toString();\n    }\n  };\n}\n\nfunction DownloadCtrl($scope, $uibModalInstance) {\n  $scope.options = {\n    minified: true,\n    tpls: true\n  };\n\n  $scope.download = function (version) {\n    var options = $scope.options;\n\n    var downloadUrl = ['ui-bootstrap-'];\n    if (options.tpls) {\n      downloadUrl.push('tpls-');\n    }\n    downloadUrl.push(version);\n    if (options.minified) {\n      downloadUrl.push('.min');\n    }\n    downloadUrl.push('.js');\n\n    return downloadUrl.join('');\n  };\n\n  $scope.cancel = function () {\n    $uibModalInstance.dismiss();\n  };\n}\n\n/*\n * The following compatibility check is from:\n *\n * Bootstrap Customizer (http://getbootstrap.com/customize/)\n * Copyright 2011-2014 Twitter, Inc.\n *\n * Licensed under the Creative Commons Attribution 3.0 Unported License. For\n * details, see http://creativecommons.org/licenses/by/3.0/.\n */\nvar isOldBrowser;\n(function () {\n\n    var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob);\n    function failback() {\n        isOldBrowser = true;\n    }\n    /**\n     * Based on:\n     *   Blob Feature Check v1.1.0\n     *   https://github.com/ssorallen/blob-feature-check/\n     *   License: Public domain (http://unlicense.org)\n     */\n    var url = window.URL;\n    var svg = new Blob(\n        ['<svg xmlns=\\'http://www.w3.org/2000/svg\\'></svg>'],\n        { type: 'image/svg+xml;charset=utf-8' }\n    );\n    var objectUrl = url.createObjectURL(svg);\n\n    if (/^blob:/.exec(objectUrl) === null || !supportsFile) {\n      // `URL.createObjectURL` created a URL that started with something other\n      // than \"blob:\", which means it has been polyfilled and is not supported by\n      // this browser.\n      failback();\n    } else {\n      angular.element('<img/>')\n          .on('load', function () {\n              isOldBrowser = false;\n          })\n          .on('error', failback)\n          .attr('src', objectUrl);\n    }\n\n  })();\n"
  },
  {
    "path": "misc/demo/assets/demo.css",
    "content": "body {\n    opacity: 1;\n    -webkit-transition: opacity 1s ease;\n    -moz-transition: opacity 1s ease;\n    transition: opacity 1s;\n}\n\n.ng-cloak {\n    opacity: 0;\n}\n\n.ng-invalid {\n    border: 1px solid red !important;\n}\n\nsection {\n    padding-top: 30px;\n}\n\n.page-header h1 > small > a {\n    color: #999;\n}\n.page-header h1 > small > a:hover {\n    text-decoration: none;\n}\n\n.footer {\n    text-align: center;\n    padding: 30px 0;\n    margin-top: 70px;\n    border-top: 1px solid #e5e5e5;\n    background-color: #f5f5f5;\n}\n\n.bs-social {\n    margin-top: 20px;\n    margin-bottom: 20px;\n    text-align: center;\n}\n\n@media (min-width: 768px) {\n\n    .bs-social {\n        text-align: left;\n    }\n\n}\n\n.nav, .pagination, .carousel, .panel-title a {\n    cursor: pointer;\n}\n\n.bs-social-buttons {\n    display: inline-block;\n    margin-bottom: 0;\n    padding-left: 0;\n    list-style: none;\n}\n.bs-social-buttons li {\n    display: inline-block;\n    padding: 5px 8px;\n    line-height: 1;\n}\n\n@media (max-width: 767px) {\n\n    .visible-xs.collapse.in {\n        display: block!important;\n    }\n    .visible-xs.collapse {\n        display: none!important;\n    }\n\n}\n\n.navbar-fixed-top .collapse {\n    border-top: 1px solid #e7e7e7;\n    margin-left: -15px;\n    margin-right: -15px;\n    padding-right: 15px;\n    padding-left: 15px;\n}\n\n.show-grid {\n  margin-bottom: 15px;\n}\n\n/*\n * Container\n *\n * Tweak to width of container.\n */\n\n/*@media (min-width: 1200px) {\n    .container{\n        max-width: 970px;\n    }\n}*/\n\n/*\n * Tabs\n *\n * Tweaks to the Tabs.\n */\n\n.code .nav-tabs {\nborder-bottom: 1px solid #ccc;\n}\n\n.code pre, .code code {\n    border-top: none;\n    border-top-left-radius: 0;\n    border-top-right-radius: 0;\n}\n\n.code .nav-tabs>li.active>a, .code .nav-tabs>li.active>a:hover, .code .nav-tabs>li.active>a:focus {\nbackground-color: #f8f8f8;\nborder: 1px solid #ccc;\nborder-bottom-color: transparent;\n}\n\n/*\n * Button Inverse\n *\n * Buttons in the masthead.\n */\n\n.btn-outline-inverse {\ncolor: #fff;\nbackground-color: transparent;\nborder-color: #cdbfe3;\nmargin: 10px;\n}\n\n@media (min-width: 768px) {\n\n    .btn-outline-inverse {\n        width: auto;\n        margin: 20px 5px 20px 0;\n        padding: 18px 24px;\n        font-size: 21px;\n    }\n\n}\n\n.btn-outline-inverse:hover, .btn-outline-inverse:focus, .btn-outline-inverse:active {\ncolor: #563d7c;\ntext-shadow: none;\nbackground-color: #fff;\nborder-color: #fff;\n}\n\n\n/* Page headers */\n.bs-header {\n  padding: 30px 15px 40px; /* side padding builds on .container 15px, so 30px */\n  font-size: 16px;\n  text-align: center;\n  text-shadow: 0 1px 0 rgba(0,0,0,.15);\n  color: #cdbfe3;\n  background-color: #563d7c;\n  background-image: url(header.png);\n}\n.bs-header a {\n  color: #fff;\n  font-weight: normal;\n}\n.bs-header h1 {\n  color: #fff;\n}\n.bs-header p {\n  font-weight: 200;\n  line-height: 1.4;\n}\n.bs-header .container {\n  position: relative;\n}\n\n@media (min-width: 768px) {\n  .bs-header {\n    font-size: 30px;\n    text-align: left;\n  }\n  .bs-header h1 {\n    font-size: 100px;\n    line-height: 1;\n  }\n}\n\n@media (min-width: 992px) {\n  .bs-header p {\n    margin-right: 25%;\n  }\n}\n\n.navbar-inner {\n    -webkit-box-shadow: 0 3px 3px rgba(0,0,0,0.175);\n    box-shadow: 0 3px 3px rgba(0,0,0,0.175);\n}\n\n/*\n * Side navigation\n *\n * Scrollspy and affixed enhanced navigation to highlight sections and secondary\n * sections of docs content.\n */\n\n/* By default it's not affixed in mobile views, so undo that */\n.bs-sidebar.affix {\n  position: static;\n}\n\n/* First level of nav */\n.bs-sidenav {\n  margin-top: 30px;\n  margin-bottom: 30px;\n  padding-top:    10px;\n  padding-bottom: 10px;\n  text-shadow: 0 1px 0 #fff;\n  background-color: #f7f5fa;\n  border-radius: 5px;\n}\n\n/* All levels of nav */\n.bs-sidebar .nav > li > a {\n  display: block;\n  color: #716b7a;\n  padding: 5px 20px;\n}\n.bs-sidebar .nav > li > a:hover,\n.bs-sidebar .nav > li > a:focus {\n  text-decoration: none;\n  background-color: #e5e3e9;\n  border-right: 1px solid #dbd8e0;\n}\n.bs-sidebar .nav > .active > a,\n.bs-sidebar .nav > .active:hover > a,\n.bs-sidebar .nav > .active:focus > a {\n  font-weight: bold;\n  color: #563d7c;\n  background-color: transparent;\n  border-right: 1px solid #563d7c;\n}\n\n/* Nav: second level (shown on .active) */\n.bs-sidebar .nav .nav {\n  display: none; /* Hide by default, but at >768px, show it */\n  margin-bottom: 8px;\n}\n.bs-sidebar .nav .nav > li > a {\n  padding-top:    3px;\n  padding-bottom: 3px;\n  padding-left: 30px;\n  font-size: 90%;\n}\n\n/* Show and affix the side nav when space allows it */\n@media (min-width: 992px) {\n  .bs-sidebar .nav > .active > ul {\n    display: block;\n  }\n  /* Widen the fixed sidebar */\n  .bs-sidebar.affix,\n  .bs-sidebar.affix-bottom {\n    width: 213px;\n  }\n  .bs-sidebar.affix {\n    position: fixed; /* Undo the static from mobile first approach */\n    top: 80px;\n  }\n  .bs-sidebar.affix-bottom {\n    position: absolute; /* Undo the static from mobile first approach */\n  }\n  .bs-sidebar.affix-bottom .bs-sidenav,\n  .bs-sidebar.affix .bs-sidenav {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n}\n@media (min-width: 1200px) {\n  /* Widen the fixed sidebar again */\n  .bs-sidebar.affix-bottom,\n  .bs-sidebar.affix {\n    width: 263px;\n  }\n}\n\n\n/* Not enough room on mobile for markup tab, js tab, and plunk btn.\n  And no one cares about plunk button on a phone anyway */\n@media only screen and (max-device-width: 480px) {\n    #plunk-btn {\n        display: none;\n    }\n}\n\n.navbar-nav .dropdown .navbar-brand {\n    max-width: 100%;\n    margin-right: inherit;\n    margin-left: inherit;\n}\n\n.header-placeholder {\n    height: 50px;\n}\n\n@media screen and (min-width: 768px) {\n\n    .dropdown.open > .navbar-brand + .dropdown-menu {\n        left: 10px;\n    }\n\n    .header-placeholder {\n        height: 50px;\n    }\n\n    .navbar-nav .dropdown .navbar-brand {\n        max-width: 200px;\n        margin-right: 5px;\n        margin-left: 10px;\n    }\n\n}\n"
  },
  {
    "path": "misc/demo/assets/plunker.js",
    "content": "angular.module('plunker', [])\n\n  .factory('plunkGenerator', function ($document) {\n\n    return function (ngVersion, bsVersion, version, module, content) {\n\n      var form = angular.element('<form style=\"display: none;\" method=\"post\" action=\"https://plnkr.co/edit/?p=preview\" target=\"_blank\"></form>');\n      var addField = function (name, value) {\n        var input = angular.element('<input type=\"hidden\" name=\"' + name + '\">');\n        input.attr('value', value);\n        form.append(input);\n      };\n\n      var indexContent = function (content, version) {\n        return '<!doctype html>\\n' +\n          '<html ng-app=\"ui.bootstrap.demo\">\\n' +\n          '  <head>\\n' +\n          '    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular.js\"></script>\\n' +\n          '    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-animate.js\"></script>\\n' +\n          '    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-sanitize.js\"></script>\\n' +\n          '    <script src=\"//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-'+version+'.js\"></script>\\n' +\n          '    <script src=\"example.js\"></script>\\n' +\n          '    <link href=\"//netdna.bootstrapcdn.com/bootstrap/'+bsVersion+'/css/bootstrap.min.css\" rel=\"stylesheet\">\\n' +\n          '  </head>\\n' +\n          '  <body>\\n\\n' +\n          content + '\\n' +\n          '  </body>\\n' +\n          '</html>\\n';\n      };\n\n      var scriptContent = function(content) {\n        return \"angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);\" + \"\\n\" + content;\n      };\n\n      addField('description', 'http://angular-ui.github.io/bootstrap/');\n      addField('files[index.html]', indexContent(content.markup, version));\n      addField('files[example.js]', scriptContent(content.javascript));\n\n      $document.find('body').append(form);\n      form[0].submit();\n      form.remove();\n    };\n  })\n\n  .controller('PlunkerCtrl', function ($scope, plunkGenerator) {\n\n    $scope.content = {};\n\n    $scope.edit = function (ngVersion, bsVersion, version, module) {\n      plunkGenerator(ngVersion, bsVersion, version, module, $scope.content);\n    };\n  })\n\n  .directive('plunkerContent', function () {\n    return {\n      link:function (scope, element, attrs) {\n        scope.content[attrs.plunkerContent] = element.text().trim();\n      }\n    }\n  });\n"
  },
  {
    "path": "misc/demo/assets/rainbow-generic.js",
    "content": "/**\n * Generic language patterns\n *\n * @author Craig Campbell\n * @version 1.0.9\n */\nRainbow.extend([\n    {\n        'matches': {\n            1: {\n                'name': 'keyword.operator',\n                'pattern': /\\=/g\n            },\n            2: {\n                'name': 'string',\n                'matches': {\n                    'name': 'constant.character.escape',\n                    'pattern': /\\\\('|\"){1}/g\n                }\n            }\n        },\n        'pattern': /(\\(|\\s|\\[|\\=|:)(('|\")([^\\\\\\1]|\\\\.)*?(\\3))/gm\n    },\n    {\n        'name': 'comment',\n        'pattern': /\\/\\*[\\s\\S]*?\\*\\/|(\\/\\/|\\#)[\\s\\S]*?$/gm\n    },\n    {\n        'name': 'constant.numeric',\n        'pattern': /\\b(\\d+(\\.\\d+)?(e(\\+|\\-)?\\d+)?(f|d)?|0x[\\da-f]+)\\b/gi\n    },\n    {\n        'matches': {\n            1: 'keyword'\n        },\n        'pattern': /\\b(and|array|as|bool(ean)?|c(atch|har|lass|onst)|d(ef|elete|o(uble)?)|e(cho|lse(if)?|xit|xtends|xcept)|f(inally|loat|or(each)?|unction)|global|if|import|int(eger)?|long|new|object|or|pr(int|ivate|otected)|public|return|self|st(ring|ruct|atic)|switch|th(en|is|row)|try|(un)?signed|var|void|while)(?=\\(|\\b)/gi\n    },\n    {\n        'name': 'constant.language',\n        'pattern': /true|false|null/g\n    },\n    {\n        'name': 'keyword.operator',\n        'pattern': /\\+|\\!|\\-|&(gt|lt|amp);|\\||\\*|\\=/g\n    },\n    {\n        'matches': {\n            1: 'function.call'\n        },\n        'pattern': /(\\w+?)(?=\\()/g\n    },\n    {\n        'matches': {\n            1: 'storage.function',\n            2: 'entity.name.function'\n        },\n        'pattern': /(function)\\s(.*?)(?=\\()/g\n    }\n]);\n"
  },
  {
    "path": "misc/demo/assets/rainbow-html.js",
    "content": "/**\n * HTML patterns\n *\n * @author Craig Campbell\n * @version 1.0.7\n */\nRainbow.extend('html', [\n    {\n        'name': 'source.php.embedded',\n        'matches': {\n            2: {\n                'language': 'php'\n            }\n        },\n        'pattern': /&lt;\\?=?(?!xml)(php)?([\\s\\S]*?)(\\?&gt;)/gm\n    },\n    {\n        'name': 'source.css.embedded',\n        'matches': {\n            0: {\n                'language': 'css'\n            }\n        },\n        'pattern': /&lt;style(.*?)&gt;([\\s\\S]*?)&lt;\\/style&gt;/gm\n    },\n    {\n        'name': 'source.js.embedded',\n        'matches': {\n            0: {\n                'language': 'javascript'\n            }\n        },\n        'pattern': /&lt;script(?! src)(.*?)&gt;([\\s\\S]*?)&lt;\\/script&gt;/gm\n    },\n    {\n        'name': 'comment.html',\n        'pattern': /&lt;\\!--[\\S\\s]*?--&gt;/g\n    },\n    {\n        'matches': {\n            1: 'support.tag.open',\n            2: 'support.tag.cclose'\n        },\n        'pattern': /(&lt;)|(\\/?\\??&gt;)/g\n    },\n    {\n        'name': 'support.tag',\n        'matches': {\n            1: 'support.tag',\n            2: 'support.tag.special',\n            3: 'support.tag-name'\n        },\n        'pattern': /(&lt;\\??)(\\/|\\!?)(\\w+)/g\n    },\n    {\n        'matches': {\n            1: 'support.attribute'\n        },\n        'pattern': /([a-z-]+)(?=\\=)/gi\n    },\n    {\n        'matches': {\n            1: 'support.operator',\n            2: 'string.quote',\n            3: 'string.value',\n            4: 'string.quote'\n        },\n        'pattern': /(=)('|\")(.*?)(\\2)/g\n    },\n    {\n        'matches': {\n            1: 'support.operator',\n            2: 'support.value'\n        },\n        'pattern': /(=)([a-zA-Z\\-0-9]*)\\b/g\n    },\n    {\n        'matches': {\n            1: 'support.attribute'\n        },\n        'pattern': /\\s(\\w+)(?=\\s|&gt;)(?![\\s\\S]*&lt;)/g\n    }\n], true);\n"
  },
  {
    "path": "misc/demo/assets/rainbow-javascript.js",
    "content": "/**\n * Javascript patterns\n *\n * @author Craig Campbell\n * @version 1.0.7\n */\nRainbow.extend('javascript', [\n\n    /**\n     * matches $. or $(\n     */\n    {\n        'name': 'selector',\n        'pattern': /(\\s|^)\\$(?=\\.|\\()/g\n    },\n    {\n        'name': 'support',\n        'pattern': /\\b(window|document)\\b/g\n    },\n    {\n        'matches': {\n            1: 'support.property'\n        },\n        'pattern': /\\.(length|node(Name|Value))\\b/g\n    },\n    {\n        'matches': {\n            1: 'support.function'\n        },\n        'pattern': /(setTimeout|setInterval)(?=\\()/g\n\n    },\n    {\n        'matches': {\n            1: 'support.method'\n        },\n        'pattern': /\\.(getAttribute|push|getElementById|getElementsByClassName|log|setTimeout|setInterval)(?=\\()/g\n    },\n    {\n        'matches': {\n            1: 'support.tag.script',\n            2: [\n                {\n                    'name': 'string',\n                    'pattern': /('|\")(.*?)(\\1)/g\n                },\n                {\n                    'name': 'entity.tag.script',\n                    'pattern': /(\\w+)/g\n                }\n            ],\n            3: 'support.tag.script'\n        },\n        'pattern': /(&lt;\\/?)(script.*?)(&gt;)/g\n    },\n\n    /**\n     * matches any escaped characters inside of a js regex pattern\n     *\n     * @see https://github.com/ccampbell/rainbow/issues/22\n     *\n     * this was causing single line comments to fail so it now makes sure\n     * the opening / is not directly followed by a *\n     *\n     * @todo check that there is valid regex in match group 1\n     */\n    {\n        'name': 'string.regexp',\n        'matches': {\n            1: 'string.regexp.open',\n            2: {\n                'name': 'constant.regexp.escape',\n                'pattern': /\\\\(.){1}/g\n            },\n            3: 'string.regexp.cclose',\n            4: 'string.regexp.modifier'\n        },\n        'pattern': /(\\/)(?!\\*)(.+)(\\/)([igm]{0,3})/g\n    },\n\n    /**\n     * matches runtime function declarations\n     */\n    {\n        'matches': {\n            1: 'storage',\n            3: 'entity.function'\n        },\n        'pattern': /(var)?(\\s|^)(.*)(?=\\s?=\\s?function\\()/g\n    },\n\n    /**\n     * matches constructor call\n     */\n    {\n        'matches': {\n            1: 'keyword',\n            2: 'entity.function'\n        },\n        'pattern': /(new)\\s+(.*)(?=\\()/g\n    },\n\n    /**\n     * matches any function call in the style functionName: function()\n     */\n    {\n        'name': 'entity.function',\n        'pattern': /(\\w+)(?=:\\s{0,}function)/g\n    }\n]);\n"
  },
  {
    "path": "misc/demo/assets/rainbow.css",
    "content": "/**\n * GitHub theme\n *\n * @author Craig Campbell\n * @version 1.0.4\n */\npre {\n    border: 1px solid #ccc;\n    word-wrap: break-word;\n    padding: 6px 10px;\n    line-height: 19px;\n    margin-bottom: 20px;\n}\n\ncode {\n    border: 1px solid #eaeaea;\n    margin: 0 2px;\n    padding: 0 5px;\n    font-size: 12px;\n}\n\npre code {\n    border: 0;\n    padding: 0;\n    margin: 0;\n    -moz-border-radius: 0;\n    -webkit-border-radius: 0;\n    border-radius: 0;\n}\n\npre, code {\n    font-family: Consolas, 'Liberation Mono', Courier, monospace;\n    color: #333;\n    background: #f8f8f8;\n    -moz-border-radius: 3px;\n    -webkit-border-radius: 3px;\n    border-radius: 3px;\n}\n\npre, pre code {\n    font-size: 13px;\n}\n\npre .comment {\n    color: #998;\n}\n\npre .support {\n    color: #0086B3;\n}\n\npre .tag, pre .tag-name {\n    color: navy;\n}\n\npre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function {\n    font-weight: bold;\n}\n\npre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace {\n    color: #333;\n}\n\npre .constant.numeric, pre .keyword.unit, pre .hex-color {\n    font-weight: normal;\n    color: #099;\n}\n\npre .entity.class {\n    color: #458;\n}\n\npre .entity.id, pre .entity.function {\n    color: #900;\n}\n\npre .attribute, pre .variable {\n    color: teal;\n}\n\npre .string, pre .support.value  {\n    font-weight: normal;\n    color: #d14;\n}\n\npre .regexp {\n    color: #009926;\n}\n"
  },
  {
    "path": "misc/demo/assets/rainbow.js",
    "content": "/**\n * Copyright 2012 Craig Campbell\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Rainbow is a simple code syntax highlighter\n *\n * @preserve @version 1.1.8\n * @url rainbowco.de\n */\nwindow['Rainbow'] = (function() {\n\n    /**\n     * array of replacements to process at the end\n     *\n     * @type {Object}\n     */\n    var replacements = {},\n\n        /**\n         * an array of start and end positions of blocks to be replaced\n         *\n         * @type {Object}\n         */\n        replacement_positions = {},\n\n        /**\n         * an array of the language patterns specified for each language\n         *\n         * @type {Object}\n         */\n        language_patterns = {},\n\n        /**\n         * an array of languages and whether they should bypass the default patterns\n         *\n         * @type {Object}\n         */\n        bypass_defaults = {},\n\n        /**\n         * processing level\n         *\n         * replacements are stored at this level so if there is a sub block of code\n         * (for example php inside of html) it runs at a different level\n         *\n         * @type {number}\n         */\n        CURRENT_LEVEL = 0,\n\n        /**\n         * constant used to refer to the default language\n         *\n         * @type {number}\n         */\n        DEFAULT_LANGUAGE = 0,\n\n        /**\n         * used as counters so we can selectively call setTimeout\n         * after processing a certain number of matches/replacements\n         *\n         * @type {number}\n         */\n        match_counter = 0,\n\n        /**\n         * @type {number}\n         */\n        replacement_counter = 0,\n\n        /**\n         * @type {null|string}\n         */\n        global_class,\n\n        /**\n         * @type {null|Function}\n         */\n        onHighlight;\n\n    /**\n     * cross browser get attribute for an element\n     *\n     * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method\n     *\n     * @param {Node} el\n     * @param {string} attr     attribute you are trying to get\n     * @returns {string|number}\n     */\n    function _attr(el, attr, attrs, i) {\n        var result = (el.getAttribute && el.getAttribute(attr)) || 0;\n\n        if (!result) {\n            attrs = el.attributes;\n\n            for (i = 0; i < attrs.length; ++i) {\n                if (attrs[i].nodeName === attr) {\n                    return attrs[i].nodeValue;\n                }\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * adds a class to a given code block\n     *\n     * @param {Element} el\n     * @param {string} class_name   class name to add\n     * @returns void\n     */\n    function _addClass(el, class_name) {\n        el.className += el.className ? ' ' + class_name : class_name;\n    }\n\n    /**\n     * checks if a block has a given class\n     *\n     * @param {Element} el\n     * @param {string} class_name   class name to check for\n     * @returns {boolean}\n     */\n    function _hasClass(el, class_name) {\n        return (' ' + el.className + ' ').indexOf(' ' + class_name + ' ') > -1;\n    }\n\n    /**\n     * gets the language for this block of code\n     *\n     * @param {Element} block\n     * @returns {string|null}\n     */\n    function _getLanguageForBlock(block) {\n\n        // if this doesn't have a language but the parent does then use that\n        // this means if for example you have: <pre data-language=\"php\">\n        // with a bunch of <code> blocks inside then you do not have\n        // to specify the language for each block\n        var language = _attr(block, 'data-language') || _attr(block.parentNode, 'data-language');\n\n        // this adds support for specifying language via a css class\n        // you can use the Google Code Prettify style: <pre class=\"lang-php\">\n        // or the HTML5 style: <pre><code class=\"language-php\">\n        if (!language) {\n            var pattern = /\\blang(?:uage)?-(\\w+)/,\n                match = block.className.match(pattern) || block.parentNode.className.match(pattern);\n\n            if (match) {\n                language = match[1];\n            }\n        }\n\n        return language;\n    }\n\n    /**\n     * makes sure html entities are always used for tags\n     *\n     * @param {string} code\n     * @returns {string}\n     */\n    function _htmlEntities(code) {\n        return code.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/&(?![\\w\\#]+;)/g, '&amp;');\n    }\n\n    /**\n     * determines if a new match intersects with an existing one\n     *\n     * @param {number} start1    start position of existing match\n     * @param {number} end1      end position of existing match\n     * @param {number} start2    start position of new match\n     * @param {number} end2      end position of new match\n     * @returns {boolean}\n     */\n    function _intersects(start1, end1, start2, end2) {\n        if (start2 >= start1 && start2 < end1) {\n            return true;\n        }\n\n        return end2 > start1 && end2 < end1;\n    }\n\n    /**\n     * determines if two different matches have complete overlap with each other\n     *\n     * @param {number} start1   start position of existing match\n     * @param {number} end1     end position of existing match\n     * @param {number} start2   start position of new match\n     * @param {number} end2     end position of new match\n     * @returns {boolean}\n     */\n    function _hasCompleteOverlap(start1, end1, start2, end2) {\n\n        // if the starting and end positions are exactly the same\n        // then the first one should stay and this one should be ignored\n        if (start2 == start1 && end2 == end1) {\n            return false;\n        }\n\n        return start2 <= start1 && end2 >= end1;\n    }\n\n    /**\n     * determines if the match passed in falls inside of an existing match\n     * this prevents a regex pattern from matching inside of a bigger pattern\n     *\n     * @param {number} start - start position of new match\n     * @param {number} end - end position of new match\n     * @returns {boolean}\n     */\n    function _matchIsInsideOtherMatch(start, end) {\n        for (var key in replacement_positions[CURRENT_LEVEL]) {\n            key = parseInt(key, 10);\n\n            // if this block completely overlaps with another block\n            // then we should remove the other block and return false\n            if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {\n                delete replacement_positions[CURRENT_LEVEL][key];\n                delete replacements[CURRENT_LEVEL][key];\n            }\n\n            if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * takes a string of code and wraps it in a span tag based on the name\n     *\n     * @param {string} name     name of the pattern (ie keyword.regex)\n     * @param {string} code     block of code to wrap\n     * @returns {string}\n     */\n    function _wrapCodeInSpan(name, code) {\n        return '<span class=\"' + name.replace(/\\./g, ' ') + (global_class ? ' ' + global_class : '') + '\">' + code + '</span>';\n    }\n\n    /**\n     * finds out the position of group match for a regular expression\n     *\n     * @see http://stackoverflow.com/questions/1985594/how-to-find-index-of-groups-in-match\n     *\n     * @param {Object} match\n     * @param {number} group_number\n     * @returns {number}\n     */\n    function _indexOfGroup(match, group_number) {\n        var index = 0,\n            i;\n\n        for (i = 1; i < group_number; ++i) {\n            if (match[i]) {\n                index += match[i].length;\n            }\n        }\n\n        return index;\n    }\n\n    /**\n     * matches a regex pattern against a block of code\n     * finds all matches that should be processed and stores the positions\n     * of where they should be replaced within the string\n     *\n     * this is where pretty much all the work is done but it should not\n     * be called directly\n     *\n     * @param {RegExp} pattern\n     * @param {string} code\n     * @returns void\n     */\n    function _processPattern(regex, pattern, code, callback)\n    {\n        var match = regex.exec(code);\n\n        if (!match) {\n            return callback();\n        }\n\n        ++match_counter;\n\n        // treat match 0 the same way as name\n        if (!pattern['name'] && typeof pattern['matches'][0] == 'string') {\n            pattern['name'] = pattern['matches'][0];\n            delete pattern['matches'][0];\n        }\n\n        var replacement = match[0],\n            start_pos = match.index,\n            end_pos = match[0].length + start_pos,\n\n            /**\n             * callback to process the next match of this pattern\n             */\n            processNext = function() {\n                var nextCall = function() {\n                    _processPattern(regex, pattern, code, callback);\n                };\n\n                // every 50 items we process let's call set timeout\n                // to let the ui breathe a little\n                return match_counter % 50 > 0 ? nextCall() : setTimeout(nextCall, 0);\n            };\n\n        // if this is not a child match and it falls inside of another\n        // match that already happened we should skip it and continue processing\n        if (_matchIsInsideOtherMatch(start_pos, end_pos)) {\n            return processNext();\n        }\n\n        /**\n         * callback for when a match was successfully processed\n         *\n         * @param {string} replacement\n         * @returns void\n         */\n        var onMatchSuccess = function(replacement) {\n                // if this match has a name then wrap it in a span tag\n                if (pattern['name']) {\n                    replacement = _wrapCodeInSpan(pattern['name'], replacement);\n                }\n\n                // console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);\n\n                // store what needs to be replaced with what at this position\n                if (!replacements[CURRENT_LEVEL]) {\n                    replacements[CURRENT_LEVEL] = {};\n                    replacement_positions[CURRENT_LEVEL] = {};\n                }\n\n                replacements[CURRENT_LEVEL][start_pos] = {\n                    'replace': match[0],\n                    'with': replacement\n                };\n\n                // store the range of this match so we can use it for comparisons\n                // with other matches later\n                replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;\n\n                // process the next match\n                processNext();\n            },\n\n            // if this pattern has sub matches for different groups in the regex\n            // then we should process them one at a time by rerunning them through\n            // this function to generate the new replacement\n            //\n            // we run through them backwards because the match position of earlier\n            // matches will not change depending on what gets replaced in later\n            // matches\n            group_keys = keys(pattern['matches']),\n\n            /**\n             * callback for processing a sub group\n             *\n             * @param {number} i\n             * @param {Array} group_keys\n             * @param {Function} callback\n             */\n            processGroup = function(i, group_keys, callback) {\n                if (i >= group_keys.length) {\n                    return callback(replacement);\n                }\n\n                var processNextGroup = function() {\n                        processGroup(++i, group_keys, callback);\n                    },\n                    block = match[group_keys[i]];\n\n                // if there is no match here then move on\n                if (!block) {\n                    return processNextGroup();\n                }\n\n                var group = pattern['matches'][group_keys[i]],\n                    language = group['language'],\n\n                    /**\n                     * process group is what group we should use to actually process\n                     * this match group\n                     *\n                     * for example if the subgroup pattern looks like this\n                     * 2: {\n                     *     'name': 'keyword',\n                     *     'pattern': /true/g\n                     * }\n                     *\n                     * then we use that as is, but if it looks like this\n                     *\n                     * 2: {\n                     *     'name': 'keyword',\n                     *     'matches': {\n                     *          'name': 'special',\n                     *          'pattern': /whatever/g\n                     *      }\n                     * }\n                     *\n                     * we treat the 'matches' part as the pattern and keep\n                     * the name around to wrap it with later\n                     */\n                    process_group = group['name'] && group['matches'] ? group['matches'] : group,\n\n                    /**\n                     * takes the code block matched at this group, replaces it\n                     * with the highlighted block, and optionally wraps it with\n                     * a span with a name\n                     *\n                     * @param {string} block\n                     * @param {string} replace_block\n                     * @param {string|null} match_name\n                     */\n                    _replaceAndContinue = function(block, replace_block, match_name) {\n                        replacement = _replaceAtPosition(_indexOfGroup(match, group_keys[i]), block, match_name ? _wrapCodeInSpan(match_name, replace_block) : replace_block, replacement);\n                        processNextGroup();\n                    };\n\n                // if this is a sublanguage go and process the block using that language\n                if (language) {\n                    return _highlightBlockForLanguage(block, language, function(code) {\n                        _replaceAndContinue(block, code);\n                    });\n                }\n\n                // if this is a string then this match is directly mapped to selector\n                // so all we have to do is wrap it in a span and continue\n                if (typeof group === 'string') {\n                    return _replaceAndContinue(block, block, group);\n                }\n\n                // the process group can be a single pattern or an array of patterns\n                // _processCodeWithPatterns always expects an array so we convert it here\n                _processCodeWithPatterns(block, process_group.length ? process_group : [process_group], function(code) {\n                    _replaceAndContinue(block, code, group['matches'] ? group['name'] : 0);\n                });\n            };\n\n        processGroup(0, group_keys, onMatchSuccess);\n    }\n\n    /**\n     * should a language bypass the default patterns?\n     *\n     * if you call Rainbow.extend() and pass true as the third argument\n     * it will bypass the defaults\n     */\n    function _bypassDefaultPatterns(language)\n    {\n        return bypass_defaults[language];\n    }\n\n    /**\n     * returns a list of regex patterns for this language\n     *\n     * @param {string} language\n     * @returns {Array}\n     */\n    function _getPatternsForLanguage(language) {\n        var patterns = language_patterns[language] || [],\n            default_patterns = language_patterns[DEFAULT_LANGUAGE] || [];\n\n        return _bypassDefaultPatterns(language) ? patterns : patterns.concat(default_patterns);\n    }\n\n    /**\n     * substring replace call to replace part of a string at a certain position\n     *\n     * @param {number} position         the position where the replacement should happen\n     * @param {string} replace          the text we want to replace\n     * @param {string} replace_with     the text we want to replace it with\n     * @param {string} code             the code we are doing the replacing in\n     * @returns {string}\n     */\n    function _replaceAtPosition(position, replace, replace_with, code) {\n        var sub_string = code.substr(position);\n        return code.substr(0, position) + sub_string.replace(replace, replace_with);\n    }\n\n   /**\n     * sorts an object by index descending\n     *\n     * @param {Object} object\n     * @return {Array}\n     */\n    function keys(object) {\n        var locations = [],\n            replacement,\n            pos;\n\n        for(var location in object) {\n            if (object.hasOwnProperty(location)) {\n                locations.push(location);\n            }\n        }\n\n        // numeric descending\n        return locations.sort(function(a, b) {\n            return b - a;\n        });\n    }\n\n    /**\n     * processes a block of code using specified patterns\n     *\n     * @param {string} code\n     * @param {Array} patterns\n     * @returns void\n     */\n    function _processCodeWithPatterns(code, patterns, callback)\n    {\n        // we have to increase the level here so that the\n        // replacements will not conflict with each other when\n        // processing sub blocks of code\n        ++CURRENT_LEVEL;\n\n        // patterns are processed one at a time through this function\n        function _workOnPatterns(patterns, i)\n        {\n            // still have patterns to process, keep going\n            if (i < patterns.length) {\n                return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {\n                    _workOnPatterns(patterns, ++i);\n                });\n            }\n\n            // we are done processing the patterns\n            // process the replacements and update the DOM\n            _processReplacements(code, function(code) {\n\n                // when we are done processing replacements\n                // we are done at this level so we can go back down\n                delete replacements[CURRENT_LEVEL];\n                delete replacement_positions[CURRENT_LEVEL];\n                --CURRENT_LEVEL;\n                callback(code);\n            });\n        }\n\n        _workOnPatterns(patterns, 0);\n    }\n\n    /**\n     * process replacements in the string of code to actually update the markup\n     *\n     * @param {string} code         the code to process replacements in\n     * @param {Function} onComplete   what to do when we are done processing\n     * @returns void\n     */\n    function _processReplacements(code, onComplete) {\n\n        /**\n         * processes a single replacement\n         *\n         * @param {string} code\n         * @param {Array} positions\n         * @param {number} i\n         * @param {Function} onComplete\n         * @returns void\n         */\n        function _processReplacement(code, positions, i, onComplete) {\n            if (i < positions.length) {\n                ++replacement_counter;\n                var pos = positions[i],\n                    replacement = replacements[CURRENT_LEVEL][pos];\n                code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);\n\n                // process next function\n                var next = function() {\n                    _processReplacement(code, positions, ++i, onComplete);\n                };\n\n                // use a timeout every 250 to not freeze up the UI\n                return replacement_counter % 250 > 0 ? next() : setTimeout(next, 0);\n            }\n\n            onComplete(code);\n        }\n\n        var string_positions = keys(replacements[CURRENT_LEVEL]);\n        _processReplacement(code, string_positions, 0, onComplete);\n    }\n\n    /**\n     * takes a string of code and highlights it according to the language specified\n     *\n     * @param {string} code\n     * @param {string} language\n     * @param {Function} onComplete\n     * @returns void\n     */\n    function _highlightBlockForLanguage(code, language, onComplete) {\n        var patterns = _getPatternsForLanguage(language);\n        _processCodeWithPatterns(_htmlEntities(code), patterns, onComplete);\n    }\n\n    /**\n     * highlight an individual code block\n     *\n     * @param {Array} code_blocks\n     * @param {number} i\n     * @returns void\n     */\n    function _highlightCodeBlock(code_blocks, i, onComplete) {\n        if (i < code_blocks.length) {\n            var block = code_blocks[i],\n                language = _getLanguageForBlock(block);\n\n            if (!_hasClass(block, 'rainbow') && language) {\n                language = language.toLowerCase();\n\n                _addClass(block, 'rainbow');\n\n                return _highlightBlockForLanguage(block.innerHTML, language, function(code) {\n                    block.innerHTML = code;\n\n                    // reset the replacement arrays\n                    replacements = {};\n                    replacement_positions = {};\n\n                    // if you have a listener attached tell it that this block is now highlighted\n                    if (onHighlight) {\n                        onHighlight(block, language);\n                    }\n\n                    // process the next block\n                    setTimeout(function() {\n                        _highlightCodeBlock(code_blocks, ++i, onComplete);\n                    }, 0);\n                });\n            }\n            return _highlightCodeBlock(code_blocks, ++i, onComplete);\n        }\n\n        if (onComplete) {\n            onComplete();\n        }\n    }\n\n    /**\n     * start highlighting all the code blocks\n     *\n     * @returns void\n     */\n    function _highlight(node, onComplete) {\n\n        // the first argument can be an Event or a DOM Element\n        // I was originally checking instanceof Event but that makes it break\n        // when using mootools\n        //\n        // @see https://github.com/ccampbell/rainbow/issues/32\n        //\n        node = node && typeof node.getElementsByTagName == 'function' ? node : document;\n\n        var pre_blocks = node.getElementsByTagName('pre'),\n            code_blocks = node.getElementsByTagName('code'),\n            i,\n            final_blocks = [];\n\n        // @see http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript\n        // we are going to process all <code> blocks\n        for (i = 0; i < code_blocks.length; ++i) {\n            final_blocks.push(code_blocks[i]);\n        }\n\n        // loop through the pre blocks to see which ones we should add\n        for (i = 0; i < pre_blocks.length; ++i) {\n\n            // if the pre block has no code blocks then process it directly\n            if (!pre_blocks[i].getElementsByTagName('code').length) {\n                final_blocks.push(pre_blocks[i]);\n            }\n        }\n\n        _highlightCodeBlock(final_blocks, 0, onComplete);\n    }\n\n    /**\n     * public methods\n     */\n    return {\n\n        /**\n         * extends the language pattern matches\n         *\n         * @param {*} language     name of language\n         * @param {*} patterns      array of patterns to add on\n         * @param {boolean|null} bypass      if true this will bypass the default language patterns\n         */\n        extend: function(language, patterns, bypass) {\n\n            // if there is only one argument then we assume that we want to\n            // extend the default language rules\n            if (arguments.length == 1) {\n                patterns = language;\n                language = DEFAULT_LANGUAGE;\n            }\n\n            bypass_defaults[language] = bypass;\n            language_patterns[language] = patterns.concat(language_patterns[language] || []);\n        },\n\n        /**\n         * call back to let you do stuff in your app after a piece of code has been highlighted\n         *\n         * @param {Function} callback\n         */\n        onHighlight: function(callback) {\n            onHighlight = callback;\n        },\n\n        /**\n         * method to set a global class that will be applied to all spans\n         *\n         * @param {string} class_name\n         */\n        addClass: function(class_name) {\n            global_class = class_name;\n        },\n\n        /**\n         * starts the magic rainbow\n         *\n         * @returns void\n         */\n        color: function() {\n\n            // if you want to straight up highlight a string you can pass the string of code,\n            // the language, and a callback function\n            if (typeof arguments[0] == 'string') {\n                return _highlightBlockForLanguage(arguments[0], arguments[1], arguments[2]);\n            }\n\n            // if you pass a callback function then we rerun the color function\n            // on all the code and call the callback function on complete\n            if (typeof arguments[0] == 'function') {\n                return _highlight(0, arguments[0]);\n            }\n\n            // otherwise we use whatever node you passed in with an optional\n            // callback function as the second parameter\n            _highlight(arguments[0], arguments[1]);\n        }\n    };\n}) ();\n\n/**\n * adds event listener to start highlighting\n */\n(function() {\n    if (window.addEventListener) {\n        return window.addEventListener('load', Rainbow.color, false);\n    }\n    window.attachEvent('onload', Rainbow.color);\n}) ();\n\n// When using Google closure compiler in advanced mode some methods\n// get renamed.  This keeps a public reference to these methods so they can\n// still be referenced from outside this library.\nRainbow[\"onHighlight\"] = Rainbow.onHighlight;\nRainbow[\"addClass\"] = Rainbow.addClass;\n"
  },
  {
    "path": "misc/demo/assets/smoothscroll-angular-custom.js",
    "content": "/*\n * https://github.com/alicelieutier/smoothScroll/\n * A teeny tiny, standard compliant, smooth scroll script with ease-in-out effect and no jQuery (or any other dependancy, FWIW).\n * MIT License\n */\nwindow.smoothScroll = (function(){\n// We do not want this script to be applied in browsers that do not support those\n// That means no smoothscroll on IE9 and below.\nif(document.querySelectorAll === void 0 || window.pageYOffset === void 0 || history.pushState === void 0) { return; }\n\n// Get the top position of an element in the document\nvar getTop = function(element) {\n    // return value of html.getBoundingClientRect().top ... IE : 0, other browsers : -pageYOffset\n    if(element.nodeName === 'HTML') return -window.pageYOffset\n    return element.getBoundingClientRect().top + window.pageYOffset;\n}\n// ease in out function thanks to:\n// http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/\nvar easeInOutCubic = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }\n\n// calculate the scroll position we should be in\n// given the start and end point of the scroll\n// the time elapsed from the beginning of the scroll\n// and the total duration of the scroll (default 500ms)\nvar position = function(start, end, elapsed, duration) {\n    if (elapsed > duration) return end;\n    return start + (end - start) * easeInOutCubic(elapsed / duration); // <-- you can change the easing funtion there\n    // return start + (end - start) * (elapsed / duration); // <-- this would give a linear scroll\n}\n\n// we use requestAnimationFrame to be called by the browser before every repaint\n// if the first argument is an element then scroll to the top of this element\n// if the first argument is numeric then scroll to this location\n// if the callback exist, it is called when the scrolling is finished\nvar smoothScroll = function(el, duration, callback){\n    duration = duration || 500;\n    var start = window.pageYOffset;\n\n    if (typeof el === 'number') {\n      var end = parseInt(el);\n    } else {\n      var end = getTop(el);\n    }\n\n    var clock = Date.now();\n    var requestAnimationFrame = window.requestAnimationFrame ||\n        window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||\n        function(fn){window.setTimeout(fn, 15);};\n\n    var step = function(){\n        var elapsed = Date.now() - clock;\n        window.scroll(0, position(start, end, elapsed, duration));\n        if (elapsed > duration) {\n            if (typeof callback === 'function') {\n                callback(el);\n            }\n        } else {\n            requestAnimationFrame(step);\n        }\n    }\n    step();\n}\n\nvar linkHandler = function(ev) {\n    ev.preventDefault();\n\n    if (location.hash !== this.hash) {\n      //NOTE(@ajoslin): Changed this line to stop $digest errors\n      //window.history.pushState(null, null, this.hash)\n      angular.element(document).injector().get('$location').hash(this.hash);\n    }\n    // using the history api to solve issue #1 - back doesn't work\n    // most browser don't update :target when the history api is used:\n    // THIS IS A BUG FROM THE BROWSERS.\n    // change the scrolling duration in this call\n    var targetEl = document.getElementById(this.hash.substring(1));\n    if (targetEl) {\n      smoothScroll(document.getElementById(this.hash.substring(1)), 500, function(el) {\n        location.replace('#' + el.id)\n        // this will cause the :target to be activated.\n      });\n    }\n}\n\n// We look for all the internal links in the documents and attach the smoothscroll function\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n    var internal = document.querySelectorAll('a[href^=\"#\"]'), a;\n    for(var i=internal.length; a=internal[--i];){\n        a.addEventListener(\"click\", linkHandler, false);\n    }\n});\n\n// return smoothscroll API\nreturn smoothScroll;\n\n})();\n\n"
  },
  {
    "path": "misc/demo/assets/uglifyjs.js",
    "content": "!function(n,e){\"use strict\";function t(n){for(var e=Object.create(null),t=0;t<n.length;++t)e[n[t]]=!0;return e}function r(n,e){return Array.prototype.slice.call(n,e||0)}function i(n){return n.split(\"\")}function o(n,e){for(var t=e.length;--t>=0;)if(e[t]==n)return!0;return!1}function a(n,e){for(var t=0,r=e.length;r>t;++t)if(n(e[t]))return e[t]}function u(n,e){if(0>=e)return\"\";if(1==e)return n;var t=u(n,e>>1);return t+=t,1&e&&(t+=n),t}function s(n,e){Error.call(this,n),this.msg=n,this.defs=e}function c(n,e,t){n===!0&&(n={});var r=n||{};if(t)for(var i in r)r.hasOwnProperty(i)&&!e.hasOwnProperty(i)&&s.croak(\"`\"+i+\"` is not a supported option\",e);for(var i in e)e.hasOwnProperty(i)&&(r[i]=n&&n.hasOwnProperty(i)?n[i]:e[i]);return r}function f(n,e){for(var t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);return n}function l(){}function p(n,e){n.indexOf(e)<0&&n.push(e)}function d(n,e){return n.replace(/\\{(.+?)\\}/g,function(n,t){return e[t]})}function h(n,e){for(var t=n.length;--t>=0;)n[t]===e&&n.splice(t,1)}function _(n,e){function t(n,t){for(var r=[],i=0,o=0,a=0;i<n.length&&o<t.length;)r[a++]=e(n[i],t[o])<=0?n[i++]:t[o++];return i<n.length&&r.push.apply(r,n.slice(i)),o<t.length&&r.push.apply(r,t.slice(o)),r}function r(n){if(n.length<=1)return n;var e=Math.floor(n.length/2),i=n.slice(0,e),o=n.slice(e);return i=r(i),o=r(o),t(i,o)}return n.length<2?n.slice():r(n)}function m(n,e){return n.filter(function(n){return e.indexOf(n)<0})}function v(n,e){return n.filter(function(n){return e.indexOf(n)>=0})}function g(n){function e(n){if(1==n.length)return t+=\"return str === \"+JSON.stringify(n[0])+\";\";t+=\"switch(str){\";for(var e=0;e<n.length;++e)t+=\"case \"+JSON.stringify(n[e])+\":\";t+=\"return true}return false;\"}n instanceof Array||(n=n.split(\" \"));var t=\"\",r=[];n:for(var i=0;i<n.length;++i){for(var o=0;o<r.length;++o)if(r[o][0].length==n[i].length){r[o].push(n[i]);continue n}r.push([n[i]])}if(r.length>3){r.sort(function(n,e){return e.length-n.length}),t+=\"switch(str.length){\";for(var i=0;i<r.length;++i){var a=r[i];t+=\"case \"+a[0].length+\":\",e(a)}t+=\"}\"}else e(n);return new Function(\"str\",t)}function b(n,e){for(var t=n.length;--t>=0;)if(!e(n[t]))return!1;return!0}function y(){this._values=Object.create(null),this._size=0}function A(n,e,t,r){arguments.length<4&&(r=W),e=e?e.split(/\\s+/):[];var i=e;r&&r.PROPS&&(e=e.concat(r.PROPS));for(var o=\"return function AST_\"+n+\"(props){ if (props) { \",a=e.length;--a>=0;)o+=\"this.\"+e[a]+\" = props.\"+e[a]+\";\";var u=r&&new r;(u&&u.initialize||t&&t.initialize)&&(o+=\"this.initialize();\"),o+=\"}}\";var s=new Function(o)();if(u&&(s.prototype=u,s.BASE=r),r&&r.SUBCLASSES.push(s),s.prototype.CTOR=s,s.PROPS=e||null,s.SELF_PROPS=i,s.SUBCLASSES=[],n&&(s.prototype.TYPE=s.TYPE=n),t)for(a in t)t.hasOwnProperty(a)&&(/^\\$/.test(a)?s[a.substr(1)]=t[a]:s.prototype[a]=t[a]);return s.DEFMETHOD=function(n,e){this.prototype[n]=e},s}function w(n,e){n.body instanceof Y?n.body._walk(e):n.body.forEach(function(n){n._walk(e)})}function E(n){this.visit=n,this.stack=[]}function D(n){return n>=97&&122>=n||n>=65&&90>=n||n>=170&&qt.letter.test(String.fromCharCode(n))}function F(n){return n>=48&&57>=n}function S(n){return F(n)||D(n)}function C(n){return qt.non_spacing_mark.test(n)||qt.space_combining_mark.test(n)}function k(n){return qt.connector_punctuation.test(n)}function x(n){return!St(n)&&/^[a-z_$][a-z0-9_$]*$/i.test(n)}function B(n){return 36==n||95==n||D(n)}function T(n){var e=n.charCodeAt(0);return B(e)||F(e)||8204==e||8205==e||C(n)||k(n)}function $(n){return/^[a-z_$][a-z0-9_$]*$/i.test(n)}function O(n){return xt.test(n)?parseInt(n.substr(2),16):Bt.test(n)?parseInt(n.substr(1),8):Tt.test(n)?parseFloat(n):void 0}function M(n,e,t,r){this.message=n,this.line=e,this.col=t,this.pos=r,this.stack=(new Error).stack}function N(n,e,t,r,i){throw new M(n,t,r,i)}function R(n,e,t){return n.type==e&&(null==t||n.value==t)}function q(n,e,t){function r(){return D.text.charAt(D.pos)}function i(n,e){var t=D.text.charAt(D.pos++);if(n&&!t)throw Ht;return\"\\n\"==t?(D.newline_before=D.newline_before||!e,++D.line,D.col=0):++D.col,t}function o(n){for(;n-->0;)i()}function a(n){return D.text.substr(D.pos,n.length)==n}function u(n,e){var t=D.text.indexOf(n,D.pos);if(e&&-1==t)throw Ht;return t}function s(){D.tokline=D.line,D.tokcol=D.col,D.tokpos=D.pos}function c(n,t,r){D.regex_allowed=\"operator\"==n&&!Pt(t)||\"keyword\"==n&&Ct(t)||\"punc\"==n&&Mt(t),C=\"punc\"==n&&\".\"==t;var i={type:n,value:t,line:D.tokline,col:D.tokcol,pos:D.tokpos,endpos:D.pos,nlb:D.newline_before,file:e};if(!r){i.comments_before=D.comments_before,D.comments_before=[];for(var o=0,a=i.comments_before.length;a>o;o++)i.nlb=i.nlb||i.comments_before[o].nlb}return D.newline_before=!1,new L(i)}function f(){for(;Ot(r());)i()}function l(n){for(var e,t=\"\",o=0;(e=r())&&n(e,o++);)t+=i();return t}function p(n){N(n,e,D.tokline,D.tokcol,D.tokpos)}function d(n){var e=!1,t=!1,r=!1,i=\".\"==n,o=l(function(o,a){var u=o.charCodeAt(0);switch(u){case 120:case 88:return r?!1:r=!0;case 101:case 69:return r?!0:e?!1:e=t=!0;case 45:return t||0==a&&!n;case 43:return t;case t=!1,46:return i||r||e?!1:i=!0}return S(u)});n&&(o=n+o);var a=O(o);return isNaN(a)?void p(\"Invalid syntax: \"+o):c(\"num\",a)}function h(n){var e=i(!0,n);switch(e.charCodeAt(0)){case 110:return\"\\n\";case 114:return\"\\r\";case 116:return\"\t\";case 98:return\"\\b\";case 118:return\"\u000b\";case 102:return\"\\f\";case 48:return\"\\x00\";case 120:return String.fromCharCode(_(2));case 117:return String.fromCharCode(_(4));case 10:return\"\";default:return e}}function _(n){for(var e=0;n>0;--n){var t=parseInt(i(!0),16);isNaN(t)&&p(\"Invalid hex-character pattern in string\"),e=e<<4|t}return e}function m(n){var e,t=D.regex_allowed,r=u(\"\\n\");return-1==r?(e=D.text.substr(D.pos),D.pos=D.text.length):(e=D.text.substring(D.pos,r),D.pos=r),D.comments_before.push(c(n,e,!0)),D.regex_allowed=t,E()}function v(){for(var n,e,t=!1,o=\"\",a=!1;null!=(n=r());)if(t)\"u\"!=n&&p(\"Expecting UnicodeEscapeSequence -- uXXXX\"),n=h(),T(n)||p(\"Unicode char: \"+n.charCodeAt(0)+\" is not valid in identifier\"),o+=n,t=!1;else if(\"\\\\\"==n)a=t=!0,i();else{if(!T(n))break;o+=i()}return Dt(o)&&a&&(e=o.charCodeAt(0).toString(16).toUpperCase(),o=\"\\\\u\"+\"0000\".substr(e.length)+e+o.slice(1)),o}function g(n){function e(n){if(!r())return n;var t=n+r();return $t(t)?(i(),e(t)):n}return c(\"operator\",e(n||i()))}function b(){switch(i(),r()){case\"/\":return i(),m(\"comment1\");case\"*\":return i(),x()}return D.regex_allowed?$(\"\"):g(\"/\")}function y(){return i(),F(r().charCodeAt(0))?d(\".\"):c(\"punc\",\".\")}function A(){var n=v();return C?c(\"name\",n):Ft(n)?c(\"atom\",n):Dt(n)?$t(n)?c(\"operator\",n):c(\"keyword\",n):c(\"name\",n)}function w(n,e){return function(t){try{return e(t)}catch(r){if(r!==Ht)throw r;p(n)}}}function E(n){if(null!=n)return $(n);if(f(),s(),t){if(a(\"<!--\"))return o(4),m(\"comment3\");if(a(\"-->\")&&D.newline_before)return o(3),m(\"comment4\")}var e=r();if(!e)return c(\"eof\");var u=e.charCodeAt(0);switch(u){case 34:case 39:return k();case 46:return y();case 47:return b()}return F(u)?d():Nt(e)?c(\"punc\",i()):kt(e)?g():92==u||B(u)?A():void p(\"Unexpected character '\"+e+\"'\")}var D={text:n.replace(/\\r\\n?|[\\n\\u2028\\u2029]/g,\"\\n\").replace(/\\uFEFF/g,\"\"),filename:e,pos:0,tokpos:0,line:1,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]},C=!1,k=w(\"Unterminated string constant\",function(){for(var n=i(),e=\"\";;){var t=i(!0);if(\"\\\\\"==t){var r=0,o=null;t=l(function(n){if(n>=\"0\"&&\"7\">=n){if(!o)return o=n,++r;if(\"3\">=o&&2>=r)return++r;if(o>=\"4\"&&1>=r)return++r}return!1}),t=r>0?String.fromCharCode(parseInt(t,8)):h(!0)}else if(t==n)break;e+=t}return c(\"string\",e)}),x=w(\"Unterminated multiline comment\",function(){var n=D.regex_allowed,e=u(\"*/\",!0),t=D.text.substring(D.pos,e),r=t.split(\"\\n\"),i=r.length;D.pos=e+2,D.line+=i-1,i>1?D.col=r[i-1].length:D.col+=r[i-1].length,D.col+=2;var o=D.newline_before=D.newline_before||t.indexOf(\"\\n\")>=0;return D.comments_before.push(c(\"comment2\",t,!0)),D.regex_allowed=n,D.newline_before=o,E()}),$=w(\"Unterminated regular expression\",function(n){for(var e,t=!1,r=!1;e=i(!0);)if(t)n+=\"\\\\\"+e,t=!1;else if(\"[\"==e)r=!0,n+=e;else if(\"]\"==e&&r)r=!1,n+=e;else{if(\"/\"==e&&!r)break;\"\\\\\"==e?t=!0:n+=e}var o=v();return c(\"regexp\",new RegExp(n,o))});return E.context=function(n){return n&&(D=n),D},E}function H(n,e){function t(n,e){return R(I.token,n,e)}function r(){return I.peeked||(I.peeked=I.input())}function i(){return I.prev=I.token,I.peeked?(I.token=I.peeked,I.peeked=null):I.token=I.input(),I.in_directives=I.in_directives&&(\"string\"==I.token.type||t(\"punc\",\";\")),I.token}function o(){return I.prev}function u(n,e,t,r){var i=I.input.context();N(n,i.filename,null!=e?e:i.tokline,null!=t?t:i.tokcol,null!=r?r:i.tokpos)}function s(n,e){u(e,n.line,n.col)}function f(n){null==n&&(n=I.token),s(n,\"Unexpected token: \"+n.type+\" (\"+n.value+\")\")}function l(n,e){return t(n,e)?i():void s(I.token,\"Unexpected token \"+I.token.type+\" «\"+I.token.value+\"», expected \"+n+\" «\"+e+\"»\")}function p(n){return l(\"punc\",n)}function d(){return!e.strict&&(I.token.nlb||t(\"eof\")||t(\"punc\",\"}\"))}function h(){t(\"punc\",\";\")?i():d()||f()}function _(){p(\"(\");var n=De(!0);return p(\")\"),n}function m(n){return function(){var e=I.token,t=n(),r=o();return t.start=e,t.end=r,t}}function v(){(t(\"operator\",\"/\")||t(\"operator\",\"/=\"))&&(I.peeked=null,I.token=I.input(I.token.value.substr(1)))}function g(){var n=M(ut);a(function(e){return e.name==n.name},I.labels)&&u(\"Label \"+n.name+\" defined twice\"),p(\":\"),I.labels.push(n);var e=U();return I.labels.pop(),e instanceof te||n.references.forEach(function(e){e instanceof Ae&&(e=e.label.start,u(\"Continue label `\"+n.name+\"` refers to non-IterationStatement.\",e.line,e.col,e.pos))}),new ee({body:e,label:n})}function b(n){return new K({body:(n=De(!0),h(),n)})}function y(n){var e,t=null;d()||(t=M(ct,!0)),null!=t?(e=a(function(n){return n.name==t.name},I.labels),e||u(\"Undefined label \"+t.name),t.thedef=e):0==I.in_loop&&u(n.TYPE+\" not inside a loop or switch\"),h();var r=new n({label:t});return e&&e.references.push(r),r}function A(){p(\"(\");var n=null;return!t(\"punc\",\";\")&&(n=t(\"keyword\",\"var\")?(i(),L(!0)):De(!0,!0),t(\"operator\",\"in\"))?(n instanceof Te&&n.definitions.length>1&&u(\"Only one variable declaration allowed in for..in loop\"),i(),E(n)):w(n)}function w(n){p(\";\");var e=t(\"punc\",\";\")?null:De(!0);p(\";\");var r=t(\"punc\",\")\")?null:De(!0);return p(\")\"),new ae({init:n,condition:e,step:r,body:j(U)})}function E(n){var e=n instanceof Te?n.definitions[0].name:null,t=De(!0);return p(\")\"),new ue({init:n,name:e,object:t,body:j(U)})}function D(){var n=_(),e=U(),r=null;return t(\"keyword\",\"else\")&&(i(),r=U()),new we({condition:n,body:e,alternative:r})}function F(){p(\"{\");for(var n=[];!t(\"punc\",\"}\");)t(\"eof\")&&f(),n.push(U());return i(),n}function S(){p(\"{\");for(var n,e=[],r=null,a=null;!t(\"punc\",\"}\");)t(\"eof\")&&f(),t(\"keyword\",\"case\")?(a&&(a.end=o()),r=[],a=new Se({start:(n=I.token,i(),n),expression:De(!0),body:r}),e.push(a),p(\":\")):t(\"keyword\",\"default\")?(a&&(a.end=o()),r=[],a=new Fe({start:(n=I.token,i(),p(\":\"),n),body:r}),e.push(a)):(r||f(),r.push(U()));return a&&(a.end=o()),i(),e}function C(){var n=F(),e=null,r=null;if(t(\"keyword\",\"catch\")){var a=I.token;i(),p(\"(\");var s=M(at);p(\")\"),e=new ke({start:a,argname:s,body:F(),end:o()})}if(t(\"keyword\",\"finally\")){var a=I.token;i(),r=new xe({start:a,body:F(),end:o()})}return e||r||u(\"Missing catch/finally blocks\"),new Ce({body:n,bcatch:e,bfinally:r})}function k(n,e){for(var r=[];r.push(new Oe({start:I.token,name:M(e?tt:et),value:t(\"operator\",\"=\")?(i(),De(!1,n)):null,end:o()})),t(\"punc\",\",\");)i();return r}function x(){var n,e=I.token;switch(e.type){case\"name\":case\"keyword\":n=O(st);break;case\"num\":n=new dt({start:e,end:e,value:e.value});break;case\"string\":n=new pt({start:e,end:e,value:e.value});break;case\"regexp\":n=new ht({start:e,end:e,value:e.value});break;case\"atom\":switch(e.value){case\"false\":n=new wt({start:e,end:e});break;case\"true\":n=new Et({start:e,end:e});break;case\"null\":n=new mt({start:e,end:e})}}return i(),n}function B(n,e,r){for(var o=!0,a=[];!t(\"punc\",n)&&(o?o=!1:p(\",\"),!e||!t(\"punc\",n));)a.push(t(\"punc\",\",\")&&r?new bt({start:I.token,end:I.token}):De(!1));return i(),a}function T(){var n=I.token;switch(i(),n.type){case\"num\":case\"string\":case\"name\":case\"operator\":case\"keyword\":case\"atom\":return n.value;default:f()}}function $(){var n=I.token;switch(i(),n.type){case\"name\":case\"operator\":case\"keyword\":case\"atom\":return n.value;default:f()}}function O(n){var e=I.token.value;return new(\"this\"==e?ft:n)({name:String(e),start:I.token,end:I.token})}function M(n,e){if(!t(\"name\"))return e||u(\"Name expected\"),null;var r=O(n);return i(),r}function H(n,e,t){return\"++\"!=e&&\"--\"!=e||P(t)||u(\"Invalid use of \"+e+\" operator\"),new n({operator:e,expression:t})}function z(n){return _e(le(!0),0,n)}function P(n){return e.strict?n instanceof ft?!1:n instanceof qe||n instanceof Ze:!0}function j(n){++I.in_loop;var e=n();return--I.in_loop,e}e=c(e,{strict:!1,filename:null,toplevel:null,expression:!1,html5_comments:!0});var I={input:\"string\"==typeof n?q(n,e.filename,e.html5_comments):n,token:null,prev:null,peeked:null,in_function:0,in_directives:!0,in_loop:0,labels:[]};I.token=i();var U=m(function(){var n;switch(v(),I.token.type){case\"string\":var e=I.in_directives,a=b();return e&&a.body instanceof pt&&!t(\"punc\",\",\")?new G({value:a.body.value}):a;case\"num\":case\"regexp\":case\"operator\":case\"atom\":return b();case\"name\":return R(r(),\"punc\",\":\")?g():b();case\"punc\":switch(I.token.value){case\"{\":return new Z({start:I.token,body:F(),end:o()});case\"[\":case\"(\":return b();case\";\":return i(),new Q;default:f()}case\"keyword\":switch(n=I.token.value,i(),n){case\"break\":return y(ye);case\"continue\":return y(Ae);case\"debugger\":return h(),new X;case\"do\":return new ie({body:j(U),condition:(l(\"keyword\",\"while\"),n=_(),h(),n)});case\"while\":return new oe({condition:_(),body:j(U)});case\"for\":return A();case\"function\":return V(he);case\"if\":return D();case\"return\":return 0==I.in_function&&u(\"'return' outside of function\"),new ve({value:t(\"punc\",\";\")?(i(),null):d()?null:(n=De(!0),h(),n)});case\"switch\":return new Ee({expression:_(),body:j(S)});case\"throw\":return I.token.nlb&&u(\"Illegal newline after 'throw'\"),new ge({value:(n=De(!0),h(),n)});case\"try\":return C();case\"var\":return n=L(),h(),n;case\"const\":return n=W(),h(),n;case\"with\":return new se({expression:_(),body:U()});default:f()}}}),V=function(n){var e=n===he,r=t(\"name\")?M(e?it:ot):null;return e&&!r&&f(),p(\"(\"),new n({name:r,argnames:function(n,e){for(;!t(\"punc\",\")\");)n?n=!1:p(\",\"),e.push(M(rt));return i(),e}(!0,[]),body:function(n,e){++I.in_function,I.in_directives=!0,I.in_loop=0,I.labels=[];var t=F();return--I.in_function,I.in_loop=n,I.labels=e,t}(I.in_loop,I.labels)})},L=function(n){return new Te({start:o(),definitions:k(n,!1),end:o()})},W=function(){return new $e({start:o(),definitions:k(!1,!0),end:o()})},Y=function(){var n=I.token;l(\"operator\",\"new\");var e,r=J(!1);return t(\"punc\",\"(\")?(i(),e=B(\")\")):e=[],ce(new Ne({start:n,expression:r,args:e,end:o()}),!0)},J=function(n){if(t(\"operator\",\"new\"))return Y();var e=I.token;if(t(\"punc\")){switch(e.value){case\"(\":i();var r=De(!0);return r.start=e,r.end=I.token,p(\")\"),ce(r,n);case\"[\":return ce(ne(),n);case\"{\":return ce(re(),n)}f()}if(t(\"keyword\",\"function\")){i();var a=V(de);return a.start=e,a.end=o(),ce(a,n)}return Vt[I.token.type]?ce(x(),n):void f()},ne=m(function(){return p(\"[\"),new We({elements:B(\"]\",!e.strict,!0)})}),re=m(function(){p(\"{\");for(var n=!0,r=[];!t(\"punc\",\"}\")&&(n?n=!1:p(\",\"),e.strict||!t(\"punc\",\"}\"));){var a=I.token,u=a.type,s=T();if(\"name\"==u&&!t(\"punc\",\":\")){if(\"get\"==s){r.push(new Je({start:a,key:x(),value:V(pe),end:o()}));continue}if(\"set\"==s){r.push(new Ke({start:a,key:x(),value:V(pe),end:o()}));continue}}p(\":\"),r.push(new Ge({start:a,key:s,value:De(!1),end:o()}))}return i(),new Ye({properties:r})}),ce=function(n,e){var r=n.start;if(t(\"punc\",\".\"))return i(),ce(new He({start:r,expression:n,property:$(),end:o()}),e);if(t(\"punc\",\"[\")){i();var a=De(!0);return p(\"]\"),ce(new ze({start:r,expression:n,property:a,end:o()}),e)}return e&&t(\"punc\",\"(\")?(i(),ce(new Me({start:r,expression:n,args:B(\")\"),end:o()}),!0)):n},le=function(n){var e=I.token;if(t(\"operator\")&&zt(e.value)){i(),v();var r=H(je,e.value,le(n));return r.start=e,r.end=o(),r}for(var a=J(n);t(\"operator\")&&Pt(I.token.value)&&!I.token.nlb;)a=H(Ie,I.token.value,a),a.start=e,a.end=I.token,i();return a},_e=function(n,e,r){var o=t(\"operator\")?I.token.value:null;\"in\"==o&&r&&(o=null);var a=null!=o?It[o]:null;if(null!=a&&a>e){i();var u=_e(le(!0),a,r);return _e(new Ue({start:n.start,left:n,operator:o,right:u,end:u.end}),e,r)}return n},me=function(n){var e=I.token,r=z(n);if(t(\"operator\",\"?\")){i();var a=De(!1);return p(\":\"),new Ve({start:e,condition:r,consequent:a,alternative:De(!1,n),end:o()})}return r},be=function(n){var e=I.token,r=me(n),a=I.token.value;if(t(\"operator\")&&jt(a)){if(P(r))return i(),new Le({start:e,left:r,operator:a,right:be(n),end:o()});u(\"Invalid assignment\")}return r},De=function(n,e){var o=I.token,a=be(e);return n&&t(\"punc\",\",\")?(i(),new Re({start:o,car:a,cdr:De(!0,e),end:r()})):a};return e.expression?De(!0):function(){for(var n=I.token,r=[];!t(\"eof\");)r.push(U());var i=o(),a=e.toplevel;return a?(a.body=a.body.concat(r),a.end=i):a=new fe({start:n,body:r,end:i}),a}()}function z(n,e){E.call(this),this.before=n,this.after=e}function P(n,e,t){this.name=t.name,this.orig=[t],this.scope=n,this.references=[],this.global=!1,this.mangled_name=null,this.undeclared=!1,this.constant=!1,this.index=e}function j(n){function e(n,e){return n.replace(/[\\u0080-\\uffff]/g,function(n){var t=n.charCodeAt(0).toString(16);if(t.length<=2&&!e){for(;t.length<2;)t=\"0\"+t;return\"\\\\x\"+t}for(;t.length<4;)t=\"0\"+t;return\"\\\\u\"+t})}function t(t){var r=0,i=0;return t=t.replace(/[\\\\\\b\\f\\n\\r\\t\\x22\\x27\\u2028\\u2029\\0]/g,function(n){switch(n){case\"\\\\\":return\"\\\\\\\\\";case\"\\b\":return\"\\\\b\";case\"\\f\":return\"\\\\f\";case\"\\n\":return\"\\\\n\";case\"\\r\":return\"\\\\r\";case\"\\u2028\":return\"\\\\u2028\";case\"\\u2029\":return\"\\\\u2029\";case'\"':return++r,'\"';case\"'\":return++i,\"'\";case\"\\x00\":return\"\\\\x00\"}return n}),n.ascii_only&&(t=e(t)),r>i?\"'\"+t.replace(/\\x27/g,\"\\\\'\")+\"'\":'\"'+t.replace(/\\x22/g,'\\\\\"')+'\"'}function r(e){var r=t(e);return n.inline_script&&(r=r.replace(/<\\x2fscript([>\\/\\t\\n\\f\\r ])/gi,\"<\\\\/script$1\")),r}function i(t){return t=t.toString(),n.ascii_only&&(t=e(t,!0)),t}function o(e){return u(\" \",n.indent_start+A-e*n.indent_level)}function a(){return k.charAt(k.length-1)}function s(){n.max_line_len&&w>n.max_line_len&&f(\"\\n\")}function f(e){e=String(e);var t=e.charAt(0);if(C&&(t&&!(\";}\".indexOf(t)<0)||/[;]$/.test(k)||(n.semicolons||x(t)?(F+=\";\",w++,D++):(F+=\"\\n\",D++,E++,w=0),n.beautify||(S=!1)),C=!1,s()),!n.beautify&&n.preserve_line&&q[q.length-1])for(var r=q[q.length-1].start.line;r>E;)F+=\"\\n\",D++,E++,w=0,S=!1;if(S){var i=a();(T(i)&&(T(t)||\"\\\\\"==t)||/^[\\+\\-\\/]$/.test(t)&&t==i)&&(F+=\" \",w++,D++),S=!1}var o=e.split(/\\r?\\n/),u=o.length-1;E+=u,0==u?w+=o[u].length:w=o[u].length,D+=e.length,k=e,F+=e}function p(){C=!1,f(\";\")}function d(){return A+n.indent_level}function h(n){var e;return f(\"{\"),M(),O(d(),function(){e=n()}),$(),f(\"}\"),e}function _(n){f(\"(\");var e=n();return f(\")\"),e}function m(n){f(\"[\");var e=n();return f(\"]\"),e}function v(){f(\",\"),B()}function b(){f(\":\"),n.space_colon&&B()}function y(){return F}n=c(n,{indent_start:0,indent_level:4,quote_keys:!1,space_colon:!0,ascii_only:!1,unescape_regexps:!1,inline_script:!1,width:80,max_line_len:32e3,beautify:!1,source_map:null,bracketize:!1,semicolons:!0,comments:!1,preserve_line:!1,screw_ie8:!1,preamble:null},!0);var A=0,w=0,E=1,D=0,F=\"\",S=!1,C=!1,k=null,x=g(\"( [ + * / - , .\"),B=n.beautify?function(){f(\" \")}:function(){S=!0},$=n.beautify?function(e){n.beautify&&f(o(e?.5:0))}:l,O=n.beautify?function(n,e){n===!0&&(n=d());var t=A;A=n;var r=e();return A=t,r}:function(n,e){return e()},M=n.beautify?function(){f(\"\\n\")}:l,N=n.beautify?function(){f(\";\")}:function(){C=!0},R=n.source_map?function(e,t){try{e&&n.source_map.add(e.file||\"?\",E,w,e.line,e.col,t||\"name\"!=e.type?t:e.value)}catch(r){W.warn(\"Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]\",{file:e.file,line:e.line,col:e.col,cline:E,ccol:w,name:t||\"\"})}}:l;n.preamble&&f(n.preamble.replace(/\\r\\n?|[\\n\\u2028\\u2029]|\\s*$/g,\"\\n\"));var q=[];return{get:y,toString:y,indent:$,indentation:function(){return A},current_width:function(){return w-A},should_break:function(){return n.width&&this.current_width()>=n.width},newline:M,print:f,space:B,comma:v,colon:b,last:function(){return k},semicolon:N,force_semicolon:p,to_ascii:e,print_name:function(n){f(i(n))},print_string:function(n){f(r(n))},next_indent:d,with_indent:O,with_block:h,with_parens:_,with_square:m,add_mapping:R,option:function(e){return n[e]},line:function(){return E},col:function(){return w},pos:function(){return D},push_node:function(n){q.push(n)},pop_node:function(){return q.pop()},stack:function(){return q},parent:function(n){return q[q.length-2-(n||0)]}}}function I(n,e){return this instanceof I?(z.call(this,this.before,this.after),void(this.options=c(n,{sequences:!e,properties:!e,dead_code:!e,drop_debugger:!e,unsafe:!1,unsafe_comps:!1,conditionals:!e,comparisons:!e,evaluate:!e,booleans:!e,loops:!e,unused:!e,hoist_funs:!e,keep_fargs:!1,hoist_vars:!1,if_return:!e,join_vars:!e,cascade:!e,side_effects:!e,pure_getters:!1,pure_funcs:null,negate_iife:!e,screw_ie8:!1,drop_console:!1,angular:!1,warnings:!0,global_defs:{}},!0))):new I(n,e)}function U(n){function e(e,i,o,a,u,s){if(r){var c=r.originalPositionFor({line:a,column:u});if(null===c.source)return;e=c.source,a=c.line,u=c.column,s=c.name}t.addMapping({generated:{line:i+n.dest_line_diff,column:o},original:{line:a+n.orig_line_diff,column:u},source:e,name:s})}n=c(n,{file:null,root:null,orig:null,orig_line_diff:0,dest_line_diff:0});var t=new MOZ_SourceMap.SourceMapGenerator({file:n.file,sourceRoot:n.root}),r=n.orig&&new MOZ_SourceMap.SourceMapConsumer(n.orig);return{add:e,get:function(){return t},toString:function(){return t.toString()}}}e.UglifyJS=n,s.prototype=Object.create(Error.prototype),s.prototype.constructor=s,s.croak=function(n,e){throw new s(n,e)};var V=function(){function n(n,o,a){function u(){var u=o(n[s],s),l=u instanceof r;return l&&(u=u.v),u instanceof e?(u=u.v,u instanceof t?f.push.apply(f,a?u.v.slice().reverse():u.v):f.push(u)):u!==i&&(u instanceof t?c.push.apply(c,a?u.v.slice().reverse():u.v):c.push(u)),l}var s,c=[],f=[];if(n instanceof Array)if(a){for(s=n.length;--s>=0&&!u(););c.reverse(),f.reverse()}else for(s=0;s<n.length&&!u();++s);else for(s in n)if(n.hasOwnProperty(s)&&u())break;return f.concat(c)}function e(n){this.v=n}function t(n){this.v=n}function r(n){this.v=n}n.at_top=function(n){return new e(n)},n.splice=function(n){return new t(n)},n.last=function(n){return new r(n)};var i=n.skip={};return n}();y.prototype={set:function(n,e){return this.has(n)||++this._size,this._values[\"$\"+n]=e,this},add:function(n,e){return this.has(n)?this.get(n).push(e):this.set(n,[e]),this},get:function(n){return this._values[\"$\"+n]},del:function(n){return this.has(n)&&(--this._size,delete this._values[\"$\"+n]),this},has:function(n){return\"$\"+n in this._values},each:function(n){for(var e in this._values)n(this._values[e],e.substr(1))},size:function(){return this._size},map:function(n){var e=[];for(var t in this._values)e.push(n(this._values[t],t.substr(1)));return e}};var L=A(\"Token\",\"type value line col pos endpos nlb comments_before file\",{},null),W=A(\"Node\",\"start end\",{clone:function(){return new this.CTOR(this)},$documentation:\"Base class of all AST nodes\",$propdoc:{start:\"[AST_Token] The first token of this node\",end:\"[AST_Token] The last token of this node\"},_walk:function(n){return n._visit(this)},walk:function(n){return this._walk(n)}},null);W.warn_function=null,W.warn=function(n,e){W.warn_function&&W.warn_function(d(n,e))};var Y=A(\"Statement\",null,{$documentation:\"Base class of all statements\"}),X=A(\"Debugger\",null,{$documentation:\"Represents a debugger statement\"},Y),G=A(\"Directive\",\"value scope\",{$documentation:'Represents a directive, like \"use strict\";',$propdoc:{value:\"[string] The value of this directive as a plain string (it's not an AST_String!)\",scope:\"[AST_Scope/S] The scope that this directive affects\"}},Y),K=A(\"SimpleStatement\",\"body\",{$documentation:\"A statement consisting of an expression, i.e. a = 1 + 2\",$propdoc:{body:\"[AST_Node] an expression node (should not be instanceof AST_Statement)\"},_walk:function(n){return n._visit(this,function(){this.body._walk(n)})}},Y),J=A(\"Block\",\"body\",{$documentation:\"A body of statements (usually bracketed)\",$propdoc:{body:\"[AST_Statement*] an array of statements\"},_walk:function(n){return n._visit(this,function(){w(this,n)})}},Y),Z=A(\"BlockStatement\",null,{$documentation:\"A block statement\"},J),Q=A(\"EmptyStatement\",null,{$documentation:\"The empty statement (empty block or simply a semicolon)\",_walk:function(n){return n._visit(this)}},Y),ne=A(\"StatementWithBody\",\"body\",{$documentation:\"Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`\",$propdoc:{body:\"[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement\"},_walk:function(n){return n._visit(this,function(){this.body._walk(n)})}},Y),ee=A(\"LabeledStatement\",\"label\",{$documentation:\"Statement with a label\",$propdoc:{label:\"[AST_Label] a label definition\"},_walk:function(n){return n._visit(this,function(){this.label._walk(n),this.body._walk(n)})}},ne),te=A(\"IterationStatement\",null,{$documentation:\"Internal class.  All loops inherit from it.\"},ne),re=A(\"DWLoop\",\"condition\",{$documentation:\"Base class for do/while statements\",$propdoc:{condition:\"[AST_Node] the loop condition.  Should not be instanceof AST_Statement\"},_walk:function(n){return n._visit(this,function(){this.condition._walk(n),this.body._walk(n)})}},te),ie=A(\"Do\",null,{$documentation:\"A `do` statement\"},re),oe=A(\"While\",null,{$documentation:\"A `while` statement\"},re),ae=A(\"For\",\"init condition step\",{$documentation:\"A `for` statement\",$propdoc:{init:\"[AST_Node?] the `for` initialization code, or null if empty\",condition:\"[AST_Node?] the `for` termination clause, or null if empty\",step:\"[AST_Node?] the `for` update clause, or null if empty\"},_walk:function(n){return n._visit(this,function(){this.init&&this.init._walk(n),this.condition&&this.condition._walk(n),this.step&&this.step._walk(n),this.body._walk(n)})}},te),ue=A(\"ForIn\",\"init name object\",{$documentation:\"A `for ... in` statement\",$propdoc:{init:\"[AST_Node] the `for/in` initialization code\",name:\"[AST_SymbolRef?] the loop variable, only if `init` is AST_Var\",object:\"[AST_Node] the object that we're looping through\"},_walk:function(n){return n._visit(this,function(){this.init._walk(n),this.object._walk(n),this.body._walk(n)})}},te),se=A(\"With\",\"expression\",{$documentation:\"A `with` statement\",$propdoc:{expression:\"[AST_Node] the `with` expression\"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),this.body._walk(n)})}},ne),ce=A(\"Scope\",\"directives variables functions uses_with uses_eval parent_scope enclosed cname\",{$documentation:\"Base class for all statements introducing a lexical scope\",$propdoc:{directives:\"[string*/S] an array of directives declared in this scope\",variables:\"[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope\",functions:\"[Object/S] like `variables`, but only lists function declarations\",uses_with:\"[boolean/S] tells whether this scope uses the `with` statement\",uses_eval:\"[boolean/S] tells whether this scope contains a direct call to the global `eval`\",parent_scope:\"[AST_Scope?/S] link to the parent scope\",enclosed:\"[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes\",cname:\"[integer/S] current index for mangling variables (used internally by the mangler)\"}},J),fe=A(\"Toplevel\",\"globals\",{$documentation:\"The toplevel scope\",$propdoc:{globals:\"[Object/S] a map of name -> SymbolDef for all undeclared names\"},wrap_enclose:function(n){var e=this,t=[],r=[];n.forEach(function(n){var e=n.lastIndexOf(\":\");t.push(n.substr(0,e)),r.push(n.substr(e+1))});var i=\"(function(\"+r.join(\",\")+\"){ '$ORIG'; })(\"+t.join(\",\")+\")\";return i=H(i),i=i.transform(new z(function(n){return n instanceof G&&\"$ORIG\"==n.value?V.splice(e.body):void 0}))},wrap_commonjs:function(n,e){var t=this,r=[];e&&(t.figure_out_scope(),t.walk(new E(function(n){n instanceof nt&&n.definition().global&&(a(function(e){return e.name==n.name},r)||r.push(n))})));var i=\"(function(exports, global){ global['\"+n+\"'] = exports; '$ORIG'; '$EXPORTS'; }({}, (function(){return this}())))\";return i=H(i),i=i.transform(new z(function(n){if(n instanceof K&&(n=n.body,n instanceof pt))switch(n.getValue()){case\"$ORIG\":return V.splice(t.body);case\"$EXPORTS\":var e=[];return r.forEach(function(n){e.push(new K({body:new Le({left:new ze({expression:new st({name:\"exports\"}),property:new pt({value:n.name})}),operator:\"=\",right:new st(n)})}))}),V.splice(e)}}))}},ce),le=A(\"Lambda\",\"name argnames uses_arguments\",{$documentation:\"Base class for functions\",$propdoc:{name:\"[AST_SymbolDeclaration?] the name of this function\",argnames:\"[AST_SymbolFunarg*] array of function arguments\",uses_arguments:\"[boolean/S] tells whether this function accesses the arguments array\"},_walk:function(n){return n._visit(this,function(){this.name&&this.name._walk(n),this.argnames.forEach(function(e){e._walk(n)}),w(this,n)})}},ce),pe=A(\"Accessor\",null,{$documentation:\"A setter/getter function.  The `name` property is always null.\"},le),de=A(\"Function\",null,{$documentation:\"A function expression\"},le),he=A(\"Defun\",null,{$documentation:\"A function definition\"},le),_e=A(\"Jump\",null,{$documentation:\"Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)\"},Y),me=A(\"Exit\",\"value\",{$documentation:\"Base class for “exits” (`return` and `throw`)\",$propdoc:{value:\"[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return\"},_walk:function(n){return n._visit(this,this.value&&function(){this.value._walk(n)})}},_e),ve=A(\"Return\",null,{$documentation:\"A `return` statement\"},me),ge=A(\"Throw\",null,{$documentation:\"A `throw` statement\"},me),be=A(\"LoopControl\",\"label\",{$documentation:\"Base class for loop control statements (`break` and `continue`)\",$propdoc:{label:\"[AST_LabelRef?] the label, or null if none\"},_walk:function(n){return n._visit(this,this.label&&function(){this.label._walk(n)})}},_e),ye=A(\"Break\",null,{$documentation:\"A `break` statement\"},be),Ae=A(\"Continue\",null,{$documentation:\"A `continue` statement\"},be),we=A(\"If\",\"condition alternative\",{$documentation:\"A `if` statement\",$propdoc:{condition:\"[AST_Node] the `if` condition\",alternative:\"[AST_Statement?] the `else` part, or null if not present\"},_walk:function(n){return n._visit(this,function(){this.condition._walk(n),this.body._walk(n),this.alternative&&this.alternative._walk(n)})}},ne),Ee=A(\"Switch\",\"expression\",{$documentation:\"A `switch` statement\",$propdoc:{expression:\"[AST_Node] the `switch` “discriminant”\"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),w(this,n)})}},J),De=A(\"SwitchBranch\",null,{$documentation:\"Base class for `switch` branches\"},J),Fe=A(\"Default\",null,{$documentation:\"A `default` switch branch\"},De),Se=A(\"Case\",\"expression\",{$documentation:\"A `case` switch branch\",$propdoc:{expression:\"[AST_Node] the `case` expression\"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),w(this,n)})}},De),Ce=A(\"Try\",\"bcatch bfinally\",{$documentation:\"A `try` statement\",$propdoc:{bcatch:\"[AST_Catch?] the catch block, or null if not present\",bfinally:\"[AST_Finally?] the finally block, or null if not present\"},_walk:function(n){return n._visit(this,function(){w(this,n),this.bcatch&&this.bcatch._walk(n),this.bfinally&&this.bfinally._walk(n)})}},J),ke=A(\"Catch\",\"argname\",{$documentation:\"A `catch` node; only makes sense as part of a `try` statement\",$propdoc:{argname:\"[AST_SymbolCatch] symbol for the exception\"},_walk:function(n){return n._visit(this,function(){this.argname._walk(n),w(this,n)\n})}},J),xe=A(\"Finally\",null,{$documentation:\"A `finally` node; only makes sense as part of a `try` statement\"},J),Be=A(\"Definitions\",\"definitions\",{$documentation:\"Base class for `var` or `const` nodes (variable declarations/initializations)\",$propdoc:{definitions:\"[AST_VarDef*] array of variable definitions\"},_walk:function(n){return n._visit(this,function(){this.definitions.forEach(function(e){e._walk(n)})})}},Y),Te=A(\"Var\",null,{$documentation:\"A `var` statement\"},Be),$e=A(\"Const\",null,{$documentation:\"A `const` statement\"},Be),Oe=A(\"VarDef\",\"name value\",{$documentation:\"A variable declaration; only appears in a AST_Definitions node\",$propdoc:{name:\"[AST_SymbolVar|AST_SymbolConst] name of the variable\",value:\"[AST_Node?] initializer, or null of there's no initializer\"},_walk:function(n){return n._visit(this,function(){this.name._walk(n),this.value&&this.value._walk(n)})}}),Me=A(\"Call\",\"expression args\",{$documentation:\"A function call expression\",$propdoc:{expression:\"[AST_Node] expression to invoke as function\",args:\"[AST_Node*] array of arguments\"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n),this.args.forEach(function(e){e._walk(n)})})}}),Ne=A(\"New\",null,{$documentation:\"An object instantiation.  Derives from a function call since it has exactly the same properties\"},Me),Re=A(\"Seq\",\"car cdr\",{$documentation:\"A sequence expression (two comma-separated expressions)\",$propdoc:{car:\"[AST_Node] first element in sequence\",cdr:\"[AST_Node] second element in sequence\"},$cons:function(n,e){var t=new Re(n);return t.car=n,t.cdr=e,t},$from_array:function(n){if(0==n.length)return null;if(1==n.length)return n[0].clone();for(var e=null,t=n.length;--t>=0;)e=Re.cons(n[t],e);for(var r=e;r;){if(r.cdr&&!r.cdr.cdr){r.cdr=r.cdr.car;break}r=r.cdr}return e},to_array:function(){for(var n=this,e=[];n;){if(e.push(n.car),n.cdr&&!(n.cdr instanceof Re)){e.push(n.cdr);break}n=n.cdr}return e},add:function(n){for(var e=this;e;){if(!(e.cdr instanceof Re)){var t=Re.cons(e.cdr,n);return e.cdr=t}e=e.cdr}},_walk:function(n){return n._visit(this,function(){this.car._walk(n),this.cdr&&this.cdr._walk(n)})}}),qe=A(\"PropAccess\",\"expression property\",{$documentation:'Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`',$propdoc:{expression:\"[AST_Node] the “container” expression\",property:\"[AST_Node|string] the property to access.  For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node\"}}),He=A(\"Dot\",null,{$documentation:\"A dotted property access expression\",_walk:function(n){return n._visit(this,function(){this.expression._walk(n)})}},qe),ze=A(\"Sub\",null,{$documentation:'Index-style property access, i.e. `a[\"foo\"]`',_walk:function(n){return n._visit(this,function(){this.expression._walk(n),this.property._walk(n)})}},qe),Pe=A(\"Unary\",\"operator expression\",{$documentation:\"Base class for unary expressions\",$propdoc:{operator:\"[string] the operator\",expression:\"[AST_Node] expression that this unary operator applies to\"},_walk:function(n){return n._visit(this,function(){this.expression._walk(n)})}}),je=A(\"UnaryPrefix\",null,{$documentation:\"Unary prefix expression, i.e. `typeof i` or `++i`\"},Pe),Ie=A(\"UnaryPostfix\",null,{$documentation:\"Unary postfix expression, i.e. `i++`\"},Pe),Ue=A(\"Binary\",\"left operator right\",{$documentation:\"Binary expression, i.e. `a + b`\",$propdoc:{left:\"[AST_Node] left-hand side expression\",operator:\"[string] the operator\",right:\"[AST_Node] right-hand side expression\"},_walk:function(n){return n._visit(this,function(){this.left._walk(n),this.right._walk(n)})}}),Ve=A(\"Conditional\",\"condition consequent alternative\",{$documentation:\"Conditional expression using the ternary operator, i.e. `a ? b : c`\",$propdoc:{condition:\"[AST_Node]\",consequent:\"[AST_Node]\",alternative:\"[AST_Node]\"},_walk:function(n){return n._visit(this,function(){this.condition._walk(n),this.consequent._walk(n),this.alternative._walk(n)})}}),Le=A(\"Assign\",null,{$documentation:\"An assignment expression — `a = b + 5`\"},Ue),We=A(\"Array\",\"elements\",{$documentation:\"An array literal\",$propdoc:{elements:\"[AST_Node*] array of elements\"},_walk:function(n){return n._visit(this,function(){this.elements.forEach(function(e){e._walk(n)})})}}),Ye=A(\"Object\",\"properties\",{$documentation:\"An object literal\",$propdoc:{properties:\"[AST_ObjectProperty*] array of properties\"},_walk:function(n){return n._visit(this,function(){this.properties.forEach(function(e){e._walk(n)})})}}),Xe=A(\"ObjectProperty\",\"key value\",{$documentation:\"Base class for literal object properties\",$propdoc:{key:\"[string] the property name converted to a string for ObjectKeyVal.  For setters and getters this is an arbitrary AST_Node.\",value:\"[AST_Node] property value.  For setters and getters this is an AST_Function.\"},_walk:function(n){return n._visit(this,function(){this.value._walk(n)})}}),Ge=A(\"ObjectKeyVal\",null,{$documentation:\"A key: value object property\"},Xe),Ke=A(\"ObjectSetter\",null,{$documentation:\"An object setter property\"},Xe),Je=A(\"ObjectGetter\",null,{$documentation:\"An object getter property\"},Xe),Ze=A(\"Symbol\",\"scope name thedef\",{$propdoc:{name:\"[string] name of this symbol\",scope:\"[AST_Scope/S] the current scope (not necessarily the definition scope)\",thedef:\"[SymbolDef/S] the definition of this symbol\"},$documentation:\"Base class for all symbols\"}),Qe=A(\"SymbolAccessor\",null,{$documentation:\"The name of a property accessor (setter/getter function)\"},Ze),nt=A(\"SymbolDeclaration\",\"init\",{$documentation:\"A declaration symbol (symbol in var/const, function name or argument, symbol in catch)\",$propdoc:{init:\"[AST_Node*/S] array of initializers for this declaration.\"}},Ze),et=A(\"SymbolVar\",null,{$documentation:\"Symbol defining a variable\"},nt),tt=A(\"SymbolConst\",null,{$documentation:\"A constant declaration\"},nt),rt=A(\"SymbolFunarg\",null,{$documentation:\"Symbol naming a function argument\"},et),it=A(\"SymbolDefun\",null,{$documentation:\"Symbol defining a function\"},nt),ot=A(\"SymbolLambda\",null,{$documentation:\"Symbol naming a function expression\"},nt),at=A(\"SymbolCatch\",null,{$documentation:\"Symbol naming the exception in catch\"},nt),ut=A(\"Label\",\"references\",{$documentation:\"Symbol naming a label (declaration)\",$propdoc:{references:\"[AST_LoopControl*] a list of nodes referring to this label\"},initialize:function(){this.references=[],this.thedef=this}},Ze),st=A(\"SymbolRef\",null,{$documentation:\"Reference to some symbol (not definition/declaration)\"},Ze),ct=A(\"LabelRef\",null,{$documentation:\"Reference to a label symbol\"},Ze),ft=A(\"This\",null,{$documentation:\"The `this` symbol\"},Ze),lt=A(\"Constant\",null,{$documentation:\"Base class for all constants\",getValue:function(){return this.value}}),pt=A(\"String\",\"value\",{$documentation:\"A string literal\",$propdoc:{value:\"[string] the contents of this string\"}},lt),dt=A(\"Number\",\"value\",{$documentation:\"A number literal\",$propdoc:{value:\"[number] the numeric value\"}},lt),ht=A(\"RegExp\",\"value\",{$documentation:\"A regexp literal\",$propdoc:{value:\"[RegExp] the actual regexp\"}},lt),_t=A(\"Atom\",null,{$documentation:\"Base class for atoms\"},lt),mt=A(\"Null\",null,{$documentation:\"The `null` atom\",value:null},_t),vt=A(\"NaN\",null,{$documentation:\"The impossible value\",value:0/0},_t),gt=A(\"Undefined\",null,{$documentation:\"The `undefined` value\",value:void 0},_t),bt=A(\"Hole\",null,{$documentation:\"A hole in an array\",value:void 0},_t),yt=A(\"Infinity\",null,{$documentation:\"The `Infinity` value\",value:1/0},_t),At=A(\"Boolean\",null,{$documentation:\"Base class for booleans\"},_t),wt=A(\"False\",null,{$documentation:\"The `false` atom\",value:!1},At),Et=A(\"True\",null,{$documentation:\"The `true` atom\",value:!0},At);E.prototype={_visit:function(n,e){this.stack.push(n);var t=this.visit(n,e?function(){e.call(n)}:l);return!t&&e&&e.call(n),this.stack.pop(),t},parent:function(n){return this.stack[this.stack.length-2-(n||0)]},push:function(n){this.stack.push(n)},pop:function(){return this.stack.pop()},self:function(){return this.stack[this.stack.length-1]},find_parent:function(n){for(var e=this.stack,t=e.length;--t>=0;){var r=e[t];if(r instanceof n)return r}},has_directive:function(n){return this.find_parent(ce).has_directive(n)},in_boolean_context:function(){for(var n=this.stack,e=n.length,t=n[--e];e>0;){var r=n[--e];if(r instanceof we&&r.condition===t||r instanceof Ve&&r.condition===t||r instanceof re&&r.condition===t||r instanceof ae&&r.condition===t||r instanceof je&&\"!\"==r.operator&&r.expression===t)return!0;if(!(r instanceof Ue)||\"&&\"!=r.operator&&\"||\"!=r.operator)return!1;t=r}},loopcontrol_target:function(n){var e=this.stack;if(n)for(var t=e.length;--t>=0;){var r=e[t];if(r instanceof ee&&r.label.name==n.name)return r.body}else for(var t=e.length;--t>=0;){var r=e[t];if(r instanceof Ee||r instanceof te)return r}}};var Dt=\"break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var void while with\",Ft=\"false null true\",St=\"abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield \"+Ft+\" \"+Dt,Ct=\"return new delete throw else case\";Dt=g(Dt),St=g(St),Ct=g(Ct),Ft=g(Ft);var kt=g(i(\"+-*&%=<>!?|~^\")),xt=/^0x[0-9a-f]+$/i,Bt=/^0[0-7]+$/,Tt=/^\\d*\\.?\\d*(?:e[+-]?\\d*(?:\\d\\.?|\\.?\\d)\\d*)?$/i,$t=g([\"in\",\"instanceof\",\"typeof\",\"new\",\"void\",\"delete\",\"++\",\"--\",\"+\",\"-\",\"!\",\"~\",\"&\",\"|\",\"^\",\"*\",\"/\",\"%\",\">>\",\"<<\",\">>>\",\"<\",\">\",\"<=\",\">=\",\"==\",\"===\",\"!=\",\"!==\",\"?\",\"=\",\"+=\",\"-=\",\"/=\",\"*=\",\"%=\",\">>=\",\"<<=\",\">>>=\",\"|=\",\"^=\",\"&=\",\"&&\",\"||\"]),Ot=g(i(\"  \\n\\r\t\\f\u000b​᠎             　\")),Mt=g(i(\"[{(,.;:\")),Nt=g(i(\"[]{}(),;:\")),Rt=g(i(\"gmsiy\")),qt={letter:new RegExp(\"[\\\\u0041-\\\\u005A\\\\u0061-\\\\u007A\\\\u00AA\\\\u00B5\\\\u00BA\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02C1\\\\u02C6-\\\\u02D1\\\\u02E0-\\\\u02E4\\\\u02EC\\\\u02EE\\\\u0370-\\\\u0374\\\\u0376\\\\u0377\\\\u037A-\\\\u037D\\\\u0386\\\\u0388-\\\\u038A\\\\u038C\\\\u038E-\\\\u03A1\\\\u03A3-\\\\u03F5\\\\u03F7-\\\\u0481\\\\u048A-\\\\u0523\\\\u0531-\\\\u0556\\\\u0559\\\\u0561-\\\\u0587\\\\u05D0-\\\\u05EA\\\\u05F0-\\\\u05F2\\\\u0621-\\\\u064A\\\\u066E\\\\u066F\\\\u0671-\\\\u06D3\\\\u06D5\\\\u06E5\\\\u06E6\\\\u06EE\\\\u06EF\\\\u06FA-\\\\u06FC\\\\u06FF\\\\u0710\\\\u0712-\\\\u072F\\\\u074D-\\\\u07A5\\\\u07B1\\\\u07CA-\\\\u07EA\\\\u07F4\\\\u07F5\\\\u07FA\\\\u0904-\\\\u0939\\\\u093D\\\\u0950\\\\u0958-\\\\u0961\\\\u0971\\\\u0972\\\\u097B-\\\\u097F\\\\u0985-\\\\u098C\\\\u098F\\\\u0990\\\\u0993-\\\\u09A8\\\\u09AA-\\\\u09B0\\\\u09B2\\\\u09B6-\\\\u09B9\\\\u09BD\\\\u09CE\\\\u09DC\\\\u09DD\\\\u09DF-\\\\u09E1\\\\u09F0\\\\u09F1\\\\u0A05-\\\\u0A0A\\\\u0A0F\\\\u0A10\\\\u0A13-\\\\u0A28\\\\u0A2A-\\\\u0A30\\\\u0A32\\\\u0A33\\\\u0A35\\\\u0A36\\\\u0A38\\\\u0A39\\\\u0A59-\\\\u0A5C\\\\u0A5E\\\\u0A72-\\\\u0A74\\\\u0A85-\\\\u0A8D\\\\u0A8F-\\\\u0A91\\\\u0A93-\\\\u0AA8\\\\u0AAA-\\\\u0AB0\\\\u0AB2\\\\u0AB3\\\\u0AB5-\\\\u0AB9\\\\u0ABD\\\\u0AD0\\\\u0AE0\\\\u0AE1\\\\u0B05-\\\\u0B0C\\\\u0B0F\\\\u0B10\\\\u0B13-\\\\u0B28\\\\u0B2A-\\\\u0B30\\\\u0B32\\\\u0B33\\\\u0B35-\\\\u0B39\\\\u0B3D\\\\u0B5C\\\\u0B5D\\\\u0B5F-\\\\u0B61\\\\u0B71\\\\u0B83\\\\u0B85-\\\\u0B8A\\\\u0B8E-\\\\u0B90\\\\u0B92-\\\\u0B95\\\\u0B99\\\\u0B9A\\\\u0B9C\\\\u0B9E\\\\u0B9F\\\\u0BA3\\\\u0BA4\\\\u0BA8-\\\\u0BAA\\\\u0BAE-\\\\u0BB9\\\\u0BD0\\\\u0C05-\\\\u0C0C\\\\u0C0E-\\\\u0C10\\\\u0C12-\\\\u0C28\\\\u0C2A-\\\\u0C33\\\\u0C35-\\\\u0C39\\\\u0C3D\\\\u0C58\\\\u0C59\\\\u0C60\\\\u0C61\\\\u0C85-\\\\u0C8C\\\\u0C8E-\\\\u0C90\\\\u0C92-\\\\u0CA8\\\\u0CAA-\\\\u0CB3\\\\u0CB5-\\\\u0CB9\\\\u0CBD\\\\u0CDE\\\\u0CE0\\\\u0CE1\\\\u0D05-\\\\u0D0C\\\\u0D0E-\\\\u0D10\\\\u0D12-\\\\u0D28\\\\u0D2A-\\\\u0D39\\\\u0D3D\\\\u0D60\\\\u0D61\\\\u0D7A-\\\\u0D7F\\\\u0D85-\\\\u0D96\\\\u0D9A-\\\\u0DB1\\\\u0DB3-\\\\u0DBB\\\\u0DBD\\\\u0DC0-\\\\u0DC6\\\\u0E01-\\\\u0E30\\\\u0E32\\\\u0E33\\\\u0E40-\\\\u0E46\\\\u0E81\\\\u0E82\\\\u0E84\\\\u0E87\\\\u0E88\\\\u0E8A\\\\u0E8D\\\\u0E94-\\\\u0E97\\\\u0E99-\\\\u0E9F\\\\u0EA1-\\\\u0EA3\\\\u0EA5\\\\u0EA7\\\\u0EAA\\\\u0EAB\\\\u0EAD-\\\\u0EB0\\\\u0EB2\\\\u0EB3\\\\u0EBD\\\\u0EC0-\\\\u0EC4\\\\u0EC6\\\\u0EDC\\\\u0EDD\\\\u0F00\\\\u0F40-\\\\u0F47\\\\u0F49-\\\\u0F6C\\\\u0F88-\\\\u0F8B\\\\u1000-\\\\u102A\\\\u103F\\\\u1050-\\\\u1055\\\\u105A-\\\\u105D\\\\u1061\\\\u1065\\\\u1066\\\\u106E-\\\\u1070\\\\u1075-\\\\u1081\\\\u108E\\\\u10A0-\\\\u10C5\\\\u10D0-\\\\u10FA\\\\u10FC\\\\u1100-\\\\u1159\\\\u115F-\\\\u11A2\\\\u11A8-\\\\u11F9\\\\u1200-\\\\u1248\\\\u124A-\\\\u124D\\\\u1250-\\\\u1256\\\\u1258\\\\u125A-\\\\u125D\\\\u1260-\\\\u1288\\\\u128A-\\\\u128D\\\\u1290-\\\\u12B0\\\\u12B2-\\\\u12B5\\\\u12B8-\\\\u12BE\\\\u12C0\\\\u12C2-\\\\u12C5\\\\u12C8-\\\\u12D6\\\\u12D8-\\\\u1310\\\\u1312-\\\\u1315\\\\u1318-\\\\u135A\\\\u1380-\\\\u138F\\\\u13A0-\\\\u13F4\\\\u1401-\\\\u166C\\\\u166F-\\\\u1676\\\\u1681-\\\\u169A\\\\u16A0-\\\\u16EA\\\\u1700-\\\\u170C\\\\u170E-\\\\u1711\\\\u1720-\\\\u1731\\\\u1740-\\\\u1751\\\\u1760-\\\\u176C\\\\u176E-\\\\u1770\\\\u1780-\\\\u17B3\\\\u17D7\\\\u17DC\\\\u1820-\\\\u1877\\\\u1880-\\\\u18A8\\\\u18AA\\\\u1900-\\\\u191C\\\\u1950-\\\\u196D\\\\u1970-\\\\u1974\\\\u1980-\\\\u19A9\\\\u19C1-\\\\u19C7\\\\u1A00-\\\\u1A16\\\\u1B05-\\\\u1B33\\\\u1B45-\\\\u1B4B\\\\u1B83-\\\\u1BA0\\\\u1BAE\\\\u1BAF\\\\u1C00-\\\\u1C23\\\\u1C4D-\\\\u1C4F\\\\u1C5A-\\\\u1C7D\\\\u1D00-\\\\u1DBF\\\\u1E00-\\\\u1F15\\\\u1F18-\\\\u1F1D\\\\u1F20-\\\\u1F45\\\\u1F48-\\\\u1F4D\\\\u1F50-\\\\u1F57\\\\u1F59\\\\u1F5B\\\\u1F5D\\\\u1F5F-\\\\u1F7D\\\\u1F80-\\\\u1FB4\\\\u1FB6-\\\\u1FBC\\\\u1FBE\\\\u1FC2-\\\\u1FC4\\\\u1FC6-\\\\u1FCC\\\\u1FD0-\\\\u1FD3\\\\u1FD6-\\\\u1FDB\\\\u1FE0-\\\\u1FEC\\\\u1FF2-\\\\u1FF4\\\\u1FF6-\\\\u1FFC\\\\u2071\\\\u207F\\\\u2090-\\\\u2094\\\\u2102\\\\u2107\\\\u210A-\\\\u2113\\\\u2115\\\\u2119-\\\\u211D\\\\u2124\\\\u2126\\\\u2128\\\\u212A-\\\\u212D\\\\u212F-\\\\u2139\\\\u213C-\\\\u213F\\\\u2145-\\\\u2149\\\\u214E\\\\u2183\\\\u2184\\\\u2C00-\\\\u2C2E\\\\u2C30-\\\\u2C5E\\\\u2C60-\\\\u2C6F\\\\u2C71-\\\\u2C7D\\\\u2C80-\\\\u2CE4\\\\u2D00-\\\\u2D25\\\\u2D30-\\\\u2D65\\\\u2D6F\\\\u2D80-\\\\u2D96\\\\u2DA0-\\\\u2DA6\\\\u2DA8-\\\\u2DAE\\\\u2DB0-\\\\u2DB6\\\\u2DB8-\\\\u2DBE\\\\u2DC0-\\\\u2DC6\\\\u2DC8-\\\\u2DCE\\\\u2DD0-\\\\u2DD6\\\\u2DD8-\\\\u2DDE\\\\u2E2F\\\\u3005\\\\u3006\\\\u3031-\\\\u3035\\\\u303B\\\\u303C\\\\u3041-\\\\u3096\\\\u309D-\\\\u309F\\\\u30A1-\\\\u30FA\\\\u30FC-\\\\u30FF\\\\u3105-\\\\u312D\\\\u3131-\\\\u318E\\\\u31A0-\\\\u31B7\\\\u31F0-\\\\u31FF\\\\u3400\\\\u4DB5\\\\u4E00\\\\u9FC3\\\\uA000-\\\\uA48C\\\\uA500-\\\\uA60C\\\\uA610-\\\\uA61F\\\\uA62A\\\\uA62B\\\\uA640-\\\\uA65F\\\\uA662-\\\\uA66E\\\\uA67F-\\\\uA697\\\\uA717-\\\\uA71F\\\\uA722-\\\\uA788\\\\uA78B\\\\uA78C\\\\uA7FB-\\\\uA801\\\\uA803-\\\\uA805\\\\uA807-\\\\uA80A\\\\uA80C-\\\\uA822\\\\uA840-\\\\uA873\\\\uA882-\\\\uA8B3\\\\uA90A-\\\\uA925\\\\uA930-\\\\uA946\\\\uAA00-\\\\uAA28\\\\uAA40-\\\\uAA42\\\\uAA44-\\\\uAA4B\\\\uAC00\\\\uD7A3\\\\uF900-\\\\uFA2D\\\\uFA30-\\\\uFA6A\\\\uFA70-\\\\uFAD9\\\\uFB00-\\\\uFB06\\\\uFB13-\\\\uFB17\\\\uFB1D\\\\uFB1F-\\\\uFB28\\\\uFB2A-\\\\uFB36\\\\uFB38-\\\\uFB3C\\\\uFB3E\\\\uFB40\\\\uFB41\\\\uFB43\\\\uFB44\\\\uFB46-\\\\uFBB1\\\\uFBD3-\\\\uFD3D\\\\uFD50-\\\\uFD8F\\\\uFD92-\\\\uFDC7\\\\uFDF0-\\\\uFDFB\\\\uFE70-\\\\uFE74\\\\uFE76-\\\\uFEFC\\\\uFF21-\\\\uFF3A\\\\uFF41-\\\\uFF5A\\\\uFF66-\\\\uFFBE\\\\uFFC2-\\\\uFFC7\\\\uFFCA-\\\\uFFCF\\\\uFFD2-\\\\uFFD7\\\\uFFDA-\\\\uFFDC]\"),non_spacing_mark:new RegExp(\"[\\\\u0300-\\\\u036F\\\\u0483-\\\\u0487\\\\u0591-\\\\u05BD\\\\u05BF\\\\u05C1\\\\u05C2\\\\u05C4\\\\u05C5\\\\u05C7\\\\u0610-\\\\u061A\\\\u064B-\\\\u065E\\\\u0670\\\\u06D6-\\\\u06DC\\\\u06DF-\\\\u06E4\\\\u06E7\\\\u06E8\\\\u06EA-\\\\u06ED\\\\u0711\\\\u0730-\\\\u074A\\\\u07A6-\\\\u07B0\\\\u07EB-\\\\u07F3\\\\u0816-\\\\u0819\\\\u081B-\\\\u0823\\\\u0825-\\\\u0827\\\\u0829-\\\\u082D\\\\u0900-\\\\u0902\\\\u093C\\\\u0941-\\\\u0948\\\\u094D\\\\u0951-\\\\u0955\\\\u0962\\\\u0963\\\\u0981\\\\u09BC\\\\u09C1-\\\\u09C4\\\\u09CD\\\\u09E2\\\\u09E3\\\\u0A01\\\\u0A02\\\\u0A3C\\\\u0A41\\\\u0A42\\\\u0A47\\\\u0A48\\\\u0A4B-\\\\u0A4D\\\\u0A51\\\\u0A70\\\\u0A71\\\\u0A75\\\\u0A81\\\\u0A82\\\\u0ABC\\\\u0AC1-\\\\u0AC5\\\\u0AC7\\\\u0AC8\\\\u0ACD\\\\u0AE2\\\\u0AE3\\\\u0B01\\\\u0B3C\\\\u0B3F\\\\u0B41-\\\\u0B44\\\\u0B4D\\\\u0B56\\\\u0B62\\\\u0B63\\\\u0B82\\\\u0BC0\\\\u0BCD\\\\u0C3E-\\\\u0C40\\\\u0C46-\\\\u0C48\\\\u0C4A-\\\\u0C4D\\\\u0C55\\\\u0C56\\\\u0C62\\\\u0C63\\\\u0CBC\\\\u0CBF\\\\u0CC6\\\\u0CCC\\\\u0CCD\\\\u0CE2\\\\u0CE3\\\\u0D41-\\\\u0D44\\\\u0D4D\\\\u0D62\\\\u0D63\\\\u0DCA\\\\u0DD2-\\\\u0DD4\\\\u0DD6\\\\u0E31\\\\u0E34-\\\\u0E3A\\\\u0E47-\\\\u0E4E\\\\u0EB1\\\\u0EB4-\\\\u0EB9\\\\u0EBB\\\\u0EBC\\\\u0EC8-\\\\u0ECD\\\\u0F18\\\\u0F19\\\\u0F35\\\\u0F37\\\\u0F39\\\\u0F71-\\\\u0F7E\\\\u0F80-\\\\u0F84\\\\u0F86\\\\u0F87\\\\u0F90-\\\\u0F97\\\\u0F99-\\\\u0FBC\\\\u0FC6\\\\u102D-\\\\u1030\\\\u1032-\\\\u1037\\\\u1039\\\\u103A\\\\u103D\\\\u103E\\\\u1058\\\\u1059\\\\u105E-\\\\u1060\\\\u1071-\\\\u1074\\\\u1082\\\\u1085\\\\u1086\\\\u108D\\\\u109D\\\\u135F\\\\u1712-\\\\u1714\\\\u1732-\\\\u1734\\\\u1752\\\\u1753\\\\u1772\\\\u1773\\\\u17B7-\\\\u17BD\\\\u17C6\\\\u17C9-\\\\u17D3\\\\u17DD\\\\u180B-\\\\u180D\\\\u18A9\\\\u1920-\\\\u1922\\\\u1927\\\\u1928\\\\u1932\\\\u1939-\\\\u193B\\\\u1A17\\\\u1A18\\\\u1A56\\\\u1A58-\\\\u1A5E\\\\u1A60\\\\u1A62\\\\u1A65-\\\\u1A6C\\\\u1A73-\\\\u1A7C\\\\u1A7F\\\\u1B00-\\\\u1B03\\\\u1B34\\\\u1B36-\\\\u1B3A\\\\u1B3C\\\\u1B42\\\\u1B6B-\\\\u1B73\\\\u1B80\\\\u1B81\\\\u1BA2-\\\\u1BA5\\\\u1BA8\\\\u1BA9\\\\u1C2C-\\\\u1C33\\\\u1C36\\\\u1C37\\\\u1CD0-\\\\u1CD2\\\\u1CD4-\\\\u1CE0\\\\u1CE2-\\\\u1CE8\\\\u1CED\\\\u1DC0-\\\\u1DE6\\\\u1DFD-\\\\u1DFF\\\\u20D0-\\\\u20DC\\\\u20E1\\\\u20E5-\\\\u20F0\\\\u2CEF-\\\\u2CF1\\\\u2DE0-\\\\u2DFF\\\\u302A-\\\\u302F\\\\u3099\\\\u309A\\\\uA66F\\\\uA67C\\\\uA67D\\\\uA6F0\\\\uA6F1\\\\uA802\\\\uA806\\\\uA80B\\\\uA825\\\\uA826\\\\uA8C4\\\\uA8E0-\\\\uA8F1\\\\uA926-\\\\uA92D\\\\uA947-\\\\uA951\\\\uA980-\\\\uA982\\\\uA9B3\\\\uA9B6-\\\\uA9B9\\\\uA9BC\\\\uAA29-\\\\uAA2E\\\\uAA31\\\\uAA32\\\\uAA35\\\\uAA36\\\\uAA43\\\\uAA4C\\\\uAAB0\\\\uAAB2-\\\\uAAB4\\\\uAAB7\\\\uAAB8\\\\uAABE\\\\uAABF\\\\uAAC1\\\\uABE5\\\\uABE8\\\\uABED\\\\uFB1E\\\\uFE00-\\\\uFE0F\\\\uFE20-\\\\uFE26]\"),space_combining_mark:new RegExp(\"[\\\\u0903\\\\u093E-\\\\u0940\\\\u0949-\\\\u094C\\\\u094E\\\\u0982\\\\u0983\\\\u09BE-\\\\u09C0\\\\u09C7\\\\u09C8\\\\u09CB\\\\u09CC\\\\u09D7\\\\u0A03\\\\u0A3E-\\\\u0A40\\\\u0A83\\\\u0ABE-\\\\u0AC0\\\\u0AC9\\\\u0ACB\\\\u0ACC\\\\u0B02\\\\u0B03\\\\u0B3E\\\\u0B40\\\\u0B47\\\\u0B48\\\\u0B4B\\\\u0B4C\\\\u0B57\\\\u0BBE\\\\u0BBF\\\\u0BC1\\\\u0BC2\\\\u0BC6-\\\\u0BC8\\\\u0BCA-\\\\u0BCC\\\\u0BD7\\\\u0C01-\\\\u0C03\\\\u0C41-\\\\u0C44\\\\u0C82\\\\u0C83\\\\u0CBE\\\\u0CC0-\\\\u0CC4\\\\u0CC7\\\\u0CC8\\\\u0CCA\\\\u0CCB\\\\u0CD5\\\\u0CD6\\\\u0D02\\\\u0D03\\\\u0D3E-\\\\u0D40\\\\u0D46-\\\\u0D48\\\\u0D4A-\\\\u0D4C\\\\u0D57\\\\u0D82\\\\u0D83\\\\u0DCF-\\\\u0DD1\\\\u0DD8-\\\\u0DDF\\\\u0DF2\\\\u0DF3\\\\u0F3E\\\\u0F3F\\\\u0F7F\\\\u102B\\\\u102C\\\\u1031\\\\u1038\\\\u103B\\\\u103C\\\\u1056\\\\u1057\\\\u1062-\\\\u1064\\\\u1067-\\\\u106D\\\\u1083\\\\u1084\\\\u1087-\\\\u108C\\\\u108F\\\\u109A-\\\\u109C\\\\u17B6\\\\u17BE-\\\\u17C5\\\\u17C7\\\\u17C8\\\\u1923-\\\\u1926\\\\u1929-\\\\u192B\\\\u1930\\\\u1931\\\\u1933-\\\\u1938\\\\u19B0-\\\\u19C0\\\\u19C8\\\\u19C9\\\\u1A19-\\\\u1A1B\\\\u1A55\\\\u1A57\\\\u1A61\\\\u1A63\\\\u1A64\\\\u1A6D-\\\\u1A72\\\\u1B04\\\\u1B35\\\\u1B3B\\\\u1B3D-\\\\u1B41\\\\u1B43\\\\u1B44\\\\u1B82\\\\u1BA1\\\\u1BA6\\\\u1BA7\\\\u1BAA\\\\u1C24-\\\\u1C2B\\\\u1C34\\\\u1C35\\\\u1CE1\\\\u1CF2\\\\uA823\\\\uA824\\\\uA827\\\\uA880\\\\uA881\\\\uA8B4-\\\\uA8C3\\\\uA952\\\\uA953\\\\uA983\\\\uA9B4\\\\uA9B5\\\\uA9BA\\\\uA9BB\\\\uA9BD-\\\\uA9C0\\\\uAA2F\\\\uAA30\\\\uAA33\\\\uAA34\\\\uAA4D\\\\uAA7B\\\\uABE3\\\\uABE4\\\\uABE6\\\\uABE7\\\\uABE9\\\\uABEA\\\\uABEC]\"),connector_punctuation:new RegExp(\"[\\\\u005F\\\\u203F\\\\u2040\\\\u2054\\\\uFE33\\\\uFE34\\\\uFE4D-\\\\uFE4F\\\\uFF3F]\")};M.prototype.toString=function(){return this.message+\" (line: \"+this.line+\", col: \"+this.col+\", pos: \"+this.pos+\")\\n\\n\"+this.stack};var Ht={},zt=g([\"typeof\",\"void\",\"delete\",\"--\",\"++\",\"!\",\"~\",\"-\",\"+\"]),Pt=g([\"--\",\"++\"]),jt=g([\"=\",\"+=\",\"-=\",\"/=\",\"*=\",\"%=\",\">>=\",\"<<=\",\">>>=\",\"|=\",\"^=\",\"&=\"]),It=function(n,e){for(var t=0;t<n.length;++t)for(var r=n[t],i=0;i<r.length;++i)e[r[i]]=t+1;return e}([[\"||\"],[\"&&\"],[\"|\"],[\"^\"],[\"&\"],[\"==\",\"===\",\"!=\",\"!==\"],[\"<\",\">\",\"<=\",\">=\",\"in\",\"instanceof\"],[\">>\",\"<<\",\">>>\"],[\"+\",\"-\"],[\"*\",\"/\",\"%\"]],{}),Ut=t([\"for\",\"do\",\"while\",\"switch\"]),Vt=t([\"atom\",\"num\",\"string\",\"regexp\",\"name\"]);z.prototype=new E,function(n){function e(e,t){e.DEFMETHOD(\"transform\",function(e,r){var i,o;return e.push(this),e.before&&(i=e.before(this,t,r)),i===n&&(e.after?(e.stack[e.stack.length-1]=i=this.clone(),t(i,e),o=e.after(i,r),o!==n&&(i=o)):(i=this,t(i,e))),e.pop(),i})}function t(n,e){return V(n,function(n){return n.transform(e,!0)})}e(W,l),e(ee,function(n,e){n.label=n.label.transform(e),n.body=n.body.transform(e)}),e(K,function(n,e){n.body=n.body.transform(e)}),e(J,function(n,e){n.body=t(n.body,e)}),e(re,function(n,e){n.condition=n.condition.transform(e),n.body=n.body.transform(e)}),e(ae,function(n,e){n.init&&(n.init=n.init.transform(e)),n.condition&&(n.condition=n.condition.transform(e)),n.step&&(n.step=n.step.transform(e)),n.body=n.body.transform(e)}),e(ue,function(n,e){n.init=n.init.transform(e),n.object=n.object.transform(e),n.body=n.body.transform(e)}),e(se,function(n,e){n.expression=n.expression.transform(e),n.body=n.body.transform(e)}),e(me,function(n,e){n.value&&(n.value=n.value.transform(e))}),e(be,function(n,e){n.label&&(n.label=n.label.transform(e))}),e(we,function(n,e){n.condition=n.condition.transform(e),n.body=n.body.transform(e),n.alternative&&(n.alternative=n.alternative.transform(e))}),e(Ee,function(n,e){n.expression=n.expression.transform(e),n.body=t(n.body,e)}),e(Se,function(n,e){n.expression=n.expression.transform(e),n.body=t(n.body,e)}),e(Ce,function(n,e){n.body=t(n.body,e),n.bcatch&&(n.bcatch=n.bcatch.transform(e)),n.bfinally&&(n.bfinally=n.bfinally.transform(e))}),e(ke,function(n,e){n.argname=n.argname.transform(e),n.body=t(n.body,e)}),e(Be,function(n,e){n.definitions=t(n.definitions,e)}),e(Oe,function(n,e){n.name=n.name.transform(e),n.value&&(n.value=n.value.transform(e))}),e(le,function(n,e){n.name&&(n.name=n.name.transform(e)),n.argnames=t(n.argnames,e),n.body=t(n.body,e)}),e(Me,function(n,e){n.expression=n.expression.transform(e),n.args=t(n.args,e)}),e(Re,function(n,e){n.car=n.car.transform(e),n.cdr=n.cdr.transform(e)}),e(He,function(n,e){n.expression=n.expression.transform(e)}),e(ze,function(n,e){n.expression=n.expression.transform(e),n.property=n.property.transform(e)}),e(Pe,function(n,e){n.expression=n.expression.transform(e)}),e(Ue,function(n,e){n.left=n.left.transform(e),n.right=n.right.transform(e)}),e(Ve,function(n,e){n.condition=n.condition.transform(e),n.consequent=n.consequent.transform(e),n.alternative=n.alternative.transform(e)}),e(We,function(n,e){n.elements=t(n.elements,e)}),e(Ye,function(n,e){n.properties=t(n.properties,e)}),e(Xe,function(n,e){n.value=n.value.transform(e)})}(),P.prototype={unmangleable:function(n){return this.global&&!(n&&n.toplevel)||this.undeclared||!(n&&n.eval)&&(this.scope.uses_eval||this.scope.uses_with)},mangle:function(n){if(!this.mangled_name&&!this.unmangleable(n)){var e=this.scope;!n.screw_ie8&&this.orig[0]instanceof ot&&(e=e.parent_scope),this.mangled_name=e.next_mangled(n,this)}}},fe.DEFMETHOD(\"figure_out_scope\",function(n){n=c(n,{screw_ie8:!1});var e=this,t=e.parent_scope=null,r=null,i=0,o=new E(function(e,a){if(n.screw_ie8&&e instanceof ke){var u=t;return t=new ce(e),t.init_scope_vars(i),t.parent_scope=u,a(),t=u,!0}if(e instanceof ce){e.init_scope_vars(i);var u=e.parent_scope=t,s=r;return r=t=e,++i,a(),--i,t=u,r=s,!0}if(e instanceof G)return e.scope=t,p(t.directives,e.value),!0;if(e instanceof se)for(var c=t;c;c=c.parent_scope)c.uses_with=!0;else if(e instanceof Ze&&(e.scope=t),e instanceof ot)r.def_function(e);else if(e instanceof it)(e.scope=r.parent_scope).def_function(e);else if(e instanceof et||e instanceof tt){var f=r.def_variable(e);f.constant=e instanceof tt,f.init=o.parent().value}else e instanceof at&&(n.screw_ie8?t:r).def_variable(e)});e.walk(o);var a=null,u=e.globals=new y,o=new E(function(n,t){if(n instanceof le){var r=a;return a=n,t(),a=r,!0}if(n instanceof st){var i=n.name,s=n.scope.find_variable(i);if(s)n.thedef=s;else{var c;if(u.has(i)?c=u.get(i):(c=new P(e,u.size(),n),c.undeclared=!0,c.global=!0,u.set(i,c)),n.thedef=c,\"eval\"==i&&o.parent()instanceof Me)for(var f=n.scope;f&&!f.uses_eval;f=f.parent_scope)f.uses_eval=!0;a&&\"arguments\"==i&&(a.uses_arguments=!0)}return n.reference(),!0}});e.walk(o)}),ce.DEFMETHOD(\"init_scope_vars\",function(n){this.directives=[],this.variables=new y,this.functions=new y,this.uses_with=!1,this.uses_eval=!1,this.parent_scope=null,this.enclosed=[],this.cname=-1,this.nesting=n}),ce.DEFMETHOD(\"strict\",function(){return this.has_directive(\"use strict\")}),le.DEFMETHOD(\"init_scope_vars\",function(){ce.prototype.init_scope_vars.apply(this,arguments),this.uses_arguments=!1}),st.DEFMETHOD(\"reference\",function(){var n=this.definition();n.references.push(this);for(var e=this.scope;e&&(p(e.enclosed,n),e!==n.scope);)e=e.parent_scope;this.frame=this.scope.nesting-n.scope.nesting}),ce.DEFMETHOD(\"find_variable\",function(n){return n instanceof Ze&&(n=n.name),this.variables.get(n)||this.parent_scope&&this.parent_scope.find_variable(n)}),ce.DEFMETHOD(\"has_directive\",function(n){return this.parent_scope&&this.parent_scope.has_directive(n)||(this.directives.indexOf(n)>=0?this:null)}),ce.DEFMETHOD(\"def_function\",function(n){this.functions.set(n.name,this.def_variable(n))}),ce.DEFMETHOD(\"def_variable\",function(n){var e;return this.variables.has(n.name)?(e=this.variables.get(n.name),e.orig.push(n)):(e=new P(this,this.variables.size(),n),this.variables.set(n.name,e),e.global=!this.parent_scope),n.thedef=e}),ce.DEFMETHOD(\"next_mangled\",function(n){var e=this.enclosed;n:for(;;){var t=Lt(++this.cname);if(x(t)&&!(n.except.indexOf(t)>=0)){for(var r=e.length;--r>=0;){var i=e[r],o=i.mangled_name||i.unmangleable(n)&&i.name;if(t==o)continue n}return t}}}),de.DEFMETHOD(\"next_mangled\",function(n,e){for(var t=e.orig[0]instanceof rt&&this.name&&this.name.definition();;){var r=le.prototype.next_mangled.call(this,n,e);if(!t||t.mangled_name!=r)return r}}),ce.DEFMETHOD(\"references\",function(n){return n instanceof Ze&&(n=n.definition()),this.enclosed.indexOf(n)<0?null:n}),Ze.DEFMETHOD(\"unmangleable\",function(n){return this.definition().unmangleable(n)}),Qe.DEFMETHOD(\"unmangleable\",function(){return!0}),ut.DEFMETHOD(\"unmangleable\",function(){return!1}),Ze.DEFMETHOD(\"unreferenced\",function(){return 0==this.definition().references.length&&!(this.scope.uses_eval||this.scope.uses_with)}),Ze.DEFMETHOD(\"undeclared\",function(){return this.definition().undeclared}),ct.DEFMETHOD(\"undeclared\",function(){return!1}),ut.DEFMETHOD(\"undeclared\",function(){return!1}),Ze.DEFMETHOD(\"definition\",function(){return this.thedef}),Ze.DEFMETHOD(\"global\",function(){return this.definition().global}),fe.DEFMETHOD(\"_default_mangler_options\",function(n){return c(n,{except:[],eval:!1,sort:!1,toplevel:!1,screw_ie8:!1})}),fe.DEFMETHOD(\"mangle_names\",function(n){n=this._default_mangler_options(n);var e=-1,t=[],r=new E(function(i,o){if(i instanceof ee){var a=e;return o(),e=a,!0}if(i instanceof ce){var u=(r.parent(),[]);return i.variables.each(function(e){n.except.indexOf(e.name)<0&&u.push(e)}),n.sort&&u.sort(function(n,e){return e.references.length-n.references.length}),void t.push.apply(t,u)}if(i instanceof ut){var s;do s=Lt(++e);while(!x(s));return i.mangled_name=s,!0}return n.screw_ie8&&i instanceof at?void t.push(i.definition()):void 0});this.walk(r),t.forEach(function(e){e.mangle(n)})}),fe.DEFMETHOD(\"compute_char_frequency\",function(n){n=this._default_mangler_options(n);var e=new E(function(e){e instanceof lt?Lt.consider(e.print_to_string()):e instanceof ve?Lt.consider(\"return\"):e instanceof ge?Lt.consider(\"throw\"):e instanceof Ae?Lt.consider(\"continue\"):e instanceof ye?Lt.consider(\"break\"):e instanceof X?Lt.consider(\"debugger\"):e instanceof G?Lt.consider(e.value):e instanceof oe?Lt.consider(\"while\"):e instanceof ie?Lt.consider(\"do while\"):e instanceof we?(Lt.consider(\"if\"),e.alternative&&Lt.consider(\"else\")):e instanceof Te?Lt.consider(\"var\"):e instanceof $e?Lt.consider(\"const\"):e instanceof le?Lt.consider(\"function\"):e instanceof ae?Lt.consider(\"for\"):e instanceof ue?Lt.consider(\"for in\"):e instanceof Ee?Lt.consider(\"switch\"):e instanceof Se?Lt.consider(\"case\"):e instanceof Fe?Lt.consider(\"default\"):e instanceof se?Lt.consider(\"with\"):e instanceof Ke?Lt.consider(\"set\"+e.key):e instanceof Je?Lt.consider(\"get\"+e.key):e instanceof Ge?Lt.consider(e.key):e instanceof Ne?Lt.consider(\"new\"):e instanceof ft?Lt.consider(\"this\"):e instanceof Ce?Lt.consider(\"try\"):e instanceof ke?Lt.consider(\"catch\"):e instanceof xe?Lt.consider(\"finally\"):e instanceof Ze&&e.unmangleable(n)?Lt.consider(e.name):e instanceof Pe||e instanceof Ue?Lt.consider(e.operator):e instanceof He&&Lt.consider(e.property)});this.walk(e),Lt.sort()});var Lt=function(){function n(){r=Object.create(null),t=i.split(\"\").map(function(n){return n.charCodeAt(0)}),t.forEach(function(n){r[n]=0})}function e(n){var e=\"\",r=54;do e+=String.fromCharCode(t[n%r]),n=Math.floor(n/r),r=64;while(n>0);return e}var t,r,i=\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789\";return e.consider=function(n){for(var e=n.length;--e>=0;){var t=n.charCodeAt(e);t in r&&++r[t]}},e.sort=function(){t=_(t,function(n,e){return F(n)&&!F(e)?1:F(e)&&!F(n)?-1:r[e]-r[n]})},e.reset=n,n(),e.get=function(){return t},e.freq=function(){return r},e}();fe.DEFMETHOD(\"scope_warnings\",function(n){n=c(n,{undeclared:!1,unreferenced:!0,assign_to_global:!0,func_arguments:!0,nested_defuns:!0,eval:!0});var e=new E(function(t){if(n.undeclared&&t instanceof st&&t.undeclared()&&W.warn(\"Undeclared symbol: {name} [{file}:{line},{col}]\",{name:t.name,file:t.start.file,line:t.start.line,col:t.start.col}),n.assign_to_global){var r=null;t instanceof Le&&t.left instanceof st?r=t.left:t instanceof ue&&t.init instanceof st&&(r=t.init),r&&(r.undeclared()||r.global()&&r.scope!==r.definition().scope)&&W.warn(\"{msg}: {name} [{file}:{line},{col}]\",{msg:r.undeclared()?\"Accidental global?\":\"Assignment to global\",name:r.name,file:r.start.file,line:r.start.line,col:r.start.col})}n.eval&&t instanceof st&&t.undeclared()&&\"eval\"==t.name&&W.warn(\"Eval is used [{file}:{line},{col}]\",t.start),n.unreferenced&&(t instanceof nt||t instanceof ut)&&t.unreferenced()&&W.warn(\"{type} {name} is declared but not referenced [{file}:{line},{col}]\",{type:t instanceof ut?\"Label\":\"Symbol\",name:t.name,file:t.start.file,line:t.start.line,col:t.start.col}),n.func_arguments&&t instanceof le&&t.uses_arguments&&W.warn(\"arguments used in function {name} [{file}:{line},{col}]\",{name:t.name?t.name.name:\"anonymous\",file:t.start.file,line:t.start.line,col:t.start.col}),n.nested_defuns&&t instanceof he&&!(e.parent()instanceof ce)&&W.warn('Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]',{name:t.name.name,type:e.parent().TYPE,file:t.start.file,line:t.start.line,col:t.start.col})});this.walk(e)}),function(){function n(n,e){n.DEFMETHOD(\"_codegen\",e)}function e(n,e){n.DEFMETHOD(\"needs_parens\",e)}function t(n){var e=n.parent();return e instanceof Pe?!0:e instanceof Ue&&!(e instanceof Le)?!0:e instanceof Me&&e.expression===this?!0:e instanceof Ve&&e.condition===this?!0:e instanceof qe&&e.expression===this?!0:void 0}function r(n,e,t){var r=n.length-1;n.forEach(function(n,i){n instanceof Q||(t.indent(),n.print(t),i==r&&e||(t.newline(),e&&t.newline()))})}function i(n,e){n.length>0?e.with_block(function(){r(n,!1,e)}):e.print(\"{}\")}function o(n,e){if(e.option(\"bracketize\"))return void h(n.body,e);if(!n.body)return e.force_semicolon();if(n.body instanceof ie&&!e.option(\"screw_ie8\"))return void h(n.body,e);for(var t=n.body;;)if(t instanceof we){if(!t.alternative)return void h(n.body,e);t=t.alternative}else{if(!(t instanceof ne))break;t=t.body}s(n.body,e)}function a(n,e,t){if(t)try{n.walk(new E(function(n){if(n instanceof Ue&&\"in\"==n.operator)throw e})),n.print(e)}catch(r){if(r!==e)throw r;n.print(e,!0)}else n.print(e)}function u(n){return[92,47,46,43,42,63,40,41,91,93,123,125,36,94,58,124,33,10,13,0,65279,8232,8233].indexOf(n)<0}function s(n,e){e.option(\"bracketize\")?!n||n instanceof Q?e.print(\"{}\"):n instanceof Z?n.print(e):e.with_block(function(){e.indent(),n.print(e),e.newline()}):!n||n instanceof Q?e.force_semicolon():n.print(e)}function c(n){for(var e=n.stack(),t=e.length,r=e[--t],i=e[--t];t>0;){if(i instanceof Y&&i.body===r)return!0;if(!(i instanceof Re&&i.car===r||i instanceof Me&&i.expression===r&&!(i instanceof Ne)||i instanceof He&&i.expression===r||i instanceof ze&&i.expression===r||i instanceof Ve&&i.condition===r||i instanceof Ue&&i.left===r||i instanceof Ie&&i.expression===r))return!1;r=i,i=e[--t]}}function f(n,e){return 0==n.args.length&&!e.option(\"beautify\")}function p(n){for(var e=n[0],t=e.length,r=1;r<n.length;++r)n[r].length<t&&(e=n[r],t=e.length);return e}function d(n){var e,t=n.toString(10),r=[t.replace(/^0\\./,\".\").replace(\"e+\",\"e\")];return Math.floor(n)===n?(n>=0?r.push(\"0x\"+n.toString(16).toLowerCase(),\"0\"+n.toString(8)):r.push(\"-0x\"+(-n).toString(16).toLowerCase(),\"-0\"+(-n).toString(8)),(e=/^(.*?)(0+)$/.exec(n))&&r.push(e[1]+\"e\"+e[2].length)):(e=/^0?\\.(0+)(.*)$/.exec(n))&&r.push(e[2]+\"e-\"+(e[1].length+e[2].length),t.substr(t.indexOf(\".\"))),p(r)}function h(n,e){return n instanceof Z?void n.print(e):void e.with_block(function(){e.indent(),n.print(e),e.newline()})}function _(n,e){n.DEFMETHOD(\"add_source_map\",function(n){e(this,n)})}function m(n,e){e.add_mapping(n.start)}W.DEFMETHOD(\"print\",function(n,e){function t(){r.add_comments(n),r.add_source_map(n),i(r,n)}var r=this,i=r._codegen;n.push_node(r),e||r.needs_parens(n)?n.with_parens(t):t(),n.pop_node()}),W.DEFMETHOD(\"print_to_string\",function(n){var e=j(n);return this.print(e),e.get()}),W.DEFMETHOD(\"add_comments\",function(n){var e=n.option(\"comments\"),t=this;if(e){var r=t.start;if(r&&!r._comments_dumped){r._comments_dumped=!0;var i=r.comments_before||[];t instanceof me&&t.value&&t.value.walk(new E(function(n){return n.start&&n.start.comments_before&&(i=i.concat(n.start.comments_before),n.start.comments_before=[]),n instanceof de||n instanceof We||n instanceof Ye?!0:void 0\n})),e.test?i=i.filter(function(n){return e.test(n.value)}):\"function\"==typeof e&&(i=i.filter(function(n){return e(t,n)})),i.forEach(function(e){/comment[134]/.test(e.type)?(n.print(\"//\"+e.value+\"\\n\"),n.indent()):\"comment2\"==e.type&&(n.print(\"/*\"+e.value+\"*/\"),r.nlb?(n.print(\"\\n\"),n.indent()):n.space())})}}}),e(W,function(){return!1}),e(de,function(n){return c(n)}),e(Ye,function(n){return c(n)}),e(Pe,function(n){var e=n.parent();return e instanceof qe&&e.expression===this}),e(Re,function(n){var e=n.parent();return e instanceof Me||e instanceof Pe||e instanceof Ue||e instanceof Oe||e instanceof qe||e instanceof We||e instanceof Xe||e instanceof Ve}),e(Ue,function(n){var e=n.parent();if(e instanceof Me&&e.expression===this)return!0;if(e instanceof Pe)return!0;if(e instanceof qe&&e.expression===this)return!0;if(e instanceof Ue){var t=e.operator,r=It[t],i=this.operator,o=It[i];if(r>o||r==o&&this===e.right)return!0}}),e(qe,function(n){var e=n.parent();if(e instanceof Ne&&e.expression===this)try{this.walk(new E(function(n){if(n instanceof Me)throw e}))}catch(t){if(t!==e)throw t;return!0}}),e(Me,function(n){var e,t=n.parent();return t instanceof Ne&&t.expression===this?!0:this.expression instanceof de&&t instanceof qe&&t.expression===this&&(e=n.parent(1))instanceof Le&&e.left===t}),e(Ne,function(n){var e=n.parent();return f(this,n)&&(e instanceof qe||e instanceof Me&&e.expression===this)?!0:void 0}),e(dt,function(n){var e=n.parent();return this.getValue()<0&&e instanceof qe&&e.expression===this?!0:void 0}),e(vt,function(n){var e=n.parent();return e instanceof qe&&e.expression===this?!0:void 0}),e(Le,t),e(Ve,t),n(G,function(n,e){e.print_string(n.value),e.semicolon()}),n(X,function(n,e){e.print(\"debugger\"),e.semicolon()}),ne.DEFMETHOD(\"_do_print_body\",function(n){s(this.body,n)}),n(Y,function(n,e){n.body.print(e),e.semicolon()}),n(fe,function(n,e){r(n.body,!0,e),e.print(\"\")}),n(ee,function(n,e){n.label.print(e),e.colon(),n.body.print(e)}),n(K,function(n,e){n.body.print(e),e.semicolon()}),n(Z,function(n,e){i(n.body,e)}),n(Q,function(n,e){e.semicolon()}),n(ie,function(n,e){e.print(\"do\"),e.space(),n._do_print_body(e),e.space(),e.print(\"while\"),e.space(),e.with_parens(function(){n.condition.print(e)}),e.semicolon()}),n(oe,function(n,e){e.print(\"while\"),e.space(),e.with_parens(function(){n.condition.print(e)}),e.space(),n._do_print_body(e)}),n(ae,function(n,e){e.print(\"for\"),e.space(),e.with_parens(function(){!n.init||n.init instanceof Q?e.print(\";\"):(n.init instanceof Be?n.init.print(e):a(n.init,e,!0),e.print(\";\"),e.space()),n.condition?(n.condition.print(e),e.print(\";\"),e.space()):e.print(\";\"),n.step&&n.step.print(e)}),e.space(),n._do_print_body(e)}),n(ue,function(n,e){e.print(\"for\"),e.space(),e.with_parens(function(){n.init.print(e),e.space(),e.print(\"in\"),e.space(),n.object.print(e)}),e.space(),n._do_print_body(e)}),n(se,function(n,e){e.print(\"with\"),e.space(),e.with_parens(function(){n.expression.print(e)}),e.space(),n._do_print_body(e)}),le.DEFMETHOD(\"_do_print\",function(n,e){var t=this;e||n.print(\"function\"),t.name&&(n.space(),t.name.print(n)),n.with_parens(function(){t.argnames.forEach(function(e,t){t&&n.comma(),e.print(n)})}),n.space(),i(t.body,n)}),n(le,function(n,e){n._do_print(e)}),me.DEFMETHOD(\"_do_print\",function(n,e){n.print(e),this.value&&(n.space(),this.value.print(n)),n.semicolon()}),n(ve,function(n,e){n._do_print(e,\"return\")}),n(ge,function(n,e){n._do_print(e,\"throw\")}),be.DEFMETHOD(\"_do_print\",function(n,e){n.print(e),this.label&&(n.space(),this.label.print(n)),n.semicolon()}),n(ye,function(n,e){n._do_print(e,\"break\")}),n(Ae,function(n,e){n._do_print(e,\"continue\")}),n(we,function(n,e){e.print(\"if\"),e.space(),e.with_parens(function(){n.condition.print(e)}),e.space(),n.alternative?(o(n,e),e.space(),e.print(\"else\"),e.space(),s(n.alternative,e)):n._do_print_body(e)}),n(Ee,function(n,e){e.print(\"switch\"),e.space(),e.with_parens(function(){n.expression.print(e)}),e.space(),n.body.length>0?e.with_block(function(){n.body.forEach(function(n,t){t&&e.newline(),e.indent(!0),n.print(e)})}):e.print(\"{}\")}),De.DEFMETHOD(\"_do_print_body\",function(n){this.body.length>0&&(n.newline(),this.body.forEach(function(e){n.indent(),e.print(n),n.newline()}))}),n(Fe,function(n,e){e.print(\"default:\"),n._do_print_body(e)}),n(Se,function(n,e){e.print(\"case\"),e.space(),n.expression.print(e),e.print(\":\"),n._do_print_body(e)}),n(Ce,function(n,e){e.print(\"try\"),e.space(),i(n.body,e),n.bcatch&&(e.space(),n.bcatch.print(e)),n.bfinally&&(e.space(),n.bfinally.print(e))}),n(ke,function(n,e){e.print(\"catch\"),e.space(),e.with_parens(function(){n.argname.print(e)}),e.space(),i(n.body,e)}),n(xe,function(n,e){e.print(\"finally\"),e.space(),i(n.body,e)}),Be.DEFMETHOD(\"_do_print\",function(n,e){n.print(e),n.space(),this.definitions.forEach(function(e,t){t&&n.comma(),e.print(n)});var t=n.parent(),r=t instanceof ae||t instanceof ue,i=r&&t.init===this;i||n.semicolon()}),n(Te,function(n,e){n._do_print(e,\"var\")}),n($e,function(n,e){n._do_print(e,\"const\")}),n(Oe,function(n,e){if(n.name.print(e),n.value){e.space(),e.print(\"=\"),e.space();var t=e.parent(1),r=t instanceof ae||t instanceof ue;a(n.value,e,r)}}),n(Me,function(n,e){n.expression.print(e),n instanceof Ne&&f(n,e)||e.with_parens(function(){n.args.forEach(function(n,t){t&&e.comma(),n.print(e)})})}),n(Ne,function(n,e){e.print(\"new\"),e.space(),Me.prototype._codegen(n,e)}),Re.DEFMETHOD(\"_do_print\",function(n){this.car.print(n),this.cdr&&(n.comma(),n.should_break()&&(n.newline(),n.indent()),this.cdr.print(n))}),n(Re,function(n,e){n._do_print(e)}),n(He,function(n,e){var t=n.expression;t.print(e),t instanceof dt&&t.getValue()>=0&&(/[xa-f.]/i.test(e.last())||e.print(\".\")),e.print(\".\"),e.add_mapping(n.end),e.print_name(n.property)}),n(ze,function(n,e){n.expression.print(e),e.print(\"[\"),n.property.print(e),e.print(\"]\")}),n(je,function(n,e){var t=n.operator;e.print(t),(/^[a-z]/i.test(t)||/[+-]$/.test(t)&&n.expression instanceof je&&/^[+-]/.test(n.expression.operator))&&e.space(),n.expression.print(e)}),n(Ie,function(n,e){n.expression.print(e),e.print(n.operator)}),n(Ue,function(n,e){n.left.print(e),e.space(),e.print(n.operator),\"<\"==n.operator&&n.right instanceof je&&\"!\"==n.right.operator&&n.right.expression instanceof je&&\"--\"==n.right.expression.operator?e.print(\" \"):e.space(),n.right.print(e)}),n(Ve,function(n,e){n.condition.print(e),e.space(),e.print(\"?\"),e.space(),n.consequent.print(e),e.space(),e.colon(),n.alternative.print(e)}),n(We,function(n,e){e.with_square(function(){var t=n.elements,r=t.length;r>0&&e.space(),t.forEach(function(n,t){t&&e.comma(),n.print(e),t===r-1&&n instanceof bt&&e.comma()}),r>0&&e.space()})}),n(Ye,function(n,e){n.properties.length>0?e.with_block(function(){n.properties.forEach(function(n,t){t&&(e.print(\",\"),e.newline()),e.indent(),n.print(e)}),e.newline()}):e.print(\"{}\")}),n(Ge,function(n,e){var t=n.key;e.option(\"quote_keys\")?e.print_string(t+\"\"):(\"number\"==typeof t||!e.option(\"beautify\")&&+t+\"\"==t)&&parseFloat(t)>=0?e.print(d(t)):(St(t)?e.option(\"screw_ie8\"):$(t))?e.print_name(t):e.print_string(t),e.colon(),n.value.print(e)}),n(Ke,function(n,e){e.print(\"set\"),e.space(),n.key.print(e),n.value._do_print(e,!0)}),n(Je,function(n,e){e.print(\"get\"),e.space(),n.key.print(e),n.value._do_print(e,!0)}),n(Ze,function(n,e){var t=n.definition();e.print_name(t?t.mangled_name||t.name:n.name)}),n(gt,function(n,e){e.print(\"void 0\")}),n(bt,l),n(yt,function(n,e){e.print(\"1/0\")}),n(vt,function(n,e){e.print(\"0/0\")}),n(ft,function(n,e){e.print(\"this\")}),n(lt,function(n,e){e.print(n.getValue())}),n(pt,function(n,e){e.print_string(n.getValue())}),n(dt,function(n,e){e.print(d(n.getValue()))}),n(ht,function(n,e){var t=n.getValue().toString();e.option(\"ascii_only\")?t=e.to_ascii(t):e.option(\"unescape_regexps\")&&(t=t.split(\"\\\\\\\\\").map(function(n){return n.replace(/\\\\u[0-9a-fA-F]{4}|\\\\x[0-9a-fA-F]{2}/g,function(n){var e=parseInt(n.substr(2),16);return u(e)?String.fromCharCode(e):n})}).join(\"\\\\\\\\\")),e.print(t);var r=e.parent();r instanceof Ue&&/^in/.test(r.operator)&&r.left===n&&e.print(\" \")}),_(W,l),_(G,m),_(X,m),_(Ze,m),_(_e,m),_(ne,m),_(ee,l),_(le,m),_(Ee,m),_(De,m),_(Z,m),_(fe,l),_(Ne,m),_(Ce,m),_(ke,m),_(xe,m),_(Be,m),_(lt,m),_(Xe,function(n,e){e.add_mapping(n.start,n.key)})}(),I.prototype=new z,f(I.prototype,{option:function(n){return this.options[n]},warn:function(){this.options.warnings&&W.warn.apply(W,arguments)},before:function(n,e){if(n._squeezed)return n;var t=!1;return n instanceof ce&&(n=n.hoist_declarations(this),t=!0),e(n,this),n=n.optimize(this),t&&n instanceof ce&&(n.drop_unused(this),e(n,this)),n._squeezed=!0,n}}),function(){function n(n,e){n.DEFMETHOD(\"optimize\",function(n){var t=this;if(t._optimized)return t;var r=e(t,n);return r._optimized=!0,r===t?r:r.transform(n)})}function e(n,e,t){return t||(t={}),e&&(t.start||(t.start=e.start),t.end||(t.end=e.end)),new n(t)}function t(n,t,r){if(t instanceof W)return t.transform(n);switch(typeof t){case\"string\":return e(pt,r,{value:t}).optimize(n);case\"number\":return e(isNaN(t)?vt:dt,r,{value:t}).optimize(n);case\"boolean\":return e(t?Et:wt,r).optimize(n);case\"undefined\":return e(gt,r).optimize(n);default:if(null===t)return e(mt,r).optimize(n);if(t instanceof RegExp)return e(ht,r).optimize(n);throw new Error(d(\"Can't handle constant of type: {type}\",{type:typeof t}))}}function r(n){if(null===n)return[];if(n instanceof Z)return n.body;if(n instanceof Q)return[];if(n instanceof Y)return[n];throw new Error(\"Can't convert thing to statement array\")}function i(n){return null===n?!0:n instanceof Q?!0:n instanceof Z?0==n.body.length:!1}function u(n){return n instanceof Ee?n:(n instanceof ae||n instanceof ue||n instanceof re)&&n.body instanceof Z?n.body:n}function s(n,t){function i(n){function r(n,t){return e(K,n,{body:e(Le,n,{operator:\"=\",left:e(He,t,{expression:e(st,t,t),property:\"$inject\"}),right:e(We,n,{elements:n.argnames.map(function(n){return e(pt,n,{value:n.name})})})})})}return n.reduce(function(n,e){n.push(e);var i=e.start,o=i.comments_before;if(o&&o.length>0){var a=o.pop();/@ngInject/.test(a.value)&&(e instanceof he?n.push(r(e,e.name)):e instanceof Be?e.definitions.forEach(function(e){e.value&&e.value instanceof le&&n.push(r(e.value,e.name))}):t.warn(\"Unknown statement marked with @ngInject [{file}:{line},{col}]\",i))}return n},[])}function o(n){var e=[];return n.reduce(function(n,t){return t instanceof Z?(_=!0,n.push.apply(n,o(t.body))):t instanceof Q?_=!0:t instanceof G?e.indexOf(t.value)<0?(n.push(t),e.push(t.value)):_=!0:n.push(t),n},[])}function a(n,t){var i=t.self(),o=i instanceof le,a=[];n:for(var s=n.length;--s>=0;){var c=n[s];switch(!0){case o&&c instanceof ve&&!c.value&&0==a.length:_=!0;continue n;case c instanceof we:if(c.body instanceof ve){if((o&&0==a.length||a[0]instanceof ve&&!a[0].value)&&!c.body.value&&!c.alternative){_=!0;var f=e(K,c.condition,{body:c.condition});a.unshift(f);continue n}if(a[0]instanceof ve&&c.body.value&&a[0].value&&!c.alternative){_=!0,c=c.clone(),c.alternative=a[0],a[0]=c.transform(t);continue n}if((0==a.length||a[0]instanceof ve)&&c.body.value&&!c.alternative&&o){_=!0,c=c.clone(),c.alternative=a[0]||e(ve,c,{value:e(gt,c)}),a[0]=c.transform(t);continue n}if(!c.body.value&&o){_=!0,c=c.clone(),c.condition=c.condition.negate(t),c.body=e(Z,c,{body:r(c.alternative).concat(a)}),c.alternative=null,a=[c.transform(t)];continue n}if(1==a.length&&o&&a[0]instanceof K&&(!c.alternative||c.alternative instanceof K)){_=!0,a.push(e(ve,a[0],{value:e(gt,a[0])}).transform(t)),a=r(c.alternative).concat(a),a.unshift(c);continue n}}var l=m(c.body),p=l instanceof be?t.loopcontrol_target(l.label):null;if(l&&(l instanceof ve&&!l.value&&o||l instanceof Ae&&i===u(p)||l instanceof ye&&p instanceof Z&&i===p)){l.label&&h(l.label.thedef.references,l),_=!0;var d=r(c.body).slice(0,-1);c=c.clone(),c.condition=c.condition.negate(t),c.body=e(Z,c,{body:r(c.alternative).concat(a)}),c.alternative=e(Z,c,{body:d}),a=[c.transform(t)];continue n}var l=m(c.alternative),p=l instanceof be?t.loopcontrol_target(l.label):null;if(l&&(l instanceof ve&&!l.value&&o||l instanceof Ae&&i===u(p)||l instanceof ye&&p instanceof Z&&i===p)){l.label&&h(l.label.thedef.references,l),_=!0,c=c.clone(),c.body=e(Z,c.body,{body:r(c.body).concat(a)}),c.alternative=e(Z,c.alternative,{body:r(c.alternative).slice(0,-1)}),a=[c.transform(t)];continue n}a.unshift(c);break;default:a.unshift(c)}}return a}function s(n,e){var t=!1,r=n.length,i=e.self();return n=n.reduce(function(n,r){if(t)c(e,r,n);else{if(r instanceof be){var o=e.loopcontrol_target(r.label);r instanceof ye&&o instanceof Z&&u(o)===i||r instanceof Ae&&u(o)===i?r.label&&h(r.label.thedef.references,r):n.push(r)}else n.push(r);m(r)&&(t=!0)}return n},[]),_=n.length!=r,n}function f(n,t){function r(){i=Re.from_array(i),i&&o.push(e(K,i,{body:i})),i=[]}if(n.length<2)return n;var i=[],o=[];return n.forEach(function(n){n instanceof K?i.push(n.body):(r(),o.push(n))}),r(),o=l(o,t),_=o.length!=n.length,o}function l(n,t){function r(n){i.pop();var e=o.body;return e instanceof Re?e.add(n):e=Re.cons(e,n),e.transform(t)}var i=[],o=null;return n.forEach(function(n){if(o)if(n instanceof ae){var t={};try{o.body.walk(new E(function(n){if(n instanceof Ue&&\"in\"==n.operator)throw t})),!n.init||n.init instanceof Be?n.init||(n.init=o.body,i.pop()):n.init=r(n.init)}catch(a){if(a!==t)throw a}}else n instanceof we?n.condition=r(n.condition):n instanceof se?n.expression=r(n.expression):n instanceof me&&n.value?n.value=r(n.value):n instanceof me?n.value=r(e(gt,n)):n instanceof Ee&&(n.expression=r(n.expression));i.push(n),o=n instanceof K?n:null}),i}function p(n){var e=null;return n.reduce(function(n,t){return t instanceof Be&&e&&e.TYPE==t.TYPE?(e.definitions=e.definitions.concat(t.definitions),_=!0):t instanceof ae&&e instanceof Be&&(!t.init||t.init.TYPE==e.TYPE)?(_=!0,n.pop(),t.init?t.init.definitions=e.definitions.concat(t.init.definitions):t.init=e,n.push(t),e=t):(e=t,n.push(t)),n},[])}function d(n){n.forEach(function(n){n instanceof K&&(n.body=function t(n){return n.transform(new z(function(n){if(n instanceof Me&&n.expression instanceof de)return e(je,n,{operator:\"!\",expression:n});if(n instanceof Me)n.expression=t(n.expression);else if(n instanceof Re)n.car=t(n.car);else if(n instanceof Ve){var r=t(n.condition);if(r!==n.condition){n.condition=r;var i=n.consequent;n.consequent=n.alternative,n.alternative=i}}return n}))}(n.body))})}var _;do _=!1,t.option(\"angular\")&&(n=i(n)),n=o(n),t.option(\"dead_code\")&&(n=s(n,t)),t.option(\"if_return\")&&(n=a(n,t)),t.option(\"sequences\")&&(n=f(n,t)),t.option(\"join_vars\")&&(n=p(n,t));while(_);return t.option(\"negate_iife\")&&d(n,t),n}function c(n,e,t){n.warn(\"Dropping unreachable code [{file}:{line},{col}]\",e.start),e.walk(new E(function(e){return e instanceof Be?(n.warn(\"Declarations in unreachable code! [{file}:{line},{col}]\",e.start),e.remove_initializers(),t.push(e),!0):e instanceof he?(t.push(e),!0):e instanceof ce?!0:void 0}))}function f(n,e){return n.print_to_string().length>e.print_to_string().length?e:n}function m(n){return n&&n.aborts()}function v(n,t){function i(i){i=r(i),n.body instanceof Z?(n.body=n.body.clone(),n.body.body=i.concat(n.body.body.slice(1)),n.body=n.body.transform(t)):n.body=e(Z,n.body,{body:i}).transform(t),v(n,t)}var o=n.body instanceof Z?n.body.body[0]:n.body;o instanceof we&&(o.body instanceof ye&&t.loopcontrol_target(o.body.label)===n?(n.condition=n.condition?e(Ue,n.condition,{left:n.condition,operator:\"&&\",right:o.condition.negate(t)}):o.condition.negate(t),i(o.alternative)):o.alternative instanceof ye&&t.loopcontrol_target(o.alternative.label)===n&&(n.condition=n.condition?e(Ue,n.condition,{left:n.condition,operator:\"&&\",right:o.condition}):o.condition,i(o.body)))}function A(n,e){var t=e.option(\"pure_getters\");e.options.pure_getters=!1;var r=n.has_side_effects(e);return e.options.pure_getters=t,r}function w(n,t){return t.option(\"booleans\")&&t.in_boolean_context()?e(Et,n):n}n(W,function(n){return n}),W.DEFMETHOD(\"equivalent_to\",function(n){return this.print_to_string()==n.print_to_string()}),function(n){var e=[\"!\",\"delete\"],t=[\"in\",\"instanceof\",\"==\",\"!=\",\"===\",\"!==\",\"<\",\"<=\",\">=\",\">\"];n(W,function(){return!1}),n(je,function(){return o(this.operator,e)}),n(Ue,function(){return o(this.operator,t)||(\"&&\"==this.operator||\"||\"==this.operator)&&this.left.is_boolean()&&this.right.is_boolean()}),n(Ve,function(){return this.consequent.is_boolean()&&this.alternative.is_boolean()}),n(Le,function(){return\"=\"==this.operator&&this.right.is_boolean()}),n(Re,function(){return this.cdr.is_boolean()}),n(Et,function(){return!0}),n(wt,function(){return!0})}(function(n,e){n.DEFMETHOD(\"is_boolean\",e)}),function(n){n(W,function(){return!1}),n(pt,function(){return!0}),n(je,function(){return\"typeof\"==this.operator}),n(Ue,function(n){return\"+\"==this.operator&&(this.left.is_string(n)||this.right.is_string(n))}),n(Le,function(n){return(\"=\"==this.operator||\"+=\"==this.operator)&&this.right.is_string(n)}),n(Re,function(n){return this.cdr.is_string(n)}),n(Ve,function(n){return this.consequent.is_string(n)&&this.alternative.is_string(n)}),n(Me,function(n){return n.option(\"unsafe\")&&this.expression instanceof st&&\"String\"==this.expression.name&&this.expression.undeclared()})}(function(n,e){n.DEFMETHOD(\"is_string\",e)}),function(n){function e(n,e){if(!e)throw new Error(\"Compressor must be passed\");return n._eval(e)}W.DEFMETHOD(\"evaluate\",function(e){if(!e.option(\"evaluate\"))return[this];try{var r=this._eval(e);return[f(t(e,r,this),this),r]}catch(i){if(i!==n)throw i;return[this]}}),n(Y,function(){throw new Error(d(\"Cannot evaluate a statement [{file}:{line},{col}]\",this.start))}),n(de,function(){throw n}),n(W,function(){throw n}),n(lt,function(){return this.getValue()}),n(je,function(t){var r=this.expression;switch(this.operator){case\"!\":return!e(r,t);case\"typeof\":if(r instanceof de)return\"function\";if(r=e(r,t),r instanceof RegExp)throw n;return typeof r;case\"void\":return void e(r,t);case\"~\":return~e(r,t);case\"-\":if(r=e(r,t),0===r)throw n;return-r;case\"+\":return+e(r,t)}throw n}),n(Ue,function(t){var r=this.left,i=this.right;switch(this.operator){case\"&&\":return e(r,t)&&e(i,t);case\"||\":return e(r,t)||e(i,t);case\"|\":return e(r,t)|e(i,t);case\"&\":return e(r,t)&e(i,t);case\"^\":return e(r,t)^e(i,t);case\"+\":return e(r,t)+e(i,t);case\"*\":return e(r,t)*e(i,t);case\"/\":return e(r,t)/e(i,t);case\"%\":return e(r,t)%e(i,t);case\"-\":return e(r,t)-e(i,t);case\"<<\":return e(r,t)<<e(i,t);case\">>\":return e(r,t)>>e(i,t);case\">>>\":return e(r,t)>>>e(i,t);case\"==\":return e(r,t)==e(i,t);case\"===\":return e(r,t)===e(i,t);case\"!=\":return e(r,t)!=e(i,t);case\"!==\":return e(r,t)!==e(i,t);case\"<\":return e(r,t)<e(i,t);case\"<=\":return e(r,t)<=e(i,t);case\">\":return e(r,t)>e(i,t);case\">=\":return e(r,t)>=e(i,t);case\"in\":return e(r,t)in e(i,t);case\"instanceof\":return e(r,t)instanceof e(i,t)}throw n}),n(Ve,function(n){return e(this.condition,n)?e(this.consequent,n):e(this.alternative,n)}),n(st,function(t){var r=this.definition();if(r&&r.constant&&r.init)return e(r.init,t);throw n}),n(He,function(t){if(t.option(\"unsafe\")&&\"length\"==this.property){var r=e(this.expression,t);if(\"string\"==typeof r)return r.length}throw n})}(function(n,e){n.DEFMETHOD(\"_eval\",e)}),function(n){function t(n){return e(je,n,{operator:\"!\",expression:n})}n(W,function(){return t(this)}),n(Y,function(){throw new Error(\"Cannot negate a statement\")}),n(de,function(){return t(this)}),n(je,function(){return\"!\"==this.operator?this.expression:t(this)}),n(Re,function(n){var e=this.clone();return e.cdr=e.cdr.negate(n),e}),n(Ve,function(n){var e=this.clone();return e.consequent=e.consequent.negate(n),e.alternative=e.alternative.negate(n),f(t(this),e)}),n(Ue,function(n){var e=this.clone(),r=this.operator;if(n.option(\"unsafe_comps\"))switch(r){case\"<=\":return e.operator=\">\",e;case\"<\":return e.operator=\">=\",e;case\">=\":return e.operator=\"<\",e;case\">\":return e.operator=\"<=\",e}switch(r){case\"==\":return e.operator=\"!=\",e;case\"!=\":return e.operator=\"==\",e;case\"===\":return e.operator=\"!==\",e;case\"!==\":return e.operator=\"===\",e;case\"&&\":return e.operator=\"||\",e.left=e.left.negate(n),e.right=e.right.negate(n),f(t(this),e);case\"||\":return e.operator=\"&&\",e.left=e.left.negate(n),e.right=e.right.negate(n),f(t(this),e)}return t(this)})}(function(n,e){n.DEFMETHOD(\"negate\",function(n){return e.call(this,n)})}),function(n){n(W,function(){return!0}),n(Q,function(){return!1}),n(lt,function(){return!1}),n(ft,function(){return!1}),n(Me,function(n){var e=n.option(\"pure_funcs\");return e?e.indexOf(this.expression.print_to_string())<0:!0}),n(J,function(n){for(var e=this.body.length;--e>=0;)if(this.body[e].has_side_effects(n))return!0;return!1}),n(K,function(n){return this.body.has_side_effects(n)}),n(he,function(){return!0}),n(de,function(){return!1}),n(Ue,function(n){return this.left.has_side_effects(n)||this.right.has_side_effects(n)}),n(Le,function(){return!0}),n(Ve,function(n){return this.condition.has_side_effects(n)||this.consequent.has_side_effects(n)||this.alternative.has_side_effects(n)}),n(Pe,function(n){return\"delete\"==this.operator||\"++\"==this.operator||\"--\"==this.operator||this.expression.has_side_effects(n)}),n(st,function(){return!1}),n(Ye,function(n){for(var e=this.properties.length;--e>=0;)if(this.properties[e].has_side_effects(n))return!0;return!1}),n(Xe,function(n){return this.value.has_side_effects(n)}),n(We,function(n){for(var e=this.elements.length;--e>=0;)if(this.elements[e].has_side_effects(n))return!0;return!1}),n(He,function(n){return n.option(\"pure_getters\")?this.expression.has_side_effects(n):!0}),n(ze,function(n){return n.option(\"pure_getters\")?this.expression.has_side_effects(n)||this.property.has_side_effects(n):!0}),n(qe,function(n){return!n.option(\"pure_getters\")}),n(Re,function(n){return this.car.has_side_effects(n)||this.cdr.has_side_effects(n)})}(function(n,e){n.DEFMETHOD(\"has_side_effects\",e)}),function(n){function e(){var n=this.body.length;return n>0&&m(this.body[n-1])}n(Y,function(){return null}),n(_e,function(){return this}),n(Z,e),n(De,e),n(we,function(){return this.alternative&&m(this.body)&&m(this.alternative)})}(function(n,e){n.DEFMETHOD(\"aborts\",e)}),n(G,function(n){return n.scope.has_directive(n.value)!==n.scope?e(Q,n):n}),n(X,function(n,t){return t.option(\"drop_debugger\")?e(Q,n):n}),n(ee,function(n,t){return n.body instanceof ye&&t.loopcontrol_target(n.body.label)===n.body?e(Q,n):0==n.label.references.length?n.body:n}),n(J,function(n,e){return n.body=s(n.body,e),n}),n(Z,function(n,t){switch(n.body=s(n.body,t),n.body.length){case 1:return n.body[0];case 0:return e(Q,n)}return n}),ce.DEFMETHOD(\"drop_unused\",function(n){var t=this;if(n.option(\"unused\")&&!(t instanceof fe)&&!t.uses_eval){var r=[],i=new y,a=this,u=new E(function(e,o){if(e!==t){if(e instanceof he)return i.add(e.name.name,e),!0;if(e instanceof Be&&a===t)return e.definitions.forEach(function(e){e.value&&(i.add(e.name.name,e.value),e.value.has_side_effects(n)&&e.value.walk(u))}),!0;if(e instanceof st)return p(r,e.definition()),!0;if(e instanceof ce){var s=a;return a=e,o(),a=s,!0}}});t.walk(u);for(var s=0;s<r.length;++s)r[s].orig.forEach(function(n){var e=i.get(n.name);e&&e.forEach(function(n){var e=new E(function(n){n instanceof st&&p(r,n.definition())});n.walk(e)})});var c=new z(function(i,a,u){if(i instanceof le&&!(i instanceof pe)&&!n.option(\"keep_fargs\"))for(var s=i.argnames,f=s.length;--f>=0;){var l=s[f];if(!l.unreferenced())break;s.pop(),n.warn(\"Dropping unused function argument {name} [{file}:{line},{col}]\",{name:l.name,file:l.start.file,line:l.start.line,col:l.start.col})}if(i instanceof he&&i!==t)return o(i.name.definition(),r)?i:(n.warn(\"Dropping unused function {name} [{file}:{line},{col}]\",{name:i.name.name,file:i.name.start.file,line:i.name.start.line,col:i.name.start.col}),e(Q,i));if(i instanceof Be&&!(c.parent()instanceof ue)){var p=i.definitions.filter(function(e){if(o(e.name.definition(),r))return!0;var t={name:e.name.name,file:e.name.start.file,line:e.name.start.line,col:e.name.start.col};return e.value&&e.value.has_side_effects(n)?(e._unused_side_effects=!0,n.warn(\"Side effects in initialization of unused variable {name} [{file}:{line},{col}]\",t),!0):(n.warn(\"Dropping unused variable {name} [{file}:{line},{col}]\",t),!1)});p=_(p,function(n,e){return!n.value&&e.value?-1:!e.value&&n.value?1:0});for(var d=[],f=0;f<p.length;){var h=p[f];h._unused_side_effects?(d.push(h.value),p.splice(f,1)):(d.length>0&&(d.push(h.value),h.value=Re.from_array(d),d=[]),++f)}return d=d.length>0?e(Z,i,{body:[e(K,i,{body:Re.from_array(d)})]}):null,0!=p.length||d?0==p.length?d:(i.definitions=p,d&&(d.body.unshift(i),i=d),i):e(Q,i)}if(i instanceof ae&&(a(i,this),i.init instanceof Z)){var m=i.init.body.slice(0,-1);return i.init=i.init.body.slice(-1)[0].body,m.push(i),u?V.splice(m):e(Z,i,{body:m})}return i instanceof ce&&i!==t?i:void 0});t.transform(c)}}),ce.DEFMETHOD(\"hoist_declarations\",function(n){var t=n.option(\"hoist_funs\"),r=n.option(\"hoist_vars\"),i=this;if(t||r){var o=[],u=[],s=new y,c=0,f=0;i.walk(new E(function(n){return n instanceof ce&&n!==i?!0:n instanceof Te?(++f,!0):void 0})),r=r&&f>1;var l=new z(function(n){if(n!==i){if(n instanceof G)return o.push(n),e(Q,n);if(n instanceof he&&t)return u.push(n),e(Q,n);if(n instanceof Te&&r){n.definitions.forEach(function(n){s.set(n.name.name,n),++c});var a=n.to_assignments(),f=l.parent();return f instanceof ue&&f.init===n?null==a?n.definitions[0].name:a:f instanceof ae&&f.init===n?a:a?e(K,n,{body:a}):e(Q,n)}if(n instanceof ce)return n}});if(i=i.transform(l),c>0){var p=[];if(s.each(function(n,e){i instanceof le&&a(function(e){return e.name==n.name.name},i.argnames)?s.del(e):(n=n.clone(),n.value=null,p.push(n),s.set(e,n))}),p.length>0){for(var d=0;d<i.body.length;){if(i.body[d]instanceof K){var _,m,v=i.body[d].body;if(v instanceof Le&&\"=\"==v.operator&&(_=v.left)instanceof Ze&&s.has(_.name)){var g=s.get(_.name);if(g.value)break;g.value=v.right,h(p,g),p.push(g),i.body.splice(d,1);continue}if(v instanceof Re&&(m=v.car)instanceof Le&&\"=\"==m.operator&&(_=m.left)instanceof Ze&&s.has(_.name)){var g=s.get(_.name);if(g.value)break;g.value=m.right,h(p,g),p.push(g),i.body[d].body=v.cdr;continue}}if(i.body[d]instanceof Q)i.body.splice(d,1);else{if(!(i.body[d]instanceof Z))break;var b=[d,1].concat(i.body[d].body);i.body.splice.apply(i.body,b)}}p=e(Te,i,{definitions:p}),u.push(p)}}i.body=o.concat(u,i.body)}return i}),n(K,function(n,t){return t.option(\"side_effects\")&&!n.body.has_side_effects(t)?(t.warn(\"Dropping side-effect-free statement [{file}:{line},{col}]\",n.start),e(Q,n)):n}),n(re,function(n,t){var r=n.condition.evaluate(t);if(n.condition=r[0],!t.option(\"loops\"))return n;if(r.length>1){if(r[1])return e(ae,n,{body:n.body});if(n instanceof oe&&t.option(\"dead_code\")){var i=[];return c(t,n.body,i),e(Z,n,{body:i})}}return n}),n(oe,function(n,t){return t.option(\"loops\")?(n=re.prototype.optimize.call(n,t),n instanceof oe&&(v(n,t),n=e(ae,n,n).transform(t)),n):n}),n(ae,function(n,t){var r=n.condition;if(r&&(r=r.evaluate(t),n.condition=r[0]),!t.option(\"loops\"))return n;if(r&&r.length>1&&!r[1]&&t.option(\"dead_code\")){var i=[];return n.init instanceof Y?i.push(n.init):n.init&&i.push(e(K,n.init,{body:n.init})),c(t,n.body,i),e(Z,n,{body:i})}return v(n,t),n}),n(we,function(n,t){if(!t.option(\"conditionals\"))return n;var r=n.condition.evaluate(t);if(n.condition=r[0],r.length>1)if(r[1]){if(t.warn(\"Condition always true [{file}:{line},{col}]\",n.condition.start),t.option(\"dead_code\")){var o=[];return n.alternative&&c(t,n.alternative,o),o.push(n.body),e(Z,n,{body:o}).transform(t)}}else if(t.warn(\"Condition always false [{file}:{line},{col}]\",n.condition.start),t.option(\"dead_code\")){var o=[];return c(t,n.body,o),n.alternative&&o.push(n.alternative),e(Z,n,{body:o}).transform(t)}i(n.alternative)&&(n.alternative=null);var a=n.condition.negate(t),u=f(n.condition,a)===a;if(n.alternative&&u){u=!1,n.condition=a;var s=n.body;n.body=n.alternative||e(Q),n.alternative=s}if(i(n.body)&&i(n.alternative))return e(K,n.condition,{body:n.condition}).transform(t);if(n.body instanceof K&&n.alternative instanceof K)return e(K,n,{body:e(Ve,n,{condition:n.condition,consequent:n.body.body,alternative:n.alternative.body})}).transform(t);if(i(n.alternative)&&n.body instanceof K)return u?e(K,n,{body:e(Ue,n,{operator:\"||\",left:a,right:n.body.body})}).transform(t):e(K,n,{body:e(Ue,n,{operator:\"&&\",left:n.condition,right:n.body.body})}).transform(t);if(n.body instanceof Q&&n.alternative&&n.alternative instanceof K)return e(K,n,{body:e(Ue,n,{operator:\"||\",left:n.condition,right:n.alternative.body})}).transform(t);if(n.body instanceof me&&n.alternative instanceof me&&n.body.TYPE==n.alternative.TYPE)return e(n.body.CTOR,n,{value:e(Ve,n,{condition:n.condition,consequent:n.body.value||e(gt,n.body).optimize(t),alternative:n.alternative.value||e(gt,n.alternative).optimize(t)})}).transform(t);if(n.body instanceof we&&!n.body.alternative&&!n.alternative&&(n.condition=e(Ue,n.condition,{operator:\"&&\",left:n.condition,right:n.body.condition}).transform(t),n.body=n.body.body),m(n.body)&&n.alternative){var l=n.alternative;return n.alternative=null,e(Z,n,{body:[n,l]}).transform(t)}if(m(n.alternative)){var p=n.body;return n.body=n.alternative,n.condition=u?a:n.condition.negate(t),n.alternative=null,e(Z,n,{body:[n,p]}).transform(t)}return n}),n(Ee,function(n,t){if(0==n.body.length&&t.option(\"conditionals\"))return e(K,n,{body:n.expression}).transform(t);for(;;){var r=n.body[n.body.length-1];if(r){var i=r.body[r.body.length-1];if(i instanceof ye&&u(t.loopcontrol_target(i.label))===n&&r.body.pop(),r instanceof Fe&&0==r.body.length){n.body.pop();continue}}break}var o=n.expression.evaluate(t);n:if(2==o.length)try{if(n.expression=o[0],!t.option(\"dead_code\"))break n;var a=o[1],s=!1,c=!1,f=!1,l=!1,p=!1,d=new z(function(r,i,o){if(r instanceof le||r instanceof K)return r;if(r instanceof Ee&&r===n)return r=r.clone(),i(r,this),p?r:e(Z,r,{body:r.body.reduce(function(n,e){return n.concat(e.body)},[])}).transform(t);if(r instanceof we||r instanceof Ce){var u=s;return s=!c,i(r,this),s=u,r}if(r instanceof ne||r instanceof Ee){var u=c;return c=!0,i(r,this),c=u,r}if(r instanceof ye&&this.loopcontrol_target(r.label)===n)return s?(p=!0,r):c?r:(l=!0,o?V.skip:e(Q,r));if(r instanceof De&&this.parent()===n){if(l)return V.skip;if(r instanceof Se){var d=r.expression.evaluate(t);if(d.length<2)throw n;return d[1]===a||f?(f=!0,m(r)&&(l=!0),i(r,this),r):V.skip}return i(r,this),r}});d.stack=t.stack.slice(),n=n.transform(d)}catch(h){if(h!==n)throw h}return n}),n(Se,function(n,e){return n.body=s(n.body,e),n}),n(Ce,function(n,e){return n.body=s(n.body,e),n}),Be.DEFMETHOD(\"remove_initializers\",function(){this.definitions.forEach(function(n){n.value=null})}),Be.DEFMETHOD(\"to_assignments\",function(){var n=this.definitions.reduce(function(n,t){if(t.value){var r=e(st,t.name,t.name);n.push(e(Le,t,{operator:\"=\",left:r,right:t.value}))}return n},[]);return 0==n.length?null:Re.from_array(n)}),n(Be,function(n){return 0==n.definitions.length?e(Q,n):n}),n(de,function(n,e){return n=le.prototype.optimize.call(n,e),e.option(\"unused\")&&n.name&&n.name.unreferenced()&&(n.name=null),n}),n(Me,function(n,r){if(r.option(\"unsafe\")){var i=n.expression;if(i instanceof st&&i.undeclared())switch(i.name){case\"Array\":if(1!=n.args.length)return e(We,n,{elements:n.args}).transform(r);break;case\"Object\":if(0==n.args.length)return e(Ye,n,{properties:[]});break;case\"String\":if(0==n.args.length)return e(pt,n,{value:\"\"});if(n.args.length<=1)return e(Ue,n,{left:n.args[0],operator:\"+\",right:e(pt,n,{value:\"\"})}).transform(r);break;case\"Number\":if(0==n.args.length)return e(dt,n,{value:0});if(1==n.args.length)return e(je,n,{expression:n.args[0],operator:\"+\"}).transform(r);case\"Boolean\":if(0==n.args.length)return e(wt,n);if(1==n.args.length)return e(je,n,{expression:e(je,null,{expression:n.args[0],operator:\"!\"}),operator:\"!\"}).transform(r);break;case\"Function\":if(b(n.args,function(n){return n instanceof pt}))try{var o=\"(function(\"+n.args.slice(0,-1).map(function(n){return n.value}).join(\",\")+\"){\"+n.args[n.args.length-1].value+\"})()\",a=H(o);\na.figure_out_scope({screw_ie8:r.option(\"screw_ie8\")});var u=new I(r.options);a=a.transform(u),a.figure_out_scope({screw_ie8:r.option(\"screw_ie8\")}),a.mangle_names();var s;try{a.walk(new E(function(n){if(n instanceof le)throw s=n,a}))}catch(c){if(c!==a)throw c}var l=s.argnames.map(function(t,r){return e(pt,n.args[r],{value:t.print_to_string()})}),o=j();return Z.prototype._codegen.call(s,s,o),o=o.toString().replace(/^\\{|\\}$/g,\"\"),l.push(e(pt,n.args[n.args.length-1],{value:o})),n.args=l,n}catch(c){if(!(c instanceof M))throw console.log(c),c;r.warn(\"Error parsing code passed to new Function [{file}:{line},{col}]\",n.args[n.args.length-1].start),r.warn(c.toString())}}else{if(i instanceof He&&\"toString\"==i.property&&0==n.args.length)return e(Ue,n,{left:e(pt,n,{value:\"\"}),operator:\"+\",right:i.expression}).transform(r);if(i instanceof He&&i.expression instanceof We&&\"join\"==i.property){var p=0==n.args.length?\",\":n.args[0].evaluate(r)[1];if(null!=p){var d=i.expression.elements.reduce(function(n,e){if(e=e.evaluate(r),0==n.length||1==e.length)n.push(e);else{var i=n[n.length-1];if(2==i.length){var o=\"\"+i[1]+p+e[1];n[n.length-1]=[t(r,o,i[0]),o]}else n.push(e)}return n},[]);if(0==d.length)return e(pt,n,{value:\"\"});if(1==d.length)return d[0][0];if(\"\"==p){var h;return h=d[0][0]instanceof pt||d[1][0]instanceof pt?d.shift()[0]:e(pt,n,{value:\"\"}),d.reduce(function(n,t){return e(Ue,t[0],{operator:\"+\",left:n,right:t[0]})},h).transform(r)}var _=n.clone();return _.expression=_.expression.clone(),_.expression.expression=_.expression.expression.clone(),_.expression.expression.elements=d.map(function(n){return n[0]}),f(n,_)}}}}return r.option(\"side_effects\")&&n.expression instanceof de&&0==n.args.length&&!J.prototype.has_side_effects.call(n.expression,r)?e(gt,n).transform(r):r.option(\"drop_console\")&&n.expression instanceof qe&&n.expression.expression instanceof st&&\"console\"==n.expression.expression.name&&n.expression.expression.undeclared()?e(gt,n).transform(r):n.evaluate(r)[0]}),n(Ne,function(n,t){if(t.option(\"unsafe\")){var r=n.expression;if(r instanceof st&&r.undeclared())switch(r.name){case\"Object\":case\"RegExp\":case\"Function\":case\"Error\":case\"Array\":return e(Me,n,n).transform(t)}}return n}),n(Re,function(n,t){if(!t.option(\"side_effects\"))return n;if(!n.car.has_side_effects(t)){var r;if(!(n.cdr instanceof st&&\"eval\"==n.cdr.name&&n.cdr.undeclared()&&(r=t.parent())instanceof Me&&r.expression===n))return n.cdr}if(t.option(\"cascade\")){if(n.car instanceof Le&&!n.car.left.has_side_effects(t)){if(n.car.left.equivalent_to(n.cdr))return n.car;if(n.cdr instanceof Me&&n.cdr.expression.equivalent_to(n.car.left))return n.cdr.expression=n.car,n.cdr}if(!n.car.has_side_effects(t)&&!n.cdr.has_side_effects(t)&&n.car.equivalent_to(n.cdr))return n.car}return n.cdr instanceof je&&\"void\"==n.cdr.operator&&!n.cdr.expression.has_side_effects(t)?(n.cdr.operator=n.car,n.cdr):n.cdr instanceof gt?e(je,n,{operator:\"void\",expression:n.car}):n}),Pe.DEFMETHOD(\"lift_sequences\",function(n){if(n.option(\"sequences\")&&this.expression instanceof Re){var e=this.expression,t=e.to_array();return this.expression=t.pop(),t.push(this),e=Re.from_array(t).transform(n)}return this}),n(Ie,function(n,e){return n.lift_sequences(e)}),n(je,function(n,t){n=n.lift_sequences(t);var r=n.expression;if(t.option(\"booleans\")&&t.in_boolean_context()){switch(n.operator){case\"!\":if(r instanceof je&&\"!\"==r.operator)return r.expression;break;case\"typeof\":return t.warn(\"Boolean expression always true [{file}:{line},{col}]\",n.start),e(Et,n)}r instanceof Ue&&\"!\"==n.operator&&(n=f(n,r.negate(t)))}return n.evaluate(t)[0]}),Ue.DEFMETHOD(\"lift_sequences\",function(n){if(n.option(\"sequences\")){if(this.left instanceof Re){var e=this.left,t=e.to_array();return this.left=t.pop(),t.push(this),e=Re.from_array(t).transform(n)}if(this.right instanceof Re&&this instanceof Le&&!A(this.left,n)){var e=this.right,t=e.to_array();return this.right=t.pop(),t.push(this),e=Re.from_array(t).transform(n)}}return this});var D=g(\"== === != !== * & | ^\");n(Ue,function(n,t){var r=t.has_directive(\"use asm\")?l:function(e,r){if(r||!n.left.has_side_effects(t)&&!n.right.has_side_effects(t)){e&&(n.operator=e);var i=n.left;n.left=n.right,n.right=i}};if(D(n.operator)&&(n.right instanceof lt&&!(n.left instanceof lt)&&(n.left instanceof Ue&&It[n.left.operator]>=It[n.operator]||r(null,!0)),/^[!=]==?$/.test(n.operator))){if(n.left instanceof st&&n.right instanceof Ve){if(n.right.consequent instanceof st&&n.right.consequent.definition()===n.left.definition()){if(/^==/.test(n.operator))return n.right.condition;if(/^!=/.test(n.operator))return n.right.condition.negate(t)}if(n.right.alternative instanceof st&&n.right.alternative.definition()===n.left.definition()){if(/^==/.test(n.operator))return n.right.condition.negate(t);if(/^!=/.test(n.operator))return n.right.condition}}if(n.right instanceof st&&n.left instanceof Ve){if(n.left.consequent instanceof st&&n.left.consequent.definition()===n.right.definition()){if(/^==/.test(n.operator))return n.left.condition;if(/^!=/.test(n.operator))return n.left.condition.negate(t)}if(n.left.alternative instanceof st&&n.left.alternative.definition()===n.right.definition()){if(/^==/.test(n.operator))return n.left.condition.negate(t);if(/^!=/.test(n.operator))return n.left.condition}}}if(n=n.lift_sequences(t),t.option(\"comparisons\"))switch(n.operator){case\"===\":case\"!==\":(n.left.is_string(t)&&n.right.is_string(t)||n.left.is_boolean()&&n.right.is_boolean())&&(n.operator=n.operator.substr(0,2));case\"==\":case\"!=\":n.left instanceof pt&&\"undefined\"==n.left.value&&n.right instanceof je&&\"typeof\"==n.right.operator&&t.option(\"unsafe\")&&(n.right.expression instanceof st&&n.right.expression.undeclared()||(n.right=n.right.expression,n.left=e(gt,n.left).optimize(t),2==n.operator.length&&(n.operator+=\"=\")))}if(t.option(\"booleans\")&&t.in_boolean_context())switch(n.operator){case\"&&\":var i=n.left.evaluate(t),o=n.right.evaluate(t);if(i.length>1&&!i[1]||o.length>1&&!o[1])return t.warn(\"Boolean && always false [{file}:{line},{col}]\",n.start),e(wt,n);if(i.length>1&&i[1])return o[0];if(o.length>1&&o[1])return i[0];break;case\"||\":var i=n.left.evaluate(t),o=n.right.evaluate(t);if(i.length>1&&i[1]||o.length>1&&o[1])return t.warn(\"Boolean || always true [{file}:{line},{col}]\",n.start),e(Et,n);if(i.length>1&&!i[1])return o[0];if(o.length>1&&!o[1])return i[0];break;case\"+\":var i=n.left.evaluate(t),o=n.right.evaluate(t);if(i.length>1&&i[0]instanceof pt&&i[1]||o.length>1&&o[0]instanceof pt&&o[1])return t.warn(\"+ in boolean context always true [{file}:{line},{col}]\",n.start),e(Et,n)}if(t.option(\"comparisons\")){if(!(t.parent()instanceof Ue)||t.parent()instanceof Le){var a=e(je,n,{operator:\"!\",expression:n.negate(t)});n=f(n,a)}switch(n.operator){case\"<\":r(\">\");break;case\"<=\":r(\">=\")}}return\"+\"==n.operator&&n.right instanceof pt&&\"\"===n.right.getValue()&&n.left instanceof Ue&&\"+\"==n.left.operator&&n.left.is_string(t)?n.left:(t.option(\"evaluate\")&&\"+\"==n.operator&&(n.left instanceof lt&&n.right instanceof Ue&&\"+\"==n.right.operator&&n.right.left instanceof lt&&n.right.is_string(t)&&(n=e(Ue,n,{operator:\"+\",left:e(pt,null,{value:\"\"+n.left.getValue()+n.right.left.getValue(),start:n.left.start,end:n.right.left.end}),right:n.right.right})),n.right instanceof lt&&n.left instanceof Ue&&\"+\"==n.left.operator&&n.left.right instanceof lt&&n.left.is_string(t)&&(n=e(Ue,n,{operator:\"+\",left:n.left.left,right:e(pt,null,{value:\"\"+n.left.right.getValue()+n.right.getValue(),start:n.left.right.start,end:n.right.end})})),n.left instanceof Ue&&\"+\"==n.left.operator&&n.left.is_string(t)&&n.left.right instanceof lt&&n.right instanceof Ue&&\"+\"==n.right.operator&&n.right.left instanceof lt&&n.right.is_string(t)&&(n=e(Ue,n,{operator:\"+\",left:e(Ue,n.left,{operator:\"+\",left:n.left.left,right:e(pt,null,{value:\"\"+n.left.right.getValue()+n.right.left.getValue(),start:n.left.right.start,end:n.right.left.end})}),right:n.right.right}))),n.right instanceof Ue&&n.right.operator==n.operator&&(\"*\"==n.operator||\"&&\"==n.operator||\"||\"==n.operator)?(n.left=e(Ue,n.left,{operator:n.operator,left:n.left,right:n.right.left}),n.right=n.right.right,n.transform(t)):n.evaluate(t)[0])}),n(st,function(n,r){if(n.undeclared()){var i=r.option(\"global_defs\");if(i&&i.hasOwnProperty(n.name))return t(r,i[n.name],n);switch(n.name){case\"undefined\":return e(gt,n);case\"NaN\":return e(vt,n);case\"Infinity\":return e(yt,n)}}return n}),n(gt,function(n,t){if(t.option(\"unsafe\")){var r=t.find_parent(ce),i=r.find_variable(\"undefined\");if(i){var o=e(st,n,{name:\"undefined\",scope:r,thedef:i});return o.reference(),o}}return n});var F=[\"+\",\"-\",\"/\",\"*\",\"%\",\">>\",\"<<\",\">>>\",\"|\",\"^\",\"&\"];n(Le,function(n,e){return n=n.lift_sequences(e),\"=\"==n.operator&&n.left instanceof st&&n.right instanceof Ue&&n.right.left instanceof st&&n.right.left.name==n.left.name&&o(n.right.operator,F)&&(n.operator=n.right.operator+\"=\",n.right=n.right.right),n}),n(Ve,function(n,t){if(!t.option(\"conditionals\"))return n;if(n.condition instanceof Re){var r=n.condition.car;return n.condition=n.condition.cdr,Re.cons(r,n)}var i=n.condition.evaluate(t);if(i.length>1)return i[1]?(t.warn(\"Condition always true [{file}:{line},{col}]\",n.start),n.consequent):(t.warn(\"Condition always false [{file}:{line},{col}]\",n.start),n.alternative);var o=i[0].negate(t);f(i[0],o)===o&&(n=e(Ve,n,{condition:o,consequent:n.alternative,alternative:n.consequent}));var a=n.consequent,u=n.alternative;if(a instanceof Le&&u instanceof Le&&a.operator==u.operator&&a.left.equivalent_to(u.left))return e(Le,n,{operator:a.operator,left:a.left,right:e(Ve,n,{condition:n.condition,consequent:a.right,alternative:u.right})});if(a instanceof Me&&u.TYPE===a.TYPE&&a.args.length==u.args.length&&a.expression.equivalent_to(u.expression)){if(0==a.args.length)return e(Re,n,{car:n.condition,cdr:a});if(1==a.args.length)return a.args[0]=e(Ve,n,{condition:n.condition,consequent:a.args[0],alternative:u.args[0]}),a}return a instanceof Ve&&a.alternative.equivalent_to(u)?e(Ve,n,{condition:e(Ue,n,{left:n.condition,operator:\"&&\",right:a.condition}),consequent:a.consequent,alternative:u}):n}),n(At,function(n,t){if(t.option(\"booleans\")){var r=t.parent();return r instanceof Ue&&(\"==\"==r.operator||\"!=\"==r.operator)?(t.warn(\"Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]\",{operator:r.operator,value:n.value,file:r.start.file,line:r.start.line,col:r.start.col}),e(dt,n,{value:+n.value})):e(je,n,{operator:\"!\",expression:e(dt,n,{value:1-n.value})})}return n}),n(ze,function(n,t){var r=n.property;if(r instanceof pt&&t.option(\"properties\")){if(r=r.getValue(),St(r)?t.option(\"screw_ie8\"):$(r))return e(He,n,{expression:n.expression,property:r}).optimize(t);var i=parseFloat(r);isNaN(i)||i.toString()!=r||(n.property=e(dt,n.property,{value:i}))}return n}),n(He,function(n,e){return n.evaluate(e)[0]}),n(We,w),n(Ye,w),n(ht,w)}(),function(){function n(n){var r=\"prefix\"in n?n.prefix:\"UnaryExpression\"==n.type?!0:!1;return new(r?je:Ie)({start:e(n),end:t(n),operator:n.operator,expression:i(n.argument)})}function e(n){return new L({file:n.loc&&n.loc.source,line:n.loc&&n.loc.start.line,col:n.loc&&n.loc.start.column,pos:n.start,endpos:n.start})}function t(n){return new L({file:n.loc&&n.loc.source,line:n.loc&&n.loc.end.line,col:n.loc&&n.loc.end.column,pos:n.end,endpos:n.end})}function r(n,r,a){var u=\"function From_Moz_\"+n+\"(M){\\n\";return u+=\"return new mytype({\\nstart: my_start_token(M),\\nend: my_end_token(M)\",a&&a.split(/\\s*,\\s*/).forEach(function(n){var e=/([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)/i.exec(n);if(!e)throw new Error(\"Can't understand property map: \"+n);var t=\"M.\"+e[1],r=e[2],i=e[3];if(u+=\",\\n\"+i+\": \",\"@\"==r)u+=t+\".map(from_moz)\";else if(\">\"==r)u+=\"from_moz(\"+t+\")\";else if(\"=\"==r)u+=t;else{if(\"%\"!=r)throw new Error(\"Can't understand operator in propmap: \"+n);u+=\"from_moz(\"+t+\").body\"}}),u+=\"\\n})}\",u=new Function(\"mytype\",\"my_start_token\",\"my_end_token\",\"from_moz\",\"return(\"+u+\")\")(r,e,t,i),o[n]=u}function i(n){a.push(n);var e=null!=n?o[n.type](n):null;return a.pop(),e}var o={TryStatement:function(n){return new Ce({start:e(n),end:t(n),body:i(n.block).body,bcatch:i(n.handlers?n.handlers[0]:n.handler),bfinally:n.finalizer?new xe(i(n.finalizer)):null})},CatchClause:function(n){return new ke({start:e(n),end:t(n),argname:i(n.param),body:i(n.body).body})},ObjectExpression:function(n){return new Ye({start:e(n),end:t(n),properties:n.properties.map(function(n){var r=n.key,o=\"Identifier\"==r.type?r.name:r.value,a={start:e(r),end:t(n.value),key:o,value:i(n.value)};switch(n.kind){case\"init\":return new Ge(a);case\"set\":return a.value.name=i(r),new Ke(a);case\"get\":return a.value.name=i(r),new Je(a)}})})},SequenceExpression:function(n){return Re.from_array(n.expressions.map(i))},MemberExpression:function(n){return new(n.computed?ze:He)({start:e(n),end:t(n),property:n.computed?i(n.property):n.property.name,expression:i(n.object)})},SwitchCase:function(n){return new(n.test?Se:Fe)({start:e(n),end:t(n),expression:i(n.test),body:n.consequent.map(i)})},Literal:function(n){var r=n.value,i={start:e(n),end:t(n)};if(null===r)return new mt(i);switch(typeof r){case\"string\":return i.value=r,new pt(i);case\"number\":return i.value=r,new dt(i);case\"boolean\":return new(r?Et:wt)(i);default:return i.value=r,new ht(i)}},UnaryExpression:n,UpdateExpression:n,Identifier:function(n){var r=a[a.length-2];return new(\"this\"==n.name?ft:\"LabeledStatement\"==r.type?ut:\"VariableDeclarator\"==r.type&&r.id===n?\"const\"==r.kind?tt:et:\"FunctionExpression\"==r.type?r.id===n?ot:rt:\"FunctionDeclaration\"==r.type?r.id===n?it:rt:\"CatchClause\"==r.type?at:\"BreakStatement\"==r.type||\"ContinueStatement\"==r.type?ct:st)({start:e(n),end:t(n),name:n.name})}};r(\"Node\",W),r(\"Program\",fe,\"body@body\"),r(\"Function\",de,\"id>name, params@argnames, body%body\"),r(\"EmptyStatement\",Q),r(\"BlockStatement\",Z,\"body@body\"),r(\"ExpressionStatement\",K,\"expression>body\"),r(\"IfStatement\",we,\"test>condition, consequent>body, alternate>alternative\"),r(\"LabeledStatement\",ee,\"label>label, body>body\"),r(\"BreakStatement\",ye,\"label>label\"),r(\"ContinueStatement\",Ae,\"label>label\"),r(\"WithStatement\",se,\"object>expression, body>body\"),r(\"SwitchStatement\",Ee,\"discriminant>expression, cases@body\"),r(\"ReturnStatement\",ve,\"argument>value\"),r(\"ThrowStatement\",ge,\"argument>value\"),r(\"WhileStatement\",oe,\"test>condition, body>body\"),r(\"DoWhileStatement\",ie,\"test>condition, body>body\"),r(\"ForStatement\",ae,\"init>init, test>condition, update>step, body>body\"),r(\"ForInStatement\",ue,\"left>init, right>object, body>body\"),r(\"DebuggerStatement\",X),r(\"FunctionDeclaration\",he,\"id>name, params@argnames, body%body\"),r(\"VariableDeclaration\",Te,\"declarations@definitions\"),r(\"VariableDeclarator\",Oe,\"id>name, init>value\"),r(\"ThisExpression\",ft),r(\"ArrayExpression\",We,\"elements@elements\"),r(\"FunctionExpression\",de,\"id>name, params@argnames, body%body\"),r(\"BinaryExpression\",Ue,\"operator=operator, left>left, right>right\"),r(\"AssignmentExpression\",Le,\"operator=operator, left>left, right>right\"),r(\"LogicalExpression\",Ue,\"operator=operator, left>left, right>right\"),r(\"ConditionalExpression\",Ve,\"test>condition, consequent>consequent, alternate>alternative\"),r(\"NewExpression\",Ne,\"callee>expression, arguments@args\"),r(\"CallExpression\",Me,\"callee>expression, arguments@args\");var a=null;W.from_mozilla_ast=function(n){var e=a;a=[];var t=i(n);return a=e,t}}(),n.array_to_hash=t,n.slice=r,n.characters=i,n.member=o,n.find_if=a,n.repeat_string=u,n.DefaultsError=s,n.defaults=c,n.merge=f,n.noop=l,n.MAP=V,n.push_uniq=p,n.string_template=d,n.remove=h,n.mergeSort=_,n.set_difference=m,n.set_intersection=v,n.makePredicate=g,n.all=b,n.Dictionary=y,n.DEFNODE=A,n.AST_Token=L,n.AST_Node=W,n.AST_Statement=Y,n.AST_Debugger=X,n.AST_Directive=G,n.AST_SimpleStatement=K,n.walk_body=w,n.AST_Block=J,n.AST_BlockStatement=Z,n.AST_EmptyStatement=Q,n.AST_StatementWithBody=ne,n.AST_LabeledStatement=ee,n.AST_IterationStatement=te,n.AST_DWLoop=re,n.AST_Do=ie,n.AST_While=oe,n.AST_For=ae,n.AST_ForIn=ue,n.AST_With=se,n.AST_Scope=ce,n.AST_Toplevel=fe,n.AST_Lambda=le,n.AST_Accessor=pe,n.AST_Function=de,n.AST_Defun=he,n.AST_Jump=_e,n.AST_Exit=me,n.AST_Return=ve,n.AST_Throw=ge,n.AST_LoopControl=be,n.AST_Break=ye,n.AST_Continue=Ae,n.AST_If=we,n.AST_Switch=Ee,n.AST_SwitchBranch=De,n.AST_Default=Fe,n.AST_Case=Se,n.AST_Try=Ce,n.AST_Catch=ke,n.AST_Finally=xe,n.AST_Definitions=Be,n.AST_Var=Te,n.AST_Const=$e,n.AST_VarDef=Oe,n.AST_Call=Me,n.AST_New=Ne,n.AST_Seq=Re,n.AST_PropAccess=qe,n.AST_Dot=He,n.AST_Sub=ze,n.AST_Unary=Pe,n.AST_UnaryPrefix=je,n.AST_UnaryPostfix=Ie,n.AST_Binary=Ue,n.AST_Conditional=Ve,n.AST_Assign=Le,n.AST_Array=We,n.AST_Object=Ye,n.AST_ObjectProperty=Xe,n.AST_ObjectKeyVal=Ge,n.AST_ObjectSetter=Ke,n.AST_ObjectGetter=Je,n.AST_Symbol=Ze,n.AST_SymbolAccessor=Qe,n.AST_SymbolDeclaration=nt,n.AST_SymbolVar=et,n.AST_SymbolConst=tt,n.AST_SymbolFunarg=rt,n.AST_SymbolDefun=it,n.AST_SymbolLambda=ot,n.AST_SymbolCatch=at,n.AST_Label=ut,n.AST_SymbolRef=st,n.AST_LabelRef=ct,n.AST_This=ft,n.AST_Constant=lt,n.AST_String=pt,n.AST_Number=dt,n.AST_RegExp=ht,n.AST_Atom=_t,n.AST_Null=mt,n.AST_NaN=vt,n.AST_Undefined=gt,n.AST_Hole=bt,n.AST_Infinity=yt,n.AST_Boolean=At,n.AST_False=wt,n.AST_True=Et,n.TreeWalker=E,n.KEYWORDS=Dt,n.KEYWORDS_ATOM=Ft,n.RESERVED_WORDS=St,n.KEYWORDS_BEFORE_EXPRESSION=Ct,n.OPERATOR_CHARS=kt,n.RE_HEX_NUMBER=xt,n.RE_OCT_NUMBER=Bt,n.RE_DEC_NUMBER=Tt,n.OPERATORS=$t,n.WHITESPACE_CHARS=Ot,n.PUNC_BEFORE_EXPRESSION=Mt,n.PUNC_CHARS=Nt,n.REGEXP_MODIFIERS=Rt,n.UNICODE=qt,n.is_letter=D,n.is_digit=F,n.is_alphanumeric_char=S,n.is_unicode_combining_mark=C,n.is_unicode_connector_punctuation=k,n.is_identifier=x,n.is_identifier_start=B,n.is_identifier_char=T,n.is_identifier_string=$,n.parse_js_number=O,n.JS_Parse_Error=M,n.js_error=N,n.is_token=R,n.EX_EOF=Ht,n.tokenizer=q,n.UNARY_PREFIX=zt,n.UNARY_POSTFIX=Pt,n.ASSIGNMENT=jt,n.PRECEDENCE=It,n.STATEMENTS_WITH_LABELS=Ut,n.ATOMIC_START_TOKEN=Vt,n.parse=H,n.TreeTransformer=z,n.SymbolDef=P,n.base54=Lt,n.OutputStream=j,n.Compressor=I,n.SourceMap=U}({},function(){return this}());"
  },
  {
    "path": "misc/demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" ng-app=\"ui.bootstrap.demo\" id=\"top\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n    <link rel=\"icon\" href=\"assets/favicon.ico\" />\n    <title>Angular directives for Bootstrap</title>\n    <meta name=\"description\" content=\"AngularJS (Angular) native directives for Bootstrap. Small footprint (5kB gzipped!), no 3rd party JS dependencies (jQuery, bootstrap JS) required! Widgets: <% demoModules.forEach(function(module) { %><%= module.displayName %>, <% }); %>\">\n    <meta name=\"google-site-verification\" content=\"7lc5HyceLDqpV_6oNHteYFfxDJH7-S3DwnJKtNUKcRg\" />\n    <script src=\"//cdnjs.cloudflare.com/ajax/libs/fastclick/0.6.7/fastclick.min.js\"></script>\n    <script src=\"//cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.0.0/FileSaver.min.js\"></script>\n    <script src=\"//cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js\"></script>\n    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular.min.js\"></script>\n    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-animate.min.js\"></script>\n    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-touch.min.js\"></script>\n    <script src=\"//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-sanitize.js\"></script>\n    <script src=\"ui-bootstrap-tpls-<%= pkg.version%>.min.js\"></script>\n    <script src=\"assets/plunker.js\"></script>\n    <script src=\"assets/app.js\"></script>\n\n    <link href=\"//netdna.bootstrapcdn.com/bootstrap/<%= bsversion %>/css/bootstrap.min.css\" rel=\"stylesheet\"/>\n    <link rel=\"stylesheet\" href=\"assets/rainbow.css\"/>\n    <link rel=\"stylesheet\" href=\"assets/demo.css\"/>\n    <link rel=\"author\" href=\"https://github.com/angular-ui/bootstrap/graphs/contributors\">\n</head>\n<body class=\"ng-cloak\" ng-controller=\"MainCtrl\">\n<header class=\"navbar navbar-default navbar-fixed-top navbar-inner\">\n    <div class=\"container\">\n        <div class=\"navbar-header\">\n            <button type=\"button\" class=\"navbar-toggle\" data-toggle=\"collapse\" data-target=\"#bs-example-navbar-collapse-3\" ng-click=\"isCollapsed = !isCollapsed\">\n                <span class=\"sr-only\">Toggle navigation</span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n            </button>\n            <a class=\"navbar-brand visible-xs\" href=\"#\">UI Bootstrap</a>\n        </div>\n        <nav class=\"hidden-xs\">\n            <ul class=\"nav navbar-nav\">\n                <a href=\"#top\" role=\"button\" class=\"navbar-brand\">\n                    UI Bootstrap\n                </a>\n                <li class=\"dropdown\" uib-dropdown>\n                    <a role=\"button\" class=\"dropdown-toggle\" uib-dropdown-toggle>\n                        Directives <b class=\"caret\"></b>\n                    </a>\n                    <ul class=\"dropdown-menu\">\n                        <% demoModules.forEach(function(module) {  %><li><a href=\"#<%= module.name %>\"><%= module.displayName %></a></li><% }); %>\n                    </ul>\n                </li>\n                <li><a href=\"#getting_started\">Getting started</a></li>\n                <li class=\"dropdown\" uib-dropdown>\n                    <a role=\"button\" class=\"dropdown-toggle\" uib-dropdown-toggle>\n                        Previous docs <b class=\"caret\"></b>\n                    </a>\n                    <ul class=\"dropdown-menu\">\n                        <li ng-repeat=\"version in oldDocs\">\n                            <a ng-href=\"{{version.url}}\">{{version.version}}</a>\n                        </li>\n                    </ul>\n                </li>\n            </ul>\n        </nav>\n        <nav class=\"visible-xs\" uib-collapse=\"!isCollapsed\">\n            <ul class=\"nav navbar-nav\">\n                <li><a href=\"#getting_started\" ng-click=\"isCollapsed = !isCollapsed\">Getting started</a></li>\n                <li><a href=\"#directives_small\" ng-click=\"isCollapsed = !isCollapsed\">Directives</a></li>\n            </ul>\n        </nav>\n    </div>\n</header>\n<div class=\"header-placeholder\"></div>\n<div role=\"main\">\n    <header class=\"bs-header text-center\" id=\"overview\">\n        <div class=\"container\">\n            <h1>\n                UI Bootstrap\n            </h1>\n\n            <p>Bootstrap components written in pure <a href=\"http://angularjs.org\">AngularJS</a> by the <a\n                    href=\"http://angular-ui.github.io\">AngularUI Team</a></p>\n\n            <p>\n                <a class=\"btn btn-outline-inverse btn-lg\" href=\"https://github.com/angular-ui/bootstrap\"><i class=\"icon-github\"></i>Code on Github</a>\n                <button type=\"button\" class=\"btn btn-outline-inverse btn-lg\" ng-click=\"showDownloadModal()\">\n                    <i class=\"glyphicon glyphicon-download-alt\"></i> Download <small>(<%= pkg.version%>)</small>\n                </button>\n                <button type=\"button\" class=\"btn btn-outline-inverse btn-lg\" ng-click=\"showBuildModal()\"><i class=\"glyphicon glyphicon-wrench\"></i> Create a Build</button>\n            </p>\n        </div>\n        <div class=\"bs-social container\">\n            <ul class=\"bs-social-buttons\">\n                <li>\n                    <iframe src=\"//ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=watch&count=true\"\n                            allowtransparency=\"true\" frameborder=\"0\" scrolling=\"0\" width=\"110\" height=\"20\"></iframe>\n                </li>\n                <li>\n                    <iframe src=\"//ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=fork&count=true\"\n                            allowtransparency=\"true\" frameborder=\"0\" scrolling=\"0\" width=\"110\" height=\"20\"></iframe>\n                </li>\n                <li>\n                    <a href=\"https://twitter.com/share\" class=\"twitter-share-button\"\n                       data-hashtags=\"angularjs\">Tweet</a>\n                    <script>!function (d, s, id) {\n                        var js, fjs = d.getElementsByTagName(s)[0];\n                        if (!d.getElementById(id)) {\n                            js = d.createElement(s);\n                            js.id = id;\n                            js.src = \"//platform.twitter.com/widgets.js\";\n                            fjs.parentNode.insertBefore(js, fjs);\n                        }\n                    }(document, \"script\", \"twitter-wjs\");</script>\n                </li>\n                <li>\n                    <!-- Place this tag where you want the +1 button to render. -->\n                    <div class=\"g-plusone\" data-size=\"medium\"></div>\n\n                    <!-- Place this tag after the last +1 button tag. -->\n                    <script type=\"text/javascript\">\n                        (function () {\n                            var po = document.createElement('script');\n                            po.type = 'text/javascript';\n                            po.async = true;\n                            po.src = 'https://apis.google.com/js/plusone.js';\n                            var s = document.getElementsByTagName('script')[0];\n                            s.parentNode.insertBefore(po, s);\n                        })();\n                    </script>\n                </li>\n            </ul>\n        </div>\n    </header>\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-md-12\">\n                <section class=\"bs-sidebar visible-xs\" id=\"directives_small\">\n                    <ul class=\"nav bs-sidenav\">\n                        <li><a href=\"#\"><strong>Directives</strong></a></li>\n                        <% demoModules.forEach(function(module) {  %>\n                            <li><a href=\"#<%= module.name %>\"><%= module.displayName %></a></li>\n                        <% }); %>\n                    </ul>\n                </section>\n\n                <section id=\"getting_started\">\n                    <div class=\"page-header\">\n                        <h1>Getting started</h1>\n                    </div>\n                    <h3>Angular 2</h3>\n                    <p>\n                        For Angular 2 support, check out\n                        <a href=\"https://ng-bootstrap.github.io\" target=\"_blank\">\n                          ng-bootstrap\n                        </a>, created by the UI Bootstrap team.\n                    </p>\n                    <h3>Dependencies</h3>\n                    <p>\n                        This repository contains a set of <strong>native AngularJS directives</strong> based on\n                        Bootstrap's markup and CSS. As a result no dependency on jQuery or Bootstrap's\n                        JavaScript is required. The <strong>only required dependencies</strong> are:\n                    </p>\n                    <ul>\n                        <li><a href=\"http://angularjs.org\" target=\"_blank\">AngularJS</a> (requires AngularJS 1.4.x or higher, tested with <%= ngversion %>).\n                            0.14.3 is the last version of this library that supports AngularJS 1.3.x and\n                            0.12.0 is the last version that supports AngularJS 1.2.x.</li>\n                        <li><a href=\"http://angularjs.org\" target=\"_blank\">Angular-animate</a> (the version should match with your angular's, tested with <%= ngversion %>) if you plan in using animations, you need to load angular-animate as well.</li>\n                        <li><a href=\"http://angularjs.org\" target=\"_blank\">Angular-touch</a> (the version should match with your angular's, tested with <%= ngversion %>) if you plan in using swipe actions, you need to load angular-touch as well.</li>\n                        <li><a href=\"http://getbootstrap.com\" target=\"_blank\">Bootstrap CSS</a> (tested with version <%= bsversion %>).\n                            This version of the library (<%= pkg.version%>) works only with Bootstrap CSS in version 3.x.\n                            0.8.0 is the last version of this library that supports Bootstrap CSS in version 2.3.x.\n                        </li>\n                    </ul>\n                    <h3>Files to download</h3>\n                    <p>\n                        Build files for all directives are distributed in several flavours: minified for production usage, un-minified\n                        for development, with or without templates. All the options are described and can be\n                        <a href=\"https://github.com/angular-ui/bootstrap/tree/gh-pages\">downloaded from here</a>. It should be noted that the <code>-tpls</code> files contain the templates bundled in JavaScript, while the regular version does not contain the bundled templates. For more information, check out the FAQ <a href=\"https://github.com/angular-ui/bootstrap/wiki/FAQ#full-explanation\">here</a> and the README <a href=\"https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files\">here</a>.\n                    </p>\n                    <p>Alternativelly, if you are only interested in a subset of directives, you can\n                        <a ng-click=\"showBuildModal()\">create your own build</a>.\n                    </p>\n                    <p>Whichever method you choose the good news that the overall size of a download is fairly small:\n                       122K minified for all directives with templates and 98K without (~31kB with gzip\n                        compression, with templates, and 28K gzipped without)</p>\n                    <h3>Installation</h3>\n                    <p>As soon as you've got all the files downloaded and included in your page you just need to declare\n                       a dependency on the <code>ui.bootstrap</code> <a href=\"http://docs.angularjs.org/guide/module\">module</a>:<br>\n                       <pre><code>angular.module('myModule', ['ui.bootstrap']);</code></pre>\n                    </p>\n                    <p>If you are using UI Bootstrap in the CSP mode, e.g. in an extension, make sure you link to the <code>ui-bootstrap-csp.css</code> in your HTML manually.</p>\n                    <p>You can fork one of the plunkers from this page to see a working example of what is described here.</p>\n                    <h3>Migration to prefixes</h3>\n                    <p>Since version 0.14.0 we started to prefix all our components. If you are upgrading from ui-bootstrap 0.13.4 or earlier,\n                    check our <a href=\"https://github.com/angular-ui/bootstrap/wiki/Migration-guide-for-prefixes\">migration guide</a>.</p>\n                    <h3>CSS</h3>\n                    <p>Original Bootstrap's CSS depends on empty <code>href</code> attributes to style cursors for several components (pagination, tabs etc.).\n                    But in AngularJS adding empty <code>href</code> attributes to link tags will cause unwanted route changes. This is why we need to remove empty <code>href</code> attributes from directive templates and as a result styling is not applied correctly. The remedy is simple, just add the following styling to your application: <pre><code>.nav, .pagination, .carousel, .panel-title a { cursor: pointer; }</code></pre>\n                    </p>\n                    <h3>FAQ</h3>\n                    <p>Please check <a href=\"https://github.com/angular-ui/bootstrap/wiki/FAQ\" target=\"_blank\">our FAQ section</a> for common problems / solutions.</p>\n                    <h3>Reading the documentation</h3>\n                    <p>\n                        Each of the components provided in <code>ui-bootstrap</code> have documentation and interactive Plunker examples.\n                    </p>\n                    <p>\n                        For the directives, we list the different attributes with their default values. In addition to this, some settings have a badge on it:\n\n                        <ul>\n                            <li><i class=\"glyphicon glyphicon-eye-open\"></i> - This setting has an angular $watch listener applied to it.</li>\n                            <li><small class=\"badge\">B</small> - This setting is a boolean. It doesn't need a parameter.</li>\n                            <li><small class=\"badge\">C</small> - This setting can be configured globally in a constant service*.</li>\n                            <li><small class=\"badge\">$</small> - This setting expects an angular expression instead of a literal string. If the expression support a boolean / integer, you can pass it directly.</li>\n                            <li><small class=\"badge\">readonly</small> - This setting is readonly.</li>\n                        </ul>\n                    </p>\n                    <p>\n                        For the services (you will recognize them with the <code>$</code> prefix), we list all the possible parameters you can pass to them and their default values if any.\n                    </p>\n                    <p>\n                        * Some directives have a config service that follows the next pattern: <code>uibDirectiveConfig</code>. The service's settings use camel case. The services can be configured in a <code>.config</code> function for example.\n                    </p>\n                </section>\n                <% demoModules.forEach(function(module) { %>\n                    <section id=\"<%= module.name %>\">\n                        <div class=\"page-header\">\n                          <h1><%= module.displayName %><small>\n                              (<a target=\"_blank\" href=\"https://github.com/angular-ui/bootstrap/tree/master/src/<%= module.name %>\">ui.bootstrap.<%= module.name %></a>)\n                          </small></h1>\n                          </div>\n                          <div class=\"row\">\n                            <div class=\"col-md-6 show-grid\">\n                                <%= module.docs.html %>\n                            </div>\n                            <div class=\"col-md-6\">\n                                <%= module.docs.md %>\n                            </div>\n                        </div>\n                        <hr>\n                        <div class=\"row code\">\n                            <div class=\"col-md-12\" ng-controller=\"PlunkerCtrl\">\n                                <div class=\"pull-right\">\n                                    <button type=\"button\" class=\"btn btn-info plunk-btn\" ng-click=\"edit('<%= ngversion%>', '<%= bsversion %>', '<%= pkg.version%>', '<%= module.name %>')\"><i class=\"glyphicon glyphicon-edit\"></i> Edit in plunker</button>\n                                </div>\n                                <uib-tabset active=\"activeTab\">\n                                    <uib-tab index=\"0\" heading=\"Markup\">\n                                        <div plunker-content=\"markup\">\n                                            <pre ng-non-bindable><code data-language=\"html\"><%- module.docs.html %></code></pre>\n                                        </div>\n                                    </uib-tab>\n                                    <uib-tab index=\"1\" heading=\"JavaScript\">\n                                        <div plunker-content=\"javascript\">\n                                            <pre ng-non-bindable><code data-language=\"javascript\"><%- module.docs.js %></code></pre>\n                                        </div>\n                                    </uib-tab>\n                                </uib-tabset>\n                            </div>\n                        </div>\n                    </section>\n                    <script><%= module.docs.js %></script>\n                <% }); %>\n            </div>\n        </div>\n    </div><!-- /.container -->\n</div><!-- /.main -->\n<footer class=\"footer\">\n    <div class=\"container\">\n        <p>Designed and built by all ui-bootstrap <a href=\"https://github.com/angular-ui/bootstrap/graphs/contributors\" target=\"_blank\">contributors</a>.</p>\n        <p>Code licensed under <a href=\"https://github.com/angular-ui/bootstrap/blob/master/LICENSE\"><%= pkg.license %> License</a>.</p>\n        <p><a href=\"https://github.com/angular-ui/bootstrap/issues?state=open\">Issues</a></p>\n    </div>\n</footer>\n<script src=\"assets/rainbow.js\"></script>\n<script src=\"assets/rainbow-generic.js\"></script>\n<script src=\"assets/rainbow-javascript.js\"></script>\n<script src=\"assets/rainbow-html.js\"></script>\n<script type=\"text/ng-template\" id=\"downloadModal.html\">\n    <div class=\"modal-header\"><h4 class=\"modal-title\">Download Angular UI Bootstrap</h4></div>\n    <div class=\"modal-body\">\n        <form class=\"form-horizontal\">\n          <div class=\"form-group\">\n            <label class=\"col-sm-3 control-label\"><strong>Build</strong></label>\n            <div class=\"col-sm-9\">\n              <span class=\"btn-group\">\n                <button type=\"button\" class=\"btn btn-default\" ng-model=\"options.minified\" uib-btn-radio=\"true\">Minified</button>\n                <button type=\"button\" class=\"btn btn-default\" ng-model=\"options.minified\" uib-btn-radio=\"false\">Uncompressed</button>\n              </span>\n              <small class=\"help-block\">Use <b>Minified</b> version in your deployed application. <b>Uncompressed</b> source code is useful only for debugging and development purpose.</small>\n            </div>\n          </div>\n          <div class=\"form-group\">\n            <label class=\"col-sm-3 control-label\"><strong>Include Templates</strong></label>\n            <div class=\"col-sm-9\">\n              <span class=\"btn-group\">\n                <button type=\"button\" class=\"btn btn-default\" ng-model=\"options.tpls\" uib-btn-radio=\"true\">Yes</button>\n                <button type=\"button\" class=\"btn btn-default\" ng-model=\"options.tpls\" uib-btn-radio=\"false\">No</button>\n              </span>\n              <small class=\"help-block\">Whether you want to include the <i>default templates</i>, bundled with most of the directives. These templates are based on Bootstrap's markup and CSS.</small>\n            </div>\n          </div>\n          <div class=\"form-group\">\n            <label class=\"col-sm-3 control-label\"><strong>npm</strong></label>\n            <div class=\"col-sm-9\">\n              <pre style=\"margin-bottom:0;\">npm install angular-ui-bootstrap</pre>\n              <small class=\"help-block\"><a href=\"https://www.npmjs.com/\" target=\"_blank\">npm</a> is a package manager for the web.</small>\n            </div>\n          </div>\n        </form>\n    </div>\n    <div class=\"modal-footer\">\n        <a class=\"btn btn-default\" ng-click=\"cancel()\">Close</a>\n        <a class=\"btn btn-primary\" ng-href=\"{{download('<%= pkg.version%>')}}\" download><i class=\"glyphicon glyphicon-download-alt\"></i> Download <%= pkg.version %></a>\n    </div>\n</script>\n<script type=\"text/ng-template\" id=\"buildModal.html\">\n    <div class=\"modal-header\">\n      <h4>\n      Create a Custom Build\n      <br>\n      <small>Select the modules you wish to download</small>\n      </h4>\n    </div>\n    <div class=\"modal-body\">\n        <div ng-show=\"isOldBrowser()\">\n            Your current browser doesn't support creating custom builds.\n            Please take a second to <a href=\"http://browsehappy.com/\">upgrade to a\n            more modern browser</a> (other than Safari).\n        </div>\n        <div ng-show=\"buildErrorText\">\n            <h4 style=\"text-align: center;\">{{buildErrorText}}</h4>\n        </div>\n        <div ng-hide=\"buildErrorText || isOldBrowser()\">\n            <% modules.forEach(function(module,i) { %>\n              <% if (i % 3 === 0) {%>\n                <div class=\"btn-group\" style=\"width: 100%;\">\n              <% } %>\n              <button type=\"button\" class=\"btn btn-default\"\n               style=\"width: 33%; border-radius: 0;\"\n               ng-class=\"{'btn-primary': module.<%= module.name %>}\"\n               ng-model=\"module.<%= module.name %>\"\n               uib-btn-checkbox\n               ng-change=\"selectedChanged('<%= module.name %>', module.<%= module.name %>)\">\n                  <%= module.displayName %>\n              </button>\n              <% if ((i+1) % 3 === 0) { //end button group%>\n                </div>\n              <% } %>\n            <% }); %>\n        </div>\n    </div>\n    <div class=\"modal-footer\">\n        <a class=\"btn btn-default\" ng-click=\"cancel()\">Close</a>\n        <a class=\"btn btn-primary\" ng-hide=\"isOldBrowser()\"\n            ng-disabled=\"isOldBrowser() !== false && !selectedModules.length\"\n            ng-click=\"selectedModules.length && build(selectedModules, '<%= pkg.version %>')\">\n            <i class=\"glyphicon glyphicon-download-alt\"></i> Download {{selectedModules.length}} Modules\n        </a>\n    </div>\n</script>\n\n<script type=\"text/javascript\">\n\n    var _gaq = _gaq || [];\n    _gaq.push(['_setAccount', 'UA-37467169-1']);\n    _gaq.push(['_trackPageview']);\n\n    (function() {\n        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;\n        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';\n        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);\n    })();\n\n</script>\n<script src=\"assets/smoothscroll-angular-custom.js\"></script>\n<script src=\"assets/uglifyjs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "misc/raw-files-generator.js",
    "content": "/*!\n * Forked from:\n * Bootstrap Grunt task for generating raw-files.min.js for the Customizer\n * http://getbootstrap.com\n * Copyright 2014 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n/* jshint node: true */\n\n'use strict';\nvar fs = require('fs');\n\nfunction getFiles(filePaths) {\n  var files = {};\n  filePaths\n    .forEach(function (path) {\n      files[path] = fs.readFileSync(path, 'utf8');\n    });\n  return files;\n}\n\nmodule.exports = function generateRawFilesJs(grunt, jsFilename, files, banner, cssBanner) {\n  if (!banner) {\n    banner = '';\n  }\n\n  if (!cssBanner) {\n    cssBanner = '';\n  }\n\n  var filesJsObject = {\n    banner: banner,\n    cssBanner: cssBanner,\n    files: getFiles(files),\n  };\n\n  var filesJsContent = JSON.stringify(filesJsObject);\n  try {\n    fs.writeFileSync(jsFilename, filesJsContent);\n  }\n  catch (err) {\n    grunt.fail.warn(err);\n  }\n  grunt.log.writeln('File ' + jsFilename.cyan + ' created.');\n};\n"
  },
  {
    "path": "misc/test-lib/helpers.js",
    "content": "// jasmine matcher for expecting an element to have a css class\n// https://github.com/angular/angular.js/blob/master/test/matchers.js\nbeforeEach(function() {\n  jasmine.addMatchers({\n    toHaveClass: function(util, customEqualityTesters) {\n      return {\n        compare: function(actual, expected) {\n          var result = {\n            pass: actual.hasClass(expected)\n          };\n\n          if (result.pass) {\n            result.message = 'Expected \"' + actual + '\" not to have the \"' + expected + '\" class.';\n          } else {\n            result.message = 'Expected \"' + actual + '\" to have the \"' + expected + '\" class.';\n          }\n\n          return result;\n        }\n      }\n    },\n    toBeHidden: function(util, customEqualityTesters) {\n      return {\n        compare: function(actual) {\n          var result = {\n            pass: actual.hasClass('ng-hide') || actual.css('display') === 'none'\n          };\n\n          if (result.pass) {\n            result.message = 'Expected \"' + actual + '\" not to be hidden';\n          } else {\n            result.message = 'Expected \"' + actual + '\" to be hidden';\n          }\n\n          return result;\n        }\n      }\n    },\n    toHaveFocus: function(util, customEqualityTesters) {\n      return {\n        compare: function(actual) {\n          var result = {\n            pass: document.activeElement === actual[0]\n          };\n\n          if (result.pass) {\n            result.message = 'Expected \"' + actual + '\" not to have focus';\n          } else {\n            result.message = 'Expected \"' + actual + '\" to have focus';\n          }\n\n          return result;\n        }\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "misc/validate-commit-msg.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Git COMMIT-MSG hook for validating commit message\n * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit\n *\n * Installation:\n * >> cd <angular-repo>\n * >> ln -s validate-commit-msg.js .git/hooks/commit-msg\n */\nvar fs = require('fs');\nvar util = require('util');\n\n\nvar MAX_LENGTH = 70;\nvar PATTERN = /^(?:fixup!\\s*)?(\\w*)(\\((\\w+)\\))?\\: (.*)$/;\nvar IGNORED = /^WIP\\:/;\nvar TYPES = {\n  chore: true,\n  demo: true,\n  docs: true,\n  feat: true,\n  fix: true,\n  refactor: true,\n  revert: true,\n  style: true,\n  test: true\n};\n\n\nvar error = function() {\n  // gitx does not display it\n  // http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails\n  // https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812\n  console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));\n};\n\n\nvar validateMessage = function(message) {\n  var isValid = true;\n\n  if (IGNORED.test(message)) {\n    console.log('Commit message validation ignored.');\n    return true;\n  }\n\n  if (message.length > MAX_LENGTH) {\n    error('is longer than %d characters !', MAX_LENGTH);\n    isValid = false;\n  }\n\n  var match = PATTERN.exec(message);\n\n  if (!match) {\n    error('does not match \"<type>(<scope>): <subject>\" ! was: \"' + message + '\"\\nNote: <scope> must be only letters.');\n    return false;\n  }\n\n  var type = match[1];\n  var scope = match[3];\n  var subject = match[4];\n\n  if (!TYPES.hasOwnProperty(type)) {\n    error('\"%s\" is not allowed type !', type);\n    return false;\n  }\n\n  // Some more ideas, do want anything like this ?\n  // - allow only specific scopes (eg. fix(docs) should not be allowed ?\n  // - auto correct the type to lower case ?\n  // - auto correct first letter of the subject to lower case ?\n  // - auto add empty line after subject ?\n  // - auto remove empty () ?\n  // - auto correct typos in type ?\n  // - store incorrect messages, so that we can learn\n\n  return isValid;\n};\n\n\nvar firstLineFromBuffer = function(buffer) {\n  return buffer.toString().split('\\n').shift();\n};\n\n\n\n// publish for testing\nexports.validateMessage = validateMessage;\n\n// hacky start if not run by jasmine :-D\nif (process.argv.join('').indexOf('jasmine-node') === -1) {\n  var commitMsgFile = process.argv[2];\n  var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');\n\n  fs.readFile(commitMsgFile, function(err, buffer) {\n    var msg = firstLineFromBuffer(buffer);\n\n    if (!validateMessage(msg)) {\n      fs.appendFile(incorrectLogFile, msg + '\\n', function() {\n        process.exit(1);\n      });\n    } else {\n      process.exit(0);\n    }\n  });\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"author\": \"https://github.com/angular-ui/bootstrap/graphs/contributors\",\n  \"name\": \"angular-ui-bootstrap\",\n  \"version\": \"2.5.4\",\n  \"description\": \"Native AngularJS (Angular) directives for Bootstrap\",\n  \"homepage\": \"http://angular-ui.github.io/bootstrap/\",\n  \"keywords\": [\n    \"angularjs\",\n    \"angular\",\n    \"bootstrap\",\n    \"ui\"\n  ],\n  \"dependencies\": {},\n  \"directories\": {\n    \"lib\": \"src/\"\n  },\n  \"files\": [\n    \"index.js\",\n    \"dist/\",\n    \"src/\",\n    \"template/\"\n  ],\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"demo\": \"grunt after-test && static dist -a 0.0.0.0 -H '{\\\"Cache-Control\\\": \\\"no-cache, must-revalidate\\\"}'\",\n    \"test\": \"grunt\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/angular-ui/bootstrap.git\"\n  },\n  \"devDependencies\": {\n    \"angular\": \"1.6.1\",\n    \"angular-mocks\": \"1.6.1\",\n    \"angular-sanitize\": \"1.6.1\",\n    \"grunt\": \"^0.4.5\",\n    \"grunt-cli\": \"^1.2.0\",\n    \"grunt-contrib-concat\": \"^1.0.0\",\n    \"grunt-contrib-copy\": \"^1.0.0\",\n    \"grunt-contrib-uglify\": \"^1.0.1\",\n    \"grunt-contrib-watch\": \"^1.0.0\",\n    \"grunt-conventional-changelog\": \"^6.1.0\",\n    \"grunt-ddescribe-iit\": \"0.0.6\",\n    \"grunt-eslint\": \"^17.3.1\",\n    \"grunt-html2js\": \"^0.3.0\",\n    \"grunt-karma\": \"^0.12.0\",\n    \"jasmine-core\": \"^2.2.0\",\n    \"karma\": \"0.13.22\",\n    \"karma-chrome-launcher\": \"^0.2.0\",\n    \"karma-coverage\": \"^0.5.0\",\n    \"karma-firefox-launcher\": \"^0.1.4\",\n    \"karma-jasmine\": \"^0.3.5\",\n    \"load-grunt-tasks\": \"^3.3.0\",\n    \"lodash\": \"^4.1.0\",\n    \"marked\": \"^0.3.5\",\n    \"node-static\": \"^0.7.8\",\n    \"semver\": \"^5.0.1\",\n    \"shelljs\": \"^0.6.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/accordion/accordion.js",
    "content": "angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])\n\n.constant('uibAccordionConfig', {\n  closeOthers: true\n})\n\n.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {\n  // This array keeps track of the accordion groups\n  this.groups = [];\n\n  // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to\n  this.closeOthers = function(openGroup) {\n    var closeOthers = angular.isDefined($attrs.closeOthers) ?\n      $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;\n    if (closeOthers) {\n      angular.forEach(this.groups, function(group) {\n        if (group !== openGroup) {\n          group.isOpen = false;\n        }\n      });\n    }\n  };\n\n  // This is called from the accordion-group directive to add itself to the accordion\n  this.addGroup = function(groupScope) {\n    var that = this;\n    this.groups.push(groupScope);\n\n    groupScope.$on('$destroy', function(event) {\n      that.removeGroup(groupScope);\n    });\n  };\n\n  // This is called from the accordion-group directive when to remove itself\n  this.removeGroup = function(group) {\n    var index = this.groups.indexOf(group);\n    if (index !== -1) {\n      this.groups.splice(index, 1);\n    }\n  };\n}])\n\n// The accordion directive simply sets up the directive controller\n// and adds an accordion CSS class to itself element.\n.directive('uibAccordion', function() {\n  return {\n    controller: 'UibAccordionController',\n    controllerAs: 'accordion',\n    transclude: true,\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/accordion/accordion.html';\n    }\n  };\n})\n\n// The accordion-group directive indicates a block of html that will expand and collapse in an accordion\n.directive('uibAccordionGroup', function() {\n  return {\n    require: '^uibAccordion',         // We need this directive to be inside an accordion\n    transclude: true,              // It transcludes the contents of the directive into the template\n    restrict: 'A',\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';\n    },\n    scope: {\n      heading: '@',               // Interpolate the heading attribute onto this scope\n      panelClass: '@?',           // Ditto with panelClass\n      isOpen: '=?',\n      isDisabled: '=?'\n    },\n    controller: function() {\n      this.setHeading = function(element) {\n        this.heading = element;\n      };\n    },\n    link: function(scope, element, attrs, accordionCtrl) {\n      element.addClass('panel');\n      accordionCtrl.addGroup(scope);\n\n      scope.openClass = attrs.openClass || 'panel-open';\n      scope.panelClass = attrs.panelClass || 'panel-default';\n      scope.$watch('isOpen', function(value) {\n        element.toggleClass(scope.openClass, !!value);\n        if (value) {\n          accordionCtrl.closeOthers(scope);\n        }\n      });\n\n      scope.toggleOpen = function($event) {\n        if (!scope.isDisabled) {\n          if (!$event || $event.which === 32) {\n            scope.isOpen = !scope.isOpen;\n          }\n        }\n      };\n\n      var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);\n      scope.headingId = id + '-tab';\n      scope.panelId = id + '-panel';\n    }\n  };\n})\n\n// Use accordion-heading below an accordion-group to provide a heading containing HTML\n.directive('uibAccordionHeading', function() {\n  return {\n    transclude: true,   // Grab the contents to be used as the heading\n    template: '',       // In effect remove this element!\n    replace: true,\n    require: '^uibAccordionGroup',\n    link: function(scope, element, attrs, accordionGroupCtrl, transclude) {\n      // Pass the heading to the accordion-group controller\n      // so that it can be transcluded into the right place in the template\n      // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]\n      accordionGroupCtrl.setHeading(transclude(scope, angular.noop));\n    }\n  };\n})\n\n// Use in the accordion-group template to indicate where you want the heading to be transcluded\n// You must provide the property on the accordion-group controller that will hold the transcluded element\n.directive('uibAccordionTransclude', function() {\n  return {\n    require: '^uibAccordionGroup',\n    link: function(scope, element, attrs, controller) {\n      scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {\n        if (heading) {\n          var elem = angular.element(element[0].querySelector(getHeaderSelectors()));\n          elem.html('');\n          elem.append(heading);\n        }\n      });\n    }\n  };\n\n  function getHeaderSelectors() {\n      return 'uib-accordion-header,' +\n          'data-uib-accordion-header,' +\n          'x-uib-accordion-header,' +\n          'uib\\\\:accordion-header,' +\n          '[uib-accordion-header],' +\n          '[data-uib-accordion-header],' +\n          '[x-uib-accordion-header]';\n  }\n});\n"
  },
  {
    "path": "src/accordion/docs/demo.html",
    "content": "<div ng-controller=\"AccordionDemoCtrl\">\n  <script type=\"text/ng-template\" id=\"group-template.html\">\n    <div class=\"panel-heading\">\n      <h4 class=\"panel-title\" style=\"color:#fa39c3\">\n        <a href tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\">\n          <span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">\n            {{heading}}\n          </span>\n        </a>\n      </h4>\n    </div>\n    <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n      <div class=\"panel-body\" style=\"text-align: right\" ng-transclude></div>\n    </div>\n  </script>\n\n  <p>\n    <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"status.open = !status.open\">Toggle last panel</button>\n    <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"status.isFirstDisabled = ! status.isFirstDisabled\">Enable / Disable first panel</button>\n  </p>\n\n  <div class=\"checkbox\">\n    <label>\n      <input type=\"checkbox\" ng-model=\"oneAtATime\">\n      Open only one at a time\n    </label>\n  </div>\n  <uib-accordion close-others=\"oneAtATime\">\n    <div uib-accordion-group class=\"panel-default\" heading=\"Static Header, initially expanded\" is-open=\"status.isFirstOpen\" is-disabled=\"status.isFirstDisabled\">\n      This content is straight in the template.\n    </div>\n    <div uib-accordion-group class=\"panel-default\" heading=\"{{group.title}}\" ng-repeat=\"group in groups\">\n      {{group.content}}\n    </div>\n    <div uib-accordion-group class=\"panel-default\" heading=\"Dynamic Body Content\">\n      <p>The body of the uib-accordion group grows to fit the contents</p>\n      <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"addItem()\">Add Item</button>\n      <div ng-repeat=\"item in items\">{{item}}</div>\n    </div>\n    <div uib-accordion-group class=\"panel-default\" heading=\"Custom template\" template-url=\"group-template.html\">\n      Hello\n    </div>\n    <div uib-accordion-group class=\"panel-default\" is-open=\"status.isCustomHeaderOpen\" template-url=\"group-template.html\">\n      <uib-accordion-heading>\n        Custom template with custom header template <i class=\"pull-right glyphicon\" ng-class=\"{'glyphicon-chevron-down': status.isCustomHeaderOpen, 'glyphicon-chevron-right': !status.isCustomHeaderOpen}\"></i>\n      </uib-accordion-heading>\n      World\n    </div>\n    <div uib-accordion-group class=\"panel-danger\" heading=\"Delete account\">\n      <p>Please, to delete your account, click the button below</p>\n      <button class=\"btn btn-danger\">Delete</button>\n    </div>\n    <div uib-accordion-group class=\"panel-default\" is-open=\"status.open\">\n      <uib-accordion-heading>\n        I can have markup, too! <i class=\"pull-right glyphicon\" ng-class=\"{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}\"></i>\n      </uib-accordion-heading>\n      This is just some content to illustrate fancy headings.\n    </div>\n  </uib-accordion>\n</div>\n"
  },
  {
    "path": "src/accordion/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('AccordionDemoCtrl', function ($scope) {\n  $scope.oneAtATime = true;\n\n  $scope.groups = [\n    {\n      title: 'Dynamic Group Header - 1',\n      content: 'Dynamic Group Body - 1'\n    },\n    {\n      title: 'Dynamic Group Header - 2',\n      content: 'Dynamic Group Body - 2'\n    }\n  ];\n\n  $scope.items = ['Item 1', 'Item 2', 'Item 3'];\n\n  $scope.addItem = function() {\n    var newItemNo = $scope.items.length + 1;\n    $scope.items.push('Item ' + newItemNo);\n  };\n\n  $scope.status = {\n    isCustomHeaderOpen: false,\n    isFirstOpen: true,\n    isFirstDisabled: false\n  };\n});"
  },
  {
    "path": "src/accordion/docs/readme.md",
    "content": "The **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header.\n\nThe body of each accordion group is transcluded into the body of the collapsible element.\n\n### uib-accordion settings\n\n* `close-others`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Control whether expanding an item will cause the other items to close.\n\n* `template-url`\n  _(Default: `template/accordion/accordion.html`)_ -\n  Add the ability to override the template used on the component.\n\n### uib-accordion-group settings\n\n* `heading`\n  _(Default: `none`)_ -\n  The clickable text on the group's header. You need one to be able to click on the header for toggling.\n\n* `is-disabled`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n   Whether the accordion group is disabled or not.\n\n* `is-open`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether accordion group is open or closed.\n\n* `template-url`\n  _(Default: `uib/template/accordion/accordion-group.html`)_ -\n  Add the ability to override the template used on the component.\n\n### Accordion heading\n\nInstead of the `heading` attribute on the `uib-accordion-group`, you can use an `uib-accordion-heading` element inside a group that will be used as the group's header.\n\nIf you're using a custom template for the `uib-accordion-group`, you'll need to have an element for the heading to be transcluded into using `uib-accordion-header` (e.g. `<div uib-accordion-header></div>`).\n\n### Known issues\n\nTo use clickable elements within the accordion, you have to override the accordion-group template to use div elements instead of anchor elements, and add `cursor: pointer` in your CSS. This is due to browsers interpreting anchor elements as the target of any click event, which triggers routing when certain elements such as buttons are nested inside the anchor element.\n\nIf custom classes on the accordion-group element are desired, one needs to either modify the template to remove the `ng-class` usage in the accordion-group template and use ng-class on the accordion-group element (not recommended), or use an interpolated expression in the class attribute, i.e. `<uib-accordion-group class=\"{{customClass()}}\"></uib-accordion-group>`.\n"
  },
  {
    "path": "src/accordion/index.js",
    "content": "require('../collapse');\nrequire('../tabindex');\nrequire('../../template/accordion/accordion-group.html.js');\nrequire('../../template/accordion/accordion.html.js');\nrequire('./accordion');\n\nvar MODULE_NAME = 'ui.bootstrap.module.accordion';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.accordion', 'uib/template/accordion/accordion.html', 'uib/template/accordion/accordion-group.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/accordion/test/accordion.spec.js",
    "content": "describe('uib-accordion', function() {\n  var $animate, $scope;\n\n  beforeEach(module('ui.bootstrap.accordion'));\n  beforeEach(module('ngAnimateMock'));\n  beforeEach(module('uib/template/accordion/accordion.html'));\n  beforeEach(module('uib/template/accordion/accordion-group.html'));\n\n  beforeEach(inject(function(_$animate_, $rootScope) {\n    $animate = _$animate_;\n    $scope = $rootScope;\n  }));\n\n  describe('controller', function () {\n    var ctrl, $element, $attrs;\n    beforeEach(inject(function($controller) {\n      $attrs = {};\n      ctrl = $controller('UibAccordionController', { $scope: $scope, $attrs: $attrs });\n    }));\n\n    describe('addGroup', function() {\n      it('adds a the specified panel to the collection', function() {\n        var group1, group2;\n        ctrl.addGroup(group1 = $scope.$new());\n        ctrl.addGroup(group2 = $scope.$new());\n        expect(ctrl.groups.length).toBe(2);\n        expect(ctrl.groups[0]).toBe(group1);\n        expect(ctrl.groups[1]).toBe(group2);\n      });\n    });\n\n    describe('closeOthers', function() {\n      var group1, group2, group3;\n      beforeEach(function() {\n        ctrl.addGroup(group1 = { isOpen: true, $on : angular.noop });\n        ctrl.addGroup(group2 = { isOpen: true, $on : angular.noop });\n        ctrl.addGroup(group3 = { isOpen: true, $on : angular.noop });\n      });\n\n      it('should close other panels if close-others attribute is not defined', function() {\n        delete $attrs.closeOthers;\n        ctrl.closeOthers(group2);\n        expect(group1.isOpen).toBe(false);\n        expect(group2.isOpen).toBe(true);\n        expect(group3.isOpen).toBe(false);\n      });\n\n      it('should close other panels if close-others attribute is true', function() {\n        $attrs.closeOthers = 'true';\n        ctrl.closeOthers(group3);\n        expect(group1.isOpen).toBe(false);\n        expect(group2.isOpen).toBe(false);\n        expect(group3.isOpen).toBe(true);\n      });\n\n      it('should not close other panels if close-others attribute is false', function() {\n        $attrs.closeOthers = 'false';\n        ctrl.closeOthers(group2);\n        expect(group1.isOpen).toBe(true);\n        expect(group2.isOpen).toBe(true);\n        expect(group3.isOpen).toBe(true);\n      });\n\n      describe('setting accordionConfig', function() {\n        var originalCloseOthers;\n        beforeEach(inject(function(uibAccordionConfig) {\n          originalCloseOthers = uibAccordionConfig.closeOthers;\n          uibAccordionConfig.closeOthers = false;\n        }));\n\n        afterEach(inject(function(uibAccordionConfig) {\n          // return it to the original value\n          uibAccordionConfig.closeOthers = originalCloseOthers;\n        }));\n\n        it('should not close other panels if accordionConfig.closeOthers is false', function() {\n          ctrl.closeOthers(group2);\n          expect(group1.isOpen).toBe(true);\n          expect(group2.isOpen).toBe(true);\n          expect(group3.isOpen).toBe(true);\n        });\n      });\n    });\n\n    describe('removeGroup', function() {\n      it('should remove the specified panel', function() {\n        var group1, group2, group3;\n        ctrl.addGroup(group1 = $scope.$new());\n        ctrl.addGroup(group2 = $scope.$new());\n        ctrl.addGroup(group3 = $scope.$new());\n        ctrl.removeGroup(group2);\n        expect(ctrl.groups.length).toBe(2);\n        expect(ctrl.groups[0]).toBe(group1);\n        expect(ctrl.groups[1]).toBe(group3);\n      });\n      it('should ignore remove of non-existing panel', function() {\n        var group1, group2;\n        ctrl.addGroup(group1 = $scope.$new());\n        ctrl.addGroup(group2 = $scope.$new());\n        expect(ctrl.groups.length).toBe(2);\n        ctrl.removeGroup({});\n        expect(ctrl.groups.length).toBe(2);\n      });\n      it('should remove a panel when the scope is destroyed', function() {\n        var group1, group2, group3;\n        ctrl.addGroup(group1 = $scope.$new());\n        ctrl.addGroup(group2 = $scope.$new());\n        ctrl.addGroup(group3 = $scope.$new());\n        group2.$destroy();\n        expect(ctrl.groups.length).toBe(2);\n        expect(ctrl.groups[0]).toBe(group1);\n        expect(ctrl.groups[1]).toBe(group3);\n      });\n    });\n  });\n\n  describe('uib-accordion', function() {\n    var scope, $compile, $templateCache, element;\n\n    beforeEach(inject(function($rootScope, _$compile_, _$templateCache_) {\n      scope = $rootScope;\n      $compile = _$compile_;\n      $templateCache = _$templateCache_;\n    }));\n\n    it('should be a tablist', function() {\n      element = $compile('<uib-accordion></uib-accordion>')(scope);\n      scope.$digest();\n      expect(element.html()).toContain('role=\"tablist\"');\n    });\n\n    it('should expose the controller on the view', function() {\n      $templateCache.put('uib/template/accordion/accordion.html', '<div>{{accordion.text}}</div>');\n\n      element = $compile('<uib-accordion></uib-accordion>')(scope);\n      scope.$digest();\n\n      var ctrl = element.controller('uibAccordion');\n      expect(ctrl).toBeDefined();\n\n      ctrl.text = 'foo';\n      scope.$digest();\n\n      expect(element.html()).toBe('<div class=\"ng-binding\">foo</div>');\n    });\n\n    it('should allow custom templates', function() {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      element = $compile('<uib-accordion template-url=\"foo/bar.html\"></uib-accordion>')(scope);\n      scope.$digest();\n      expect(element.html()).toBe('<div>baz</div>');\n    });\n  });\n\n  describe('uib-accordion-group', function() {\n    var scope, $compile;\n    var element, groups;\n    var findGroupHeading = function(index) {\n      return groups.eq(index).find('.panel-heading').eq(0);\n    };\n    var findGroupLink = function(index) {\n      return groups.eq(index).find('.accordion-toggle').eq(0);\n    };\n    var findGroupBody = function(index) {\n      return groups.eq(index).find('.panel-collapse').eq(0);\n    };\n\n    beforeEach(inject(function(_$rootScope_, _$compile_) {\n      scope = _$rootScope_;\n      $compile = _$compile_;\n    }));\n\n    it('should allow custom templates', inject(function($templateCache) {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      var tpl =\n        '<uib-accordion>' +\n          '<div uib-accordion-group heading=\"title 1\" template-url=\"foo/bar.html\"></div>' +\n        '</uib-accordion>';\n\n      element = $compile(tpl)(scope);\n      scope.$digest();\n      expect(element.find('[template-url]').html()).toBe('<div>baz</div>');\n    }));\n\n    describe('with static panels', function() {\n      beforeEach(function() {\n        spyOn(Math, 'random').and.returnValue(0.1);\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group heading=\"title 1\">Content 1</div>' +\n            '<div uib-accordion-group heading=\"title 2\">Content 2</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        $compile(element)(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n      });\n\n      afterEach(function() {\n        element.remove();\n      });\n\n      it('should create accordion panels with content', function() {\n        expect(groups.length).toEqual(2);\n        expect(findGroupLink(0).text()).toEqual('title 1');\n        expect(findGroupBody(0).text().trim()).toEqual('Content 1');\n        expect(findGroupLink(1).text()).toEqual('title 2');\n        expect(findGroupBody(1).text().trim()).toEqual('Content 2');\n      });\n\n      it('should change selected element on click', function() {\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(findGroupBody(0).scope().isOpen).toBe(true);\n        expect(findGroupHeading(0).html()).toContain('aria-expanded=\"true\"');\n\n        findGroupLink(1).click();\n        scope.$digest();\n        expect(findGroupBody(0).scope().isOpen).toBe(false);\n        expect(findGroupHeading(0).html()).toContain('aria-expanded=\"false\"');\n        expect(findGroupBody(1).scope().isOpen).toBe(true);\n        expect(findGroupHeading(1).html()).toContain('aria-expanded=\"true\"');\n      });\n\n      it('should toggle element on click', function() {\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(findGroupBody(0).scope().isOpen).toBe(true);\n        expect(groups.eq(0).html()).toContain('aria-hidden=\"false\"');\n\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(findGroupBody(0).scope().isOpen).toBe(false);\n        expect(groups.eq(0).html()).toContain('aria-hidden=\"true\"');\n      });\n\n      it('should add, by default, \"panel-open\" when opened', function() {\n        var group = groups.eq(0);\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(group).toHaveClass('panel-open');\n\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(group).not.toHaveClass('panel-open');\n      });\n\n      it('should toggle element on spacebar when focused', function() {\n        var group = groups.eq(0);\n        findGroupLink(0)[0].focus();\n        var e = $.Event('keypress');\n        e.which = 32;\n        findGroupLink(0).trigger(e);\n\n        expect(group).toHaveClass('panel-open');\n\n        e = $.Event('keypress');\n        e.which = 32;\n        findGroupLink(0).trigger(e);\n\n        expect(group).not.toHaveClass('panel-open');\n      });\n\n      it('should not toggle with any other keyCode', function() {\n        var group = groups.eq(0);\n        findGroupLink(0)[0].focus();\n        var e = $.Event('keypress');\n        e.which = 65;\n        findGroupLink(0).trigger(e);\n\n        expect(group).not.toHaveClass('panel-open');\n      });\n\n      it('should generate an Id for the heading', function() {\n        var groupScope = findGroupBody(0).scope();\n        expect(groupScope.headingId).toEqual('accordiongroup-' + groupScope.$id + '-1000-tab');\n      });\n\n      it('should generate an Id for the panel', function() {\n        var groupScope = findGroupBody(0).scope();\n        expect(groupScope.panelId).toEqual('accordiongroup-' + groupScope.$id + '-1000-panel');\n      });\n    });\n\n    describe('with open-class attribute', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group heading=\"title 1\" open-class=\"custom-open-class\">Content 1</div>' +\n            '<div uib-accordion-group heading=\"title 2\" open-class=\"custom-open-class\">Content 2</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        $compile(element)(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n      });\n\n      afterEach(function() {\n        element.remove();\n      });\n\n      it('should add custom-open-class when opened', function() {\n        var group = groups.eq(0);\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(group).toHaveClass('custom-open-class');\n\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(group).not.toHaveClass('custom-open-class');\n      });\n    });\n\n    describe('with dynamic panels', function() {\n      var model;\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group ng-repeat=\"group in groups\" heading=\"{{group.name}}\">{{group.content}}</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        model = [\n          {name: 'title 1', content: 'Content 1'},\n          {name: 'title 2', content: 'Content 2'}\n        ];\n\n        $compile(element)(scope);\n        scope.$digest();\n      });\n\n      it('should have no panels initially', function() {\n        groups = element.find('.panel');\n        expect(groups.length).toEqual(0);\n      });\n\n      it('should have a panel for each model item', function() {\n        scope.groups = model;\n        scope.$digest();\n        groups = element.find('.panel');\n        expect(groups.length).toEqual(2);\n        expect(findGroupLink(0).text()).toEqual('title 1');\n        expect(findGroupBody(0).text().trim()).toEqual('Content 1');\n        expect(findGroupLink(1).text()).toEqual('title 2');\n        expect(findGroupBody(1).text().trim()).toEqual('Content 2');\n      });\n\n      it('should react properly on removing items from the model', function() {\n        scope.groups = model;\n        scope.$digest();\n        groups = element.find('.panel');\n        expect(groups.length).toEqual(2);\n\n        scope.groups.splice(0,1);\n        scope.$digest();\n        groups = element.find('.panel');\n        expect(groups.length).toEqual(1);\n      });\n    });\n\n    describe('is-open attribute', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group heading=\"title 1\" is-open=\"open.first\">Content 1</div>' +\n            '<div uib-accordion-group heading=\"title 2\" is-open=\"open.second\">Content 2</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        scope.open = { first: false, second: true };\n        $compile(element)(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n      });\n\n      it('should open the panel with isOpen set to true', function() {\n        expect(findGroupBody(0).scope().isOpen).toBe(false);\n        expect(findGroupBody(1).scope().isOpen).toBe(true);\n      });\n\n      it('should toggle variable on element click', function() {\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(scope.open.first).toBe(true);\n\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(scope.open.second).toBe(false);\n      });\n    });\n\n    describe('is-open attribute with dynamic content', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group heading=\"title 1\" is-open=\"open1\"><div ng-repeat=\"item in items\">{{item}}</div></div>' +\n            '<div uib-accordion-group heading=\"title 2\" is-open=\"open2\">Static content</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        scope.items = ['Item 1', 'Item 2', 'Item 3'];\n        scope.open1 = true;\n        scope.open2 = false;\n        angular.element(document.body).append(element);\n        $compile(element)(scope);\n        scope.$digest();\n        $animate.flush();\n        groups = element.find('.panel');\n      });\n\n      afterEach(function() {\n        element.remove();\n      });\n\n      it('should have visible panel body when the group with isOpen set to true', function() {\n        expect(findGroupBody(0)).toHaveClass('in');\n        expect(findGroupBody(1)).not.toHaveClass('in');\n      });\n    });\n\n    describe('is-open attribute with dynamic groups', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group ng-repeat=\"group in groups\" heading=\"{{group.name}}\" is-open=\"group.open\">{{group.content}}</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        scope.groups = [\n          {name: 'title 1', content: 'Content 1', open: false},\n          {name: 'title 2', content: 'Content 2', open: true}\n        ];\n        $compile(element)(scope);\n        scope.$digest();\n\n        groups = element.find('.panel');\n      });\n\n      it('should have visible group body when the group with isOpen set to true', function() {\n        expect(findGroupBody(0).scope().isOpen).toBe(false);\n        expect(findGroupBody(1).scope().isOpen).toBe(true);\n      });\n\n      it('should toggle element on click', function() {\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(findGroupBody(0).scope().isOpen).toBe(true);\n        expect(scope.groups[0].open).toBe(true);\n\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(findGroupBody(0).scope().isOpen).toBe(false);\n        expect(scope.groups[0].open).toBe(false);\n      });\n    });\n\n    describe('is-open attribute with custom class', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group ng-repeat=\"group in groups\" heading=\"{{group.name}}\" is-open=\"group.open\" class=\"testClass\">{{group.content}}</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        scope.groups = [\n          {name: 'title 1', content: 'Content 1', open: false},\n          {name: 'title 2', content: 'Content 2', open: true}\n        ];\n        $compile(element)(scope);\n        scope.$digest();\n\n        groups = element.find('.panel');\n      });\n\n      it('should add \"panel-open\" class', function(){\n        expect(groups.eq(0)).not.toHaveClass('panel-open');\n        expect(groups.eq(1)).toHaveClass('panel-open');\n      });\n    });\n\n    describe('`is-disabled` attribute', function() {\n      var groupBody;\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion>' +\n            '<div uib-accordion-group heading=\"title 1\" is-disabled=\"disabled\">Content 1</div>' +\n          '</uib-accordion>';\n        element = angular.element(tpl);\n        scope.disabled = true;\n        $compile(element)(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n        groupBody = findGroupBody(0);\n      });\n\n      it('should open the panel with isOpen set to true', function() {\n        expect(groupBody.scope().isOpen).toBeFalsy();\n      });\n\n      it('should not toggle if disabled', function() {\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(groupBody.scope().isOpen).toBeFalsy();\n      });\n\n      it('should toggle after enabling', function() {\n        scope.disabled = false;\n        scope.$digest();\n        expect(groupBody.scope().isOpen).toBeFalsy();\n\n        findGroupLink(0).click();\n        scope.$digest();\n        expect(groupBody.scope().isOpen).toBeTruthy();\n      });\n\n      it('should have text-muted styling', function() {\n        expect(findGroupLink(0).find('span:first')).toHaveClass('text-muted');\n      });\n    });\n\n    // This is re-used in both the uib-accordion-heading element and the uib-accordion-heading attribute tests\n    function isDisabledStyleCheck() {\n      var tpl =\n        '<uib-accordion ng-init=\"a = [1,2,3]\">' +\n          '<div uib-accordion-group heading=\"I get overridden\" is-disabled=\"true\">' +\n            '<uib-accordion-heading>Heading Element <span ng-repeat=\"x in a\">{{x}}</span> </uib-accordion-heading>' +\n            'Body' +\n          '</div>' +\n        '</uib-accordion>';\n      scope.disabled = true;\n      element = $compile(tpl)(scope);\n      scope.$digest();\n      groups = element.find('.panel');\n\n      expect(findGroupLink(0).find('span').hasClass('text-muted')).toBe(true);\n    }\n\n    describe('uib-accordion-heading element', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion ng-init=\"a = [1,2,3]\">' +\n            '<div uib-accordion-group heading=\"I get overridden\">' +\n              '<uib-accordion-heading>Heading Element <span ng-repeat=\"x in a\">{{x}}</span> </uib-accordion-heading>' +\n              'Body' +\n            '</div>' +\n          '</uib-accordion>';\n        element = $compile(tpl)(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n      });\n\n      it('transcludes the <uib-accordion-heading> content into the heading link', function() {\n        expect(findGroupLink(0).text()).toBe('Heading Element 123 ');\n      });\n\n      it('attaches the same scope to the transcluded heading and body', function() {\n        expect(findGroupLink(0).scope().$id).toBe(findGroupBody(0).scope().$id);\n      });\n\n      it('should wrap the transcluded content in a span', function() {\n        expect(findGroupLink(0).find('span:first').length).toEqual(1);\n      });\n\n      it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);\n    });\n\n    describe('uib-accordion-heading attribute', function() {\n      beforeEach(function() {\n        var tpl =\n          '<uib-accordion ng-init=\"a = [1,2,3]\">' +\n            '<div uib-accordion-group heading=\"I get overridden\">' +\n              '<div uib-accordion-heading>Heading Element <span ng-repeat=\"x in a\">{{x}}</span> </div>' +\n              'Body' +\n            '</div>' +\n          '</uib-accordion>';\n        element = $compile(tpl)(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n      });\n\n      it('transcludes the <uib-accordion-heading> content into the heading link', function() {\n        expect(findGroupLink(0).text()).toBe('Heading Element 123 ');\n      });\n\n      it('attaches the same scope to the transcluded heading and body', function() {\n        expect(findGroupLink(0).scope().$id).toBe(findGroupBody(0).scope().$id);\n      });\n\n      it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);\n    });\n\n    describe('uib-accordion-heading, with repeating uib-accordion-groups', function() {\n      it('should clone the uib-accordion-heading for each group', function() {\n        element = $compile('<uib-accordion><div uib-accordion-group ng-repeat=\"x in [1,2,3]\"><uib-accordion-heading>{{x}}</uib-accordion-heading></div></uib-accordion>')(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n        expect(groups.length).toBe(3);\n        expect(findGroupLink(0).text()).toBe('1');\n        expect(findGroupLink(1).text()).toBe('2');\n        expect(findGroupLink(2).text()).toBe('3');\n      });\n    });\n\n    describe('uib-accordion-heading attribute, with repeating uib-accordion-groups', function() {\n      it('should clone the uib-accordion-heading for each group', function() {\n        element = $compile('<uib-accordion><div uib-accordion-group ng-repeat=\"x in [1,2,3]\"><div uib-accordion-heading>{{x}}</div></div></uib-accordion>')(scope);\n        scope.$digest();\n        groups = element.find('.panel');\n        expect(groups.length).toBe(3);\n        expect(findGroupLink(0).text()).toBe('1');\n        expect(findGroupLink(1).text()).toBe('2');\n        expect(findGroupLink(2).text()).toBe('3');\n      });\n    });\n\n    describe('uib-accordion-heading attribute, with custom template', function() {\n        it('should transclude heading to a template using data-uib-accordion-header', inject(function($templateCache) {\n          $templateCache.put('foo/bar.html', '<div class=\"panel\"><a uib-accordion-transclude=\"heading\" class=\"accordion-toggle\"><span data-uib-accordion-header></span></a><div ng-transclude></div></div>');\n\n          element = $compile('<uib-accordion><div uib-accordion-group  template-url=\"foo/bar.html\"><uib-accordion-heading>baz</uib-accordion-heading></div></uib-accordion>')(scope);\n          scope.$digest();\n          groups = element.find('.panel');\n          expect(findGroupLink(0).text()).toBe('baz');\n      }));\n    });\n  });\n});\n"
  },
  {
    "path": "src/alert/alert.js",
    "content": "angular.module('ui.bootstrap.alert', [])\n\n.controller('UibAlertController', ['$scope', '$element', '$attrs', '$interpolate', '$timeout', function($scope, $element, $attrs, $interpolate, $timeout) {\n  $scope.closeable = !!$attrs.close;\n  $element.addClass('alert');\n  $attrs.$set('role', 'alert');\n  if ($scope.closeable) {\n    $element.addClass('alert-dismissible');\n  }\n\n  var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?\n    $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;\n\n  if (dismissOnTimeout) {\n    $timeout(function() {\n      $scope.close();\n    }, parseInt(dismissOnTimeout, 10));\n  }\n}])\n\n.directive('uibAlert', function() {\n  return {\n    controller: 'UibAlertController',\n    controllerAs: 'alert',\n    restrict: 'A',\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/alert/alert.html';\n    },\n    transclude: true,\n    scope: {\n      close: '&'\n    }\n  };\n});\n"
  },
  {
    "path": "src/alert/docs/demo.html",
    "content": "<div ng-controller=\"AlertDemoCtrl\">\n  <script type=\"text/ng-template\" id=\"alert.html\">\n    <div ng-transclude></div>\n  </script>\n\n  <div uib-alert ng-repeat=\"alert in alerts\" ng-class=\"'alert-' + (alert.type || 'warning')\" close=\"closeAlert($index)\">{{alert.msg}}</div>\n  <div uib-alert template-url=\"alert.html\" style=\"background-color:#fa39c3;color:white\">A happy alert!</div>\n  <button type=\"button\" class='btn btn-default' ng-click=\"addAlert()\">Add Alert</button>\n</div>\n"
  },
  {
    "path": "src/alert/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('AlertDemoCtrl', function ($scope) {\n  $scope.alerts = [\n    { type: 'danger', msg: 'Oh snap! Change a few things up and try submitting again.' },\n    { type: 'success', msg: 'Well done! You successfully read this important alert message.' }\n  ];\n\n  $scope.addAlert = function() {\n    $scope.alerts.push({msg: 'Another alert!'});\n  };\n\n  $scope.closeAlert = function(index) {\n    $scope.alerts.splice(index, 1);\n  };\n});"
  },
  {
    "path": "src/alert/docs/readme.md",
    "content": "This directive can be used both to generate alerts from static and dynamic model data (using the `ng-repeat` directive).\n\n### uib-alert settings\n\n* `close()`\n  <small class=\"badge\">$</small> -\n  A callback function that gets fired when an `alert` is closed. If the attribute exists, a close button is displayed as well.\n\n* `dismiss-on-timeout`\n  _(Default: `none`)_ -\n  Takes the number of milliseconds that specify the timeout duration, after which the alert will be closed. This attribute requires the presence of the `close` attribute.\n\n* `template-url`\n  _(Default: `uib/template/alert/alert.html`)_ -\n  Add the ability to override the template used in the component.\n"
  },
  {
    "path": "src/alert/index.js",
    "content": "require('../../template/alert/alert.html.js');\nrequire('./alert');\n\nvar MODULE_NAME = 'ui.bootstrap.module.alert';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.alert', 'uib/template/alert/alert.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/alert/test/alert.spec.js",
    "content": "describe('uib-alert', function() {\n  var element, scope, $compile, $templateCache, $timeout;\n\n  beforeEach(module('ui.bootstrap.alert'));\n  beforeEach(module('uib/template/alert/alert.html'));\n\n  beforeEach(inject(function($rootScope, _$compile_, _$templateCache_, _$timeout_) {\n    scope = $rootScope;\n    $compile = _$compile_;\n    $templateCache = _$templateCache_;\n    $timeout = _$timeout_;\n\n    element = angular.element(\n      '<div>' +\n        '<div uib-alert ng-repeat=\"alert in alerts\" ' +\n          'ng-class=\"\\'alert-\\' + (alert.type || \\'warning\\')\" ' +\n          'close=\"removeAlert($index)\">{{alert.msg}}' +\n        '</div>' +\n      '</div>');\n\n    scope.alerts = [\n      { msg:'foo', type:'success'},\n      { msg:'bar', type:'error'},\n      { msg:'baz'}\n    ];\n  }));\n\n  function createAlerts() {\n    $compile(element)(scope);\n    scope.$digest();\n    return element.find('.alert');\n  }\n\n  function findCloseButton(index) {\n    return element.find('.close').eq(index);\n  }\n\n  function findContent(index) {\n    return element.find('div[ng-transclude]').eq(index);\n  }\n\n  it('should expose the controller to the view', function() {\n    $templateCache.put('uib/template/alert/alert.html', '<div>{{alert.text}}</div>');\n\n    element = $compile('<div uib-alert></div>')(scope);\n    scope.$digest();\n\n    var ctrl = element.controller('uib-alert');\n    expect(ctrl).toBeDefined();\n\n    ctrl.text = 'foo';\n    scope.$digest();\n\n    expect(element.html()).toBe('<div class=\"ng-binding\">foo</div>');\n  });\n\n  it('should support custom templates', function() {\n    $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n    element = $compile('<div uib-alert template-url=\"foo/bar.html\"></div>')(scope);\n    scope.$digest();\n\n    expect(element.html()).toBe('<div>baz</div>');\n  });\n\n  it('should generate alerts using ng-repeat', function() {\n    var alerts = createAlerts();\n    expect(alerts.length).toEqual(3);\n  });\n\n  it('should show the alert content', function() {\n    var alerts = createAlerts();\n\n    for (var i = 0, n = alerts.length; i < n; i++) {\n      expect(findContent(i).text()).toBe(scope.alerts[i].msg);\n    }\n  });\n\n  it('should show close buttons and have the dismissible class', function() {\n    var alerts = createAlerts();\n\n    for (var i = 0, n = alerts.length; i < n; i++) {\n      expect(findCloseButton(i).css('display')).not.toBe('none');\n      expect(alerts.eq(i)).toHaveClass('alert-dismissible');\n    }\n  });\n\n  it('should fire callback when closed', function() {\n    var alerts = createAlerts();\n\n    scope.$apply(function() {\n      scope.removeAlert = jasmine.createSpy();\n    });\n\n    expect(findCloseButton(0).css('display')).not.toBe('none');\n    findCloseButton(1).click();\n\n    expect(scope.removeAlert).toHaveBeenCalledWith(1);\n  });\n\n  it('should not show close button and have the dismissible class if no close callback specified', function() {\n    element = $compile('<div uib-alert>No close</div>')(scope);\n    scope.$digest();\n    expect(findCloseButton(0)).toBeHidden();\n    expect(element).not.toHaveClass('alert-dismissible');\n  });\n\n  it('should close automatically if dismiss-on-timeout is defined on the element', function() {\n    scope.removeAlert = jasmine.createSpy();\n    $compile('<div uib-alert close=\"removeAlert()\" dismiss-on-timeout=\"500\">Default alert!</div>')(scope);\n    scope.$digest();\n\n    $timeout.flush();\n    expect(scope.removeAlert).toHaveBeenCalled();\n  });\n\n  it('should not close immediately with a dynamic dismiss-on-timeout', function() {\n    scope.removeAlert = jasmine.createSpy();\n    scope.dismissTime = 500;\n    $compile('<div uib-alert close=\"removeAlert()\" dismiss-on-timeout=\"{{dismissTime}}\">Default alert!</div>')(scope);\n    scope.$digest();\n\n    $timeout.flush(100);\n    expect(scope.removeAlert).not.toHaveBeenCalled();\n\n    $timeout.flush(500);\n    expect(scope.removeAlert).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/buttons/buttons.js",
    "content": "angular.module('ui.bootstrap.buttons', [])\n\n.constant('uibButtonConfig', {\n  activeClass: 'active',\n  toggleEvent: 'click'\n})\n\n.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {\n  this.activeClass = buttonConfig.activeClass || 'active';\n  this.toggleEvent = buttonConfig.toggleEvent || 'click';\n}])\n\n.directive('uibBtnRadio', ['$parse', function($parse) {\n  return {\n    require: ['uibBtnRadio', 'ngModel'],\n    controller: 'UibButtonsController',\n    controllerAs: 'buttons',\n    link: function(scope, element, attrs, ctrls) {\n      var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n      var uncheckableExpr = $parse(attrs.uibUncheckable);\n\n      element.find('input').css({display: 'none'});\n\n      //model -> UI\n      ngModelCtrl.$render = function() {\n        element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));\n      };\n\n      //ui->model\n      element.on(buttonsCtrl.toggleEvent, function() {\n        if (attrs.disabled) {\n          return;\n        }\n\n        var isActive = element.hasClass(buttonsCtrl.activeClass);\n\n        if (!isActive || angular.isDefined(attrs.uncheckable)) {\n          scope.$apply(function() {\n            ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));\n            ngModelCtrl.$render();\n          });\n        }\n      });\n\n      if (attrs.uibUncheckable) {\n        scope.$watch(uncheckableExpr, function(uncheckable) {\n          attrs.$set('uncheckable', uncheckable ? '' : undefined);\n        });\n      }\n    }\n  };\n}])\n\n.directive('uibBtnCheckbox', function() {\n  return {\n    require: ['uibBtnCheckbox', 'ngModel'],\n    controller: 'UibButtonsController',\n    controllerAs: 'button',\n    link: function(scope, element, attrs, ctrls) {\n      var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n\n      element.find('input').css({display: 'none'});\n\n      function getTrueValue() {\n        return getCheckboxValue(attrs.btnCheckboxTrue, true);\n      }\n\n      function getFalseValue() {\n        return getCheckboxValue(attrs.btnCheckboxFalse, false);\n      }\n\n      function getCheckboxValue(attribute, defaultValue) {\n        return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;\n      }\n\n      //model -> UI\n      ngModelCtrl.$render = function() {\n        element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));\n      };\n\n      //ui->model\n      element.on(buttonsCtrl.toggleEvent, function() {\n        if (attrs.disabled) {\n          return;\n        }\n\n        scope.$apply(function() {\n          ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());\n          ngModelCtrl.$render();\n        });\n      });\n    }\n  };\n});\n"
  },
  {
    "path": "src/buttons/docs/demo.html",
    "content": "<div ng-controller=\"ButtonsCtrl\">\n    <h4>Single toggle</h4>\n    <pre>{{singleModel}}</pre>\n    <button type=\"button\" class=\"btn btn-primary\" ng-model=\"singleModel\" uib-btn-checkbox btn-checkbox-true=\"1\" btn-checkbox-false=\"0\">\n        Single Toggle\n    </button>\n    <h4>Checkbox</h4>\n    <pre>Model: {{checkModel}}</pre>\n    <pre>Results: {{checkResults}}</pre>\n    <div class=\"btn-group\">\n        <label class=\"btn btn-primary\" ng-model=\"checkModel.left\" uib-btn-checkbox>Left</label>\n        <label class=\"btn btn-primary\" ng-model=\"checkModel.middle\" uib-btn-checkbox>Middle</label>\n        <label class=\"btn btn-primary\" ng-model=\"checkModel.right\" uib-btn-checkbox>Right</label>\n    </div>\n    <h4>Radio &amp; Uncheckable Radio</h4>\n    <pre>{{radioModel || 'null'}}</pre>\n    <div class=\"btn-group\">\n        <label class=\"btn btn-primary\" ng-model=\"radioModel\" uib-btn-radio=\"'Left'\">Left</label>\n        <label class=\"btn btn-primary\" ng-model=\"radioModel\" uib-btn-radio=\"'Middle'\">Middle</label>\n        <label class=\"btn btn-primary\" ng-model=\"radioModel\" uib-btn-radio=\"'Right'\">Right</label>\n    </div>\n    <div class=\"btn-group\">\n        <label class=\"btn btn-success\" ng-model=\"radioModel\" uib-btn-radio=\"'Left'\" uncheckable>Left</label>\n        <label class=\"btn btn-success\" ng-model=\"radioModel\" uib-btn-radio=\"'Middle'\" uncheckable>Middle</label>\n        <label class=\"btn btn-success\" ng-model=\"radioModel\" uib-btn-radio=\"'Right'\" uib-uncheckable=\"uncheckable\">Right</label>\n    </div>\n    <div>\n        <button class=\"btn btn-default\" ng-click=\"uncheckable = !uncheckable\">\n            Toggle uncheckable\n        </button>\n    </div>\n</div>\n"
  },
  {
    "path": "src/buttons/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('ButtonsCtrl', function ($scope) {\n  $scope.singleModel = 1;\n\n  $scope.radioModel = 'Middle';\n\n  $scope.checkModel = {\n    left: false,\n    middle: true,\n    right: false\n  };\n\n  $scope.checkResults = [];\n\n  $scope.$watchCollection('checkModel', function () {\n    $scope.checkResults = [];\n    angular.forEach($scope.checkModel, function (value, key) {\n      if (value) {\n        $scope.checkResults.push(key);\n      }\n    });\n  });\n});"
  },
  {
    "path": "src/buttons/docs/readme.md",
    "content": "With the buttons directive, we can make a group of buttons behave like a set of checkboxes (`uib-btn-checkbox`) or behave like a set of radio buttons (`uib-btn-radio`).\n\n### uib-btn-checkbox settings\n\n* `btn-checkbox-false`\n  _(Default: `false`)_ -\n  Sets the value for the unchecked status.\n  \n* `btn-checkbox-true`\n  _(Default: `true`)_ -\n  Sets the value for the checked status.\n  \n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Model where we set the checkbox status. By default `true` or `false`.\n\n### uib-btn-radio settings\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Model where we set the radio status. All radio buttons in a group should use the same `ng-model`.\n    \n* `uib-btn-radio` -\n  <small class=\"badge\">$</small>\n  Value to assign to the `ng-model` if we check this radio button.\n\n* `uib-uncheckable`\n  <small class=\"badge\">$</small>\n  _(Default: `null`)_ -\n  An expression that evaluates to a truthy or falsy value that determines whether the `uncheckable` attribute is present.\n  \n* `uncheckable`\n  <small class=\"badge\">B</small> -\n  Whether a radio button can be unchecked or not.\n  \n### Additional settings `uibButtonConfig`\n\n* `activeClass`\n  _(Default: `active`)_ -\n  Class to apply to the checked buttons.\n  \n* `toggleEvent`\n  _(Default: `click`)_ -\n  Event used to toggle the buttons.\n\n### Known issues\n\nTo use tooltips or popovers on elements within a `btn-group`, set the tooltip/popover `appendToBody` option to `true`. This is due to Bootstrap CSS styling. See [here](http://getbootstrap.com/components/#btn-groups) for more information.\n"
  },
  {
    "path": "src/buttons/index.js",
    "content": "require('./buttons');\n\nvar MODULE_NAME = 'ui.bootstrap.module.buttons';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.buttons']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/buttons/test/buttons.spec.js",
    "content": "describe('buttons', function() {\n\n  var $scope, $compile;\n\n  beforeEach(module('ui.bootstrap.buttons'));\n  beforeEach(inject(function(_$rootScope_, _$compile_) {\n    $scope = _$rootScope_;\n    $compile = _$compile_;\n  }));\n\n  describe('checkbox', function() {\n    var compileButton = function(markup, scope) {\n      var el = $compile(markup)(scope);\n      scope.$digest();\n      return el;\n    };\n\n    it('should expose the controller to the view', inject(function($templateCache) {\n      var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox>{{button.text}}</button>', $scope);\n      var ctrl = btn.controller('uibBtnCheckbox');\n      expect(ctrl).toBeDefined();\n\n      ctrl.text = 'foo';\n      $scope.$digest();\n\n      expect(btn.html()).toBe('foo');\n    }));\n\n    //model -> UI\n    it('should work correctly with default model values', function() {\n      $scope.model = false;\n      var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox>click</button>', $scope);\n      expect(btn).not.toHaveClass('active');\n\n      $scope.model = true;\n      $scope.$digest();\n      expect(btn).toHaveClass('active');\n    });\n\n    it('should bind custom model values', function() {\n      $scope.model = 1;\n      var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox btn-checkbox-true=\"1\" btn-checkbox-false=\"0\">click</button>', $scope);\n      expect(btn).toHaveClass('active');\n\n      $scope.model = 0;\n      $scope.$digest();\n      expect(btn).not.toHaveClass('active');\n    });\n\n    //UI-> model\n    it('should toggle default model values on click', function() {\n      $scope.model = false;\n      var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox>click</button>', $scope);\n\n      btn.click();\n      expect($scope.model).toEqual(true);\n      expect(btn).toHaveClass('active');\n\n      btn.click();\n      expect($scope.model).toEqual(false);\n      expect(btn).not.toHaveClass('active');\n    });\n\n    it('should toggle custom model values on click', function() {\n      $scope.model = 0;\n      var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox btn-checkbox-true=\"1\" btn-checkbox-false=\"0\">click</button>', $scope);\n\n      btn.click();\n      expect($scope.model).toEqual(1);\n      expect(btn).toHaveClass('active');\n\n      btn.click();\n      expect($scope.model).toEqual(0);\n      expect(btn).not.toHaveClass('active');\n    });\n\n    it('should monitor true / false value changes - issue 666', function() {\n\n      $scope.model = 1;\n      $scope.trueVal = 1;\n      var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox btn-checkbox-true=\"trueVal\">click</button>', $scope);\n\n      expect(btn).toHaveClass('active');\n      expect($scope.model).toEqual(1);\n\n      $scope.model = 2;\n      $scope.trueVal = 2;\n      $scope.$digest();\n\n      expect(btn).toHaveClass('active');\n      expect($scope.model).toEqual(2);\n    });\n\n    it('should not toggle when disabled - issue 4013', function() {\n      $scope.model = 1;\n      $scope.falseVal = 0;\n      var btn = compileButton('<button disabled ng-model=\"model\" uib-btn-checkbox btn-checkbox-true=\"falseVal\">click</button>', $scope);\n\n      expect(btn).not.toHaveClass('active');\n      expect($scope.model).toEqual(1);\n\n      btn.click();\n\n      expect(btn).not.toHaveClass('active');\n\n      $scope.$digest();\n\n      expect(btn).not.toHaveClass('active');\n    });\n\n    describe('setting buttonConfig', function() {\n      var uibButtonConfig, originalActiveClass, originalToggleEvent;\n\n      beforeEach(inject(function(_uibButtonConfig_) {\n        uibButtonConfig = _uibButtonConfig_;\n        originalActiveClass = uibButtonConfig.activeClass;\n        originalToggleEvent = uibButtonConfig.toggleEvent;\n        uibButtonConfig.activeClass = false;\n        uibButtonConfig.toggleEvent = false;\n      }));\n\n      afterEach(function() {\n        // return it to the original value\n        uibButtonConfig.activeClass = originalActiveClass;\n        uibButtonConfig.toggleEvent = originalToggleEvent;\n      });\n\n      it('should use default config when buttonConfig.activeClass and buttonConfig.toggleEvent is false', function() {\n        $scope.model = false;\n        var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox>click</button>', $scope);\n        expect(btn).not.toHaveClass('active');\n\n        $scope.model = true;\n        $scope.$digest();\n        expect(btn).toHaveClass('active');\n      });\n\n      it('should be able to use a different active class', function() {\n        uibButtonConfig.activeClass = 'foo';\n        $scope.model = false;\n        var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox>click</button>', $scope);\n        expect(btn).not.toHaveClass('foo');\n\n        $scope.model = true;\n        $scope.$digest();\n        expect(btn).toHaveClass('foo');\n      });\n\n      it('should be able to use a different toggle event', function() {\n        uibButtonConfig.toggleEvent = 'mouseenter';\n        $scope.model = false;\n        var btn = compileButton('<button ng-model=\"model\" uib-btn-checkbox>click</button>', $scope);\n        expect(btn).not.toHaveClass('active');\n\n        btn.trigger('mouseenter');\n\n        $scope.$digest();\n        expect(btn).toHaveClass('active');\n      });\n    });\n\n  });\n\n  describe('radio', function() {\n    var compileButtons = function(markup, scope) {\n      var el = $compile('<div>'+markup+'</div>')(scope);\n      scope.$digest();\n      return el.find('button');\n    };\n\n    it('should expose the controller to the view', inject(function($templateCache) {\n      var btn = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\">{{buttons.text}}</button>', $scope);\n      var ctrl = btn.controller('uibBtnRadio');\n      expect(ctrl).toBeDefined();\n\n      ctrl.text = 'foo';\n      $scope.$digest();\n\n      expect(btn.html()).toBe('foo');\n    }));\n\n    //model -> UI\n    it('should set active class based on model', function() {\n      var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\">click1</button><button ng-model=\"model\" uib-btn-radio=\"2\">click2</button>', $scope);\n      expect(btns.eq(0)).not.toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n\n      $scope.model = 2;\n      $scope.$digest();\n      expect(btns.eq(0)).not.toHaveClass('active');\n      expect(btns.eq(1)).toHaveClass('active');\n    });\n\n    //UI->model\n    it('should set active class via click', function() {\n      var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\">click1</button><button ng-model=\"model\" uib-btn-radio=\"2\">click2</button>', $scope);\n      expect($scope.model).toBeUndefined();\n\n      btns.eq(0).click();\n      expect($scope.model).toEqual(1);\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n\n      btns.eq(1).click();\n      expect($scope.model).toEqual(2);\n      expect(btns.eq(1)).toHaveClass('active');\n      expect(btns.eq(0)).not.toHaveClass('active');\n    });\n\n    it('should watch uib-btn-radio values and update state accordingly', function() {\n      $scope.values = ['value1', 'value2'];\n\n      var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"values[0]\">click1</button><button ng-model=\"model\" uib-btn-radio=\"values[1]\">click2</button>', $scope);\n      expect(btns.eq(0)).not.toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n\n      $scope.model = 'value2';\n      $scope.$digest();\n      expect(btns.eq(0)).not.toHaveClass('active');\n      expect(btns.eq(1)).toHaveClass('active');\n\n      $scope.values[1] = 'value3';\n      $scope.model = 'value3';\n      $scope.$digest();\n      expect(btns.eq(0)).not.toHaveClass('active');\n      expect(btns.eq(1)).toHaveClass('active');\n    });\n\n    it('should do nothing when clicking an active radio', function() {\n      $scope.model = 1;\n      var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\">click1</button><button ng-model=\"model\" uib-btn-radio=\"2\">click2</button>', $scope);\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n\n      btns.eq(0).click();\n      $scope.$digest();\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n    });\n\n    it('should not toggle when disabled - issue 4013', function() {\n      $scope.model = 1;\n      var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\">click1</button><button disabled ng-model=\"model\" uib-btn-radio=\"2\">click2</button>', $scope);\n\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n\n      btns.eq(1).click();\n\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n\n      $scope.$digest();\n\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n    });\n\n    it('should handle string values in uib-btn-radio value', function() {\n      $scope.model = 'Two';\n      var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"\\'One\\'\">click1</button><button ng-model=\"model\" uib-btn-radio=\"\\'Two\\'\">click2</button>', $scope);\n\n      expect(btns.eq(0)).not.toHaveClass('active');\n      expect(btns.eq(1)).toHaveClass('active');\n\n      btns.eq(0).click();\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n      expect($scope.model).toEqual('One');\n\n      $scope.$digest();\n\n      expect(btns.eq(0)).toHaveClass('active');\n      expect(btns.eq(1)).not.toHaveClass('active');\n      expect($scope.model).toEqual('One');\n    });\n\n    describe('uncheckable', function() {\n      //model -> UI\n      it('should set active class based on model', function() {\n        var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\" uncheckable>click1</button><button ng-model=\"model\" uib-btn-radio=\"2\" uncheckable>click2</button>', $scope);\n        expect(btns.eq(0)).not.toHaveClass('active');\n        expect(btns.eq(1)).not.toHaveClass('active');\n\n        $scope.model = 2;\n        $scope.$digest();\n        expect(btns.eq(0)).not.toHaveClass('active');\n        expect(btns.eq(1)).toHaveClass('active');\n      });\n\n      //UI->model\n      it('should unset active class via click', function() {\n        var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\" uncheckable>click1</button><button ng-model=\"model\" uib-btn-radio=\"2\" uncheckable>click2</button>', $scope);\n        expect($scope.model).toBeUndefined();\n\n        btns.eq(0).click();\n        expect($scope.model).toEqual(1);\n        expect(btns.eq(0)).toHaveClass('active');\n        expect(btns.eq(1)).not.toHaveClass('active');\n\n        btns.eq(0).click();\n        expect($scope.model).toBeNull();\n        expect(btns.eq(1)).not.toHaveClass('active');\n        expect(btns.eq(0)).not.toHaveClass('active');\n      });\n\n      it('should watch uib-btn-radio values and update state', function() {\n        $scope.values = ['value1', 'value2'];\n\n        var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"values[0]\" uncheckable>click1</button><button ng-model=\"model\" uib-btn-radio=\"values[1]\" uncheckable>click2</button>', $scope);\n        expect(btns.eq(0)).not.toHaveClass('active');\n        expect(btns.eq(1)).not.toHaveClass('active');\n\n        $scope.model = 'value2';\n        $scope.$digest();\n        expect(btns.eq(0)).not.toHaveClass('active');\n        expect(btns.eq(1)).toHaveClass('active');\n\n        $scope.model = undefined;\n        $scope.$digest();\n        expect(btns.eq(0)).not.toHaveClass('active');\n        expect(btns.eq(1)).not.toHaveClass('active');\n      });\n    });\n\n    describe('uibUncheckable', function() {\n      it('should set uncheckable', function() {\n        $scope.uncheckable = false;\n        var btns = compileButtons('<button ng-model=\"model\" uib-btn-radio=\"1\">click1</button><button ng-model=\"model\" uib-btn-radio=\"2\" uib-uncheckable=\"uncheckable\">click2</button>', $scope);\n        expect(btns.eq(0).attr('uncheckable')).toBeUndefined();\n        expect(btns.eq(1).attr('uncheckable')).toBeUndefined();\n\n        expect($scope.model).toBeUndefined();\n\n        btns.eq(0).click();\n        expect($scope.model).toEqual(1);\n\n        btns.eq(0).click();\n        expect($scope.model).toEqual(1);\n\n        btns.eq(1).click();\n        expect($scope.model).toEqual(2);\n\n        btns.eq(1).click();\n        expect($scope.model).toEqual(2);\n\n        $scope.uncheckable = true;\n        $scope.$digest();\n        expect(btns.eq(0).attr('uncheckable')).toBeUndefined();\n        expect(btns.eq(1).attr('uncheckable')).toBeDefined();\n\n        btns.eq(0).click();\n        expect($scope.model).toEqual(1);\n\n        btns.eq(0).click();\n        expect($scope.model).toEqual(1);\n\n        btns.eq(1).click();\n        expect($scope.model).toEqual(2);\n\n        btns.eq(1).click();\n        expect($scope.model).toBeNull();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/carousel/carousel.css",
    "content": ".ng-animate.item:not(.left):not(.right) {\n  -webkit-transition: 0s ease-in-out left;\n  transition: 0s ease-in-out left\n}"
  },
  {
    "path": "src/carousel/carousel.js",
    "content": "angular.module('ui.bootstrap.carousel', [])\n\n.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {\n  var self = this,\n    slides = self.slides = $scope.slides = [],\n    SLIDE_DIRECTION = 'uib-slideDirection',\n    currentIndex = $scope.active,\n    currentInterval, isPlaying;\n\n  var destroyed = false;\n  $element.addClass('carousel');\n\n  self.addSlide = function(slide, element) {\n    slides.push({\n      slide: slide,\n      element: element\n    });\n    slides.sort(function(a, b) {\n      return +a.slide.index - +b.slide.index;\n    });\n    //if this is the first slide or the slide is set to active, select it\n    if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {\n      if ($scope.$currentTransition) {\n        $scope.$currentTransition = null;\n      }\n\n      currentIndex = slide.index;\n      $scope.active = slide.index;\n      setActive(currentIndex);\n      self.select(slides[findSlideIndex(slide)]);\n      if (slides.length === 1) {\n        $scope.play();\n      }\n    }\n  };\n\n  self.getCurrentIndex = function() {\n    for (var i = 0; i < slides.length; i++) {\n      if (slides[i].slide.index === currentIndex) {\n        return i;\n      }\n    }\n  };\n\n  self.next = $scope.next = function() {\n    var newIndex = (self.getCurrentIndex() + 1) % slides.length;\n\n    if (newIndex === 0 && $scope.noWrap()) {\n      $scope.pause();\n      return;\n    }\n\n    return self.select(slides[newIndex], 'next');\n  };\n\n  self.prev = $scope.prev = function() {\n    var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;\n\n    if ($scope.noWrap() && newIndex === slides.length - 1) {\n      $scope.pause();\n      return;\n    }\n\n    return self.select(slides[newIndex], 'prev');\n  };\n\n  self.removeSlide = function(slide) {\n    var index = findSlideIndex(slide);\n\n    //get the index of the slide inside the carousel\n    slides.splice(index, 1);\n    if (slides.length > 0 && currentIndex === index) {\n      if (index >= slides.length) {\n        currentIndex = slides.length - 1;\n        $scope.active = currentIndex;\n        setActive(currentIndex);\n        self.select(slides[slides.length - 1]);\n      } else {\n        currentIndex = index;\n        $scope.active = currentIndex;\n        setActive(currentIndex);\n        self.select(slides[index]);\n      }\n    } else if (currentIndex > index) {\n      currentIndex--;\n      $scope.active = currentIndex;\n    }\n\n    //clean the active value when no more slide\n    if (slides.length === 0) {\n      currentIndex = null;\n      $scope.active = null;\n    }\n  };\n\n  /* direction: \"prev\" or \"next\" */\n  self.select = $scope.select = function(nextSlide, direction) {\n    var nextIndex = findSlideIndex(nextSlide.slide);\n    //Decide direction if it's not given\n    if (direction === undefined) {\n      direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';\n    }\n    //Prevent this user-triggered transition from occurring if there is already one in progress\n    if (nextSlide.slide.index !== currentIndex &&\n      !$scope.$currentTransition) {\n      goNext(nextSlide.slide, nextIndex, direction);\n    }\n  };\n\n  /* Allow outside people to call indexOf on slides array */\n  $scope.indexOfSlide = function(slide) {\n    return +slide.slide.index;\n  };\n\n  $scope.isActive = function(slide) {\n    return $scope.active === slide.slide.index;\n  };\n\n  $scope.isPrevDisabled = function() {\n    return $scope.active === 0 && $scope.noWrap();\n  };\n\n  $scope.isNextDisabled = function() {\n    return $scope.active === slides.length - 1 && $scope.noWrap();\n  };\n\n  $scope.pause = function() {\n    if (!$scope.noPause) {\n      isPlaying = false;\n      resetTimer();\n    }\n  };\n\n  $scope.play = function() {\n    if (!isPlaying) {\n      isPlaying = true;\n      restartTimer();\n    }\n  };\n\n  $element.on('mouseenter', $scope.pause);\n  $element.on('mouseleave', $scope.play);\n\n  $scope.$on('$destroy', function() {\n    destroyed = true;\n    resetTimer();\n  });\n\n  $scope.$watch('noTransition', function(noTransition) {\n    $animate.enabled($element, !noTransition);\n  });\n\n  $scope.$watch('interval', restartTimer);\n\n  $scope.$watchCollection('slides', resetTransition);\n\n  $scope.$watch('active', function(index) {\n    if (angular.isNumber(index) && currentIndex !== index) {\n      for (var i = 0; i < slides.length; i++) {\n        if (slides[i].slide.index === index) {\n          index = i;\n          break;\n        }\n      }\n\n      var slide = slides[index];\n      if (slide) {\n        setActive(index);\n        self.select(slides[index]);\n        currentIndex = index;\n      }\n    }\n  });\n\n  function getSlideByIndex(index) {\n    for (var i = 0, l = slides.length; i < l; ++i) {\n      if (slides[i].index === index) {\n        return slides[i];\n      }\n    }\n  }\n\n  function setActive(index) {\n    for (var i = 0; i < slides.length; i++) {\n      slides[i].slide.active = i === index;\n    }\n  }\n\n  function goNext(slide, index, direction) {\n    if (destroyed) {\n      return;\n    }\n\n    angular.extend(slide, {direction: direction});\n    angular.extend(slides[currentIndex].slide || {}, {direction: direction});\n    if ($animate.enabled($element) && !$scope.$currentTransition &&\n      slides[index].element && self.slides.length > 1) {\n      slides[index].element.data(SLIDE_DIRECTION, slide.direction);\n      var currentIdx = self.getCurrentIndex();\n\n      if (angular.isNumber(currentIdx) && slides[currentIdx].element) {\n        slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);\n      }\n\n      $scope.$currentTransition = true;\n      $animate.on('addClass', slides[index].element, function(element, phase) {\n        if (phase === 'close') {\n          $scope.$currentTransition = null;\n          $animate.off('addClass', element);\n        }\n      });\n    }\n\n    $scope.active = slide.index;\n    currentIndex = slide.index;\n    setActive(index);\n\n    //every time you change slides, reset the timer\n    restartTimer();\n  }\n\n  function findSlideIndex(slide) {\n    for (var i = 0; i < slides.length; i++) {\n      if (slides[i].slide === slide) {\n        return i;\n      }\n    }\n  }\n\n  function resetTimer() {\n    if (currentInterval) {\n      $interval.cancel(currentInterval);\n      currentInterval = null;\n    }\n  }\n\n  function resetTransition(slides) {\n    if (!slides.length) {\n      $scope.$currentTransition = null;\n    }\n  }\n\n  function restartTimer() {\n    resetTimer();\n    var interval = +$scope.interval;\n    if (!isNaN(interval) && interval > 0) {\n      currentInterval = $interval(timerFn, interval);\n    }\n  }\n\n  function timerFn() {\n    var interval = +$scope.interval;\n    if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {\n      $scope.next();\n    } else {\n      $scope.pause();\n    }\n  }\n}])\n\n.directive('uibCarousel', function() {\n  return {\n    transclude: true,\n    controller: 'UibCarouselController',\n    controllerAs: 'carousel',\n    restrict: 'A',\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/carousel/carousel.html';\n    },\n    scope: {\n      active: '=',\n      interval: '=',\n      noTransition: '=',\n      noPause: '=',\n      noWrap: '&'\n    }\n  };\n})\n\n.directive('uibSlide', ['$animate', function($animate) {\n  return {\n    require: '^uibCarousel',\n    restrict: 'A',\n    transclude: true,\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/carousel/slide.html';\n    },\n    scope: {\n      actual: '=?',\n      index: '=?'\n    },\n    link: function (scope, element, attrs, carouselCtrl) {\n      element.addClass('item');\n      carouselCtrl.addSlide(scope, element);\n      //when the scope is destroyed then remove the slide from the current slides array\n      scope.$on('$destroy', function() {\n        carouselCtrl.removeSlide(scope);\n      });\n\n      scope.$watch('active', function(active) {\n        $animate[active ? 'addClass' : 'removeClass'](element, 'active');\n      });\n    }\n  };\n}])\n\n.animation('.item', ['$animateCss',\nfunction($animateCss) {\n  var SLIDE_DIRECTION = 'uib-slideDirection';\n\n  function removeClass(element, className, callback) {\n    element.removeClass(className);\n    if (callback) {\n      callback();\n    }\n  }\n\n  return {\n    beforeAddClass: function(element, className, done) {\n      if (className === 'active') {\n        var stopped = false;\n        var direction = element.data(SLIDE_DIRECTION);\n        var directionClass = direction === 'next' ? 'left' : 'right';\n        var removeClassFn = removeClass.bind(this, element,\n          directionClass + ' ' + direction, done);\n        element.addClass(direction);\n\n        $animateCss(element, {addClass: directionClass})\n          .start()\n          .done(removeClassFn);\n\n        return function() {\n          stopped = true;\n        };\n      }\n      done();\n    },\n    beforeRemoveClass: function (element, className, done) {\n      if (className === 'active') {\n        var stopped = false;\n        var direction = element.data(SLIDE_DIRECTION);\n        var directionClass = direction === 'next' ? 'left' : 'right';\n        var removeClassFn = removeClass.bind(this, element, directionClass, done);\n\n        $animateCss(element, {addClass: directionClass})\n          .start()\n          .done(removeClassFn);\n\n        return function() {\n          stopped = true;\n        };\n      }\n      done();\n    }\n  };\n}]);\n"
  },
  {
    "path": "src/carousel/docs/README.md",
    "content": "Carousel creates a carousel similar to bootstrap's image carousel.\n\nThe carousel also offers support for touchscreen devices in the form of swiping. To enable swiping, load the `ngTouch` module as a dependency.\n\nUse a `<uib-carousel>` element with `<uib-slide>` elements inside it.\n\n### uib-carousel settings\n\n* `active`\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `Index of first slide`)_ -\n  Index of current active slide.\n\n* `interval`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `none`)_ -\n  Sets an interval to cycle through the slides. You need a number bigger than 0 to make the interval work.\n\n* `no-pause`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  The interval pauses on mouseover. Setting this to truthy, disables this pause.\n\n* `no-transition`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether to disable the transition animation between slides. Setting this to truthy, disables this transition.\n\n* `no-wrap`\n  <small class=\"badge\">$</small>\n  _(Default: `false`)_ -\n  Disables the looping of slides. Setting `no-wrap` to an expression which evaluates to a truthy value will prevent looping.\n\n* `template-url`\n  _(Default: `uib/template/carousel/carousel.html`)_ -\n  Add the ability to override the template used on the component.\n\n### uib-slide settings\n\n* `actual`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `none`)_ -\n  Use this attribute to bind the slide model (or any object of interest) onto the slide scope, which makes it available for customization in the carousel template.\n\n* `index`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `none`)_ -\n  The index of the slide. Must be unique.\n\n* `template-url`\n  _(Default: `uib/template/carousel/slide.html`)_ -\n  Add the ability to override the template used on the component.\n"
  },
  {
    "path": "src/carousel/docs/demo.html",
    "content": "<div ng-controller=\"CarouselDemoCtrl\">\n  <div style=\"height: 305px\">\n    <div uib-carousel active=\"active\" interval=\"myInterval\" no-wrap=\"noWrapSlides\">\n      <div uib-slide ng-repeat=\"slide in slides track by slide.id\" index=\"slide.id\">\n        <img ng-src=\"{{slide.image}}\" style=\"margin:auto;\">\n        <div class=\"carousel-caption\">\n          <h4>Slide {{slide.id}}</h4>\n          <p>{{slide.text}}</p>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <button type=\"button\" class=\"btn btn-info\" ng-click=\"addSlide()\">Add Slide</button>\n      <button type=\"button\" class=\"btn btn-info\" ng-click=\"randomize()\">Randomize slides</button>\n      <div class=\"checkbox\">\n        <label>\n          <input type=\"checkbox\" ng-model=\"noWrapSlides\">\n          Disable Slide Looping\n        </label>\n      </div>\n    </div>\n    <div class=\"col-md-6\">\n      Interval, in milliseconds: <input type=\"number\" class=\"form-control\" ng-model=\"myInterval\">\n      <br />Enter a negative number or 0 to stop the interval.\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "src/carousel/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($scope) {\n  $scope.myInterval = 5000;\n  $scope.noWrapSlides = false;\n  $scope.active = 0;\n  var slides = $scope.slides = [];\n  var currIndex = 0;\n\n  $scope.addSlide = function() {\n    var newWidth = 600 + slides.length + 1;\n    slides.push({\n      image: '//unsplash.it/' + newWidth + '/300',\n      text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4],\n      id: currIndex++\n    });\n  };\n\n  $scope.randomize = function() {\n    var indexes = generateIndexesArray();\n    assignNewIndexesToSlides(indexes);\n  };\n\n  for (var i = 0; i < 4; i++) {\n    $scope.addSlide();\n  }\n\n  // Randomize logic below\n\n  function assignNewIndexesToSlides(indexes) {\n    for (var i = 0, l = slides.length; i < l; i++) {\n      slides[i].id = indexes.pop();\n    }\n  }\n\n  function generateIndexesArray() {\n    var indexes = [];\n    for (var i = 0; i < currIndex; ++i) {\n      indexes[i] = i;\n    }\n    return shuffle(indexes);\n  }\n\n  // http://stackoverflow.com/questions/962802#962890\n  function shuffle(array) {\n    var tmp, current, top = array.length;\n\n    if (top) {\n      while (--top) {\n        current = Math.floor(Math.random() * (top + 1));\n        tmp = array[current];\n        array[current] = array[top];\n        array[top] = tmp;\n      }\n    }\n\n    return array;\n  }\n});\n"
  },
  {
    "path": "src/carousel/index-nocss.js",
    "content": "require('../../template/carousel/carousel.html.js');\nrequire('../../template/carousel/slide.html.js');\nrequire('./carousel');\n\nvar MODULE_NAME = 'ui.bootstrap.module.carousel';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.carousel', 'uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/carousel/index.js",
    "content": "require('./carousel.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/carousel/test/carousel.spec.js",
    "content": "describe('carousel', function() {\n  beforeEach(module('ui.bootstrap.carousel'));\n  beforeEach(module('ngAnimateMock'));\n  beforeEach(module('uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html'));\n\n  var $rootScope, $compile, $controller, $interval, $templateCache, $timeout, $animate;\n  beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$interval_, _$templateCache_, _$timeout_, _$animate_) {\n    $rootScope = _$rootScope_;\n    $compile = _$compile_;\n    $controller = _$controller_;\n    $interval = _$interval_;\n    $templateCache = _$templateCache_;\n    $timeout = _$timeout_;\n    $animate = _$animate_;\n  }));\n\n  describe('basics', function() {\n    var elm, scope;\n    beforeEach(function() {\n      scope = $rootScope.$new();\n      scope.slides = [\n        {content: 'one', index: 0},\n        {content: 'two', index: 1},\n        {content: 'three', index: 2}\n      ];\n      elm = $compile(\n        '<div uib-carousel active=\"active\" interval=\"interval\" no-transition=\"true\" no-pause=\"nopause\">' +\n          '<div uib-slide ng-repeat=\"slide in slides track by slide.index\" index=\"slide.index\">' +\n            '{{slide.content}}' +\n          '</div>' +\n        '</div>'\n      )(scope);\n      scope.interval = 5000;\n      scope.nopause = undefined;\n      scope.$apply();\n    });\n\n    function testSlideActive(slideIndex) {\n      for (var i = 0; i < scope.slides.length; i++) {\n        if (i === slideIndex) {\n          expect(scope.active).toBe(scope.slides[i].index);\n        } else {\n          expect(scope.active).not.toBe(scope.slides[i].index);\n        }\n      }\n    }\n\n    it('should allow overriding of the carousel template', function() {\n      $templateCache.put('foo/bar.html', '<div>foo</div>');\n\n      elm = $compile('<div uib-carousel template-url=\"foo/bar.html\"></div>')(scope);\n      $rootScope.$digest();\n\n      expect(elm.html()).toBe('<div>foo</div>');\n    });\n\n    it('should allow overriding of the slide template', function() {\n      $templateCache.put('foo/bar.html', '<div class=\"slide\">bar</div>');\n\n      elm = $compile(\n        '<div uib-carousel interval=\"interval\" no-transition=\"true\" no-pause=\"nopause\">' +\n          '<div uib-slide template-url=\"foo/bar.html\"></div>' +\n        '</div>'\n      )(scope);\n      $rootScope.$digest();\n\n      var slide = elm.find('.slide');\n      expect(slide.html()).toBe('bar');\n    });\n\n    it('should be able to select a slide via model changes', function() {\n      testSlideActive(0);\n      scope.$apply('active=1');\n      testSlideActive(1);\n    });\n\n    it('should create clickable prev nav button', function() {\n      var navPrev = elm.find('a.left');\n      var navNext = elm.find('a.right');\n\n      expect(navPrev.length).toBe(1);\n      expect(navNext.length).toBe(1);\n    });\n\n    it('should display clickable slide indicators', function () {\n      var indicators = elm.find('ol.carousel-indicators > li');\n      expect(indicators.length).toBe(3);\n    });\n\n    it('should stop cycling slides forward when noWrap is truthy', function () {\n      elm = $compile(\n        '<div uib-carousel active=\"active\" interval=\"interval\" no-wrap=\"noWrap\">' +\n          '<div uib-slide ng-repeat=\"slide in slides track by slide.index\" index=\"slide.index\">' +\n            '{{slide.content}}' +\n          '</div>' +\n        '</div>'\n      )(scope);\n\n      scope.noWrap = true;\n      scope.$apply();\n\n      var $scope = elm.isolateScope();\n      spyOn($scope, 'pause');\n\n      scope.active = $scope.slides.length - 1;\n      scope.$apply();\n      testSlideActive($scope.slides.length - 1);\n      $scope.next();\n      testSlideActive($scope.slides.length - 1);\n      expect($scope.pause).toHaveBeenCalled();\n    });\n\n    it('should stop cycling slides backward when noWrap is truthy', function () {\n      elm = $compile(\n        '<div uib-carousel active=\"active\" interval=\"interval\" no-wrap=\"noWrap\">' +\n          '<div uib-slide ng-repeat=\"slide in slides track by slide.index\" index=\"slide.index\">' +\n            '{{slide.content}}' +\n          '</div>' +\n        '</div>'\n      )(scope);\n\n      scope.noWrap = true;\n      scope.$apply();\n\n      var $scope = elm.isolateScope();\n      spyOn($scope, 'pause');\n\n      testSlideActive(0);\n      $scope.prev();\n      testSlideActive(0);\n      expect($scope.pause).toHaveBeenCalled();\n    });\n\n    it('should hide navigation when only one slide', function () {\n      scope.slides = [{active:false,content:'one'}];\n      scope.$apply();\n      elm = $compile(\n        '<div uib-carousel active=\"active\" interval=\"interval\" no-transition=\"true\">' +\n          '<div uib-slide ng-repeat=\"slide in slides\" index=\"$index\">' +\n            '{{slide.content}}' +\n          '</div>' +\n        '</div>'\n      )(scope);\n      var indicators = elm.find('ol.carousel-indicators > li');\n      expect(indicators.length).toBe(0);\n\n      var navNext = elm.find('a.right');\n      expect(navNext.length).toBe(0);\n\n      var navPrev = elm.find('a.left');\n      expect(navPrev.length).toBe(0);\n    });\n\n    it('should disable prev button when slide index is 0 and noWrap is truthy', function() {\n      scope.$apply();\n\n      var $scope = elm.isolateScope();\n      $scope.noWrap = function() {return true;};\n\n      $scope.isPrevDisabled();\n      scope.$apply();\n\n      var navPrev = elm.find('a.left');\n      expect(navPrev.hasClass('disabled')).toBe(true);\n    });\n\n    it('should disable next button when last slide is active and noWrap is truthy', function() {\n      scope.slides = [\n        {content: 'one', index: 0},\n        {content: 'two', index: 1}\n      ];\n\n      scope.$apply();\n\n      var $scope = elm.isolateScope();\n      $scope.noWrap = function() {return true;};\n      $scope.next();\n\n      $scope.isNextDisabled();\n      scope.$apply();\n\n      var navNext = elm.find('a.right');\n      expect(navNext.hasClass('disabled')).toBe(true);\n    });\n\n    it('should show navigation when there are 3 slides', function () {\n      var indicators = elm.find('ol.carousel-indicators > li');\n      expect(indicators.length).not.toBe(0);\n\n      var navNext = elm.find('a.right');\n      expect(navNext.length).not.toBe(0);\n\n      var navPrev = elm.find('a.left');\n      expect(navPrev.length).not.toBe(0);\n    });\n\n    it('should go to next when clicking next button', function() {\n      var navNext = elm.find('a.right');\n      testSlideActive(0);\n      navNext.click();\n      testSlideActive(1);\n      navNext.click();\n      testSlideActive(2);\n      navNext.click();\n      testSlideActive(0);\n    });\n\n    it('should go to prev when clicking prev button', function() {\n      var navPrev = elm.find('a.left');\n      testSlideActive(0);\n      navPrev.click();\n      testSlideActive(2);\n      navPrev.click();\n      testSlideActive(1);\n      navPrev.click();\n      testSlideActive(0);\n    });\n\n    it('should select a slide when clicking on slide indicators', function () {\n      var indicators = elm.find('ol.carousel-indicators > li');\n      indicators.eq(1).click();\n      testSlideActive(1);\n    });\n\n    it('shouldnt go forward if interval is NaN or negative or has no slides', function() {\n      testSlideActive(0);\n      var previousInterval = scope.interval;\n      scope.$apply('interval = -1');\n      $interval.flush(previousInterval);\n      testSlideActive(0);\n      scope.$apply('interval = 1000');\n      $interval.flush(1000);\n      testSlideActive(1);\n      scope.$apply('interval = false');\n      $interval.flush(1000);\n      testSlideActive(1);\n      scope.$apply('interval = 1000');\n      $interval.flush(1000);\n      testSlideActive(2);\n      scope.$apply('slides = []');\n      $interval.flush(1000);\n      testSlideActive(2);\n    });\n\n    it('should bind the content to slides', function() {\n      var contents = elm.find('div.item [ng-transclude]');\n\n      expect(contents.length).toBe(3);\n      expect(contents.eq(0).text()).toBe('one');\n      expect(contents.eq(1).text()).toBe('two');\n      expect(contents.eq(2).text()).toBe('three');\n\n      scope.$apply(function() {\n        scope.slides[0].content = 'what';\n        scope.slides[1].content = 'no';\n        scope.slides[2].content = 'maybe';\n      });\n\n      expect(contents.eq(0).text()).toBe('what');\n      expect(contents.eq(1).text()).toBe('no');\n      expect(contents.eq(2).text()).toBe('maybe');\n    });\n\n    it('should be playing by default and cycle through slides', function() {\n      testSlideActive(0);\n      $interval.flush(scope.interval);\n      testSlideActive(1);\n      $interval.flush(scope.interval);\n      testSlideActive(2);\n      $interval.flush(scope.interval);\n      testSlideActive(0);\n    });\n\n    it('should pause and play on mouseover', function() {\n      testSlideActive(0);\n      $interval.flush(scope.interval);\n      testSlideActive(1);\n      elm.trigger('mouseenter');\n      testSlideActive(1);\n      $interval.flush(scope.interval);\n      testSlideActive(1);\n      elm.trigger('mouseleave');\n      $interval.flush(scope.interval);\n      testSlideActive(2);\n    });\n\n    it('should not pause on mouseover if noPause', function() {\n      scope.$apply('nopause = true');\n      testSlideActive(0);\n      elm.trigger('mouseenter');\n      $interval.flush(scope.interval);\n      testSlideActive(1);\n      elm.trigger('mouseleave');\n      $interval.flush(scope.interval);\n      testSlideActive(2);\n    });\n\n    it('should remove slide from dom and change active slide', function() {\n      scope.$apply('active = 2');\n      testSlideActive(2);\n      scope.$apply('slides.splice(2,1)');\n      $timeout.flush(0);\n      expect(elm.find('div.item').length).toBe(2);\n      testSlideActive(1);\n      $interval.flush(scope.interval);\n      testSlideActive(0);\n      scope.$apply('slides.splice(1,1)');\n      $timeout.flush(0);\n      expect(elm.find('div.item').length).toBe(1);\n      testSlideActive(0);\n    });\n\n    it('should change dom when you reassign ng-repeat slides array', function() {\n      scope.slides = [\n        {content:'new1', index: 4},\n        {content:'new2', index: 5},\n        {content:'new3', index: 6}\n      ];\n      scope.$apply();\n      var contents = elm.find('div.item [ng-transclude]');\n      expect(contents.length).toBe(3);\n      expect(contents.eq(0).text()).toBe('new1');\n      expect(contents.eq(1).text()).toBe('new2');\n      expect(contents.eq(2).text()).toBe('new3');\n    });\n\n    it('should not change if next is clicked while transitioning', function() {\n      var carouselScope = elm.children().scope();\n      var next = elm.find('a.right');\n\n      testSlideActive(0);\n      carouselScope.$currentTransition = true;\n      next.click();\n\n      testSlideActive(0);\n\n      carouselScope.$currentTransition = null;\n      next.click();\n      testSlideActive(1);\n    });\n\n    it('should buffer the slides if transition is clicked and only transition to the last requested', function() {\n      var carouselScope = elm.children().scope();\n\n      testSlideActive(0);\n      carouselScope.$currentTransition = null;\n      carouselScope.select(carouselScope.slides[1]);\n      $animate.flush();\n\n      testSlideActive(1);\n\n      carouselScope.$currentTransition = true;\n      carouselScope.select(carouselScope.slides[2]);\n      scope.$apply();\n\n      testSlideActive(1);\n\n      carouselScope.select(carouselScope.slides[0]);\n      scope.$apply();\n\n      testSlideActive(1);\n\n      carouselScope.$currentTransition = null;\n      $interval.flush(scope.interval);\n      $animate.flush();\n\n      testSlideActive(2);\n\n      $interval.flush(scope.interval);\n      $animate.flush();\n\n      testSlideActive(0);\n    });\n\n    it('issue 1414 - should not continue running timers after scope is destroyed', function() {\n      testSlideActive(0);\n      $interval.flush(scope.interval);\n      testSlideActive(1);\n      $interval.flush(scope.interval);\n      testSlideActive(2);\n      $interval.flush(scope.interval);\n      testSlideActive(0);\n      spyOn($interval, 'cancel').and.callThrough();\n      scope.$destroy();\n      expect($interval.cancel).toHaveBeenCalled();\n    });\n\n    it('issue 4390 - should reset the currentTransition if there are no slides', function() {\n      var carouselScope = elm.children().scope();\n      var next = elm.find('a.right');\n      scope.slides = [\n        {content:'new1', index: 1},\n        {content:'new2', index: 2},\n        {content:'new3', index: 3}\n      ];\n      scope.$apply();\n\n      testSlideActive(0);\n      carouselScope.$currentTransition = true;\n\n      scope.slides = [];\n      scope.$apply();\n\n      expect(carouselScope.$currentTransition).toBe(null);\n    });\n  });\n\n  describe('slide order', function() {\n    var elm, scope;\n    beforeEach(function() {\n      scope = $rootScope.$new();\n      scope.slides = [\n        {content: 'one', id: 3},\n        {content: 'two', id: 1},\n        {content: 'three', id: 2}\n      ];\n      elm = $compile(\n        '<div uib-carousel active=\"active\" interval=\"interval\" no-transition=\"true\" no-pause=\"nopause\">' +\n          '<div uib-slide ng-repeat=\"slide in slides | orderBy: \\'id\\' track by slide.id\" index=\"slide.id\">' +\n            '{{slide.content}}' +\n          '</div>' +\n        '</div>'\n      )(scope);\n      scope.$apply();\n    });\n\n    function testSlideActive(slideIndex) {\n      for (var i = 0; i < scope.slides.length; i++) {\n        if (i === slideIndex) {\n          expect(scope.active).toBe(scope.slides[i].id);\n        } else {\n          expect(scope.active).not.toBe(scope.slides[i].id);\n        }\n      }\n    }\n\n    it('should change dom when the order of the slides changes', function() {\n      scope.slides[0].id = 3;\n      scope.slides[1].id = 2;\n      scope.slides[2].id = 1;\n      scope.$apply();\n      var contents = elm.find('div.item [ng-transclude]');\n      expect(contents.length).toBe(3);\n      expect(contents.eq(0).text()).toBe('three');\n      expect(contents.eq(1).text()).toBe('two');\n      expect(contents.eq(2).text()).toBe('one');\n    });\n\n    it('should select next after order change', function() {\n      testSlideActive(1);\n      var next = elm.find('a.right');\n      next.click();\n      testSlideActive(2);\n    });\n\n    it('should select prev after order change', function() {\n      testSlideActive(1);\n      var prev = elm.find('a.left');\n      prev.click();\n      testSlideActive(0);\n    });\n\n    it('should add slide in the specified position', function() {\n      testSlideActive(1);\n      scope.slides[2].id = 4;\n      scope.slides.push({content:'four', id: 5});\n      scope.$apply();\n      var contents = elm.find('div.item [ng-transclude]');\n      expect(contents.length).toBe(4);\n      expect(contents.eq(0).text()).toBe('two');\n      expect(contents.eq(1).text()).toBe('one');\n      expect(contents.eq(2).text()).toBe('three');\n      expect(contents.eq(3).text()).toBe('four');\n    });\n\n    it('should remove slide after order change', function() {\n      testSlideActive(1);\n      scope.slides.splice(1, 1);\n      scope.$apply();\n      var contents = elm.find('div.item [ng-transclude]');\n      expect(contents.length).toBe(2);\n      expect(contents.eq(0).text()).toBe('three');\n      expect(contents.eq(1).text()).toBe('one');\n    });\n  });\n\n  describe('controller', function() {\n    var scope, ctrl;\n    //create an array of slides and add to the scope\n    var slides = [\n      {'content': 1, index: 0},\n      {'content': 2, index: 1},\n      {'content': 3, index: 2},\n      {'content': 4, index: 3}\n    ];\n\n    beforeEach(function() {\n      scope = $rootScope.$new();\n      scope.noWrap = angular.noop;\n      ctrl = $controller('UibCarouselController', {$scope: scope, $element: angular.element('<div></div>')});\n      for (var i = 0; i < slides.length; i++) {\n        ctrl.addSlide(slides[i]);\n      }\n    });\n\n    it('should set first slide to active = true and the rest to false', function() {\n      angular.forEach(ctrl.slides, function(slide, i) {\n        if (i !== 0) {\n          expect(slide.slide.active).not.toBe(true);\n        } else {\n          expect(slide.slide.active).toBe(true);\n        }\n      });\n    });\n\n    it('should add a new slide and not change the active slide', function() {\n      var newSlide = {active: false, index: 4};\n      expect(ctrl.slides.length).toBe(4);\n      ctrl.addSlide(newSlide);\n      expect(ctrl.slides.length).toBe(5);\n      expect(ctrl.slides[4].slide.active).toBe(false);\n      expect(ctrl.slides[0].slide.active).toBe(true);\n    });\n\n    it('should remove slide and change active slide if needed', function() {\n      expect(ctrl.slides.length).toBe(4);\n      ctrl.removeSlide(ctrl.slides[0].slide);\n      $timeout.flush(0);\n      expect(ctrl.slides.length).toBe(3);\n      expect(scope.active).toBe(1);\n      ctrl.select(ctrl.slides[2]);\n      ctrl.removeSlide(ctrl.slides[2].slide);\n      $timeout.flush(0);\n      expect(ctrl.slides.length).toBe(2);\n      expect(scope.active).toBe(2);\n      ctrl.removeSlide(ctrl.slides[0].slide);\n      $timeout.flush(0);\n      expect(ctrl.slides.length).toBe(1);\n      expect(scope.active).toBe(1);\n    });\n\n    it('issue 1414 - should not continue running timers after scope is destroyed', function() {\n      spyOn(scope, 'next');\n      scope.interval = 2000;\n      scope.$digest();\n\n      $interval.flush(scope.interval);\n      expect(scope.next.calls.count()).toBe(1);\n\n      scope.$destroy();\n\n      $interval.flush(scope.interval);\n      expect(scope.next.calls.count()).toBe(1);\n    });\n\n    it('should be exposed in the template', inject(function($templateCache) {\n      $templateCache.put('uib/template/carousel/carousel.html', '<div>{{carousel.text}}</div>');\n\n      var scope = $rootScope.$new();\n      var elm = $compile('<div uib-carousel interval=\"bar\" no-transition=\"false\" no-pause=\"true\"></div>')(scope);\n      $rootScope.$digest();\n\n      var ctrl = elm.controller('uibCarousel');\n\n      expect(ctrl).toBeDefined();\n\n      ctrl.text = 'foo';\n      $rootScope.$digest();\n\n      expect(elm.html()).toBe('<div class=\"ng-binding\">foo</div>');\n    }));\n  });\n\n  it('should expose a custom model in the carousel slide', function() {\n    var scope = $rootScope.$new();\n    scope.slides = [\n      {active:false,content:'one'},\n      {active:false,content:'two'},\n      {active:false,content:'three'}\n    ];\n    var elm = $compile(\n      '<div uib-carousel active=\"active\" interval=\"interval\" no-transition=\"true\" no-pause=\"nopause\">' +\n        '<div uib-slide ng-repeat=\"slide in slides\" index=\"$index\" actual=\"slide\">' +\n          '{{slide.content}}' +\n        '</div>' +\n      '</div>'\n    )(scope);\n    $rootScope.$digest();\n\n    var ctrl = elm.controller('uibCarousel');\n\n    expect(angular.equals(ctrl.slides.map(function(slide) {\n      return slide.slide.actual;\n    }), scope.slides)).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/collapse/collapse.js",
    "content": "angular.module('ui.bootstrap.collapse', [])\n\n  .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {\n    var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;\n    return {\n      link: function(scope, element, attrs) {\n        var expandingExpr = $parse(attrs.expanding),\n          expandedExpr = $parse(attrs.expanded),\n          collapsingExpr = $parse(attrs.collapsing),\n          collapsedExpr = $parse(attrs.collapsed),\n          horizontal = false,\n          css = {},\n          cssTo = {};\n\n        init();\n\n        function init() {\n          horizontal = !!('horizontal' in attrs);\n          if (horizontal) {\n            css = {\n              width: ''\n            };\n            cssTo = {width: '0'};\n          } else {\n            css = {\n              height: ''\n            };\n            cssTo = {height: '0'};\n          }\n          if (!scope.$eval(attrs.uibCollapse)) {\n            element.addClass('in')\n              .addClass('collapse')\n              .attr('aria-expanded', true)\n              .attr('aria-hidden', false)\n              .css(css);\n          }\n        }\n\n        function getScrollFromElement(element) {\n          if (horizontal) {\n            return {width: element.scrollWidth + 'px'};\n          }\n          return {height: element.scrollHeight + 'px'};\n        }\n\n        function expand() {\n          if (element.hasClass('collapse') && element.hasClass('in')) {\n            return;\n          }\n\n          $q.resolve(expandingExpr(scope))\n            .then(function() {\n              element.removeClass('collapse')\n                .addClass('collapsing')\n                .attr('aria-expanded', true)\n                .attr('aria-hidden', false);\n\n              if ($animateCss) {\n                $animateCss(element, {\n                  addClass: 'in',\n                  easing: 'ease',\n                  css: {\n                    overflow: 'hidden'\n                  },\n                  to: getScrollFromElement(element[0])\n                }).start()['finally'](expandDone);\n              } else {\n                $animate.addClass(element, 'in', {\n                  css: {\n                    overflow: 'hidden'\n                  },\n                  to: getScrollFromElement(element[0])\n                }).then(expandDone);\n              }\n            }, angular.noop);\n        }\n\n        function expandDone() {\n          element.removeClass('collapsing')\n            .addClass('collapse')\n            .css(css);\n          expandedExpr(scope);\n        }\n\n        function collapse() {\n          if (!element.hasClass('collapse') && !element.hasClass('in')) {\n            return collapseDone();\n          }\n\n          $q.resolve(collapsingExpr(scope))\n            .then(function() {\n              element\n              // IMPORTANT: The width must be set before adding \"collapsing\" class.\n              // Otherwise, the browser attempts to animate from width 0 (in\n              // collapsing class) to the given width here.\n                .css(getScrollFromElement(element[0]))\n                // initially all panel collapse have the collapse class, this removal\n                // prevents the animation from jumping to collapsed state\n                .removeClass('collapse')\n                .addClass('collapsing')\n                .attr('aria-expanded', false)\n                .attr('aria-hidden', true);\n\n              if ($animateCss) {\n                $animateCss(element, {\n                  removeClass: 'in',\n                  to: cssTo\n                }).start()['finally'](collapseDone);\n              } else {\n                $animate.removeClass(element, 'in', {\n                  to: cssTo\n                }).then(collapseDone);\n              }\n            }, angular.noop);\n        }\n\n        function collapseDone() {\n          element.css(cssTo); // Required so that collapse works when animation is disabled\n          element.removeClass('collapsing')\n            .addClass('collapse');\n          collapsedExpr(scope);\n        }\n\n        scope.$watch(attrs.uibCollapse, function(shouldCollapse) {\n          if (shouldCollapse) {\n            collapse();\n          } else {\n            expand();\n          }\n        });\n      }\n    };\n  }]);\n"
  },
  {
    "path": "src/collapse/docs/demo.html",
    "content": "<style>\n  .horizontal-collapse {\n    height: 70px;\n  }\n  .navbar-collapse.in {\n    overflow-y: hidden;\n  }\n</style>\n<div ng-controller=\"CollapseDemoCtrl\">\n\t<p>Resize window to less than 768 pixels to display mobile menu toggle button.</p>\n\t<nav class=\"navbar navbar-default\" role=\"navigation\">\n\t\t<div class=\"navbar-header\">\n\t\t\t<button type=\"button\" class=\"navbar-toggle\" ng-click=\"isNavCollapsed = !isNavCollapsed\">\n\t\t\t\t<span class=\"sr-only\">Toggle navigation</span>\n\t\t\t\t<span class=\"icon-bar\"></span>\n\t\t\t\t<span class=\"icon-bar\"></span>\n\t\t\t\t<span class=\"icon-bar\"></span>\n\t\t\t</button>\n\t\t\t<a class=\"navbar-brand\" href=\"#\">A menu</a>\n\t\t</div>\n\t\t<div class=\"collapse navbar-collapse\" uib-collapse=\"isNavCollapsed\">\n\t\t\t<ul class=\"nav navbar-nav\">\n\t\t\t\t<li><a href=\"#\">Link 1</a></li>\n\t\t\t\t<li><a href=\"#\">Link 2</a></li>\n\t\t\t</ul>\n\t\t</div>\n\t</nav>\n\t<hr>\n\t<button type=\"button\" class=\"btn btn-default\" ng-click=\"isCollapsed = !isCollapsed\">Toggle collapse Vertically</button>\n\t<hr>\n\t<div uib-collapse=\"isCollapsed\">\n\t\t<div class=\"well well-lg\">Some content</div>\n\t</div>\n\n\t<button type=\"button\" class=\"btn btn-default\" ng-click=\"isCollapsedHorizontal = !isCollapsedHorizontal\">Toggle collapse Horizontally</button>\n\t<hr>\n\t<div class=\"horizontal-collapse\" uib-collapse=\"isCollapsedHorizontal\" horizontal>\n\t\t<div class=\"well well-lg\">Some content</div>\n\t</div>\n</div>\n"
  },
  {
    "path": "src/collapse/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('CollapseDemoCtrl', function ($scope) {\n  $scope.isNavCollapsed = true;\n  $scope.isCollapsed = false;\n  $scope.isCollapsedHorizontal = false;\n});\n"
  },
  {
    "path": "src/collapse/docs/readme.md",
    "content": "**uib-collapse** provides a simple way to hide and show an element with a css transition\n\n### uib-collapse settings\n\n* `collapsed()`\n  <small class=\"badge\">$</small> -\n  An optional expression called after the element finished collapsing.\n\n* `collapsing()`\n  <small class=\"badge\">$</small> -\n  An optional expression called before the element begins collapsing.\n  If the expression returns a promise, animation won't start until the promise resolves.\n  If the returned promise is rejected, collapsing will be cancelled.\n\n* `expanded()`\n  <small class=\"badge\">$</small> -\n  An optional expression called after the element finished expanding.\n\n* `expanding()`\n  <small class=\"badge\">$</small> -\n  An optional expression called before the element begins expanding.\n  If the expression returns a promise, animation won't start until the promise resolves.\n  If the returned promise is rejected, expanding will be cancelled.\n\n* `uib-collapse`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether the element should be collapsed or not.\n\n* `horizontal`\n  <small class=\"badge\">$</small> -\n  An optional attribute that permit to collapse horizontally.\n\n### Known Issues\n\nWhen using the `horizontal` attribute with this directive, CSS can reflow as the collapse element goes from `0px` to its desired end width, which can result in height changes. This can cause animations to not appear to run. The best way around this is to set a fixed height via CSS on the horizontal collapse element so that this situation does not occur, and so the animation can run as expected.\n"
  },
  {
    "path": "src/collapse/index.js",
    "content": "require('./collapse');\n\nvar MODULE_NAME = 'ui.bootstrap.module.collapse';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.collapse']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/collapse/test/collapse.spec.js",
    "content": "describe('collapse directive', function() {\n  var element, compileFn, scope, $compile, $animate, $q;\n\n  beforeEach(module('ui.bootstrap.collapse'));\n  beforeEach(module('ngAnimateMock'));\n  beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {\n    scope = _$rootScope_;\n    $compile = _$compile_;\n    $animate = _$animate_;\n    $q = _$q_;\n  }));\n\n  beforeEach(function() {\n    element = angular.element(\n      '<div uib-collapse=\"isCollapsed\" '\n        + 'expanding=\"expanding()\" '\n        + 'expanded=\"expanded()\" '\n        + 'collapsing=\"collapsing()\" '\n        + 'collapsed=\"collapsed()\">'\n      + 'Some Content</div>');\n    compileFn = $compile(element);\n    angular.element(document.body).append(element);\n  });\n\n  afterEach(function() {\n    element.remove();\n  });\n\n  function initCallbacks() {\n    scope.collapsing = jasmine.createSpy('scope.collapsing');\n    scope.collapsed = jasmine.createSpy('scope.collapsed');\n    scope.expanding = jasmine.createSpy('scope.expanding');\n    scope.expanded = jasmine.createSpy('scope.expanded');\n  }\n\n  function assertCallbacks(expected) {\n    ['collapsing', 'collapsed', 'expanding', 'expanded'].forEach(function(cbName) {\n      if (expected[cbName]) {\n        expect(scope[cbName]).toHaveBeenCalled();\n      } else {\n        expect(scope[cbName]).not.toHaveBeenCalled();\n      }\n    });\n  }\n\n  it('should be hidden on initialization if isCollapsed = true', function() {\n    initCallbacks();\n    scope.isCollapsed = true;\n    compileFn(scope);\n    scope.$digest();\n    expect(element.height()).toBe(0);\n    assertCallbacks({ collapsed: true });\n  });\n\n  it('should not trigger any animation on initialization if isCollapsed = true', function() {\n    var wrapperFn = function() {\n      $animate.flush();\n    };\n\n    scope.isCollapsed = true;\n    compileFn(scope);\n    scope.$digest();\n\n    expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);\n  });\n\n  it('should collapse if isCollapsed = true on subsequent use', function() {\n    scope.isCollapsed = false;\n    compileFn(scope);\n    scope.$digest();\n    initCallbacks();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(element.height()).toBe(0);\n    assertCallbacks({ collapsing: true, collapsed: true });\n  });\n\n  it('should show after toggled from collapsed', function() {\n    initCallbacks();\n    scope.isCollapsed = true;\n    compileFn(scope);\n    scope.$digest();\n    expect(element.height()).toBe(0);\n    assertCallbacks({ collapsed: true });\n    scope.collapsed.calls.reset();\n\n    scope.isCollapsed = false;\n    scope.$digest();\n    $animate.flush();\n    expect(element.height()).not.toBe(0);\n    assertCallbacks({ expanding: true, expanded: true });\n  });\n\n  it('should not trigger any animation on initialization if isCollapsed = false', function() {\n    var wrapperFn = function() {\n      $animate.flush();\n    };\n\n    scope.isCollapsed = false;\n    compileFn(scope);\n    scope.$digest();\n\n    expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);\n  });\n\n  it('should expand if isCollapsed = false on subsequent use', function() {\n    scope.isCollapsed = false;\n    compileFn(scope);\n    scope.$digest();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    initCallbacks();\n    scope.isCollapsed = false;\n    scope.$digest();\n    $animate.flush();\n    expect(element.height()).not.toBe(0);\n    assertCallbacks({ expanding: true, expanded: true });\n  });\n\n  it('should collapse if isCollapsed = true on subsequent uses', function() {\n    scope.isCollapsed = false;\n    compileFn(scope);\n    scope.$digest();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    scope.isCollapsed = false;\n    scope.$digest();\n    $animate.flush();\n    initCallbacks();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(element.height()).toBe(0);\n    assertCallbacks({ collapsing: true, collapsed: true });\n  });\n\n  it('should change aria-expanded attribute', function() {\n    scope.isCollapsed = false;\n    compileFn(scope);\n    scope.$digest();\n    expect(element.attr('aria-expanded')).toBe('true');\n\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(element.attr('aria-expanded')).toBe('false');\n  });\n\n  it('should change aria-hidden attribute', function() {\n    scope.isCollapsed = false;\n    compileFn(scope);\n    scope.$digest();\n    expect(element.attr('aria-hidden')).toBe('false');\n\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(element.attr('aria-hidden')).toBe('true');\n  });\n\n  describe('dynamic content', function() {\n    var element;\n\n    beforeEach(function() {\n      element = angular.element('<div uib-collapse=\"isCollapsed\"><p>Initial content</p><div ng-show=\"exp\">Additional content</div></div>');\n      $compile(element)(scope);\n      angular.element(document.body).append(element);\n    });\n\n    afterEach(function() {\n      element.remove();\n    });\n\n    it('should grow accordingly when content size inside collapse increases', function() {\n      scope.exp = false;\n      scope.isCollapsed = false;\n      scope.$digest();\n      var collapseHeight = element.height();\n      scope.exp = true;\n      scope.$digest();\n      expect(element.height()).toBeGreaterThan(collapseHeight);\n    });\n\n    it('should shrink accordingly when content size inside collapse decreases', function() {\n      scope.exp = true;\n      scope.isCollapsed = false;\n      scope.$digest();\n      var collapseHeight = element.height();\n      scope.exp = false;\n      scope.$digest();\n      expect(element.height()).toBeLessThan(collapseHeight);\n    });\n  });\n\n  describe('expanding callback returning a promise', function() {\n    var defer, collapsedHeight;\n\n    beforeEach(function() {\n      defer = $q.defer();\n\n      scope.isCollapsed = true;\n      scope.expanding = function() {\n        return defer.promise;\n      };\n      compileFn(scope);\n      scope.$digest();\n      collapsedHeight = element.height();\n\n      // set flag to expand ...\n      scope.isCollapsed = false;\n      scope.$digest();\n\n      // ... shouldn't expand yet ...\n      expect(element.attr('aria-expanded')).not.toBe('true');\n      expect(element.height()).toBe(collapsedHeight);\n    });\n\n    it('should wait for it to resolve before animating', function() {\n      defer.resolve();\n\n      // should now expand\n      scope.$digest();\n      $animate.flush();\n\n      expect(element.attr('aria-expanded')).toBe('true');\n      expect(element.height()).toBeGreaterThan(collapsedHeight);\n    });\n\n    it('should not animate if it rejects', function() {\n      defer.reject();\n\n      // should NOT expand\n      scope.$digest();\n\n      expect(element.attr('aria-expanded')).not.toBe('true');\n      expect(element.height()).toBe(collapsedHeight);\n    });\n  });\n\n  describe('collapsing callback returning a promise', function() {\n    var defer, expandedHeight;\n\n    beforeEach(function() {\n      defer = $q.defer();\n      scope.isCollapsed = false;\n      scope.collapsing = function() {\n        return defer.promise;\n      };\n      compileFn(scope);\n      scope.$digest();\n\n      expandedHeight = element.height();\n\n      // set flag to collapse ...\n      scope.isCollapsed = true;\n      scope.$digest();\n\n      // ... but it shouldn't collapse yet ...\n      expect(element.attr('aria-expanded')).not.toBe('false');\n      expect(element.height()).toBe(expandedHeight);\n    });\n\n    it('should wait for it to resolve before animating', function() {\n      defer.resolve();\n\n      // should now collapse\n      scope.$digest();\n      $animate.flush();\n\n      expect(element.attr('aria-expanded')).toBe('false');\n      expect(element.height()).toBeLessThan(expandedHeight);\n    });\n\n    it('should not animate if it rejects', function() {\n      defer.reject();\n\n      // should NOT collapse\n      scope.$digest();\n\n      expect(element.attr('aria-expanded')).not.toBe('false');\n      expect(element.height()).toBe(expandedHeight);\n    });\n  });\n\n});\n"
  },
  {
    "path": "src/collapse/test/collapseHorizontally.spec.js",
    "content": "describe('collapse directive', function() {\n  var elementH, compileFnH, scope, $compile, $animate, $q;\n\n  beforeEach(module('ui.bootstrap.collapse'));\n  beforeEach(module('ngAnimateMock'));\n  beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {\n    scope = _$rootScope_;\n    $compile = _$compile_;\n    $animate = _$animate_;\n    $q = _$q_;\n  }));\n\n  beforeEach(function() {\n    elementH = angular.element(\n      '<div uib-collapse=\"isCollapsed\" '\n      + 'expanding=\"expanding()\" '\n      + 'expanded=\"expanded()\" '\n      + 'collapsing=\"collapsing()\" '\n      + 'collapsed=\"collapsed()\" '\n      + 'horizontal>'\n      + 'Some Content</div>');\n    compileFnH = $compile(elementH);\n    angular.element(document.body).append(elementH);\n  });\n\n  afterEach(function() {\n    elementH.remove();\n  });\n\n  function initCallbacks() {\n    scope.collapsing = jasmine.createSpy('scope.collapsing');\n    scope.collapsed = jasmine.createSpy('scope.collapsed');\n    scope.expanding = jasmine.createSpy('scope.expanding');\n    scope.expanded = jasmine.createSpy('scope.expanded');\n  }\n\n  function assertCallbacks(expected) {\n    ['collapsing', 'collapsed', 'expanding', 'expanded'].forEach(function(cbName) {\n      if (expected[cbName]) {\n        expect(scope[cbName]).toHaveBeenCalled();\n      } else {\n        expect(scope[cbName]).not.toHaveBeenCalled();\n      }\n    });\n  }\n\n  it('should be hidden on initialization if isCollapsed = true', function() {\n    initCallbacks();\n    scope.isCollapsed = true;\n    compileFnH(scope);\n    scope.$digest();\n    expect(elementH.width()).toBe(0);\n    assertCallbacks({ collapsed: true });\n  });\n\n  it('should not trigger any animation on initialization if isCollapsed = true', function() {\n    var wrapperFn = function() {\n      $animate.flush();\n    };\n\n    scope.isCollapsed = true;\n    compileFnH(scope);\n    scope.$digest();\n\n    expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);\n  });\n\n  it('should collapse if isCollapsed = true on subsequent use', function() {\n    scope.isCollapsed = false;\n    compileFnH(scope);\n    scope.$digest();\n    initCallbacks();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(elementH.width()).toBe(0);\n    assertCallbacks({ collapsing: true, collapsed: true });\n  });\n\n  it('should show after toggled from collapsed', function() {\n    initCallbacks();\n    scope.isCollapsed = true;\n    compileFnH(scope);\n    scope.$digest();\n    expect(elementH.width()).toBe(0);\n    assertCallbacks({ collapsed: true });\n    scope.collapsed.calls.reset();\n\n    scope.isCollapsed = false;\n    scope.$digest();\n    $animate.flush();\n    expect(elementH.width()).not.toBe(0);\n    assertCallbacks({ expanding: true, expanded: true });\n  });\n\n  it('should not trigger any animation on initialization if isCollapsed = false', function() {\n    var wrapperFn = function() {\n      $animate.flush();\n    };\n\n    scope.isCollapsed = false;\n    compileFnH(scope);\n    scope.$digest();\n\n    expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);\n  });\n\n  it('should expand if isCollapsed = false on subsequent use', function() {\n    scope.isCollapsed = false;\n    compileFnH(scope);\n    scope.$digest();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    initCallbacks();\n    scope.isCollapsed = false;\n    scope.$digest();\n    $animate.flush();\n    expect(elementH.width()).not.toBe(0);\n    assertCallbacks({ expanding: true, expanded: true });\n  });\n\n  it('should collapse if isCollapsed = true on subsequent uses', function() {\n    scope.isCollapsed = false;\n    compileFnH(scope);\n    scope.$digest();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    scope.isCollapsed = false;\n    scope.$digest();\n    $animate.flush();\n    initCallbacks();\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(elementH.width()).toBe(0);\n    assertCallbacks({ collapsing: true, collapsed: true });\n  });\n\n  it('should change aria-expanded attribute', function() {\n    scope.isCollapsed = false;\n    compileFnH(scope);\n    scope.$digest();\n    expect(elementH.attr('aria-expanded')).toBe('true');\n\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(elementH.attr('aria-expanded')).toBe('false');\n  });\n\n  it('should change aria-hidden attribute', function() {\n    scope.isCollapsed = false;\n    compileFnH(scope);\n    scope.$digest();\n    expect(elementH.attr('aria-hidden')).toBe('false');\n\n    scope.isCollapsed = true;\n    scope.$digest();\n    $animate.flush();\n    expect(elementH.attr('aria-hidden')).toBe('true');\n  });\n\n  describe('expanding callback returning a promise', function() {\n    var defer, collapsedWidth;\n\n    beforeEach(function() {\n      defer = $q.defer();\n\n      scope.isCollapsed = true;\n      scope.expanding = function() {\n        return defer.promise;\n      };\n      compileFnH(scope);\n      scope.$digest();\n      collapsedWidth = elementH.width();\n\n      // set flag to expand ...\n      scope.isCollapsed = false;\n      scope.$digest();\n\n      // ... shouldn't expand yet ...\n      expect(elementH.attr('aria-expanded')).not.toBe('true');\n      expect(elementH.width()).toBe(collapsedWidth);\n    });\n\n    it('should wait for it to resolve before animating', function() {\n      defer.resolve();\n\n      // should now expand\n      scope.$digest();\n      $animate.flush();\n\n      expect(elementH.attr('aria-expanded')).toBe('true');\n      expect(elementH.width()).toBeGreaterThan(collapsedWidth);\n    });\n\n    it('should not animate if it rejects', function() {\n      defer.reject();\n\n      // should NOT expand\n      scope.$digest();\n\n      expect(elementH.attr('aria-expanded')).not.toBe('true');\n      expect(elementH.width()).toBe(collapsedWidth);\n    });\n  });\n\n  describe('collapsing callback returning a promise', function() {\n    var defer, expandedWidth;\n\n    beforeEach(function() {\n      defer = $q.defer();\n      scope.isCollapsed = false;\n      scope.collapsing = function() {\n        return defer.promise;\n      };\n      compileFnH(scope);\n      scope.$digest();\n\n      expandedWidth = elementH.width();\n\n      // set flag to collapse ...\n      scope.isCollapsed = true;\n      scope.$digest();\n\n      // ... but it shouldn't collapse yet ...\n      expect(elementH.attr('aria-expanded')).not.toBe('false');\n      expect(elementH.width()).toBe(expandedWidth);\n    });\n\n    it('should wait for it to resolve before animating', function() {\n      defer.resolve();\n\n      // should now collapse\n      scope.$digest();\n      $animate.flush();\n\n      expect(elementH.attr('aria-expanded')).toBe('false');\n      expect(elementH.width()).toBeLessThan(expandedWidth);\n    });\n\n    it('should not animate if it rejects', function() {\n      defer.reject();\n\n      // should NOT collapse\n      scope.$digest();\n\n      expect(elementH.attr('aria-expanded')).not.toBe('false');\n      expect(elementH.width()).toBe(expandedWidth);\n    });\n  });\n\n});\n"
  },
  {
    "path": "src/dateparser/dateparser.js",
    "content": "angular.module('ui.bootstrap.dateparser', [])\n\n.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) {\n  // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js\n  var SPECIAL_CHARACTERS_REGEXP = /[\\\\\\^\\$\\*\\+\\?\\|\\[\\]\\(\\)\\.\\{\\}]/g;\n\n  var localeId;\n  var formatCodeToRegex;\n\n  this.init = function() {\n    localeId = $locale.id;\n\n    this.parsers = {};\n    this.formatters = {};\n\n    formatCodeToRegex = [\n      {\n        key: 'yyyy',\n        regex: '\\\\d{4}',\n        apply: function(value) { this.year = +value; },\n        formatter: function(date) {\n          var _date = new Date();\n          _date.setFullYear(Math.abs(date.getFullYear()));\n          return dateFilter(_date, 'yyyy');\n        }\n      },\n      {\n        key: 'yy',\n        regex: '\\\\d{2}',\n        apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },\n        formatter: function(date) {\n          var _date = new Date();\n          _date.setFullYear(Math.abs(date.getFullYear()));\n          return dateFilter(_date, 'yy');\n        }\n      },\n      {\n        key: 'y',\n        regex: '\\\\d{1,4}',\n        apply: function(value) { this.year = +value; },\n        formatter: function(date) {\n          var _date = new Date();\n          _date.setFullYear(Math.abs(date.getFullYear()));\n          return dateFilter(_date, 'y');\n        }\n      },\n      {\n        key: 'M!',\n        regex: '0?[1-9]|1[0-2]',\n        apply: function(value) { this.month = value - 1; },\n        formatter: function(date) {\n          var value = date.getMonth();\n          if (/^[0-9]$/.test(value)) {\n            return dateFilter(date, 'MM');\n          }\n\n          return dateFilter(date, 'M');\n        }\n      },\n      {\n        key: 'MMMM',\n        regex: $locale.DATETIME_FORMATS.MONTH.join('|'),\n        apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },\n        formatter: function(date) { return dateFilter(date, 'MMMM'); }\n      },\n      {\n        key: 'MMM',\n        regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),\n        apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },\n        formatter: function(date) { return dateFilter(date, 'MMM'); }\n      },\n      {\n        key: 'MM',\n        regex: '0[1-9]|1[0-2]',\n        apply: function(value) { this.month = value - 1; },\n        formatter: function(date) { return dateFilter(date, 'MM'); }\n      },\n      {\n        key: 'M',\n        regex: '[1-9]|1[0-2]',\n        apply: function(value) { this.month = value - 1; },\n        formatter: function(date) { return dateFilter(date, 'M'); }\n      },\n      {\n        key: 'd!',\n        regex: '[0-2]?[0-9]{1}|3[0-1]{1}',\n        apply: function(value) { this.date = +value; },\n        formatter: function(date) {\n          var value = date.getDate();\n          if (/^[1-9]$/.test(value)) {\n            return dateFilter(date, 'dd');\n          }\n\n          return dateFilter(date, 'd');\n        }\n      },\n      {\n        key: 'dd',\n        regex: '[0-2][0-9]{1}|3[0-1]{1}',\n        apply: function(value) { this.date = +value; },\n        formatter: function(date) { return dateFilter(date, 'dd'); }\n      },\n      {\n        key: 'd',\n        regex: '[1-2]?[0-9]{1}|3[0-1]{1}',\n        apply: function(value) { this.date = +value; },\n        formatter: function(date) { return dateFilter(date, 'd'); }\n      },\n      {\n        key: 'EEEE',\n        regex: $locale.DATETIME_FORMATS.DAY.join('|'),\n        formatter: function(date) { return dateFilter(date, 'EEEE'); }\n      },\n      {\n        key: 'EEE',\n        regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),\n        formatter: function(date) { return dateFilter(date, 'EEE'); }\n      },\n      {\n        key: 'HH',\n        regex: '(?:0|1)[0-9]|2[0-3]',\n        apply: function(value) { this.hours = +value; },\n        formatter: function(date) { return dateFilter(date, 'HH'); }\n      },\n      {\n        key: 'hh',\n        regex: '0[0-9]|1[0-2]',\n        apply: function(value) { this.hours = +value; },\n        formatter: function(date) { return dateFilter(date, 'hh'); }\n      },\n      {\n        key: 'H',\n        regex: '1?[0-9]|2[0-3]',\n        apply: function(value) { this.hours = +value; },\n        formatter: function(date) { return dateFilter(date, 'H'); }\n      },\n      {\n        key: 'h',\n        regex: '[0-9]|1[0-2]',\n        apply: function(value) { this.hours = +value; },\n        formatter: function(date) { return dateFilter(date, 'h'); }\n      },\n      {\n        key: 'mm',\n        regex: '[0-5][0-9]',\n        apply: function(value) { this.minutes = +value; },\n        formatter: function(date) { return dateFilter(date, 'mm'); }\n      },\n      {\n        key: 'm',\n        regex: '[0-9]|[1-5][0-9]',\n        apply: function(value) { this.minutes = +value; },\n        formatter: function(date) { return dateFilter(date, 'm'); }\n      },\n      {\n        key: 'sss',\n        regex: '[0-9][0-9][0-9]',\n        apply: function(value) { this.milliseconds = +value; },\n        formatter: function(date) { return dateFilter(date, 'sss'); }\n      },\n      {\n        key: 'ss',\n        regex: '[0-5][0-9]',\n        apply: function(value) { this.seconds = +value; },\n        formatter: function(date) { return dateFilter(date, 'ss'); }\n      },\n      {\n        key: 's',\n        regex: '[0-9]|[1-5][0-9]',\n        apply: function(value) { this.seconds = +value; },\n        formatter: function(date) { return dateFilter(date, 's'); }\n      },\n      {\n        key: 'a',\n        regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),\n        apply: function(value) {\n          if (this.hours === 12) {\n            this.hours = 0;\n          }\n\n          if (value === 'PM') {\n            this.hours += 12;\n          }\n        },\n        formatter: function(date) { return dateFilter(date, 'a'); }\n      },\n      {\n        key: 'Z',\n        regex: '[+-]\\\\d{4}',\n        apply: function(value) {\n          var matches = value.match(/([+-])(\\d{2})(\\d{2})/),\n            sign = matches[1],\n            hours = matches[2],\n            minutes = matches[3];\n          this.hours += toInt(sign + hours);\n          this.minutes += toInt(sign + minutes);\n        },\n        formatter: function(date) {\n          return dateFilter(date, 'Z');\n        }\n      },\n      {\n        key: 'ww',\n        regex: '[0-4][0-9]|5[0-3]',\n        formatter: function(date) { return dateFilter(date, 'ww'); }\n      },\n      {\n        key: 'w',\n        regex: '[0-9]|[1-4][0-9]|5[0-3]',\n        formatter: function(date) { return dateFilter(date, 'w'); }\n      },\n      {\n        key: 'GGGG',\n        regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\\s/g, '\\\\s'),\n        formatter: function(date) { return dateFilter(date, 'GGGG'); }\n      },\n      {\n        key: 'GGG',\n        regex: $locale.DATETIME_FORMATS.ERAS.join('|'),\n        formatter: function(date) { return dateFilter(date, 'GGG'); }\n      },\n      {\n        key: 'GG',\n        regex: $locale.DATETIME_FORMATS.ERAS.join('|'),\n        formatter: function(date) { return dateFilter(date, 'GG'); }\n      },\n      {\n        key: 'G',\n        regex: $locale.DATETIME_FORMATS.ERAS.join('|'),\n        formatter: function(date) { return dateFilter(date, 'G'); }\n      }\n    ];\n\n    if (angular.version.major >= 1 && angular.version.minor > 4) {\n      formatCodeToRegex.push({\n        key: 'LLLL',\n        regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'),\n        apply: function(value) { this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value); },\n        formatter: function(date) { return dateFilter(date, 'LLLL'); }\n      });\n    }\n  };\n\n  this.init();\n\n  function getFormatCodeToRegex(key) {\n    return filterFilter(formatCodeToRegex, {key: key}, true)[0];\n  }\n\n  this.getParser = function (key) {\n    var f = getFormatCodeToRegex(key);\n    return f && f.apply || null;\n  };\n\n  this.overrideParser = function (key, parser) {\n    var f = getFormatCodeToRegex(key);\n    if (f && angular.isFunction(parser)) {\n      this.parsers = {};\n      f.apply = parser;\n    }\n  }.bind(this);\n\n  function createParser(format) {\n    var map = [], regex = format.split('');\n\n    // check for literal values\n    var quoteIndex = format.indexOf('\\'');\n    if (quoteIndex > -1) {\n      var inLiteral = false;\n      format = format.split('');\n      for (var i = quoteIndex; i < format.length; i++) {\n        if (inLiteral) {\n          if (format[i] === '\\'') {\n            if (i + 1 < format.length && format[i+1] === '\\'') { // escaped single quote\n              format[i+1] = '$';\n              regex[i+1] = '';\n            } else { // end of literal\n              regex[i] = '';\n              inLiteral = false;\n            }\n          }\n          format[i] = '$';\n        } else {\n          if (format[i] === '\\'') { // start of literal\n            format[i] = '$';\n            regex[i] = '';\n            inLiteral = true;\n          }\n        }\n      }\n\n      format = format.join('');\n    }\n\n    angular.forEach(formatCodeToRegex, function(data) {\n      var index = format.indexOf(data.key);\n\n      if (index > -1) {\n        format = format.split('');\n\n        regex[index] = '(' + data.regex + ')';\n        format[index] = '$'; // Custom symbol to define consumed part of format\n        for (var i = index + 1, n = index + data.key.length; i < n; i++) {\n          regex[i] = '';\n          format[i] = '$';\n        }\n        format = format.join('');\n\n        map.push({\n          index: index,\n          key: data.key,\n          apply: data.apply,\n          matcher: data.regex\n        });\n      }\n    });\n\n    return {\n      regex: new RegExp('^' + regex.join('') + '$'),\n      map: orderByFilter(map, 'index')\n    };\n  }\n\n  function createFormatter(format) {\n    var formatters = [];\n    var i = 0;\n    var formatter, literalIdx;\n    while (i < format.length) {\n      if (angular.isNumber(literalIdx)) {\n        if (format.charAt(i) === '\\'') {\n          if (i + 1 >= format.length || format.charAt(i + 1) !== '\\'') {\n            formatters.push(constructLiteralFormatter(format, literalIdx, i));\n            literalIdx = null;\n          }\n        } else if (i === format.length) {\n          while (literalIdx < format.length) {\n            formatter = constructFormatterFromIdx(format, literalIdx);\n            formatters.push(formatter);\n            literalIdx = formatter.endIdx;\n          }\n        }\n\n        i++;\n        continue;\n      }\n\n      if (format.charAt(i) === '\\'') {\n        literalIdx = i;\n        i++;\n        continue;\n      }\n\n      formatter = constructFormatterFromIdx(format, i);\n\n      formatters.push(formatter.parser);\n      i = formatter.endIdx;\n    }\n\n    return formatters;\n  }\n\n  function constructLiteralFormatter(format, literalIdx, endIdx) {\n    return function() {\n      return format.substr(literalIdx + 1, endIdx - literalIdx - 1);\n    };\n  }\n\n  function constructFormatterFromIdx(format, i) {\n    var currentPosStr = format.substr(i);\n    for (var j = 0; j < formatCodeToRegex.length; j++) {\n      if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {\n        var data = formatCodeToRegex[j];\n        return {\n          endIdx: i + data.key.length,\n          parser: data.formatter\n        };\n      }\n    }\n\n    return {\n      endIdx: i + 1,\n      parser: function() {\n        return currentPosStr.charAt(0);\n      }\n    };\n  }\n\n  this.filter = function(date, format) {\n    if (!angular.isDate(date) || isNaN(date) || !format) {\n      return '';\n    }\n\n    format = $locale.DATETIME_FORMATS[format] || format;\n\n    if ($locale.id !== localeId) {\n      this.init();\n    }\n\n    if (!this.formatters[format]) {\n      this.formatters[format] = createFormatter(format);\n    }\n\n    var formatters = this.formatters[format];\n\n    return formatters.reduce(function(str, formatter) {\n      return str + formatter(date);\n    }, '');\n  };\n\n  this.parse = function(input, format, baseDate) {\n    if (!angular.isString(input) || !format) {\n      return input;\n    }\n\n    format = $locale.DATETIME_FORMATS[format] || format;\n    format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\\\$&');\n\n    if ($locale.id !== localeId) {\n      this.init();\n    }\n\n    if (!this.parsers[format]) {\n      this.parsers[format] = createParser(format, 'apply');\n    }\n\n    var parser = this.parsers[format],\n        regex = parser.regex,\n        map = parser.map,\n        results = input.match(regex),\n        tzOffset = false;\n    if (results && results.length) {\n      var fields, dt;\n      if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {\n        fields = {\n          year: baseDate.getFullYear(),\n          month: baseDate.getMonth(),\n          date: baseDate.getDate(),\n          hours: baseDate.getHours(),\n          minutes: baseDate.getMinutes(),\n          seconds: baseDate.getSeconds(),\n          milliseconds: baseDate.getMilliseconds()\n        };\n      } else {\n        if (baseDate) {\n          $log.warn('dateparser:', 'baseDate is not a valid date');\n        }\n        fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };\n      }\n\n      for (var i = 1, n = results.length; i < n; i++) {\n        var mapper = map[i - 1];\n        if (mapper.matcher === 'Z') {\n          tzOffset = true;\n        }\n\n        if (mapper.apply) {\n          mapper.apply.call(fields, results[i]);\n        }\n      }\n\n      var datesetter = tzOffset ? Date.prototype.setUTCFullYear :\n        Date.prototype.setFullYear;\n      var timesetter = tzOffset ? Date.prototype.setUTCHours :\n        Date.prototype.setHours;\n\n      if (isValid(fields.year, fields.month, fields.date)) {\n        if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {\n          dt = new Date(baseDate);\n          datesetter.call(dt, fields.year, fields.month, fields.date);\n          timesetter.call(dt, fields.hours, fields.minutes,\n            fields.seconds, fields.milliseconds);\n        } else {\n          dt = new Date(0);\n          datesetter.call(dt, fields.year, fields.month, fields.date);\n          timesetter.call(dt, fields.hours || 0, fields.minutes || 0,\n            fields.seconds || 0, fields.milliseconds || 0);\n        }\n      }\n\n      return dt;\n    }\n  };\n\n  // Check if date is valid for specific month (and year for February).\n  // Month: 0 = Jan, 1 = Feb, etc\n  function isValid(year, month, date) {\n    if (date < 1) {\n      return false;\n    }\n\n    if (month === 1 && date > 28) {\n      return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);\n    }\n\n    if (month === 3 || month === 5 || month === 8 || month === 10) {\n      return date < 31;\n    }\n\n    return true;\n  }\n\n  function toInt(str) {\n    return parseInt(str, 10);\n  }\n\n  this.toTimezone = toTimezone;\n  this.fromTimezone = fromTimezone;\n  this.timezoneToOffset = timezoneToOffset;\n  this.addDateMinutes = addDateMinutes;\n  this.convertTimezoneToLocal = convertTimezoneToLocal;\n\n  function toTimezone(date, timezone) {\n    return date && timezone ? convertTimezoneToLocal(date, timezone) : date;\n  }\n\n  function fromTimezone(date, timezone) {\n    return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;\n  }\n\n  //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207\n  function timezoneToOffset(timezone, fallback) {\n    timezone = timezone.replace(/:/g, '');\n    var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;\n    return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;\n  }\n\n  function addDateMinutes(date, minutes) {\n    date = new Date(date.getTime());\n    date.setMinutes(date.getMinutes() + minutes);\n    return date;\n  }\n\n  function convertTimezoneToLocal(date, timezone, reverse) {\n    reverse = reverse ? -1 : 1;\n    var dateTimezoneOffset = date.getTimezoneOffset();\n    var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);\n    return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));\n  }\n}]);\n"
  },
  {
    "path": "src/dateparser/docs/README.md",
    "content": "The `uibDateParser` is what the `uib-datepicker` uses internally to parse the dates. You can use it standalone by injecting the `uibDateParser` service where you need it.\n\nThe public API for the dateParser is a single method called `parse`.\n\nCertain format codes support i18n. Check this [guide](https://docs.angularjs.org/guide/i18n) for more information.\n\n### uibDateParser's parse function\n\n##### parameters\n\n* `input`\n  _(Type: `string`, Example: `2004/Sep/4`)_ -\n  The input date to parse.\n\n* `format`\n  _(Type: `string`, Example: `yyyy/MMM/d`)_ -\n  The format we want to use. Check all the supported formats below.\n\n* `baseDate`\n  _(Type: `Date`, Example: `new Date()`)_ -\n  If you want to parse a date but maintain the timezone, you can pass an existing date here.\n\n##### return\n\n* If the specified input matches the format, a new date with the input will be returned, otherwise, it will return undefined.\n\n### uibDateParser's format codes\n\n* `yyyy`\n  _(Example: `2015`)_ -\n  Parses a 4 digits year.\n\n* `yy`\n  _(Example: `15`)_ -\n  Parses a 2 digits year.\n\n* `y`\n  _(Example: `15`)_ -\n  Parses a year with 1, 2, 3, or 4 digits.\n\n* `MMMM`\n  _(Example: `February`, i18n support)_ -\n  Parses the full name of a month.\n\n* `MMM`\n  _(Example: `Feb`, i18n support)_ -\n  Parses the short name of a month.\n\n* `MM`\n  _(Example: `12`, Leading 0)_ -\n  Parses a numeric month.\n\n* `M`\n  _(Example: `3`)_ -\n  Parses a numeric month.\n\n* `M!`\n  _(Example: `3` or `03`)_ -\n  Parses a numeric month, but allowing an optional leading zero\n\n* `LLLL`\n  _(Example: `February`, i18n support)_ - Stand-alone month in year (January-December). Requires Angular version 1.5.1 or higher.\n\n* `dd`\n  _(Example: `05`, Leading 0)_ -\n  Parses a numeric day.\n\n* `d`\n  _(Example: `5`)_ -\n  Parses a numeric day.\n\n* `d!`\n  _(Example: `3` or `03`)_ -\n  Parses a numeric day, but allowing an optional leading zero\n\n* `EEEE`\n  _(Example: `Sunday`, i18n support)_ -\n  Parses the full name of a day.\n\n* `EEE`\n  _(Example: `Mon`, i18n support)_ -\n  Parses the short name of a day.\n\n* `HH`\n  _(Example: `14`, Leading 0)_ -\n  Parses a 24 hours time.\n\n* `H`\n  _(Example: `3`)_ -\n  Parses a 24 hours time.\n\n* `hh`\n  _(Example: `11`, Leading 0)_ -\n  Parses a 12 hours time.\n\n* `h`\n  _(Example: `3`)_ -\n  Parses a 12 hours time.\n\n* `mm`\n  _(Example: `09`, Leading 0)_ -\n  Parses the minutes.\n\n* `m`\n  _(Example: `3`)_ -\n  Parses the minutes.\n\n* `sss`\n  _(Example: `094`, Leading 0)_ -\n  Parses the milliseconds.\n\n* `ss`\n  _(Example: `08`, Leading 0)_ -\n  Parses the seconds.\n\n* `s`\n  _(Example: `22`)_ -\n  Parses the seconds.\n\n* `a`\n  _(Example: `10AM`)_ -\n  Parses a 12 hours time with AM/PM.\n\n* `Z`\n  _(Example: `-0800`)_ -\n  Parses the timezone offset in a signed 4 digit representation\n\n* `ww`\n  _(Example: `03`, Leading 0)_ -\n  Parses the week number\n\n* `w`\n  _(Example: `03`)_ -\n  Parses the week number\n\n* `G`, `GG`, `GGG`\n  _(Example: `AD`)_ -\n  Parses the era (`AD` or `BC`)\n* `GGGG`\n  _(Example: `Anno Domini`)_ -\n  Parses the long form of the era (`Anno Domini` or `Before Christ`)\n\n\\* The ones marked with `Leading 0`, needs a leading 0 for values less than 10. Exception being milliseconds which needs it for values under 100.\n\n\\** It also supports `fullDate|longDate|medium|mediumDate|mediumTime|short|shortDate|shortTime` as the format for parsing.\n\n\\*** It supports template literals as a string between the single quote `'` character, i.e. `'The Date is' MM/DD/YYYY`. If one wants the literal single quote character, one must use `''''`.\n"
  },
  {
    "path": "src/dateparser/docs/demo.html",
    "content": "<div ng-controller=\"DateParserDemoCtrl\">\n  <h4>Formatting codes playground</h4>\n  <p class=\"form-group\">\n    <label>Define your format</label>\n    <input type=\"text\" ng-model=\"format\" class=\"form-control\">\n  </p>\n  <p class=\"form-group\">\n    <label>Result</label>\n    <input type=\"text\" class=\"form-control\" uib-datepicker-popup=\"{{format}}\" ng-model=\"date\" />\n  </p>\n</div>\n"
  },
  {
    "path": "src/dateparser/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('DateParserDemoCtrl', function ($scope, uibDateParser) {\n  $scope.format = 'yyyy/MM/dd';\n  $scope.date = new Date();\n});\n"
  },
  {
    "path": "src/dateparser/index.js",
    "content": "require('./dateparser');\n\nvar MODULE_NAME = 'ui.bootstrap.module.dateparser';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.dateparser']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/dateparser/test/dateparser.spec.js",
    "content": "describe('date parser', function() {\n  var dateParser, oldDate;\n\n  beforeEach(module('ui.bootstrap.dateparser'));\n  beforeEach(inject(function (uibDateParser) {\n    dateParser = uibDateParser;\n    oldDate = new Date(1, 2, 6);\n    oldDate.setFullYear(1);\n  }));\n\n  function expectFilter(date, format, display) {\n    expect(dateParser.filter(date, format)).toEqual(display);\n  }\n\n  function expectParse(input, format, date) {\n    expect(dateParser.parse(input, format)).toEqual(date);\n  }\n\n  function expectBaseParse(input, format, baseDate, date) {\n    expect(dateParser.parse(input, format, baseDate)).toEqual(date);\n  }\n\n  describe('filter', function() {\n    it('should work correctly for `dd`, `MM`, `yyyy`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy', '17.11.2013');\n      expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.yyyy', '31.12.2013');\n      expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-yyyy', '08-03-1991');\n      expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/yyyy', '03/05/1980');\n      expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/yyyy', '10.01/1983');\n      expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-yyyy', '11-09-1980');\n      expectFilter(new Date(2011, 1, 5, 0), 'yyyy/MM/dd', '2011/02/05');\n      expectFilter(oldDate, 'yyyy/MM/dd', '0001/03/06');\n    });\n\n    it('should work correctly for `yy`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yy', '17.11.13');\n      expectFilter(new Date(2011, 4, 2, 0), 'dd-MM-yy', '02-05-11');\n      expectFilter(new Date(2080, 1, 5, 0), 'MM/dd/yy', '02/05/80');\n      expectFilter(new Date(2055, 1, 5, 0), 'yy/MM/dd', '55/02/05');\n      expectFilter(new Date(2013, 7, 11, 0), 'dd-MM-yy', '11-08-13');\n    });\n\n    it('should work correctly for `y`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.y', '17.11.2013');\n      expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.y', '31.12.2013');\n      expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-y', '08-03-1991');\n      expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/y', '03/05/1980');\n      expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/y', '10.01/1983');\n      expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-y', '11-09-1980');\n      expectFilter(new Date(2011, 1, 5, 0), 'y/MM/dd', '2011/02/05');\n    });\n\n    it('should work correctly for `MMMM`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'dd.MMMM.yy', '17.November.13');\n      expectFilter(new Date(1980, 2, 5, 0), 'dd-MMMM-yyyy', '05-March-1980');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/dd/yyyy', 'February/05/1980');\n      expectFilter(new Date(1949, 11, 20, 0), 'yyyy/MMMM/dd', '1949/December/20');\n      expectFilter(oldDate, 'yyyy/MMMM/dd', '0001/March/06');\n    });\n\n    it('should work correctly for `MMM`', function() {\n      expectFilter(new Date(2010, 8, 30, 0), 'dd.MMM.yy', '30.Sep.10');\n      expectFilter(new Date(2011, 4, 2, 0), 'dd-MMM-yy', '02-May-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMM/dd/yyyy', 'Feb/05/1980');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMM/dd', '1955/Feb/05');\n      expectFilter(oldDate, 'yyyy/MMM/dd', '0001/Mar/06');\n    });\n\n    it('should work correctly for `M`', function() {\n      expectFilter(new Date(2013, 7, 11, 0), 'M/dd/yyyy', '8/11/2013');\n      expectFilter(new Date(2005, 10, 7, 0), 'dd.M.yy', '07.11.05');\n      expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'M/dd/yyyy', '2/05/1980');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M/dd', '1955/2/05');\n      expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11');\n    });\n\n    it('should work correctly for `M!`', function() {\n      expectFilter(new Date(2013, 7, 11, 0), 'M!/dd/yyyy', '08/11/2013');\n      expectFilter(new Date(2005, 10, 7, 0), 'dd.M!.yy', '07.11.05');\n      expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'M!/dd/yyyy', '02/05/1980');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M!/dd', '1955/02/05');\n      expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');\n      expectFilter(oldDate, 'yyyy/M!/dd', '0001/03/06');\n    });\n    \n    it('should work correctly for `LLLL`', function() {\n      expectFilter(new Date(2013, 7, 24, 0), 'LLLL/dd/yyyy', 'August/24/2013');\n      expectFilter(new Date(2004, 10, 7, 0), 'dd.LLLL.yy', '07.November.04');\n      expectFilter(new Date(2011, 4, 18, 0), 'dd-LLLL-yy', '18-May-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'LLLL/dd/yyyy', 'February/05/1980');\n      expectFilter(new Date(1955, 2, 5, 0), 'yyyy/LLLL/dd', '1955/March/05');\n      expectFilter(new Date(2011, 5, 2, 0), 'dd-LLLL-yy', '02-June-11');\n      expectFilter(oldDate, 'yyyy/LLLL/dd', '0001/March/06');\n    });\n\n    it('should work correctly for `d`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy', '17.November.13');\n      expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy', '8-March-1991');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy', 'February/5/1980');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d', '1955/February/5');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy', '11-08-13');\n      expectFilter(oldDate, 'yyyy/MM/d', '0001/03/6');\n    });\n\n    it('should work correctly for `d!`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd!.MMMM.yy', '17.November.13');\n      expectFilter(new Date(1991, 2, 8, 0), 'd!-MMMM-yyyy', '08-March-1991');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d!/yyyy', 'February/05/1980');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d!', '1955/February/05');\n      expectFilter(new Date(2013, 7, 11, 0), 'd!-MM-yy', '11-08-13');\n      expectFilter(oldDate, 'yyyy/MM/d!', '0001/03/06');\n    });\n\n    it('should work correctly for `EEEE`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'EEEE.d.MMMM.yy', 'Sunday.17.November.13');\n      expectFilter(new Date(1991, 2, 8, 0), 'd-EEEE-MMMM-yyyy', '8-Friday-March-1991');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEEE', 'February/5/1980/Tuesday');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEEE/MMMM/d', '1955/Saturday/February/5');\n    });\n\n    it('should work correctly for `EEE`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'EEE.d.MMMM.yy', 'Sun.17.November.13');\n      expectFilter(new Date(1991, 2, 8, 0), 'd-EEE-MMMM-yyyy', '8-Fri-March-1991');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEE', 'February/5/1980/Tue');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEE/MMMM/d', '1955/Sat/February/5');\n    });\n\n    it('should work correctly for `HH`', function() {\n      expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.HH', '22.March.15.22');\n      expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-HH', '8-March-1991-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/HH', 'February/5/1980/00');\n      expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d HH', '1955/February/5 03');\n      expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy HH', '11-08-13 23');\n    });\n\n    it('should work correctly for `H`', function() {\n      expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.H', '22.March.15.22');\n      expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-H', '8-March-1991-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/H', 'February/5/1980/0');\n      expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d H', '1955/February/5 3');\n      expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy H', '11-08-13 23');\n    });\n\n    it('should work correctly for `hh`', function() {\n      expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.hh', '22.March.15.12');\n      expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hh', '8-March-1991-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hh', 'February/5/1980/12');\n      expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hh', '1955/February/5 03');\n      expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hh', '11-08-13 09');\n    });\n\n    it('should work correctly for `h`', function() {\n      expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.h', '22.March.15.12');\n      expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-h', '8-March-1991-11');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/h', 'February/5/1980/12');\n      expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d h', '1955/February/5 3');\n      expectFilter(new Date(2013, 7, 11, 3), 'd-MM-yy h', '11-08-13 3');\n    });\n\n    it('should work correctly for `mm`', function() {\n      expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.mm', '22.March.15.22');\n      expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-mm', '8-March-1991-59');\n      expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/mm', 'February/5/1980/00');\n      expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d mm', '1955/February/5 03');\n      expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy mm', '11-08-13 46');\n      expectFilter(new Date(2015, 2, 22, 22, 33), 'd.MMMM.yy.HH:mm', '22.March.15.22:33');\n      expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:mm', '22.March.15.2:01');\n    });\n\n    it('should work correctly for `m`', function() {\n      expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.m', '22.March.15.22');\n      expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-m', '8-March-1991-59');\n      expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/m', 'February/5/1980/0');\n      expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d m', '1955/February/5 3');\n      expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy m', '11-08-13 46');\n      expectFilter(new Date(2015, 2, 22, 22, 3), 'd.MMMM.yy.HH:m', '22.March.15.22:3');\n      expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:m', '22.March.15.2:1');\n    });\n\n    it('should work correctly for `sss`', function() {\n      expectFilter(new Date(2015, 2, 22, 0, 0, 0, 123), 'd.MMMM.yy.sss', '22.March.15.123');\n      expectFilter(new Date(1991, 2, 8, 0, 0, 0, 59), 'd-MMMM-yyyy-sss', '8-March-1991-059');\n      expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/sss', 'February/5/1980/000');\n      expectFilter(new Date(1955, 1, 5, 0, 0, 0, 3), 'yyyy/MMMM/d sss', '1955/February/5 003');\n      expectFilter(new Date(2013, 7, 11, 0, 0, 0, 46), 'd-MM-yy sss', '11-08-13 046');\n      expectFilter(new Date(2015, 2, 22, 22, 33, 0, 44), 'd.MMMM.yy.HH:mm:sss', '22.March.15.22:33:044');\n      expectFilter(new Date(2015, 2, 22, 0, 0, 0, 1), 'd.MMMM.yy.H:m:sss', '22.March.15.0:0:001');\n    });\n\n    it('should work correctly for `ss`', function() {\n      expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.ss', '22.March.15.22');\n      expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-ss', '8-March-1991-59');\n      expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/ss', 'February/5/1980/00');\n      expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d ss', '1955/February/5 03');\n      expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy ss', '11-08-13 46');\n      expectFilter(new Date(2015, 2, 22, 22, 33, 44), 'd.MMMM.yy.HH:mm:ss', '22.March.15.22:33:44');\n      expectFilter(new Date(2015, 2, 22, 0, 0, 1), 'd.MMMM.yy.H:m:ss', '22.March.15.0:0:01');\n    });\n\n    it('should work correctly for `s`', function() {\n      expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.s', '22.March.15.22');\n      expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-s', '8-March-1991-59');\n      expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/s', 'February/5/1980/0');\n      expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d s', '1955/February/5 3');\n      expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy s', '11-08-13 46');\n      expectFilter(new Date(2015, 2, 22, 22, 33, 4), 'd.MMMM.yy.HH:mm:s', '22.March.15.22:33:4');\n      expectFilter(new Date(2015, 2, 22, 22, 3, 4), 'd.MMMM.yy.HH:m:s', '22.March.15.22:3:4');\n    });\n\n    it('should work correctly for `a`', function() {\n      expectFilter(new Date(2015, 2, 22, 10), 'd.MMMM.yy.hha', '22.March.15.10AM');\n      expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.hha', '22.March.15.10PM');\n      expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hha', '8-March-1991-11AM');\n      expectFilter(new Date(1991, 2, 8, 23), 'd-MMMM-yyyy-hha', '8-March-1991-11PM');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hha', 'February/5/1980/12AM');\n      expectFilter(new Date(1980, 1, 5, 12), 'MMMM/d/yyyy/hha', 'February/5/1980/12PM');\n      expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hha', '1955/February/5 03AM');\n      expectFilter(new Date(1955, 1, 5, 15), 'yyyy/MMMM/d hha', '1955/February/5 03PM');\n      expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hha', '11-08-13 09AM');\n      expectFilter(new Date(2013, 7, 11, 21), 'd-MM-yy hha', '11-08-13 09PM');\n    });\n\n    it('should work correctly for `ww`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.ww', '17.November.13.47');\n      expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-ww', '8-March-1991-10');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/ww', 'February/5/1980/06');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/ww', '1955/February/5/05');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy ww', '11-08-13 33');\n      expectFilter(oldDate, 'yyyy/MM/d ww', '0001/03/6 10');\n    });\n\n    it('should work correctly for `w`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.w', '17.November.13.47');\n      expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-w', '8-March-1991-10');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/w', 'February/5/1980/6');\n      expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/w', '1955/February/5/5');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy w', '11-08-13 33');\n      expectFilter(oldDate, 'yyyy/MM/d w', '0001/03/6 10');\n    });\n\n    it('should work correctly for `G`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.G', '17.November.13.AD');\n      expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-G', '8-March-1991-BC');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/G', 'February/5/1980/AD');\n      expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/G', '1955/February/5/BC');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy G', '11-08-13 AD');\n    });\n\n    it('should work correctly for `GG`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GG', '17.November.13.AD');\n      expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GG', '8-March-1991-BC');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GG', 'February/5/1980/AD');\n      expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GG', '1955/February/5/BC');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GG', '11-08-13 AD');\n    });\n\n    it('should work correctly for `GGG`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGG', '17.November.13.AD');\n      expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGG', '8-March-1991-BC');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGG', 'February/5/1980/AD');\n      expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGG', '1955/February/5/BC');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGG', '11-08-13 AD');\n    });\n\n    it('should work correctly for `GGGG`', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGGG', '17.November.13.Anno Domini');\n      expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGGG', '8-March-1991-Before Christ');\n      expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGGG', 'February/5/1980/Anno Domini');\n      expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGGG', '1955/February/5/Before Christ');\n      expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGGG', '11-08-13 Anno Domini');\n    });\n\n    it('should work correctly for literal text', function() {\n      expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy foo', '17.11.2013 foo');\n    });\n  });\n\n  describe('with custom formats', function() {\n    it('should work correctly for `dd`, `MM`, `yyyy`', function() {\n      expectParse('17.11.2013', 'dd.MM.yyyy', new Date(2013, 10, 17, 0));\n      expectParse('31.12.2013', 'dd.MM.yyyy', new Date(2013, 11, 31, 0));\n      expectParse('08-03-1991', 'dd-MM-yyyy', new Date(1991, 2, 8, 0));\n      expectParse('03/05/1980', 'MM/dd/yyyy', new Date(1980, 2, 5, 0));\n      expectParse('10.01/1983', 'dd.MM/yyyy', new Date(1983, 0, 10, 0));\n      expectParse('11-09-1980', 'MM-dd-yyyy', new Date(1980, 10, 9, 0));\n      expectParse('2011/02/05', 'yyyy/MM/dd', new Date(2011, 1, 5, 0));\n      expectParse('0001/03/06', 'yyyy/MM/dd', oldDate);\n    });\n\n    it('should work correctly for `yy`', function() {\n      expectParse('17.11.13', 'dd.MM.yy', new Date(2013, 10, 17, 0));\n      expectParse('02-05-11', 'dd-MM-yy', new Date(2011, 4, 2, 0));\n      expectParse('02/05/80', 'MM/dd/yy', new Date(1980, 1, 5, 0));\n      expectParse('55/02/05', 'yy/MM/dd', new Date(2055, 1, 5, 0));\n      expectParse('11-08-13', 'dd-MM-yy', new Date(2013, 7, 11, 0));\n    });\n\n    it('should use `68` as the pivot year for `yy`', function() {\n      expectParse('17.11.68', 'dd.MM.yy', new Date(2068, 10, 17, 0));\n      expectParse('17.11.69', 'dd.MM.yy', new Date(1969, 10, 17, 0));\n    });\n\n    it('should work correctly for `y`', function() {\n      expectParse('17.11.2013', 'dd.MM.y', new Date(2013, 10, 17, 0));\n      expectParse('31.12.2013', 'dd.MM.y', new Date(2013, 11, 31, 0));\n      expectParse('08-03-1991', 'dd-MM-y', new Date(1991, 2, 8, 0));\n      expectParse('03/05/1980', 'MM/dd/y', new Date(1980, 2, 5, 0));\n      expectParse('10.01/1983', 'dd.MM/y', new Date(1983, 0, 10, 0));\n      expectParse('11-09-1980', 'MM-dd-y', new Date(1980, 10, 9, 0));\n      expectParse('2011/02/05', 'y/MM/dd', new Date(2011, 1, 5, 0));\n    });\n\n    it('should work correctly for `MMMM`', function() {\n      expectParse('17.November.13', 'dd.MMMM.yy', new Date(2013, 10, 17, 0));\n      expectParse('05-March-1980', 'dd-MMMM-yyyy', new Date(1980, 2, 5, 0));\n      expectParse('February/05/1980', 'MMMM/dd/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1949/December/20', 'yyyy/MMMM/dd', new Date(1949, 11, 20, 0));\n      expectParse('0001/March/06', 'yyyy/MMMM/dd', oldDate);\n    });\n\n    it('should work correctly for `MMM`', function() {\n      expectParse('30.Sep.10', 'dd.MMM.yy', new Date(2010, 8, 30, 0));\n      expectParse('02-May-11', 'dd-MMM-yy', new Date(2011, 4, 2, 0));\n      expectParse('Feb/05/1980', 'MMM/dd/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/Feb/05', 'yyyy/MMM/dd', new Date(1955, 1, 5, 0));\n      expectParse('0001/Mar/06', 'yyyy/MMM/dd', oldDate);\n    });\n\n    it('should work correctly for `M`', function() {\n      expectParse('8/11/2013', 'M/dd/yyyy', new Date(2013, 7, 11, 0));\n      expectParse('07.11.05', 'dd.M.yy', new Date(2005, 10, 7, 0));\n      expectParse('02-5-11', 'dd-M-yy', new Date(2011, 4, 2, 0));\n      expectParse('2/05/1980', 'M/dd/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/2/05', 'yyyy/M/dd', new Date(1955, 1, 5, 0));\n      expectParse('02-5-11', 'dd-M-yy', new Date(2011, 4, 2, 0));\n    });\n\n    it('should work correctly for `M!`', function() {\n      expectParse('8/11/2013', 'M!/dd/yyyy', new Date(2013, 7, 11, 0));\n      expectParse('07.11.05', 'dd.M!.yy', new Date(2005, 10, 7, 0));\n      expectParse('02-5-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));\n      expectParse('2/05/1980', 'M!/dd/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/2/05', 'yyyy/M!/dd', new Date(1955, 1, 5, 0));\n      expectParse('02-5-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));\n      expectParse('0001/3/06', 'yyyy/M!/dd', oldDate);\n\n      expectParse('08/11/2013', 'M!/dd/yyyy', new Date(2013, 7, 11, 0));\n      expectParse('07.11.05', 'dd.M!.yy', new Date(2005, 10, 7, 0));\n      expectParse('02-05-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));\n      expectParse('02/05/1980', 'M!/dd/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/02/05', 'yyyy/M!/dd', new Date(1955, 1, 5, 0));\n      expectParse('02-05-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));\n      expectParse('0001/03/06', 'yyyy/M!/dd', oldDate);\n    });\n\n    it('should work correctly for `d`', function() {\n      expectParse('17.November.13', 'd.MMMM.yy', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991', 'd-MMMM-yyyy', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980', 'MMMM/d/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5', 'yyyy/MMMM/d', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13', 'd-MM-yy', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6', 'yyyy/MM/d', oldDate);\n    });\n\n    it('should work correctly for `d!`', function() {\n      expectParse('17.November.13', 'd!.MMMM.yy', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991', 'd!-MMMM-yyyy', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980', 'MMMM/d!/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5', 'yyyy/MMMM/d!', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13', 'd!-MM-yy', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6', 'yyyy/MM/d!', oldDate);\n\n      expectParse('17.November.13', 'd!.MMMM.yy', new Date(2013, 10, 17, 0));\n      expectParse('08-March-1991', 'd!-MMMM-yyyy', new Date(1991, 2, 8, 0));\n      expectParse('February/05/1980', 'MMMM/d!/yyyy', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/05', 'yyyy/MMMM/d!', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13', 'd!-MM-yy', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/06', 'yyyy/MM/d!', oldDate);\n    });\n\n    it('should work correctly for `EEEE`', function() {\n      expectParse('Sunday.17.November.13', 'EEEE.d.MMMM.yy', new Date(2013, 10, 17, 0));\n      expectParse('8-Friday-March-1991', 'd-EEEE-MMMM-yyyy', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/Tuesday', 'MMMM/d/yyyy/EEEE', new Date(1980, 1, 5, 0));\n      expectParse('1955/Saturday/February/5', 'yyyy/EEEE/MMMM/d', new Date(1955, 1, 5, 0));\n    });\n\n    it('should work correctly for `EEE`', function() {\n      expectParse('Sun.17.November.13', 'EEE.d.MMMM.yy', new Date(2013, 10, 17, 0));\n      expectParse('8-Fri-March-1991', 'd-EEE-MMMM-yyyy', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/Tue', 'MMMM/d/yyyy/EEE', new Date(1980, 1, 5, 0));\n      expectParse('1955/Sat/February/5', 'yyyy/EEE/MMMM/d', new Date(1955, 1, 5, 0));\n    });\n\n    it('should work correctly for `HH`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.HH', new Date(2015, 2, 22, 22));\n      expectParse('8-March-1991-11', 'd-MMMM-yyyy-HH', new Date(1991, 2, 8, 11));\n      expectParse('February/5/1980/00', 'MMMM/d/yyyy/HH', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5 03', 'yyyy/MMMM/d HH', new Date(1955, 1, 5, 3));\n      expectParse('11-08-13 23', 'd-MM-yy HH', new Date(2013, 7, 11, 23));\n    });\n\n    it('should work correctly for `H`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.H', new Date(2015, 2, 22, 22));\n      expectParse('8-March-1991-11', 'd-MMMM-yyyy-H', new Date(1991, 2, 8, 11));\n      expectParse('February/5/1980/0', 'MMMM/d/yyyy/H', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5 3', 'yyyy/MMMM/d H', new Date(1955, 1, 5, 3));\n      expectParse('11-08-13 23', 'd-MM-yy H', new Date(2013, 7, 11, 23));\n    });\n\n    it('should work correctly for `hh`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.hh', undefined);\n      expectParse('22.March.15.12', 'd.MMMM.yy.hh', new Date(2015, 2, 22, 12));\n      expectParse('8-March-1991-11', 'd-MMMM-yyyy-hh', new Date(1991, 2, 8, 11));\n      expectParse('February/5/1980/00', 'MMMM/d/yyyy/hh', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5 03', 'yyyy/MMMM/d hh', new Date(1955, 1, 5, 3));\n      expectParse('11-08-13 23', 'd-MM-yy hh', undefined);\n      expectParse('11-08-13 09', 'd-MM-yy hh', new Date(2013, 7, 11, 9));\n    });\n\n    it('should work correctly for `h`', function() {\n      expectParse('22.March.15.12', 'd.MMMM.yy.h', new Date(2015, 2, 22, 12));\n      expectParse('8-March-1991-11', 'd-MMMM-yyyy-h', new Date(1991, 2, 8, 11));\n      expectParse('February/5/1980/0', 'MMMM/d/yyyy/h', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5 3', 'yyyy/MMMM/d h', new Date(1955, 1, 5, 3));\n      expectParse('11-08-13 3', 'd-MM-yy h', new Date(2013, 7, 11, 3));\n    });\n\n    it('should work correctly for `mm`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.mm', new Date(2015, 2, 22, 0, 22));\n      expectParse('8-March-1991-59', 'd-MMMM-yyyy-mm', new Date(1991, 2, 8, 0, 59));\n      expectParse('February/5/1980/00', 'MMMM/d/yyyy/mm', new Date(1980, 1, 5, 0, 0));\n      expectParse('1955/February/5 03', 'yyyy/MMMM/d mm', new Date(1955, 1, 5, 0, 3));\n      expectParse('11-08-13 46', 'd-MM-yy mm', new Date(2013, 7, 11, 0, 46));\n      expectParse('22.March.15.22:33', 'd.MMMM.yy.HH:mm', new Date(2015, 2, 22, 22, 33));\n      expectParse('22.March.15.2:01', 'd.MMMM.yy.H:mm', new Date(2015, 2, 22, 2, 1));\n    });\n\n    it('should work correctly for `m`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.m', new Date(2015, 2, 22, 0, 22));\n      expectParse('8-March-1991-59', 'd-MMMM-yyyy-m', new Date(1991, 2, 8, 0, 59));\n      expectParse('February/5/1980/0', 'MMMM/d/yyyy/m', new Date(1980, 1, 5, 0, 0));\n      expectParse('1955/February/5 3', 'yyyy/MMMM/d m', new Date(1955, 1, 5, 0, 3));\n      expectParse('11-08-13 46', 'd-MM-yy m', new Date(2013, 7, 11, 0, 46));\n      expectParse('22.March.15.22:3', 'd.MMMM.yy.HH:m', new Date(2015, 2, 22, 22, 3));\n      expectParse('22.March.15.2:1', 'd.MMMM.yy.H:m', new Date(2015, 2, 22, 2, 1));\n    });\n\n    it('should work correctly for `sss`', function() {\n      expectParse('22.March.15.123', 'd.MMMM.yy.sss', new Date(2015, 2, 22, 0, 0, 0, 123));\n      expectParse('8-March-1991-059', 'd-MMMM-yyyy-sss', new Date(1991, 2, 8, 0, 0, 0, 59));\n      expectParse('February/5/1980/000', 'MMMM/d/yyyy/sss', new Date(1980, 1, 5, 0, 0, 0));\n      expectParse('1955/February/5 003', 'yyyy/MMMM/d sss', new Date(1955, 1, 5, 0, 0, 0, 3));\n      expectParse('11-08-13 046', 'd-MM-yy sss', new Date(2013, 7, 11, 0, 0, 0, 46));\n      expectParse('22.March.15.22:33:044', 'd.MMMM.yy.HH:mm:sss', new Date(2015, 2, 22, 22, 33, 0, 44));\n      expectParse('22.March.15.0:0:001', 'd.MMMM.yy.H:m:sss', new Date(2015, 2, 22, 0, 0, 0, 1));\n    });\n\n    it('should work correctly for `ss`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.ss', new Date(2015, 2, 22, 0, 0, 22));\n      expectParse('8-March-1991-59', 'd-MMMM-yyyy-ss', new Date(1991, 2, 8, 0, 0, 59));\n      expectParse('February/5/1980/00', 'MMMM/d/yyyy/ss', new Date(1980, 1, 5, 0, 0, 0));\n      expectParse('1955/February/5 03', 'yyyy/MMMM/d ss', new Date(1955, 1, 5, 0, 0, 3));\n      expectParse('11-08-13 46', 'd-MM-yy ss', new Date(2013, 7, 11, 0, 0, 46));\n      expectParse('22.March.15.22:33:44', 'd.MMMM.yy.HH:mm:ss', new Date(2015, 2, 22, 22, 33, 44));\n      expectParse('22.March.15.0:0:01', 'd.MMMM.yy.H:m:ss', new Date(2015, 2, 22, 0, 0, 1));\n    });\n\n    it('should work correctly for `s`', function() {\n      expectParse('22.March.15.22', 'd.MMMM.yy.s', new Date(2015, 2, 22, 0, 0, 22));\n      expectParse('8-March-1991-59', 'd-MMMM-yyyy-s', new Date(1991, 2, 8, 0, 0, 59));\n      expectParse('February/5/1980/0', 'MMMM/d/yyyy/s', new Date(1980, 1, 5, 0, 0, 0));\n      expectParse('1955/February/5 3', 'yyyy/MMMM/d s', new Date(1955, 1, 5, 0, 0, 3));\n      expectParse('11-08-13 46', 'd-MM-yy s', new Date(2013, 7, 11, 0, 0, 46));\n      expectParse('22.March.15.22:33:4', 'd.MMMM.yy.HH:mm:s', new Date(2015, 2, 22, 22, 33, 4));\n      expectParse('22.March.15.22:3:4', 'd.MMMM.yy.HH:m:s', new Date(2015, 2, 22, 22, 3, 4));\n    });\n\n    it('should work correctly for `a`', function() {\n      expectParse('22.March.15.10AM', 'd.MMMM.yy.hha', new Date(2015, 2, 22, 10));\n      expectParse('22.March.15.10PM', 'd.MMMM.yy.hha', new Date(2015, 2, 22, 22));\n      expectParse('8-March-1991-11AM', 'd-MMMM-yyyy-hha', new Date(1991, 2, 8, 11));\n      expectParse('8-March-1991-11PM', 'd-MMMM-yyyy-hha', new Date(1991, 2, 8, 23));\n      expectParse('February/5/1980/12AM', 'MMMM/d/yyyy/hha', new Date(1980, 1, 5, 0));\n      expectParse('February/5/1980/12PM', 'MMMM/d/yyyy/hha', new Date(1980, 1, 5, 12));\n      expectParse('1955/February/5 03AM', 'yyyy/MMMM/d hha', new Date(1955, 1, 5, 3));\n      expectParse('1955/February/5 03PM', 'yyyy/MMMM/d hha', new Date(1955, 1, 5, 15));\n      expectParse('11-08-13 09AM', 'd-MM-yy hha', new Date(2013, 7, 11, 9));\n      expectParse('11-08-13 09PM', 'd-MM-yy hha', new Date(2013, 7, 11, 21));\n    });\n\n    it('should work correctly for `Z`', function() {\n      expectParse('22.March.15 -0700', 'd.MMMM.yy Z', new Date(2015, 2, 21, 17, 0, 0));\n      expectParse('8-March-1991 +0800', 'd-MMMM-yyyy Z', new Date(1991, 2, 8, 8, 0, 0));\n      expectParse('February/5/1980 -0200', 'MMMM/d/yyyy Z', new Date(1980, 1, 4, 22, 0, 0));\n      expectParse('1955/February/5 +0400', 'yyyy/MMMM/d Z', new Date(1955, 1, 5, 4, 0, 0));\n      expectParse('11-08-13 -1234', 'd-MM-yy Z', new Date(2013, 7, 10, 11, 26, 0));\n      expectParse('22.March.15.22:33:4 -1200', 'd.MMMM.yy.HH:mm:s Z', new Date(2015, 2, 22, 10, 33, 4));\n      expectParse('22.March.15.22:3:4 +1500', 'd.MMMM.yy.HH:m:s Z', new Date(2015, 2, 23, 13, 3, 4));\n    });\n\n    it('should work correctly for `ww`', function() {\n      expectParse('17.November.13.45', 'd.MMMM.yy.ww', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991-09', 'd-MMMM-yyyy-ww', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/05', 'MMMM/d/yyyy/ww', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5/04', 'yyyy/MMMM/d/ww', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13 44', 'd-MM-yy ww', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6 10', 'yyyy/MM/d ww', oldDate);\n    });\n\n    it('should work correctly for `w`', function() {\n      expectParse('17.November.13.45', 'd.MMMM.yy.w', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991-9', 'd-MMMM-yyyy-w', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/5', 'MMMM/d/yyyy/w', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5/4', 'yyyy/MMMM/d/w', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13 44', 'd-MM-yy w', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6 10', 'yyyy/MM/d w', oldDate);\n    });\n\n    it('should work correctly for `G`', function() {\n      expectParse('17.November.13.AD', 'd.MMMM.yy.G', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991-BC', 'd-MMMM-yyyy-G', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/AD', 'MMMM/d/yyyy/G', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5/BC', 'yyyy/MMMM/d/G', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13 AD', 'd-MM-yy G', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6 BC', 'yyyy/MM/d G', oldDate);\n    });\n\n    it('should work correctly for `GG`', function() {\n      expectParse('17.November.13.AD', 'd.MMMM.yy.GG', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991-BC', 'd-MMMM-yyyy-GG', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/AD', 'MMMM/d/yyyy/GG', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5/BC', 'yyyy/MMMM/d/GG', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13 AD', 'd-MM-yy GG', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6 BC', 'yyyy/MM/d GG', oldDate);\n    });\n\n    it('should work correctly for `GGG`', function() {\n      expectParse('17.November.13.AD', 'd.MMMM.yy.GGG', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991-BC', 'd-MMMM-yyyy-GGG', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/AD', 'MMMM/d/yyyy/GGG', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5/BC', 'yyyy/MMMM/d/GGG', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13 AD', 'd-MM-yy GGG', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6 BC', 'yyyy/MM/d GGG', oldDate);\n    });\n\n    it('should work correctly for `GGGG`', function() {\n      expectParse('17.November.13.Anno Domini', 'd.MMMM.yy.GGGG', new Date(2013, 10, 17, 0));\n      expectParse('8-March-1991-Before Christ', 'd-MMMM-yyyy-GGGG', new Date(1991, 2, 8, 0));\n      expectParse('February/5/1980/Anno Domini', 'MMMM/d/yyyy/GGGG', new Date(1980, 1, 5, 0));\n      expectParse('1955/February/5/Before Christ', 'yyyy/MMMM/d/GGGG', new Date(1955, 1, 5, 0));\n      expectParse('11-08-13 Anno Domini', 'd-MM-yy GGGG', new Date(2013, 7, 11, 0));\n      expectParse('0001/03/6 Before Christ', 'yyyy/MM/d GGGG', oldDate);\n    });\n  });\n\n  describe('with predefined formats', function() {\n    it('should work correctly for `shortDate`', function() {\n      expectParse('9/3/10', 'shortDate', new Date(2010, 8, 3, 0));\n    });\n\n    it('should work correctly for `mediumDate`', function() {\n      expectParse('Sep 3, 2010', 'mediumDate', new Date(2010, 8, 3, 0));\n    });\n\n    it('should work correctly for `longDate`', function() {\n      expectParse('September 3, 2010', 'longDate', new Date(2010, 8, 3, 0));\n    });\n\n    it('should work correctly for `fullDate`', function() {\n      expectParse('Friday, September 3, 2010', 'fullDate', new Date(2010, 8, 3, 0));\n    });\n  });\n\n  describe('with value literals', function() {\n    describe('filter', function() {\n      it('should work with multiple literals', function() {\n        expect(dateParser.filter(new Date(2013, 0, 29), 'd \\'de\\' MMMM \\'de\\' y')).toEqual('29 de January de 2013');\n      });\n\n      it('should work with escaped single quote', function() {\n        expect(dateParser.filter(new Date(2015, 2, 22, 12), 'd.MMMM.yy h \\'o\\'\\'clock\\'')).toEqual('22.March.15 12 o\\'clock');\n      });\n\n      it('should work with only a single quote', function() {\n        expect(dateParser.filter(new Date(2015, 2, 22), 'd.MMMM.yy \\'\\'\\'')).toEqual('22.March.15 \\'');\n      });\n\n      it('should work with trailing literal', function() {\n        expect(dateParser.filter(new Date(2013, 0, 1), '\\'year\\' y')).toEqual('year 2013');\n      });\n\n      it('should work without whitespace', function() {\n        expect(dateParser.filter(new Date(2013, 0, 1), '\\'year:\\'y')).toEqual('year:2013');\n      });\n    });\n\n    describe('parse', function() {\n      it('should work with multiple literals', function() {\n        expect(dateParser.parse('29 de January de 2013', 'd \\'de\\' MMMM \\'de\\' y')).toEqual(new Date(2013, 0, 29));\n      });\n\n      it('should work with escaped single quote', function() {\n        expect(dateParser.parse('22.March.15 12 o\\'clock', 'd.MMMM.yy h \\'o\\'\\'clock\\'')).toEqual(new Date(2015, 2, 22, 12));\n      });\n\n      it('should work with only a single quote', function() {\n        expect(dateParser.parse('22.March.15 \\'', 'd.MMMM.yy \\'\\'\\'')).toEqual(new Date(2015, 2, 22));\n      });\n\n      it('should work with trailing literal', function() {\n        expect(dateParser.parse('year 2013', '\\'year\\' y')).toEqual(new Date(2013, 0, 1));\n      });\n\n      it('should work without whitespace', function() {\n        expect(dateParser.parse('year:2013', '\\'year:\\'y')).toEqual(new Date(2013, 0, 1));\n      });\n    });\n  });\n\n  describe('with edge case', function() {\n    it('should not work for invalid number of days in February', function() {\n      expectParse('29.02.2013', 'dd.MM.yyyy', undefined);\n    });\n\n    it('should not work for 0 number of days', function() {\n      expectParse('00.02.2013', 'dd.MM.yyyy', undefined);\n    });\n\n    it('should work for 29 days in February for leap years', function() {\n      expectParse('29.02.2000', 'dd.MM.yyyy', new Date(2000, 1, 29, 0));\n    });\n\n    it('should not work for 31 days for some months', function() {\n      expectParse('31-04-2013', 'dd-MM-yyyy', undefined);\n      expectParse('November 31, 2013', 'MMMM d, yyyy', undefined);\n    });\n  });\n\n  describe('base date', function() {\n    var baseDate;\n\n    beforeEach(function() {\n      baseDate = new Date(2010, 10, 10);\n    });\n\n    it('should pre-initialize our date with a base date', function() {\n      expect(expectBaseParse('2015', 'yyyy', baseDate, new Date(2015, 10, 10)));\n      expect(expectBaseParse('1', 'M', baseDate, new Date(2010, 0, 10)));\n      expect(expectBaseParse('1', 'd', baseDate, new Date(2010, 10, 1)));\n    });\n\n    it('should ignore the base date when it is an invalid date', inject(function($log) {\n      spyOn($log, 'warn');\n      expect(expectBaseParse('30-12', 'dd-MM', new Date('foo'), new Date(1900, 11, 30)));\n      expect(expectBaseParse('30-2015', 'dd-yyyy', 'I am a cat', new Date(2015, 0, 30)));\n      expect($log.warn).toHaveBeenCalledWith('dateparser:', 'baseDate is not a valid date');\n    }));\n  });\n\n  it('should not parse non-string inputs', function() {\n    expectParse(123456, 'dd.MM.yyyy', 123456);\n    var date = new Date();\n    expectParse(date, 'dd.MM.yyyy', date);\n  });\n\n  it('should not parse if no format is specified', function() {\n    expectParse('21.08.1951', '', '21.08.1951');\n  });\n\n  it('should reinitialize when locale changes', inject(function($locale) {\n    spyOn(dateParser, 'init').and.callThrough();\n    expect($locale.id).toBe('en-us');\n\n    $locale.id = 'en-uk';\n\n    dateParser.parse('22.March.15.22', 'd.MMMM.yy.s');\n\n    expect(dateParser.init).toHaveBeenCalled();\n  }));\n\n  describe('timezone functions', function() {\n    describe('toTimezone', function() {\n      it('adjusts date: PST - EST', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toWestDate = dateParser.toTimezone(date, 'PST');\n        var toEastDate = dateParser.toTimezone(date, 'EST');\n        expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 3);\n      });\n\n      it('adjusts date: GMT-0500 - GMT+0500', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toWestDate = dateParser.toTimezone(date, 'GMT-0500');\n        var toEastDate = dateParser.toTimezone(date, 'GMT+0500');\n        expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 10);\n      });\n\n      it('adjusts date: -600 - +600', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toWestDate = dateParser.toTimezone(date, '-600');\n        var toEastDate = dateParser.toTimezone(date, '+600');\n        expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 12);\n      });\n\n      it('tolerates null date', function() {\n        var date = null;\n        var toNullDate = dateParser.toTimezone(date, '-600');\n        expect(toNullDate).toEqual(date);\n      });\n\n      it('tolerates null timezone', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toNullTimezoneDate = dateParser.toTimezone(date, null);\n        expect(toNullTimezoneDate).toEqual(date);\n      });\n    });\n\n    describe('fromTimezone', function() {\n      it('adjusts date: PST - EST', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var fromWestDate = dateParser.fromTimezone(date, 'PST');\n        var fromEastDate = dateParser.fromTimezone(date, 'EST');\n        expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -3);\n      });\n\n      it('adjusts date: GMT-0500 - GMT+0500', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var fromWestDate = dateParser.fromTimezone(date, 'GMT-0500');\n        var fromEastDate = dateParser.fromTimezone(date, 'GMT+0500');\n        expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -10);\n      });\n\n      it('adjusts date: -600 - +600', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var fromWestDate = dateParser.fromTimezone(date, '-600');\n        var fromEastDate = dateParser.fromTimezone(date, '+600');\n        expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -12);\n      });\n\n      it('tolerates null date', function() {\n        var date = null;\n        var toNullDate = dateParser.fromTimezone(date, '-600');\n        expect(toNullDate).toEqual(date);\n      });\n\n      it('tolerates null timezone', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toNullTimezoneDate = dateParser.fromTimezone(date, null);\n        expect(toNullTimezoneDate).toEqual(date);\n      });\n    });\n\n    describe('timezoneToOffset', function() {\n      it('calculates minutes off from current timezone', function() {\n        var offsetMinutesUtc = dateParser.timezoneToOffset('UTC');\n        var offsetMinutesUtcPlus1 = dateParser.timezoneToOffset('GMT+0100');\n        expect(offsetMinutesUtc - offsetMinutesUtcPlus1).toEqual(60);\n      });\n    });\n\n    describe('addDateMinutes', function() {\n      it('adds minutes to a date', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var oneHourMore = dateParser.addDateMinutes(date, 60);\n        expect(oneHourMore).toEqual(new Date('2008-01-01T01:00:00.000Z'));\n      });\n    });\n\n    describe('convertTimezoneToLocal', function() {\n      it('adjusts date: PST - EST', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toWestDate = dateParser.convertTimezoneToLocal(date, 'PST');\n        var toEastDate = dateParser.convertTimezoneToLocal(date, 'EST');\n        expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 3);\n      });\n\n      it('adjusts date: GMT-0500 - GMT+0500', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toWestDate = dateParser.convertTimezoneToLocal(date, 'GMT-0500');\n        var toEastDate = dateParser.convertTimezoneToLocal(date, 'GMT+0500');\n        expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 10);\n      });\n\n      it('adjusts date: -600 - +600', function() {\n        var date = new Date('2008-01-01T00:00:00.000Z');\n        var toWestDate = dateParser.convertTimezoneToLocal(date, '-600');\n        var toEastDate = dateParser.convertTimezoneToLocal(date, '+600');\n        expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 12);\n      });\n    });\n  });\n\n  describe('overrideParser', function() {\n    var twoDigitYearParser = function (value) {\n      this.year = +value + (+value > 30 ? 1900 : 2000);\n    };\n\n    it('should get the current parser', function() {\n      expect(dateParser.getParser('yy')).toBeTruthy();\n    });\n\n    it('should override the parser', function() {\n      dateParser.overrideParser('yy', twoDigitYearParser);\n      expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);\n      expect(dateParser.parse('67', 'yy').getFullYear()).toEqual(1967);\n      expect(dateParser.parse('31', 'yy').getFullYear()).toEqual(1931);\n      expect(dateParser.parse('30', 'yy').getFullYear()).toEqual(2030);\n    });\n\n    it('should clear cached parsers', function() {\n      expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(2068);\n      dateParser.overrideParser('yy', twoDigitYearParser);\n      expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);\n    });\n  });\n});\n"
  },
  {
    "path": "src/datepicker/datepicker.css",
    "content": ".uib-datepicker .uib-title {\n  width: 100%;\n}\n\n.uib-day button, .uib-month button, .uib-year button {\n  min-width: 100%;\n}\n\n.uib-left, .uib-right {\n  width: 100%\n}\n"
  },
  {
    "path": "src/datepicker/datepicker.js",
    "content": "angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])\n\n.value('$datepickerSuppressError', false)\n\n.value('$datepickerLiteralWarning', true)\n\n.constant('uibDatepickerConfig', {\n  datepickerMode: 'day',\n  formatDay: 'dd',\n  formatMonth: 'MMMM',\n  formatYear: 'yyyy',\n  formatDayHeader: 'EEE',\n  formatDayTitle: 'MMMM yyyy',\n  formatMonthTitle: 'yyyy',\n  maxDate: null,\n  maxMode: 'year',\n  minDate: null,\n  minMode: 'day',\n  monthColumns: 3,\n  ngModelOptions: {},\n  shortcutPropagation: false,\n  showWeeks: true,\n  yearColumns: 5,\n  yearRows: 4\n})\n\n.controller('UibDatepickerController', ['$scope', '$element', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',\n  function($scope, $element, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {\n  var self = this,\n      ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;\n      ngModelOptions = {},\n      watchListeners = [];\n\n  $element.addClass('uib-datepicker');\n  $attrs.$set('role', 'application');\n\n  if (!$scope.datepickerOptions) {\n    $scope.datepickerOptions = {};\n  }\n\n  // Modes chain\n  this.modes = ['day', 'month', 'year'];\n\n  [\n    'customClass',\n    'dateDisabled',\n    'datepickerMode',\n    'formatDay',\n    'formatDayHeader',\n    'formatDayTitle',\n    'formatMonth',\n    'formatMonthTitle',\n    'formatYear',\n    'maxDate',\n    'maxMode',\n    'minDate',\n    'minMode',\n    'monthColumns',\n    'showWeeks',\n    'shortcutPropagation',\n    'startingDay',\n    'yearColumns',\n    'yearRows'\n  ].forEach(function(key) {\n    switch (key) {\n      case 'customClass':\n      case 'dateDisabled':\n        $scope[key] = $scope.datepickerOptions[key] || angular.noop;\n        break;\n      case 'datepickerMode':\n        $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?\n          $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;\n        break;\n      case 'formatDay':\n      case 'formatDayHeader':\n      case 'formatDayTitle':\n      case 'formatMonth':\n      case 'formatMonthTitle':\n      case 'formatYear':\n        self[key] = angular.isDefined($scope.datepickerOptions[key]) ?\n          $interpolate($scope.datepickerOptions[key])($scope.$parent) :\n          datepickerConfig[key];\n        break;\n      case 'monthColumns':\n      case 'showWeeks':\n      case 'shortcutPropagation':\n      case 'yearColumns':\n      case 'yearRows':\n        self[key] = angular.isDefined($scope.datepickerOptions[key]) ?\n          $scope.datepickerOptions[key] : datepickerConfig[key];\n        break;\n      case 'startingDay':\n        if (angular.isDefined($scope.datepickerOptions.startingDay)) {\n          self.startingDay = $scope.datepickerOptions.startingDay;\n        } else if (angular.isNumber(datepickerConfig.startingDay)) {\n          self.startingDay = datepickerConfig.startingDay;\n        } else {\n          self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;\n        }\n\n        break;\n      case 'maxDate':\n      case 'minDate':\n        $scope.$watch('datepickerOptions.' + key, function(value) {\n          if (value) {\n            if (angular.isDate(value)) {\n              self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.getOption('timezone'));\n            } else {\n              if ($datepickerLiteralWarning) {\n                $log.warn('Literal date support has been deprecated, please switch to date object usage');\n              }\n\n              self[key] = new Date(dateFilter(value, 'medium'));\n            }\n          } else {\n            self[key] = datepickerConfig[key] ?\n              dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.getOption('timezone')) :\n              null;\n          }\n\n          self.refreshView();\n        });\n\n        break;\n      case 'maxMode':\n      case 'minMode':\n        if ($scope.datepickerOptions[key]) {\n          $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {\n            self[key] = $scope[key] = angular.isDefined(value) ? value : $scope.datepickerOptions[key];\n            if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||\n              key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {\n              $scope.datepickerMode = self[key];\n              $scope.datepickerOptions.datepickerMode = self[key];\n            }\n          });\n        } else {\n          self[key] = $scope[key] = datepickerConfig[key] || null;\n        }\n\n        break;\n    }\n  });\n\n  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);\n\n  $scope.disabled = angular.isDefined($attrs.disabled) || false;\n  if (angular.isDefined($attrs.ngDisabled)) {\n    watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {\n      $scope.disabled = disabled;\n      self.refreshView();\n    }));\n  }\n\n  $scope.isActive = function(dateObject) {\n    if (self.compare(dateObject.date, self.activeDate) === 0) {\n      $scope.activeDateId = dateObject.uid;\n      return true;\n    }\n    return false;\n  };\n\n  this.init = function(ngModelCtrl_) {\n    ngModelCtrl = ngModelCtrl_;\n    ngModelOptions = extractOptions(ngModelCtrl);\n\n    if ($scope.datepickerOptions.initDate) {\n      self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date();\n      $scope.$watch('datepickerOptions.initDate', function(initDate) {\n        if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {\n          self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone'));\n          self.refreshView();\n        }\n      });\n    } else {\n      self.activeDate = new Date();\n    }\n\n    var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();\n    this.activeDate = !isNaN(date) ?\n      dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) :\n      dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));\n\n    ngModelCtrl.$render = function() {\n      self.render();\n    };\n  };\n\n  this.render = function() {\n    if (ngModelCtrl.$viewValue) {\n      var date = new Date(ngModelCtrl.$viewValue),\n          isValid = !isNaN(date);\n\n      if (isValid) {\n        this.activeDate = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));\n      } else if (!$datepickerSuppressError) {\n        $log.error('Datepicker directive: \"ng-model\" value must be a Date object');\n      }\n    }\n    this.refreshView();\n  };\n\n  this.refreshView = function() {\n    if (this.element) {\n      $scope.selectedDt = null;\n      this._refreshView();\n      if ($scope.activeDt) {\n        $scope.activeDateId = $scope.activeDt.uid;\n      }\n\n      var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;\n      date = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));\n      ngModelCtrl.$setValidity('dateDisabled', !date ||\n        this.element && !this.isDisabled(date));\n    }\n  };\n\n  this.createDateObject = function(date, format) {\n    var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;\n    model = dateParser.fromTimezone(model, ngModelOptions.getOption('timezone'));\n    var today = new Date();\n    today = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));\n    var time = this.compare(date, today);\n    var dt = {\n      date: date,\n      label: dateParser.filter(date, format),\n      selected: model && this.compare(date, model) === 0,\n      disabled: this.isDisabled(date),\n      past: time < 0,\n      current: time === 0,\n      future: time > 0,\n      customClass: this.customClass(date) || null\n    };\n\n    if (model && this.compare(date, model) === 0) {\n      $scope.selectedDt = dt;\n    }\n\n    if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {\n      $scope.activeDt = dt;\n    }\n\n    return dt;\n  };\n\n  this.isDisabled = function(date) {\n    return $scope.disabled ||\n      this.minDate && this.compare(date, this.minDate) < 0 ||\n      this.maxDate && this.compare(date, this.maxDate) > 0 ||\n      $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});\n  };\n\n  this.customClass = function(date) {\n    return $scope.customClass({date: date, mode: $scope.datepickerMode});\n  };\n\n  // Split array into smaller arrays\n  this.split = function(arr, size) {\n    var arrays = [];\n    while (arr.length > 0) {\n      arrays.push(arr.splice(0, size));\n    }\n    return arrays;\n  };\n\n  $scope.select = function(date) {\n    if ($scope.datepickerMode === self.minMode) {\n      var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.getOption('timezone')) : new Date(0, 0, 0, 0, 0, 0, 0);\n      dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());\n      dt = dateParser.toTimezone(dt, ngModelOptions.getOption('timezone'));\n      ngModelCtrl.$setViewValue(dt);\n      ngModelCtrl.$render();\n    } else {\n      self.activeDate = date;\n      setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);\n\n      $scope.$emit('uib:datepicker.mode');\n    }\n\n    $scope.$broadcast('uib:datepicker.focus');\n  };\n\n  $scope.move = function(direction) {\n    var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),\n        month = self.activeDate.getMonth() + direction * (self.step.months || 0);\n    self.activeDate.setFullYear(year, month, 1);\n    self.refreshView();\n  };\n\n  $scope.toggleMode = function(direction) {\n    direction = direction || 1;\n\n    if ($scope.datepickerMode === self.maxMode && direction === 1 ||\n      $scope.datepickerMode === self.minMode && direction === -1) {\n      return;\n    }\n\n    setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);\n\n    $scope.$emit('uib:datepicker.mode');\n  };\n\n  // Key event mapper\n  $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };\n\n  var focusElement = function() {\n    self.element[0].focus();\n  };\n\n  // Listen for focus requests from popup directive\n  $scope.$on('uib:datepicker.focus', focusElement);\n\n  $scope.keydown = function(evt) {\n    var key = $scope.keys[evt.which];\n\n    if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {\n      return;\n    }\n\n    evt.preventDefault();\n    if (!self.shortcutPropagation) {\n      evt.stopPropagation();\n    }\n\n    if (key === 'enter' || key === 'space') {\n      if (self.isDisabled(self.activeDate)) {\n        return; // do nothing\n      }\n      $scope.select(self.activeDate);\n    } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {\n      $scope.toggleMode(key === 'up' ? 1 : -1);\n    } else {\n      self.handleKeyDown(key, evt);\n      self.refreshView();\n    }\n  };\n\n  $element.on('keydown', function(evt) {\n    $scope.$apply(function() {\n      $scope.keydown(evt);\n    });\n  });\n\n  $scope.$on('$destroy', function() {\n    //Clear all watch listeners on destroy\n    while (watchListeners.length) {\n      watchListeners.shift()();\n    }\n  });\n\n  function setMode(mode) {\n    $scope.datepickerMode = mode;\n    $scope.datepickerOptions.datepickerMode = mode;\n  }\n\n  function extractOptions(ngModelCtrl) {\n    var ngModelOptions;\n\n    if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing\n      // guarantee a value\n      ngModelOptions = ngModelCtrl.$options ||\n        $scope.datepickerOptions.ngModelOptions ||\n        datepickerConfig.ngModelOptions ||\n        {};\n\n      // mimic 1.6+ api\n      ngModelOptions.getOption = function (key) {\n        return ngModelOptions[key];\n      };\n    } else { // in angular >=1.6 $options is always present\n      // ng-model-options defaults timezone to null; don't let its precedence squash a non-null value\n      var timezone = ngModelCtrl.$options.getOption('timezone') ||\n        ($scope.datepickerOptions.ngModelOptions ? $scope.datepickerOptions.ngModelOptions.timezone : null) ||\n        (datepickerConfig.ngModelOptions ? datepickerConfig.ngModelOptions.timezone : null);\n\n      // values passed to createChild override existing values\n      ngModelOptions = ngModelCtrl.$options // start with a ModelOptions instance\n        .createChild(datepickerConfig.ngModelOptions) // lowest precedence\n        .createChild($scope.datepickerOptions.ngModelOptions)\n        .createChild(ngModelCtrl.$options) // highest precedence\n        .createChild({timezone: timezone}); // to keep from squashing a non-null value\n    }\n\n    return ngModelOptions;\n  }\n}])\n\n.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {\n  var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n\n  this.step = { months: 1 };\n  this.element = $element;\n  function getDaysInMonth(year, month) {\n    return month === 1 && year % 4 === 0 &&\n      (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];\n  }\n\n  this.init = function(ctrl) {\n    angular.extend(ctrl, this);\n    scope.showWeeks = ctrl.showWeeks;\n    ctrl.refreshView();\n  };\n\n  this.getDates = function(startDate, n) {\n    var dates = new Array(n), current = new Date(startDate), i = 0, date;\n    while (i < n) {\n      date = new Date(current);\n      dates[i++] = date;\n      current.setDate(current.getDate() + 1);\n    }\n    return dates;\n  };\n\n  this._refreshView = function() {\n    var year = this.activeDate.getFullYear(),\n      month = this.activeDate.getMonth(),\n      firstDayOfMonth = new Date(this.activeDate);\n\n    firstDayOfMonth.setFullYear(year, month, 1);\n\n    var difference = this.startingDay - firstDayOfMonth.getDay(),\n      numDisplayedFromPreviousMonth = difference > 0 ?\n        7 - difference : - difference,\n      firstDate = new Date(firstDayOfMonth);\n\n    if (numDisplayedFromPreviousMonth > 0) {\n      firstDate.setDate(-numDisplayedFromPreviousMonth + 1);\n    }\n\n    // 42 is the number of days on a six-week calendar\n    var days = this.getDates(firstDate, 42);\n    for (var i = 0; i < 42; i ++) {\n      days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {\n        secondary: days[i].getMonth() !== month,\n        uid: scope.uniqueId + '-' + i\n      });\n    }\n\n    scope.labels = new Array(7);\n    for (var j = 0; j < 7; j++) {\n      scope.labels[j] = {\n        abbr: dateFilter(days[j].date, this.formatDayHeader),\n        full: dateFilter(days[j].date, 'EEEE')\n      };\n    }\n\n    scope.title = dateFilter(this.activeDate, this.formatDayTitle);\n    scope.rows = this.split(days, 7);\n\n    if (scope.showWeeks) {\n      scope.weekNumbers = [];\n      var thursdayIndex = (4 + 7 - this.startingDay) % 7,\n          numWeeks = scope.rows.length;\n      for (var curWeek = 0; curWeek < numWeeks; curWeek++) {\n        scope.weekNumbers.push(\n          getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));\n      }\n    }\n  };\n\n  this.compare = function(date1, date2) {\n    var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());\n    var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());\n    _date1.setFullYear(date1.getFullYear());\n    _date2.setFullYear(date2.getFullYear());\n    return _date1 - _date2;\n  };\n\n  function getISO8601WeekNumber(date) {\n    var checkDate = new Date(date);\n    checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday\n    var time = checkDate.getTime();\n    checkDate.setMonth(0); // Compare with Jan 1\n    checkDate.setDate(1);\n    return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;\n  }\n\n  this.handleKeyDown = function(key, evt) {\n    var date = this.activeDate.getDate();\n\n    if (key === 'left') {\n      date = date - 1;\n    } else if (key === 'up') {\n      date = date - 7;\n    } else if (key === 'right') {\n      date = date + 1;\n    } else if (key === 'down') {\n      date = date + 7;\n    } else if (key === 'pageup' || key === 'pagedown') {\n      var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);\n      this.activeDate.setMonth(month, 1);\n      date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);\n    } else if (key === 'home') {\n      date = 1;\n    } else if (key === 'end') {\n      date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());\n    }\n    this.activeDate.setDate(date);\n  };\n}])\n\n.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {\n  this.step = { years: 1 };\n  this.element = $element;\n\n  this.init = function(ctrl) {\n    angular.extend(ctrl, this);\n    ctrl.refreshView();\n  };\n\n  this._refreshView = function() {\n    var months = new Array(12),\n        year = this.activeDate.getFullYear(),\n        date;\n\n    for (var i = 0; i < 12; i++) {\n      date = new Date(this.activeDate);\n      date.setFullYear(year, i, 1);\n      months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {\n        uid: scope.uniqueId + '-' + i\n      });\n    }\n\n    scope.title = dateFilter(this.activeDate, this.formatMonthTitle);\n    scope.rows = this.split(months, this.monthColumns);\n    scope.yearHeaderColspan = this.monthColumns > 3 ? this.monthColumns - 2 : 1;\n  };\n\n  this.compare = function(date1, date2) {\n    var _date1 = new Date(date1.getFullYear(), date1.getMonth());\n    var _date2 = new Date(date2.getFullYear(), date2.getMonth());\n    _date1.setFullYear(date1.getFullYear());\n    _date2.setFullYear(date2.getFullYear());\n    return _date1 - _date2;\n  };\n\n  this.handleKeyDown = function(key, evt) {\n    var date = this.activeDate.getMonth();\n\n    if (key === 'left') {\n      date = date - 1;\n    } else if (key === 'up') {\n      date = date - this.monthColumns;\n    } else if (key === 'right') {\n      date = date + 1;\n    } else if (key === 'down') {\n      date = date + this.monthColumns;\n    } else if (key === 'pageup' || key === 'pagedown') {\n      var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);\n      this.activeDate.setFullYear(year);\n    } else if (key === 'home') {\n      date = 0;\n    } else if (key === 'end') {\n      date = 11;\n    }\n    this.activeDate.setMonth(date);\n  };\n}])\n\n.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {\n  var columns, range;\n  this.element = $element;\n\n  function getStartingYear(year) {\n    return parseInt((year - 1) / range, 10) * range + 1;\n  }\n\n  this.yearpickerInit = function() {\n    columns = this.yearColumns;\n    range = this.yearRows * columns;\n    this.step = { years: range };\n  };\n\n  this._refreshView = function() {\n    var years = new Array(range), date;\n\n    for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {\n      date = new Date(this.activeDate);\n      date.setFullYear(start + i, 0, 1);\n      years[i] = angular.extend(this.createDateObject(date, this.formatYear), {\n        uid: scope.uniqueId + '-' + i\n      });\n    }\n\n    scope.title = [years[0].label, years[range - 1].label].join(' - ');\n    scope.rows = this.split(years, columns);\n    scope.columns = columns;\n  };\n\n  this.compare = function(date1, date2) {\n    return date1.getFullYear() - date2.getFullYear();\n  };\n\n  this.handleKeyDown = function(key, evt) {\n    var date = this.activeDate.getFullYear();\n\n    if (key === 'left') {\n      date = date - 1;\n    } else if (key === 'up') {\n      date = date - columns;\n    } else if (key === 'right') {\n      date = date + 1;\n    } else if (key === 'down') {\n      date = date + columns;\n    } else if (key === 'pageup' || key === 'pagedown') {\n      date += (key === 'pageup' ? - 1 : 1) * range;\n    } else if (key === 'home') {\n      date = getStartingYear(this.activeDate.getFullYear());\n    } else if (key === 'end') {\n      date = getStartingYear(this.activeDate.getFullYear()) + range - 1;\n    }\n    this.activeDate.setFullYear(date);\n  };\n}])\n\n.directive('uibDatepicker', function() {\n  return {\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';\n    },\n    scope: {\n      datepickerOptions: '=?'\n    },\n    require: ['uibDatepicker', '^ngModel'],\n    restrict: 'A',\n    controller: 'UibDatepickerController',\n    controllerAs: 'datepicker',\n    link: function(scope, element, attrs, ctrls) {\n      var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n\n      datepickerCtrl.init(ngModelCtrl);\n    }\n  };\n})\n\n.directive('uibDaypicker', function() {\n  return {\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/datepicker/day.html';\n    },\n    require: ['^uibDatepicker', 'uibDaypicker'],\n    restrict: 'A',\n    controller: 'UibDaypickerController',\n    link: function(scope, element, attrs, ctrls) {\n      var datepickerCtrl = ctrls[0],\n        daypickerCtrl = ctrls[1];\n\n      daypickerCtrl.init(datepickerCtrl);\n    }\n  };\n})\n\n.directive('uibMonthpicker', function() {\n  return {\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/datepicker/month.html';\n    },\n    require: ['^uibDatepicker', 'uibMonthpicker'],\n    restrict: 'A',\n    controller: 'UibMonthpickerController',\n    link: function(scope, element, attrs, ctrls) {\n      var datepickerCtrl = ctrls[0],\n        monthpickerCtrl = ctrls[1];\n\n      monthpickerCtrl.init(datepickerCtrl);\n    }\n  };\n})\n\n.directive('uibYearpicker', function() {\n  return {\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/datepicker/year.html';\n    },\n    require: ['^uibDatepicker', 'uibYearpicker'],\n    restrict: 'A',\n    controller: 'UibYearpickerController',\n    link: function(scope, element, attrs, ctrls) {\n      var ctrl = ctrls[0];\n      angular.extend(ctrl, ctrls[1]);\n      ctrl.yearpickerInit();\n\n      ctrl.refreshView();\n    }\n  };\n});\n"
  },
  {
    "path": "src/datepicker/docs/demo.html",
    "content": "<style>\n  .full button span {\n    background-color: limegreen;\n    border-radius: 32px;\n    color: black;\n  }\n  .partially button span {\n    background-color: orange;\n    border-radius: 32px;\n    color: black;\n  }\n</style>\n<div ng-controller=\"DatepickerDemoCtrl\">\n    <pre>Selected date is: <em>{{dt | date:'fullDate' }}</em></pre>\n\n    <h4>Inline</h4>\n    <div style=\"display:inline-block; min-height:290px;\">\n      <div uib-datepicker ng-model=\"dt\" class=\"well well-sm\" datepicker-options=\"options\"></div>\n    </div>\n\n    <hr />\n    <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"today()\">Today</button>\n    <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"setDate(2009, 7, 24)\">2009-08-24</button>\n    <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"clear()\">Clear</button>\n    <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"toggleMin()\" uib-tooltip=\"After today restriction\">Min date</button>\n</div>\n"
  },
  {
    "path": "src/datepicker/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($scope) {\n  $scope.today = function() {\n    $scope.dt = new Date();\n  };\n  $scope.today();\n\n  $scope.clear = function() {\n    $scope.dt = null;\n  };\n\n  $scope.options = {\n    customClass: getDayClass,\n    minDate: new Date(),\n    showWeeks: true\n  };\n\n  // Disable weekend selection\n  function disabled(data) {\n    var date = data.date,\n      mode = data.mode;\n    return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6);\n  }\n\n  $scope.toggleMin = function() {\n    $scope.options.minDate = $scope.options.minDate ? null : new Date();\n  };\n\n  $scope.toggleMin();\n\n  $scope.setDate = function(year, month, day) {\n    $scope.dt = new Date(year, month, day);\n  };\n\n  var tomorrow = new Date();\n  tomorrow.setDate(tomorrow.getDate() + 1);\n  var afterTomorrow = new Date(tomorrow);\n  afterTomorrow.setDate(tomorrow.getDate() + 1);\n  $scope.events = [\n    {\n      date: tomorrow,\n      status: 'full'\n    },\n    {\n      date: afterTomorrow,\n      status: 'partially'\n    }\n  ];\n\n  function getDayClass(data) {\n    var date = data.date,\n      mode = data.mode;\n    if (mode === 'day') {\n      var dayToCheck = new Date(date).setHours(0,0,0,0);\n\n      for (var i = 0; i < $scope.events.length; i++) {\n        var currentDay = new Date($scope.events[i].date).setHours(0,0,0,0);\n\n        if (dayToCheck === currentDay) {\n          return $scope.events[i].status;\n        }\n      }\n    }\n\n    return '';\n  }\n});\n"
  },
  {
    "path": "src/datepicker/docs/readme.md",
    "content": "Our datepicker is flexible and fully customizable.\n\nYou can navigate through days, months and years.\n\nThe datepicker has 3 modes:\n\n* `day` - In this mode you're presented with a 6-week calendar for a specified month.\n* `month` - In this mode you can select a month within a selected year.\n* `year` - In this mode you are presented with a range of years (20 by default).\n\n### uib-datepicker settings\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  The date object. Must be a Javascript `Date` object. You may use the `uibDateParser` service to assist in string-to-object conversion.\n\n* `ng-model-options`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `{}`)_ -\n  Supported [angular ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions):\n  * allowInvalid\n  * timezone\n\n* `template-url`\n  _(Default: `uib/template/datepicker/datepicker.html`)_ -\n  Add the ability to override the template used on the component.\n\nApart from the previous settings, to configure the uib-datepicker you need to create an object in Javascript with all the options and use it on the `datepicker-options` attribute:\n\n* `datepicker-options`\n  <small class=\"badge\">$</small> -\n  An object to configure the datepicker in one place.\n\n  * `customClass ({date: date, mode: mode})` -\n    An optional expression to add classes based on passing an object with date and current mode properties.\n\n  * `dateDisabled ({date: date, mode: mode})` -\n    An optional expression to disable visible options based on passing an object with date and current mode properties.\n\n  * `datepickerMode`\n    <small class=\"badge\">C</small>\n    <i class=\"glyphicon glyphicon-eye-open\"></i>\n    _(Default: `day`)_ -\n    Current mode of the datepicker _(day|month|year)_. Can be used to initialize the datepicker in a specific mode.\n\n  * `formatDay`\n    <small class=\"badge\">C</small>\n    _(Default: `dd`)_ -\n    Format of day in month.\n\n  * `formatMonth`\n    <small class=\"badge\">C</small>\n    _(Default: `MMMM`)_ -\n    Format of month in year.\n\n  * `formatYear`\n    <small class=\"badge\">C</small>\n    _(Default: `yyyy`)_ -\n    Format of year in year range.\n\n  * `formatDayHeader`\n    <small class=\"badge\">C</small>\n    _(Default: `EEE`)_ -\n    Format of day in week header.\n\n  * `formatDayTitle`\n    <small class=\"badge\">C</small>\n    _(Default: `MMMM yyyy`)_ -\n    Format of title when selecting day.\n\n  * `formatMonthTitle`\n    <small class=\"badge\">C</small>\n    _(Default: `yyyy`)_ -\n    Format of title when selecting month.\n\n  * `initDate`\n    <i class=\"glyphicon glyphicon-eye-open\"></i>\n    _(Default: `null`)_ -\n    The initial date view when no model value is specified.\n\n  * `maxDate`\n    <small class=\"badge\">C</small>\n    <i class=\"glyphicon glyphicon-eye-open\"></i>\n    _(Default: `null`)_ -\n    Defines the maximum available date. Requires a Javascript Date object.\n\n  * `maxMode`\n    <small class=\"badge\">C</small>\n      <i class=\"glyphicon glyphicon-eye-open\"></i>\n    _(Default: `year`)_ -\n    Sets an upper limit for mode.\n\n  * `minDate`\n    <small class=\"badge\">C</small>\n    <i class=\"glyphicon glyphicon-eye-open\"></i>\n    _(Default: `null`)_ -\n    Defines the minimum available date. Requires a Javascript Date object.\n\n  * `minMode`\n    <small class=\"badge\">C</small>\n    <i class=\"glyphicon glyphicon-eye-open\"></i>\n    _(Default: `day`)_ -\n    Sets a lower limit for mode.\n\n  * `monthColumns`\n    <small class=\"badge\">C</small>\n    _(Default: `3`)_ -\n    Number of columns displayed in month selection.\n    \n  * `ngModelOptions`\n    <small class=\"badge\">C</small>\n    _(Default: `null`)_ -\n    Sets `ngModelOptions` for datepicker. This can be overridden through attribute usage\n\n  * `shortcutPropagation`\n    <small class=\"badge\">C</small>\n    _(Default: `false`)_ -\n    An option to disable the propagation of the keydown event.\n\n  * `showWeeks`\n    <small class=\"badge\">C</small>\n    _(Default: `true`)_ -\n    Whether to display week numbers.\n\n  * `startingDay`\n    <small class=\"badge\">C</small>\n    *(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)* -\n    Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday).\n    \n  * `yearRows`\n    <small class=\"badge\">C</small>\n    _(Default: `4`)_ -\n    Number of rows displayed in year selection.\n\n  * `yearColumns`\n    <small class=\"badge\">C</small>\n    _(Default: `5`)_ -\n  Number of columns displayed in year selection.\n\n### Keyboard support\n\nDepending on datepicker's current mode, the date may refer either to day, month or year. Accordingly, the term view refers either to a month, year or year range.\n\n * `Left`: Move focus to the previous date. Will move to the last date of the previous view, if the current date is the first date of a view.\n * `Right`: Move focus to the next date. Will move to the first date of the following view, if the current date is the last date of a view.\n * `Up`: Move focus to the same column of the previous row. Will wrap to the appropriate row in the previous view.\n * `Down`: Move focus to the same column of the following row. Will wrap to the appropriate row in the following view.\n * `PgUp`: Move focus to the same date of the previous view. If that date does not exist, focus is placed on the last date of the month.\n * `PgDn`: Move focus to the same date of the following view. If that date does not exist, focus is placed on the last date of the month.\n * `Home`: Move to the first date of the view.\n * `End`: Move to the last date of the view.\n * `Enter`/`Space`: Select date.\n * `Ctrl`+`Up`: Move to an upper mode.\n * `Ctrl`+`Down`: Move to a lower mode.\n * `Esc`: Will close popup, and move focus to the input.\n\n**Notes**\n\nIf the date a user enters falls outside of the min-/max-date range, a `dateDisabled` validation error will show on the form.\n"
  },
  {
    "path": "src/datepicker/index-nocss.js",
    "content": "require('../dateparser');\nrequire('../isClass');\nrequire('../../template/datepicker/datepicker.html.js');\nrequire('../../template/datepicker/day.html.js');\nrequire('../../template/datepicker/month.html.js');\nrequire('../../template/datepicker/year.html.js');\nrequire('./datepicker');\n\nvar MODULE_NAME = 'ui.bootstrap.module.datepicker';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.datepicker', 'uib/template/datepicker/datepicker.html', 'uib/template/datepicker/day.html', 'uib/template/datepicker/month.html', 'uib/template/datepicker/year.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/datepicker/index.js",
    "content": "require('./datepicker.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/datepicker/test/datepicker.spec.js",
    "content": "describe('datepicker', function() {\n  var $rootScope, $compile, $templateCache, element;\n  beforeEach(module('ui.bootstrap.datepicker'));\n  beforeEach(module('uib/template/datepicker/datepicker.html'));\n  beforeEach(module('uib/template/datepicker/day.html'));\n  beforeEach(module('uib/template/datepicker/month.html'));\n  beforeEach(module('uib/template/datepicker/year.html'));\n  beforeEach(module(function($compileProvider) {\n    $compileProvider.directive('dateModel', function() {\n      return {\n        restrict: 'A',\n        require: 'ngModel',\n        link: function(scope, element, attrs, modelController) {\n          modelController.$formatters.push(function(object) {\n            return new Date(object.date);\n          });\n\n          modelController.$parsers.push(function(date) {\n            return {\n              type: 'date',\n              date: date.toUTCString()\n            };\n          });\n        }\n      };\n    });\n  }));\n\n  function getTitleCell() {\n    return element.find('th').eq(1);\n  }\n\n  function getTitleButton() {\n    return getTitleCell().find('button').first();\n  }\n\n  function getTitle() {\n    return getTitleButton().text();\n  }\n\n  function clickTitleButton() {\n    getTitleButton().click();\n  }\n\n  function clickPreviousButton(times) {\n    var el = element.find('th').eq(0).find('button').eq(0);\n    for (var i = 0, n = times || 1; i < n; i++) {\n      el.click();\n    }\n  }\n\n  function clickNextButton() {\n    element.find('th').eq(2).find('button').eq(0).click();\n  }\n\n  function getLabelsRow() {\n    return element.find('thead').find('tr').eq(1);\n  }\n\n  function getLabels(dayMode) {\n    var els = getLabelsRow().find('th'),\n        labels = [];\n    for (var i = dayMode ? 1 : 0, n = els.length; i < n; i++) {\n      labels.push(els.eq(i).text());\n    }\n    return labels;\n  }\n\n  function getWeeks() {\n    var rows = element.find('tbody').find('tr'),\n        weeks = [];\n    for (var i = 0, n = rows.length; i < n; i++) {\n      weeks.push(rows.eq(i).find('td').eq(0).first().text());\n    }\n    return weeks;\n  }\n\n  function getOptions(dayMode) {\n    var tr = element.find('tbody').find('tr');\n    var rows = [];\n\n    for (var j = 0, numRows = tr.length; j < numRows; j++) {\n      var cols = tr.eq(j).find('td'), days = [];\n      for (var i = dayMode ? 1 : 0, n = cols.length; i < n; i++) {\n        days.push(cols.eq(i).find('button').text());\n      }\n      rows.push(days);\n    }\n    return rows;\n  }\n\n  function clickOption(index) {\n    getAllOptionsEl().eq(index).click();\n  }\n\n  function getAllOptionsEl(dayMode) {\n    return element.find('tbody').find('button');\n  }\n\n  function selectedElementIndex() {\n    var buttons = getAllOptionsEl();\n    for (var i = 0; i < buttons.length; i++) {\n      if (angular.element(buttons[i]).hasClass('btn-info')) {\n        return i;\n      }\n    }\n  }\n\n  function expectSelectedElement(index) {\n    var buttons = getAllOptionsEl();\n    angular.forEach( buttons, function(button, idx) {\n      expect(angular.element(button).hasClass('btn-info')).toBe(idx === index);\n    });\n  }\n\n  function getSelectedElement(index) {\n    var buttons = getAllOptionsEl();\n    var el = $.grep(buttons, function(button, idx) {\n      return angular.element(button).hasClass('btn-info');\n    })[0];\n    return angular.element(el);\n  }\n\n  function triggerKeyDown(element, key, ctrl) {\n    var keyCodes = {\n      'enter': 13,\n      'space': 32,\n      'pageup': 33,\n      'pagedown': 34,\n      'end': 35,\n      'home': 36,\n      'left': 37,\n      'up': 38,\n      'right': 39,\n      'down': 40,\n      'esc': 27\n    };\n    var e = $.Event('keydown');\n    e.which = keyCodes[key];\n    if (ctrl) {\n      e.ctrlKey = true;\n    }\n    element.trigger(e);\n  }\n\n  describe('$datepickerLiteralWarning', function() {\n    var $compile,\n      $log,\n      $scope;\n\n    it('should warn when using literals for min date by default', function() {\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n\n      spyOn($log, 'warn');\n      $scope.options = {\n        minDate: '1984-01-01'\n      };\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\" datepicker-options=\"options\"></div>')($scope);\n      $scope.$digest();\n\n      expect($log.warn).toHaveBeenCalledWith('Literal date support has been deprecated, please switch to date object usage');\n    });\n\n    it('should suppress warning when using literals for min date', function() {\n      module(function($provide) {\n        $provide.value('$datepickerLiteralWarning', false);\n      });\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n\n      spyOn($log, 'warn');\n      $scope.options = {\n        minDate: '1984-01-01'\n      };\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\" datepicker-options=\"options\"></div>')($scope);\n      $scope.$digest();\n\n      expect($log.warn).not.toHaveBeenCalled();\n    });\n\n    it('should warn when using literals for max date by default', function() {\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n\n      spyOn($log, 'warn');\n      $scope.options = {\n        maxDate: '1984-01-01'\n      };\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\" datepicker-options=\"options\"></div>')($scope);\n      $scope.$digest();\n\n      expect($log.warn).toHaveBeenCalledWith('Literal date support has been deprecated, please switch to date object usage');\n    });\n\n    it('should suppress warning when using literals for max date', function() {\n      module(function($provide) {\n        $provide.value('$datepickerLiteralWarning', false);\n      });\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n\n      spyOn($log, 'warn');\n      $scope.options = {\n        maxDate: '1984-01-01'\n      };\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\" datepicker-options=\"options\"></div>')($scope);\n      $scope.$digest();\n\n      expect($log.warn).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('$datepickerSuppressError', function() {\n    var $compile,\n        $log,\n        $scope;\n\n    it('should not suppress log error message for ng-model date error by default', function() {\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n\n      spyOn($log, 'error');\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\"></div>')($scope);\n\n      $scope.locals = {\n        date: 'lalala'\n      };\n      $scope.$digest();\n      expect($log.error).toHaveBeenCalled();\n    });\n\n    it('should not suppress log error message for ng-model date error when false', function() {\n      module(function($provide) {\n        $provide.value('$datepickerSuppressError', false);\n      });\n\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n\n      spyOn($log, 'error');\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\"></div>')($scope);\n\n      $scope.locals = {\n        date: 'lalala'\n      };\n      $scope.$digest();\n      expect($log.error).toHaveBeenCalled();\n    });\n\n    it('should suppress log error message for ng-model date error when true', function() {\n      module(function($provide) {\n        $provide.value('$datepickerSuppressError', true);\n      });\n\n      inject(function(_$log_, _$rootScope_, _$compile_) {\n        $log = _$log_;\n        $scope = _$rootScope_.$new();\n        $compile = _$compile_;\n      });\n      spyOn($log, 'error');\n\n      element = $compile('<div uib-datepicker ng-model=\"locals.date\"></div>')($scope);\n\n      $scope.locals = {\n        date: 'lalala'\n      };\n      $scope.$digest();\n      expect($log.error).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('', function() {\n    beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_) {\n      $compile = _$compile_;\n      $rootScope = _$rootScope_;\n      $rootScope.date = new Date('September 30, 2010 15:30:00');\n      $templateCache = _$templateCache_;\n    }));\n\n    describe('with no initial date', function() {\n      beforeEach(function() {\n        jasmine.clock().install();\n      });\n\n      afterEach(function() {\n        jasmine.clock().uninstall();\n      });\n\n      it('should have an active date equal to the current date', function() {\n        var baseTime = new Date(2015, 2, 23);\n        jasmine.clock().mockDate(baseTime);\n\n        element = $compile('<div uib-datepicker ng-model=\"fooDate\"></div')($rootScope);\n        $rootScope.$digest();\n\n        expect(element.controller('uibDatepicker').activeDate.getTime()).toEqual(baseTime.getTime());\n      });\n    });\n\n    it('should support custom templates', function() {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      element = $compile('<div uib-datepicker ng-model=\"date\" template-url=\"foo/bar.html\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      expect(element.html()).toBe('<div>baz</div>');\n    });\n\n    it('should support custom day, month and year templates', function() {\n      $templateCache.put('foo/day.html', '<div>day</div>');\n      $templateCache.put('foo/month.html', '<div>month</div>');\n      $templateCache.put('foo/year.html', '<div>year</div>');\n\n      $templateCache.put('foo/bar.html', '<div>' +\n        '<div uib-daypicker template-url=\"foo/day.html\"></div>' +\n        '<div uib-monthpicker template-url=\"foo/month.html\"></div>' +\n        '<div uib-yearpicker template-url=\"foo/year.html\"></div>' +\n      '</div>');\n\n      element = $compile('<div uib-datepicker ng-model=\"date\" template-url=\"foo/bar.html\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var expectedHtml = '<div><div uib-daypicker=\"\" template-url=\"foo/day.html\"><div>day</div></div><div uib-monthpicker=\"\" template-url=\"foo/month.html\"><div>month</div></div><div uib-yearpicker=\"\" template-url=\"foo/year.html\"><div>year</div></div></div>';\n\n      expect(element.html()).toBe(expectedHtml);\n    });\n\n    it('should expose the controller in the template', function() {\n      $templateCache.put('uib/template/datepicker/datepicker.html', '<div>{{datepicker.text}}</div>');\n\n      element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var ctrl = element.controller('uib-datepicker');\n      expect(ctrl).toBeDefined();\n      expect(element.html()).toBe('<div class=\"ng-binding\"></div>');\n\n      ctrl.text = 'baz';\n      $rootScope.$digest();\n\n      expect(element.html()).toBe('<div class=\"ng-binding\">baz</div>');\n    });\n\n    describe('basic functionality', function() {\n      beforeEach(function() {\n        element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('is has a `<table>` element', function() {\n        expect(element.find('table').length).toBe(1);\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('September 2010');\n      });\n\n      it('shows the label row & the correct day labels', function() {\n        expect(getLabelsRow().css('display')).not.toBe('none');\n        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);\n      });\n\n      it('renders the calendar days correctly', function() {\n        expect(getOptions(true)).toEqual([\n          ['29', '30', '31', '01', '02', '03', '04'],\n          ['05', '06', '07', '08', '09', '10', '11'],\n          ['12', '13', '14', '15', '16', '17', '18'],\n          ['19', '20', '21', '22', '23', '24', '25'],\n          ['26', '27', '28', '29', '30', '01', '02'],\n          ['03', '04', '05', '06', '07', '08', '09']\n        ]);\n      });\n\n      it('renders the week numbers based on ISO 8601', function() {\n        expect(getWeeks()).toEqual(['35', '36', '37', '38', '39', '40']);\n      });\n\n      it('value is correct', function() {\n        expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n      });\n\n      it('has activeDate value of model', function() {\n        expect(element.controller('uibDatepicker').activeDate).toEqual(new Date('September 30, 2010 15:30:00'));\n      });\n\n      it('has `selected` only the correct day', function() {\n        expectSelectedElement(32);\n      });\n\n      it('has no `selected` day when model is cleared', function() {\n        $rootScope.date = null;\n        $rootScope.$digest();\n\n        expect($rootScope.date).toBe(null);\n        expectSelectedElement(null);\n      });\n\n      it('does not change current view when model is cleared', function() {\n        $rootScope.date = null;\n        $rootScope.$digest();\n\n        expect($rootScope.date).toBe(null);\n        expect(getTitle()).toBe('September 2010');\n      });\n\n      it('`disables` visible dates from other months', function() {\n        var buttons = getAllOptionsEl();\n        angular.forEach(buttons, function(button, index) {\n          expect(angular.element(button).find('span').hasClass('text-muted')).toBe( index < 3 || index > 32 );\n        });\n      });\n\n      it('updates the model when a day is clicked', function() {\n        clickOption(17);\n        expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));\n      });\n\n      it('moves to the previous month & renders correctly when `previous` button is clicked', function() {\n        clickPreviousButton();\n\n        expect(getTitle()).toBe('August 2010');\n        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);\n        expect(getOptions(true)).toEqual([\n          ['01', '02', '03', '04', '05', '06', '07'],\n          ['08', '09', '10', '11', '12', '13', '14'],\n          ['15', '16', '17', '18', '19', '20', '21'],\n          ['22', '23', '24', '25', '26', '27', '28'],\n          ['29', '30', '31', '01', '02', '03', '04'],\n          ['05', '06', '07', '08', '09', '10', '11']\n        ]);\n\n        expectSelectedElement(null, null);\n      });\n\n      it('updates the model only when a day is clicked in the `previous` month', function() {\n        clickPreviousButton();\n        expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n\n        clickOption(17);\n        expect($rootScope.date).toEqual(new Date('August 18, 2010 15:30:00'));\n      });\n\n      it('moves to the next month & renders correctly when `next` button is clicked', function() {\n        clickNextButton();\n\n        expect(getTitle()).toBe('October 2010');\n        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);\n        expect(getOptions(true)).toEqual([\n          ['26', '27', '28', '29', '30', '01', '02'],\n          ['03', '04', '05', '06', '07', '08', '09'],\n          ['10', '11', '12', '13', '14', '15', '16'],\n          ['17', '18', '19', '20', '21', '22', '23'],\n          ['24', '25', '26', '27', '28', '29', '30'],\n          ['31', '01', '02', '03', '04', '05', '06']\n        ]);\n\n        expectSelectedElement(4);\n      });\n\n      it('updates the model only when a day is clicked in the `next` month', function() {\n        clickNextButton();\n        expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n\n        clickOption(17);\n        expect($rootScope.date).toEqual(new Date('October 13, 2010 15:30:00'));\n      });\n\n      it('updates the calendar when a day of another month is selected', function() {\n        clickOption(33);\n        expect($rootScope.date).toEqual(new Date('October 01, 2010 15:30:00'));\n        expect(getTitle()).toBe('October 2010');\n        expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);\n        expect(getOptions(true)).toEqual([\n          ['26', '27', '28', '29', '30', '01', '02'],\n          ['03', '04', '05', '06', '07', '08', '09'],\n          ['10', '11', '12', '13', '14', '15', '16'],\n          ['17', '18', '19', '20', '21', '22', '23'],\n          ['24', '25', '26', '27', '28', '29', '30'],\n          ['31', '01', '02', '03', '04', '05', '06']\n        ]);\n\n        expectSelectedElement(5);\n      });\n\n      // issue #1697\n      it('should not \"jump\" months', function() {\n        $rootScope.date = new Date('January 30, 2014');\n        $rootScope.$digest();\n        clickNextButton();\n        expect(getTitle()).toBe('February 2014');\n        clickPreviousButton();\n        expect(getTitle()).toBe('January 2014');\n      });\n\n      it('should not change model when going to next month - #5441', function() {\n        $rootScope.date = new Date('January 30, 2014');\n        $rootScope.$digest();\n        clickNextButton();\n        expect($rootScope.date).toEqual(new Date('January 30, 2014'));\n      });\n\n      describe('when `model` changes', function() {\n        function testCalendar() {\n          expect(getTitle()).toBe('November 2005');\n          expect(getOptions(true)).toEqual([\n            ['30', '31', '01', '02', '03', '04', '05'],\n            ['06', '07', '08', '09', '10', '11', '12'],\n            ['13', '14', '15', '16', '17', '18', '19'],\n            ['20', '21', '22', '23', '24', '25', '26'],\n            ['27', '28', '29', '30', '01', '02', '03'],\n            ['04', '05', '06', '07', '08', '09', '10']\n          ]);\n\n          expectSelectedElement(8);\n        }\n\n        describe('to a Date object', function() {\n          it('updates', function() {\n            $rootScope.date = new Date('November 7, 2005 23:30:00');\n            $rootScope.$digest();\n            testCalendar();\n            expect(angular.isDate($rootScope.date)).toBe(true);\n          });\n\n          it('to a date that is invalid, it doesn\\`t update', function() {\n            $rootScope.date = new Date('pizza');\n            $rootScope.$digest();\n            expect(getTitle()).toBe('September 2010');\n            expect(angular.isDate($rootScope.date)).toBe(true);\n            expect(isNaN($rootScope.date)).toBe(true);\n          });\n        });\n\n        describe('not to a Date object', function() {\n          it('to a Number, it updates calendar', function() {\n            $rootScope.date = parseInt((new Date('November 7, 2005 23:30:00')).getTime(), 10);\n            $rootScope.$digest();\n            testCalendar();\n            expect(angular.isNumber($rootScope.date)).toBe(true);\n          });\n\n          it('to a string that can be parsed by Date, it updates calendar', function() {\n            $rootScope.date = 'November 7, 2005 23:30:00';\n            $rootScope.$digest();\n            testCalendar();\n            expect(angular.isString($rootScope.date)).toBe(true);\n          });\n\n          it('to a string that cannot be parsed by Date, it doesn\\'t update', function() {\n            $rootScope.date = 'pizza';\n            $rootScope.$digest();\n            expect(getTitle()).toBe('September 2010');\n            expect($rootScope.date).toBe('pizza');\n          });\n        });\n      });\n\n      it('does not loop between after max mode', function() {\n        expect(getTitle()).toBe('September 2010');\n\n        clickTitleButton();\n        expect(getTitle()).toBe('2010');\n\n        clickTitleButton();\n        expect(getTitle()).toBe('2001 - 2020');\n\n        clickTitleButton();\n        expect(getTitle()).toBe('2001 - 2020');\n      });\n\n      describe('month selection mode', function() {\n        beforeEach(function() {\n          clickTitleButton();\n        });\n\n        it('shows the year as title', function() {\n          expect(getTitle()).toBe('2010');\n        });\n\n        it('shows months as options', function() {\n          expect(getOptions()).toEqual([\n            ['January', 'February', 'March'],\n            ['April', 'May', 'June'],\n            ['July', 'August', 'September'],\n            ['October', 'November', 'December']\n          ]);\n        });\n\n        it('does not change the model', function() {\n          expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n        });\n\n        it('has `selected` only the correct month', function() {\n          expectSelectedElement(8);\n        });\n\n        it('moves to the previous year when `previous` button is clicked', function() {\n          clickPreviousButton();\n\n          expect(getTitle()).toBe('2009');\n          expect(getOptions()).toEqual([\n            ['January', 'February', 'March'],\n            ['April', 'May', 'June'],\n            ['July', 'August', 'September'],\n            ['October', 'November', 'December']\n          ]);\n\n          expectSelectedElement(null);\n        });\n\n        it('moves to the next year when `next` button is clicked', function() {\n          clickNextButton();\n\n          expect(getTitle()).toBe('2011');\n          expect(getOptions()).toEqual([\n            ['January', 'February', 'March'],\n            ['April', 'May', 'June'],\n            ['July', 'August', 'September'],\n            ['October', 'November', 'December']\n          ]);\n\n          expectSelectedElement(null);\n        });\n\n        it('renders correctly when a month is clicked', function() {\n          clickPreviousButton(5);\n          expect(getTitle()).toBe('2005');\n\n          clickOption(10);\n          expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n          expect(getTitle()).toBe('November 2005');\n          expect(getOptions(true)).toEqual([\n            ['30', '31', '01', '02', '03', '04', '05'],\n            ['06', '07', '08', '09', '10', '11', '12'],\n            ['13', '14', '15', '16', '17', '18', '19'],\n            ['20', '21', '22', '23', '24', '25', '26'],\n            ['27', '28', '29', '30', '01', '02', '03'],\n            ['04', '05', '06', '07', '08', '09', '10']\n          ]);\n\n          clickOption(17);\n          expect($rootScope.date).toEqual(new Date('November 16, 2005 15:30:00'));\n        });\n      });\n\n      describe('year selection mode', function() {\n        beforeEach(function() {\n          clickTitleButton();\n          clickTitleButton();\n        });\n\n        it('shows the year range as title', function() {\n          expect(getTitle()).toBe('2001 - 2020');\n        });\n\n        it('shows years as options', function() {\n          expect(getOptions()).toEqual([\n            ['2001', '2002', '2003', '2004', '2005'],\n            ['2006', '2007', '2008', '2009', '2010'],\n            ['2011', '2012', '2013', '2014', '2015'],\n            ['2016', '2017', '2018', '2019', '2020']\n          ]);\n        });\n\n        it('does not change the model', function() {\n          expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n        });\n\n        it('has `selected` only the selected year', function() {\n          expectSelectedElement(9);\n        });\n\n        it('moves to the previous year set when `previous` button is clicked', function() {\n          clickPreviousButton();\n\n          expect(getTitle()).toBe('1981 - 2000');\n          expect(getOptions()).toEqual([\n            ['1981', '1982', '1983', '1984', '1985'],\n            ['1986', '1987', '1988', '1989', '1990'],\n            ['1991', '1992', '1993', '1994', '1995'],\n            ['1996', '1997', '1998', '1999', '2000']\n          ]);\n          expectSelectedElement(null);\n        });\n\n        it('moves to the next year set when `next` button is clicked', function() {\n          clickNextButton();\n\n          expect(getTitle()).toBe('2021 - 2040');\n          expect(getOptions()).toEqual([\n            ['2021', '2022', '2023', '2024', '2025'],\n            ['2026', '2027', '2028', '2029', '2030'],\n            ['2031', '2032', '2033', '2034', '2035'],\n            ['2036', '2037', '2038', '2039', '2040']\n          ]);\n\n          expectSelectedElement(null);\n        });\n      });\n\n      describe('keyboard navigation', function() {\n        function getActiveLabel() {\n          return element.find('.active').eq(0).text();\n        }\n\n        describe('day mode', function() {\n          it('will be able to activate previous day', function() {\n            triggerKeyDown(element, 'left');\n            expect(getActiveLabel()).toBe('29');\n          });\n\n          it('will be able to select with enter', function() {\n            triggerKeyDown(element, 'left');\n            triggerKeyDown(element, 'enter');\n            expect($rootScope.date).toEqual(new Date('September 29, 2010 15:30:00'));\n          });\n\n          it('will be able to select with space', function() {\n            triggerKeyDown(element, 'left');\n            triggerKeyDown(element, 'space');\n            expect($rootScope.date).toEqual(new Date('September 29, 2010 15:30:00'));\n          });\n\n          it('will be able to activate next day', function() {\n            triggerKeyDown(element, 'right');\n            expect(getActiveLabel()).toBe('01');\n            expect(getTitle()).toBe('October 2010');\n          });\n\n          it('will be able to activate same day in previous week', function() {\n            triggerKeyDown(element, 'up');\n            expect(getActiveLabel()).toBe('23');\n          });\n\n          it('will be able to activate same day in next week', function() {\n            triggerKeyDown(element, 'down');\n            expect(getActiveLabel()).toBe('07');\n            expect(getTitle()).toBe('October 2010');\n          });\n\n          it('will be able to activate same date in previous month', function() {\n            triggerKeyDown(element, 'pageup');\n            expect(getActiveLabel()).toBe('30');\n            expect(getTitle()).toBe('August 2010');\n          });\n\n          it('will be able to activate same date in next month', function() {\n            triggerKeyDown(element, 'pagedown');\n            expect(getActiveLabel()).toBe('30');\n            expect(getTitle()).toBe('October 2010');\n          });\n\n          it('will be able to activate first day of the month', function() {\n            triggerKeyDown(element, 'home');\n            expect(getActiveLabel()).toBe('01');\n            expect(getTitle()).toBe('September 2010');\n          });\n\n          it('will be able to activate last day of the month', function() {\n            $rootScope.date = new Date('September 1, 2010 15:30:00');\n            $rootScope.$digest();\n\n            triggerKeyDown(element, 'end');\n            expect(getActiveLabel()).toBe('30');\n            expect(getTitle()).toBe('September 2010');\n          });\n\n          it('will be able to move to month mode', function() {\n            triggerKeyDown(element, 'up', true);\n            expect(getActiveLabel()).toBe('September');\n            expect(getTitle()).toBe('2010');\n          });\n\n          it('will not respond when trying to move to lower mode', function() {\n            triggerKeyDown(element, 'down', true);\n            expect(getActiveLabel()).toBe('30');\n            expect(getTitle()).toBe('September 2010');\n          });\n        });\n\n        describe('month mode', function() {\n          beforeEach(function() {\n            triggerKeyDown(element, 'up', true);\n          });\n\n          it('will be able to activate previous month', function() {\n            triggerKeyDown(element, 'left');\n            expect(getActiveLabel()).toBe('August');\n          });\n\n          it('will be able to activate next month', function() {\n            triggerKeyDown(element, 'right');\n            expect(getActiveLabel()).toBe('October');\n          });\n\n          it('will be able to activate same month in previous row', function() {\n            triggerKeyDown(element, 'up');\n            expect(getActiveLabel()).toBe('June');\n          });\n\n          it('will be able to activate same month in next row', function() {\n            triggerKeyDown(element, 'down');\n            expect(getActiveLabel()).toBe('December');\n          });\n\n          it('will be able to activate same date in previous year', function() {\n            triggerKeyDown(element, 'pageup');\n            expect(getActiveLabel()).toBe('September');\n            expect(getTitle()).toBe('2009');\n          });\n\n          it('will be able to activate same date in next year', function() {\n            triggerKeyDown(element, 'pagedown');\n            expect(getActiveLabel()).toBe('September');\n            expect(getTitle()).toBe('2011');\n          });\n\n          it('will be able to activate first month of the year', function() {\n            triggerKeyDown(element, 'home');\n            expect(getActiveLabel()).toBe('January');\n            expect(getTitle()).toBe('2010');\n          });\n\n          it('will be able to activate last month of the year', function() {\n            triggerKeyDown(element, 'end');\n            expect(getActiveLabel()).toBe('December');\n            expect(getTitle()).toBe('2010');\n          });\n\n          it('will be able to move to year mode', function() {\n            triggerKeyDown(element, 'up', true);\n            expect(getActiveLabel()).toBe('2010');\n            expect(getTitle()).toBe('2001 - 2020');\n          });\n\n          it('will be able to move to day mode', function() {\n            triggerKeyDown(element, 'down', true);\n            expect(getActiveLabel()).toBe('30');\n            expect(getTitle()).toBe('September 2010');\n          });\n\n          it('will move to day mode when selecting', function() {\n            triggerKeyDown(element, 'left', true);\n            triggerKeyDown(element, 'enter', true);\n            expect(getActiveLabel()).toBe('30');\n            expect(getTitle()).toBe('August 2010');\n            expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n          });\n        });\n\n        describe('year mode', function() {\n          beforeEach(function() {\n            triggerKeyDown(element, 'up', true);\n            triggerKeyDown(element, 'up', true);\n          });\n\n          it('will be able to activate previous year', function() {\n            triggerKeyDown(element, 'left');\n            expect(getActiveLabel()).toBe('2009');\n          });\n\n          it('will be able to activate next year', function() {\n            triggerKeyDown(element, 'right');\n            expect(getActiveLabel()).toBe('2011');\n          });\n\n          it('will be able to activate same year in previous row', function() {\n            triggerKeyDown(element, 'up');\n            expect(getActiveLabel()).toBe('2005');\n          });\n\n          it('will be able to activate same year in next row', function() {\n            triggerKeyDown(element, 'down');\n            expect(getActiveLabel()).toBe('2015');\n          });\n\n          it('will be able to activate same date in previous view', function() {\n            triggerKeyDown(element, 'pageup');\n            expect(getActiveLabel()).toBe('1990');\n          });\n\n          it('will be able to activate same date in next view', function() {\n            triggerKeyDown(element, 'pagedown');\n            expect(getActiveLabel()).toBe('2030');\n          });\n\n          it('will be able to activate first year of the year', function() {\n            triggerKeyDown(element, 'home');\n            expect(getActiveLabel()).toBe('2001');\n          });\n\n          it('will be able to activate last year of the year', function() {\n            triggerKeyDown(element, 'end');\n            expect(getActiveLabel()).toBe('2020');\n          });\n\n          it('will not respond when trying to move to upper mode', function() {\n            triggerKeyDown(element, 'up', true);\n            expect(getTitle()).toBe('2001 - 2020');\n          });\n\n          it('will be able to move to month mode', function() {\n            triggerKeyDown(element, 'down', true);\n            expect(getActiveLabel()).toBe('September');\n            expect(getTitle()).toBe('2010');\n          });\n\n          it('will move to month mode when selecting', function() {\n            triggerKeyDown(element, 'left', true);\n            triggerKeyDown(element, 'enter', true);\n            expect(getActiveLabel()).toBe('September');\n            expect(getTitle()).toBe('2009');\n            expect($rootScope.date).toEqual(new Date('September 30, 2010 15:30:00'));\n          });\n        });\n\n        describe('`aria-activedescendant`', function() {\n          function checkActivedescendant() {\n            var activeId = element.find('table').attr('aria-activedescendant');\n            expect(element.find('#' + activeId + ' > button')).toHaveClass('active');\n          }\n\n          it('updates correctly', function() {\n            triggerKeyDown(element, 'left');\n            checkActivedescendant();\n\n            triggerKeyDown(element, 'down');\n            checkActivedescendant();\n\n            triggerKeyDown(element, 'up', true);\n            checkActivedescendant();\n\n            triggerKeyDown(element, 'up', true);\n            checkActivedescendant();\n          });\n        });\n      });\n    });\n\n    describe('attribute `datepicker-options`', function() {\n      describe('ngModelOptions', function() {\n          beforeEach(inject(function() {\n            $rootScope.date = new Date('2005-11-07T10:00:00.000Z');\n            $rootScope.options = {\n              ngModelOptions: {\n                timezone: '+600'\n              }\n            };\n            element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n            $rootScope.$digest();\n          }));\n\n          it('supports ngModelOptions from options object and sets date to appropriate date', function() {\n            expectSelectedElement(8);\n          });\n      });\n\n      describe('startingDay', function() {\n        beforeEach(function() {\n          $rootScope.options = {\n            startingDay: 1\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('shows the day labels rotated', function() {\n          expect(getLabels(true)).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);\n        });\n\n        it('renders the calendar days correctly', function() {\n          expect(getOptions(true)).toEqual([\n            ['30', '31', '01', '02', '03', '04', '05'],\n            ['06', '07', '08', '09', '10', '11', '12'],\n            ['13', '14', '15', '16', '17', '18', '19'],\n            ['20', '21', '22', '23', '24', '25', '26'],\n            ['27', '28', '29', '30', '01', '02', '03'],\n            ['04', '05', '06', '07', '08', '09', '10']\n          ]);\n        });\n\n        it('renders the week numbers correctly', function() {\n          expect(getWeeks()).toEqual(['35', '36', '37', '38', '39', '40']);\n        });\n      });\n\n      describe('showWeeks', function() {\n        beforeEach(function() {\n          $rootScope.options = {\n            showWeeks: false\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('hides week numbers based on variable', function() {\n          expect(getLabelsRow().find('th').length).toEqual(7);\n          var tr = element.find('tbody').find('tr');\n          for (var i = 0; i < 5; i++) {\n            expect(tr.eq(i).find('td').length).toEqual(7);\n          }\n        });\n      });\n\n      describe('minDate with no initial value', function() {\n        beforeEach(function() {\n          $rootScope.options = {};\n          $rootScope.date = new Date('September 10, 2010');\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('should toggle appropriately', function() {\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n\n          $rootScope.options.minDate = new Date('September 12, 2010');\n          $rootScope.$digest();\n\n          refreshedButtons = getAllOptionsEl();\n          angular.forEach(refreshedButtons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index < 14);\n          });\n        });\n      });\n\n      describe('minDate', function() {\n        beforeEach(function() {\n          $rootScope.options = {\n            minDate: new Date('September 12, 2010')\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('disables appropriate days in current month', function() {\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index < 14);\n          });\n        });\n\n        it('disables appropriate days when min date changes', function() {\n          $rootScope.options.minDate = new Date('September 5, 2010');\n          $rootScope.$digest();\n\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index < 7);\n          });\n        });\n\n        it('invalidates when model is a disabled date', function() {\n          $rootScope.options.minDate = new Date('September 5, 2010');\n          $rootScope.date = new Date('September 2, 2010');\n          $rootScope.$digest();\n          expect(element.hasClass('ng-invalid')).toBeTruthy();\n          expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy();\n        });\n\n        it('disables all days in previous month', function() {\n          clickPreviousButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(true);\n          });\n        });\n\n        it('disables no days in next month', function() {\n          clickNextButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n        });\n\n        it('disables appropriate months in current year', function() {\n          clickTitleButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index < 8);\n          });\n        });\n\n        it('disables all months in previous year', function() {\n          clickTitleButton();\n          clickPreviousButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(true);\n          });\n        });\n\n        it('disables no months in next year', function() {\n          clickTitleButton();\n          clickNextButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n        });\n\n        it('enables everything before if it is cleared', function() {\n          $rootScope.options.minDate = null;\n          $rootScope.date = new Date('December 20, 1949');\n          $rootScope.$digest();\n\n          clickTitleButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n        });\n\n        it('accepts literals, \\'yyyy-MM-dd\\' case', function() {\n          $rootScope.options.minDate = '2010-09-05';\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index < 7);\n          });\n        });\n      });\n\n      describe('maxDate with no initial value', function() {\n        beforeEach(function() {\n          $rootScope.options = {};\n          $rootScope.date = new Date('September 10, 2010');\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('should toggle appropriately', function() {\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n\n          $rootScope.options.maxDate = new Date('September 25, 2010');\n          $rootScope.$digest();\n\n          refreshedButtons = getAllOptionsEl();\n          angular.forEach(refreshedButtons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index > 27);\n          });\n        });\n      });\n\n      describe('maxDate', function() {\n        beforeEach(function() {\n          $rootScope.options = {\n            maxDate: new Date('September 25, 2010')\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('disables appropriate days in current month', function() {\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index > 27);\n          });\n        });\n\n        it('disables appropriate days when max date changes', function() {\n          $rootScope.options.maxDate = new Date('September 18, 2010');\n          $rootScope.$digest();\n\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index > 20);\n          });\n        });\n\n        it('invalidates when model is a disabled date', function() {\n          $rootScope.options.maxDate = new Date('September 18, 2010');\n          $rootScope.$digest();\n          expect(element.hasClass('ng-invalid')).toBeTruthy();\n          expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy();\n        });\n\n        it('disables no days in previous month', function() {\n          clickPreviousButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n        });\n\n        it('disables all days in next month', function() {\n          clickNextButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(true);\n          });\n        });\n\n        it('disables appropriate months in current year', function() {\n          clickTitleButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(index > 8);\n          });\n        });\n\n        it('disables no months in previous year', function() {\n          clickTitleButton();\n          clickPreviousButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n        });\n\n        it('disables all months in next year', function() {\n          clickTitleButton();\n          clickNextButton();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(true);\n          });\n        });\n\n        it('enables everything after if it is cleared', function() {\n          $rootScope.options.maxDate = null;\n          $rootScope.$digest();\n          var buttons = getAllOptionsEl();\n          angular.forEach(buttons, function(button, index) {\n            expect(angular.element(button).prop('disabled')).toBe(false);\n          });\n        });\n      });\n\n      describe('formatting', function() {\n        beforeEach(function() {\n          $rootScope.options = {\n            formatDay: 'd',\n            formatDayHeader: 'EEEE',\n            formatDayTitle: 'MMMM, yy',\n            formatMonth: 'MMM',\n            formatMonthTitle: 'yy',\n            formatYear: 'yy',\n            yearColumns: 4,\n            yearRows: 3\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\"' +\n            'datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n        });\n\n        it('changes the title format in `day` mode', function() {\n          expect(getTitle()).toBe('September, 10');\n        });\n\n        it('changes the title & months format in `month` mode', function() {\n          clickTitleButton();\n\n          expect(getTitle()).toBe('10');\n          expect(getOptions()).toEqual([\n            ['Jan', 'Feb', 'Mar'],\n            ['Apr', 'May', 'Jun'],\n            ['Jul', 'Aug', 'Sep'],\n            ['Oct', 'Nov', 'Dec']\n          ]);\n        });\n\n        it('changes the title, year format & range in `year` mode', function() {\n          clickTitleButton();\n          clickTitleButton();\n\n          expect(getTitle()).toBe('05 - 16');\n          expect(getOptions()).toEqual([\n            ['05', '06', '07', '08'],\n            ['09', '10', '11', '12'],\n            ['13', '14', '15', '16']\n          ]);\n        });\n\n        it('shows day labels', function() {\n          expect(getLabels(true)).toEqual(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);\n        });\n\n        it('changes the day format', function() {\n          expect(getOptions(true)).toEqual([\n            ['29', '30', '31', '1', '2', '3', '4'],\n            ['5', '6', '7', '8', '9', '10', '11'],\n            ['12', '13', '14', '15', '16', '17', '18'],\n            ['19', '20', '21', '22', '23', '24', '25'],\n            ['26', '27', '28', '29', '30', '1', '2'],\n            ['3', '4', '5', '6', '7', '8', '9']\n          ]);\n        });\n      });\n    });\n\n    describe('setting datepickerConfig', function() {\n      var originalConfig = {};\n      beforeEach(inject(function(uibDatepickerConfig) {\n        angular.extend(originalConfig, uibDatepickerConfig);\n        uibDatepickerConfig.formatDay = 'd';\n        uibDatepickerConfig.formatMonth = 'MMM';\n        uibDatepickerConfig.formatYear = 'yy';\n        uibDatepickerConfig.formatDayHeader = 'EEEE';\n        uibDatepickerConfig.formatDayTitle = 'MMM, yy';\n        uibDatepickerConfig.formatMonthTitle = 'yy';\n        uibDatepickerConfig.showWeeks = false;\n        uibDatepickerConfig.yearRows = 2;\n        uibDatepickerConfig.yearColumns = 5;\n        uibDatepickerConfig.startingDay = 6;\n        uibDatepickerConfig.monthColumns = 4;\n\n        element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n      afterEach(inject(function(uibDatepickerConfig) {\n        // return it to the original state\n        Object.keys(uibDatepickerConfig).forEach(function(key) {\n          delete uibDatepickerConfig[key];\n        });\n        angular.extend(uibDatepickerConfig, originalConfig);\n      }));\n\n      it('changes the title format in `day` mode', function() {\n        expect(getTitle()).toBe('Sep, 10');\n      });\n\n      it('changes the title & months format in `month` mode', function() {\n        clickTitleButton();\n\n        expect(getTitle()).toBe('10');\n        expect(getOptions()).toEqual([\n          ['Jan', 'Feb', 'Mar', 'Apr'],\n          ['May', 'Jun', 'Jul', 'Aug'],\n          ['Sep', 'Oct', 'Nov', 'Dec']\n        ]);\n      });\n\n      it('shows title year button to expand to fill width in `month` mode', function() {\n        clickTitleButton();\n        expect(getTitleCell().attr('colspan')).toBe('2');\n      });\n\n      it('changes the title, year format & range in `year` mode', function() {\n        clickTitleButton();\n        clickTitleButton();\n\n        expect(getTitle()).toBe('01 - 10');\n        expect(getOptions()).toEqual([\n          ['01', '02', '03', '04', '05'],\n          ['06', '07', '08', '09', '10']\n        ]);\n      });\n\n      it('changes the `starting-day` & day headers & format', function() {\n        expect(getLabels()).toEqual(['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']);\n        expect(getOptions(false)).toEqual([\n          ['28', '29', '30', '31', '1', '2', '3'],\n          ['4', '5', '6', '7', '8', '9', '10'],\n          ['11', '12', '13', '14', '15', '16', '17'],\n          ['18', '19', '20', '21', '22', '23', '24'],\n          ['25', '26', '27', '28', '29', '30', '1'],\n          ['2', '3', '4', '5', '6', '7', '8']\n        ]);\n      });\n\n      it('changes initial visibility for weeks', function() {\n        expect(getLabelsRow().find('th').length).toEqual(7);\n        var tr = element.find('tbody').find('tr');\n        for (var i = 0; i < 5; i++) {\n          expect(tr.eq(i).find('td').length).toEqual(7);\n        }\n      });\n    });\n\n    describe('disabled', function() {\n      beforeEach(function() {\n        element = $compile('<div uib-datepicker ng-model=\"date\" disabled></div>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should have all dates disabled', function() {\n        element.find('.uib-day button').each(function(idx, elem) {\n          expect($(elem).prop('disabled')).toBe(true);\n        });\n      });\n    });\n\n    describe('ng-disabled', function() {\n      beforeEach(function() {\n        $rootScope.disabled = false;\n        element = $compile('<div uib-datepicker ng-model=\"date\" ng-disabled=\"disabled\"></div>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should toggle disabled state with value of ng-disabled', function() {\n        element.find('.uib-day button').each(function(idx, elem) {\n          expect($(elem).prop('disabled')).toBe(false);\n        });\n\n        $rootScope.disabled = true;\n        $rootScope.$digest();\n\n        element.find('.uib-day button').each(function(idx, elem) {\n          expect($(elem).prop('disabled')).toBe(true);\n        });\n\n        $rootScope.disabled = false;\n        $rootScope.$digest();\n\n        element.find('.uib-day button').each(function(idx, elem) {\n          expect($(elem).prop('disabled')).toBe(false);\n        });\n      });\n    });\n\n    describe('datepickerConfig ngModelOptions', function() {\n      describe('timezone', function() {\n        var originalConfig = {};\n        beforeEach(inject(function(uibDatepickerConfig) {\n          angular.extend(originalConfig, uibDatepickerConfig);\n          uibDatepickerConfig.ngModelOptions = { timezone: '+600' };\n          $rootScope.date = new Date('2005-11-07T10:00:00.000Z');\n        }));\n\n        afterEach(inject(function(uibDatepickerConfig) {\n          // return it to the original state\n          angular.extend(uibDatepickerConfig, originalConfig);\n        }));\n\n        describe('basics', function() {\n          beforeEach(function() {\n            element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n            $rootScope.$digest();\n          });\n\n          it('sets date to appropriate date', function() {\n            expectSelectedElement(8);\n          });\n\n          it('updates the input when a day is clicked', function() {\n            clickOption(9);\n            expect($rootScope.date).toEqual(new Date('2005-11-08T10:00:00.000Z'));\n          });\n        });\n\n        it('init date', function() {\n          $rootScope.options = {\n            initDate: new Date('2006-01-01T00:00:00.000Z')\n          };\n          $rootScope.date = null;\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getTitle()).toEqual('January 2006');\n        });\n\n        it('min date', function() {\n          $rootScope.options = {\n            minDate: new Date('2010-10-01T00:00:00.000Z')\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getSelectedElement().prop('disabled')).toBe(true);\n        });\n      });\n    });\n\n    describe('uib-datepicker ng-model-options', function() {\n      describe('timezone', function() {\n        beforeEach(inject(function() {\n          $rootScope.date = new Date('2005-11-07T10:00:00.000Z');\n          $rootScope.ngModelOptions = { timezone: '+600'};\n          element = $compile('<div uib-datepicker ng-model=\"date\" ng-model-options=\"ngModelOptions\"></div>')($rootScope);\n          $rootScope.$digest();\n        }));\n\n        it('sets date to appropriate date', function() {\n          expectSelectedElement(8);\n        });\n\n        it('updates the input when a day is clicked', function() {\n          clickOption(9);\n          expect($rootScope.date).toEqual(new Date('2005-11-08T10:00:00.000Z'));\n        });\n      });\n    });\n\n    describe('with empty initial state', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = null;\n        element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('is has a `<table>` element', function() {\n        expect(element.find('table').length).toBe(1);\n      });\n\n      it('is shows rows with days', function() {\n        expect(element.find('tbody').find('tr').length).toBeGreaterThan(3);\n      });\n\n      it('sets default 00:00:00 time for selected date', function() {\n        $rootScope.date = new Date('August 1, 2013');\n        $rootScope.$digest();\n        $rootScope.date = null;\n        $rootScope.$digest();\n\n        clickOption(14);\n        expect($rootScope.date).toEqual(new Date('August 11, 2013 00:00:00'));\n      });\n    });\n\n    describe('`init-date`', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = null;\n        $rootScope.options = {\n          initDate: new Date('November 9, 1980')\n        };\n        element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('does not alter the model', function() {\n        expect($rootScope.date).toBe(null);\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('November 1980');\n      });\n    });\n\n    describe('`datepicker-mode`', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = new Date('August 11, 2013');\n        $rootScope.options = {\n          datepickerMode: 'month'\n        };\n        element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('2013');\n      });\n\n      it('updates binding', function() {\n        clickTitleButton();\n        expect($rootScope.options.datepickerMode).toBe('year');\n      });\n    });\n\n    describe('`min-mode`', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = new Date('August 11, 2013');\n        $rootScope.options = {\n          minMode: 'month',\n          datepickerMode: 'month'\n        };\n        element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('does not move below it', function() {\n        expect(getTitle()).toBe('2013');\n        clickOption( 5 );\n        expect(getTitle()).toBe('2013');\n        clickTitleButton();\n        expect(getTitle()).toBe('2001 - 2020');\n        $rootScope.options.minMode = 'year';\n        $rootScope.$digest();\n        clickOption( 5 );\n        expect(getTitle()).toBe('2001 - 2020');\n      });\n\n      it('updates current mode if necessary', function() {\n        expect(getTitle()).toBe('2013');\n        $rootScope.options.minMode = 'year';\n        $rootScope.$digest();\n        expect(getTitle()).toBe('2001 - 2020');\n      });\n    });\n\n    describe('`max-mode`', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = new Date('August 11, 2013');\n        $rootScope.options = {\n          maxMode: 'month'\n        };\n        element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('does not move above it', function() {\n        expect(getTitle()).toBe('August 2013');\n        clickTitleButton();\n        expect(getTitle()).toBe('2013');\n        clickTitleButton();\n        expect(getTitle()).toBe('2013');\n        clickOption( 10 );\n        expect(getTitle()).toBe('November 2013');\n        $rootScope.options.maxMode = 'day';\n        $rootScope.$digest();\n        clickTitleButton();\n        expect(getTitle()).toBe('November 2013');\n      });\n\n      it('disables the title button at it', function() {\n        expect(getTitleButton().prop('disabled')).toBe(false);\n        clickTitleButton();\n        expect(getTitleButton().prop('disabled')).toBe(true);\n        clickTitleButton();\n        expect(getTitleButton().prop('disabled')).toBe(true);\n        clickOption( 10 );\n        expect(getTitleButton().prop('disabled')).toBe(false);\n        $rootScope.options.maxMode = 'day';\n        $rootScope.$digest();\n        expect(getTitleButton().prop('disabled')).toBe(true);\n      });\n\n      it('updates current mode if necessary', function() {\n        expect(getTitle()).toBe('August 2013');\n        clickTitleButton();\n        expect(getTitle()).toBe('2013');\n        $rootScope.options.maxMode = 'day';\n        $rootScope.$digest();\n        expect(getTitle()).toBe('August 2013');\n      });\n    });\n\n    describe('with an ngModelController having formatters and parsers', function() {\n      beforeEach(inject(function() {\n        // Custom date object.\n        $rootScope.date = { type: 'date', date: 'April 1, 2015 00:00:00' };\n\n        // Use dateModel directive to add formatters and parsers to the\n        // ngModelController that translate the custom date object.\n        element = $compile('<div uib-datepicker ng-model=\"date\" date-model></div>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('updates the view', function() {\n        $rootScope.date = { type: 'date', date: 'April 15, 2015 00:00:00' };\n        $rootScope.$digest();\n\n        expectSelectedElement(17);\n      });\n\n      it('updates the model', function() {\n        clickOption(17);\n\n        expect($rootScope.date.type).toEqual('date');\n        expect(new Date($rootScope.date.date)).toEqual(new Date('April 15, 2015 00:00:00'));\n      });\n    });\n\n    describe('thursdays determine week count', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = new Date('June 07, 2014');\n      }));\n\n      it('with the default starting day (sunday)', function() {\n        element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n        $rootScope.$digest();\n\n        expect(getWeeks()).toEqual(['23', '24', '25', '26', '27', '28']);\n      });\n\n      describe('when starting date', function() {\n        it('is monday', function() {\n          $rootScope.options = {\n            startingDay: 1\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getWeeks()).toEqual(['22', '23', '24', '25', '26', '27']);\n        });\n\n        it('is thursday', function() {\n          $rootScope.options = {\n            startingDay: 4\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getWeeks()).toEqual(['22', '23', '24', '25', '26', '27']);\n        });\n\n        it('is saturday', function() {\n          $rootScope.options = {\n            startingDay: 6\n          };\n          element = $compile('<div uib-datepicker ng-model=\"date\" datepicker-options=\"options\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getWeeks()).toEqual(['23', '24', '25', '26', '27', '28']);\n        });\n      });\n\n      describe('first week in january', function() {\n        it('in current year', function() {\n          $rootScope.date = new Date('January 07, 2014');\n          element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getWeeks()).toEqual(['1', '2', '3', '4', '5', '6']);\n        });\n\n        it('in last year', function() {\n          $rootScope.date = new Date('January 07, 2010');\n          element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getWeeks()).toEqual(['53', '1', '2', '3', '4', '5']);\n        });\n      });\n\n      describe('last week(s) in december', function() {\n        beforeEach(inject(function() {\n          $rootScope.date = new Date('December 07, 2014');\n        }));\n\n        it('in next year', function() {\n          element = $compile('<div uib-datepicker ng-model=\"date\"></div>')($rootScope);\n          $rootScope.$digest();\n\n          expect(getWeeks()).toEqual(['49', '50', '51', '52', '1', '2']);\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/datepickerPopup/docs/demo.html",
    "content": "<style>\n  .full button span {\n    background-color: limegreen;\n    border-radius: 32px;\n    color: black;\n  }\n  .partially button span {\n    background-color: orange;\n    border-radius: 32px;\n    color: black;\n  }\n</style>\n<div ng-controller=\"DatepickerPopupDemoCtrl\">\n    <pre>Selected date is: <em>{{dt | date:'fullDate' }}</em></pre>\n\n    <h4>Popup</h4>\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <p class=\"input-group\">\n          <input type=\"text\" class=\"form-control\" uib-datepicker-popup=\"{{format}}\" ng-model=\"dt\" is-open=\"popup1.opened\" datepicker-options=\"dateOptions\" ng-required=\"true\" close-text=\"Close\" alt-input-formats=\"altInputFormats\" />\n          <span class=\"input-group-btn\">\n            <button type=\"button\" class=\"btn btn-default\" ng-click=\"open1()\"><i class=\"glyphicon glyphicon-calendar\"></i></button>\n          </span>\n        </p>\n      </div>\n\n      <div class=\"col-md-6\">\n        <p class=\"input-group\">\n          <input type=\"text\" class=\"form-control\" uib-datepicker-popup ng-model=\"dt\" is-open=\"popup2.opened\" datepicker-options=\"dateOptions\" ng-required=\"true\" close-text=\"Close\" />\n          <span class=\"input-group-btn\">\n            <button type=\"button\" class=\"btn btn-default\" ng-click=\"open2()\"><i class=\"glyphicon glyphicon-calendar\"></i></button>\n          </span>\n        </p>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <label>Format: <span class=\"muted-text\">(manual alternate <em>{{altInputFormats[0]}}</em>)</span></label> <select class=\"form-control\" ng-model=\"format\" ng-options=\"f for f in formats\"><option></option></select>\n      </div>\n    </div>\n\n    <hr />\n    <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"today()\">Today</button>\n    <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"setDate(2009, 7, 24)\">2009-08-24</button>\n    <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"clear()\">Clear</button>\n    <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"toggleMin()\" uib-tooltip=\"After today restriction\">Min date</button>\n</div>\n"
  },
  {
    "path": "src/datepickerPopup/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('DatepickerPopupDemoCtrl', function ($scope) {\n  $scope.today = function() {\n    $scope.dt = new Date();\n  };\n  $scope.today();\n\n  $scope.clear = function() {\n    $scope.dt = null;\n  };\n\n  $scope.inlineOptions = {\n    customClass: getDayClass,\n    minDate: new Date(),\n    showWeeks: true\n  };\n\n  $scope.dateOptions = {\n    dateDisabled: disabled,\n    formatYear: 'yy',\n    maxDate: new Date(2020, 5, 22),\n    minDate: new Date(),\n    startingDay: 1\n  };\n\n  // Disable weekend selection\n  function disabled(data) {\n    var date = data.date,\n      mode = data.mode;\n    return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6);\n  }\n\n  $scope.toggleMin = function() {\n    $scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date();\n    $scope.dateOptions.minDate = $scope.inlineOptions.minDate;\n  };\n\n  $scope.toggleMin();\n\n  $scope.open1 = function() {\n    $scope.popup1.opened = true;\n  };\n\n  $scope.open2 = function() {\n    $scope.popup2.opened = true;\n  };\n\n  $scope.setDate = function(year, month, day) {\n    $scope.dt = new Date(year, month, day);\n  };\n\n  $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];\n  $scope.format = $scope.formats[0];\n  $scope.altInputFormats = ['M!/d!/yyyy'];\n\n  $scope.popup1 = {\n    opened: false\n  };\n\n  $scope.popup2 = {\n    opened: false\n  };\n\n  var tomorrow = new Date();\n  tomorrow.setDate(tomorrow.getDate() + 1);\n  var afterTomorrow = new Date();\n  afterTomorrow.setDate(tomorrow.getDate() + 1);\n  $scope.events = [\n    {\n      date: tomorrow,\n      status: 'full'\n    },\n    {\n      date: afterTomorrow,\n      status: 'partially'\n    }\n  ];\n\n  function getDayClass(data) {\n    var date = data.date,\n      mode = data.mode;\n    if (mode === 'day') {\n      var dayToCheck = new Date(date).setHours(0,0,0,0);\n\n      for (var i = 0; i < $scope.events.length; i++) {\n        var currentDay = new Date($scope.events[i].date).setHours(0,0,0,0);\n\n        if (dayToCheck === currentDay) {\n          return $scope.events[i].status;\n        }\n      }\n    }\n\n    return '';\n  }\n});\n"
  },
  {
    "path": "src/datepickerPopup/docs/readme.md",
    "content": "The datepicker popup is meant to be used with an input element. To understand usage of the datepicker, please refer to its documentation [here](https://angular-ui.github.io/bootstrap/#/datepicker).\n\n### uib-datepicker-popup settings\n\nThe popup is a wrapper that you can use in an input to toggle a datepicker. To configure the datepicker, use `datepicker-options` as documented in the [inline datepicker](https://angular-ui.github.io/bootstrap/#/datepicker).\n\n* `alt-input-formats`\n<small class=\"badge\">$</small>\n<small class=\"badge\">C</small>\n_(Default: `[]`)_ -\nA list of alternate formats acceptable for manual entry.\n\n* `clear-text`\n<small class=\"badge\">C</small>\n_(Default: `Clear`)_ -\nThe text to display for the clear button.\n\n* `close-on-date-selection`\n<small class=\"badge\">$</small>\n<small class=\"badge\">C</small>\n_(Default: `true`)_ -\nWhether to close calendar when a date is chosen.\n\n* `close-text`\n<small class=\"badge\">C</small>\n_(Default: `Done`)_ -\nThe text to display for the close button.\n\n* `current-text`\n<small class=\"badge\">C</small>\n_(Default: `Today`)_ -\nThe text to display for the current day button.\n\n* `datepicker-append-to-body`\n<small class=\"badge\">$</small>\n<small class=\"badge\">C</small>\n_(Default: `false`, Config: `appendToBody`)_ -\nAppend the datepicker popup element to `body`, rather than inserting after `datepicker-popup`.\n\n* `datepicker-options`\n<small class=\"badge\">$</small> -\nAn object with any combination of the datepicker settings (in camelCase) used to configure the wrapped datepicker.\n\n* `datepicker-popup-template-url`\n<small class=\"badge\">C</small>\n_(Default: `uib/template/datepickerPopup/popup.html`)_ -\nAdd the ability to override the template used on the component.\n\n* `datepicker-template-url`\n<small class=\"badge\">C</small>\n_(Default: `uib/template/datepicker/datepicker.html`)_ -\nAdd the ability to override the template used on the component (inner uib-datepicker).\n\n* `is-open`\n<small class=\"badge\">$</small>\n<i class=\"glyphicon glyphicon-eye-open\"></i>\n_(Default: `false`)_ -\nWhether or not to show the datepicker.\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  The date object. Must be a Javascript `Date` object. You may use the `uibDateParser` service to assist in string-to-object conversion.\n\n* `on-open-focus`\n<small class=\"badge\">$</small>\n<small class=\"badge\">C</small>\n_(Default: `true`)_ -\nWhether or not to focus the datepicker popup upon opening.\n\n* `show-button-bar`\n<small class=\"badge\">$</small>\n<small class=\"badge\">C</small>\n_(Default: `true`)_ -\nWhether or not to display a button bar underneath the uib-datepicker.\n\n* `type`\n<small class=\"badge\">C</small>\n_(Default: `text`, Config: `html5Types`)_ -\nYou can override the input type to be _(date|datetime-local|month)_. That will change the date format of the popup.\n\n* `popup-placement`\n <small class=\"badge\">C</small>\n _(Default: `auto bottom-left`, Config: 'placement')_ -\nPassing in 'auto' separated by a space before the placement will enable auto positioning, e.g: \"auto bottom-left\". The popup will attempt to position where it fits in the closest scrollable ancestor. Accepts:\n\n * `top` - popup on top, horizontally centered on input element.\n * `top-left` - popup on top, left edge aligned with input element left edge.\n * `top-right` - popup on top, right edge aligned with input element right edge.\n * `bottom` - popup on bottom, horizontally centered on input element.\n * `bottom-left` - popup on bottom, left edge aligned with input element left edge.\n * `bottom-right` - popup on bottom, right edge aligned with input element right edge.\n * `left` - popup on left, vertically centered on input element.\n * `left-top` - popup on left, top edge aligned with input element top edge.\n * `left-bottom` - popup on left, bottom edge aligned with input element bottom edge.\n * `right` - popup on right, vertically centered on input element.\n * `right-top` - popup on right, top edge aligned with input element top edge.\n * `right-bottom` - popup on right, bottom edge aligned with input element bottom edge.\n\n* `uib-datepicker-popup`\n<small class=\"badge\">C</small>\n_(Default: `yyyy-MM-dd`, Config: `datepickerConfig`)_ -\nThe format for displayed dates. This string can take string literals by surrounding the value with single quotes, i.e. `yyyy-MM-dd h 'o\\'clock'`.\n\n**Notes**\n\nIf using this directive on input type date, a native browser datepicker could also appear.\n"
  },
  {
    "path": "src/datepickerPopup/index-nocss.js",
    "content": "require('../datepicker/index-nocss.js');\nrequire('../position/index-nocss.js');\nrequire('../../template/datepickerPopup/popup.html.js');\nrequire('./popup.js');\n\nvar MODULE_NAME = 'ui.bootstrap.module.datepickerPopup';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.datepickerPopup', 'uib/template/datepickerPopup/popup.html', 'ui.bootstrap.module.datepicker']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/datepickerPopup/index.js",
    "content": "require('../datepicker/datepicker.css');\nrequire('../position/position.css');\nrequire('./popup.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/datepickerPopup/popup.css",
    "content": ".uib-datepicker-popup.dropdown-menu {\n  display: block;\n  float: none;\n  margin: 0;\n}\n\n.uib-button-bar {\n  padding: 10px 9px 2px;\n}\n"
  },
  {
    "path": "src/datepickerPopup/popup.js",
    "content": "angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])\n\n.value('$datepickerPopupLiteralWarning', true)\n\n.constant('uibDatepickerPopupConfig', {\n  altInputFormats: [],\n  appendToBody: false,\n  clearText: 'Clear',\n  closeOnDateSelection: true,\n  closeText: 'Done',\n  currentText: 'Today',\n  datepickerPopup: 'yyyy-MM-dd',\n  datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',\n  datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',\n  html5Types: {\n    date: 'yyyy-MM-dd',\n    'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',\n    'month': 'yyyy-MM'\n  },\n  onOpenFocus: true,\n  showButtonBar: true,\n  placement: 'auto bottom-left'\n})\n\n.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',\nfunction($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {\n  var cache = {},\n    isHtml5DateInput = false;\n  var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,\n    datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,\n    ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];\n\n  this.init = function(_ngModel_) {\n    ngModel = _ngModel_;\n    ngModelOptions = extractOptions(ngModel);\n    closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?\n      $scope.$parent.$eval($attrs.closeOnDateSelection) :\n      datepickerPopupConfig.closeOnDateSelection;\n    appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?\n      $scope.$parent.$eval($attrs.datepickerAppendToBody) :\n      datepickerPopupConfig.appendToBody;\n    onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?\n      $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;\n    datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?\n      $attrs.datepickerPopupTemplateUrl :\n      datepickerPopupConfig.datepickerPopupTemplateUrl;\n    datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?\n      $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;\n    altInputFormats = angular.isDefined($attrs.altInputFormats) ?\n      $scope.$parent.$eval($attrs.altInputFormats) :\n      datepickerPopupConfig.altInputFormats;\n\n    $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?\n      $scope.$parent.$eval($attrs.showButtonBar) :\n      datepickerPopupConfig.showButtonBar;\n\n    if (datepickerPopupConfig.html5Types[$attrs.type]) {\n      dateFormat = datepickerPopupConfig.html5Types[$attrs.type];\n      isHtml5DateInput = true;\n    } else {\n      dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;\n      $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {\n        var newDateFormat = value || datepickerPopupConfig.datepickerPopup;\n        // Invalidate the $modelValue to ensure that formatters re-run\n        // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764\n        if (newDateFormat !== dateFormat) {\n          dateFormat = newDateFormat;\n          ngModel.$modelValue = null;\n\n          if (!dateFormat) {\n            throw new Error('uibDatepickerPopup must have a date format specified.');\n          }\n        }\n      });\n    }\n\n    if (!dateFormat) {\n      throw new Error('uibDatepickerPopup must have a date format specified.');\n    }\n\n    if (isHtml5DateInput && $attrs.uibDatepickerPopup) {\n      throw new Error('HTML5 date input types do not support custom formats.');\n    }\n\n    // popup element used to display calendar\n    popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');\n\n    popupEl.attr({\n      'ng-model': 'date',\n      'ng-change': 'dateSelection(date)',\n      'template-url': datepickerPopupTemplateUrl\n    });\n\n    // datepicker element\n    datepickerEl = angular.element(popupEl.children()[0]);\n    datepickerEl.attr('template-url', datepickerTemplateUrl);\n\n    if (!$scope.datepickerOptions) {\n      $scope.datepickerOptions = {};\n    }\n\n    if (isHtml5DateInput) {\n      if ($attrs.type === 'month') {\n        $scope.datepickerOptions.datepickerMode = 'month';\n        $scope.datepickerOptions.minMode = 'month';\n      }\n    }\n\n    datepickerEl.attr('datepicker-options', 'datepickerOptions');\n\n    if (!isHtml5DateInput) {\n      // Internal API to maintain the correct ng-invalid-[key] class\n      ngModel.$$parserName = 'date';\n      ngModel.$validators.date = validator;\n      ngModel.$parsers.unshift(parseDate);\n      ngModel.$formatters.push(function(value) {\n        if (ngModel.$isEmpty(value)) {\n          $scope.date = value;\n          return value;\n        }\n\n        if (angular.isNumber(value)) {\n          value = new Date(value);\n        }\n\n        $scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));\n\n        return dateParser.filter($scope.date, dateFormat);\n      });\n    } else {\n      ngModel.$formatters.push(function(value) {\n        $scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));\n        return value;\n      });\n    }\n\n    // Detect changes in the view from the text box\n    ngModel.$viewChangeListeners.push(function() {\n      $scope.date = parseDateString(ngModel.$viewValue);\n    });\n\n    $element.on('keydown', inputKeydownBind);\n\n    $popup = $compile(popupEl)($scope);\n    // Prevent jQuery cache memory leak (template is now redundant after linking)\n    popupEl.remove();\n\n    if (appendToBody) {\n      $document.find('body').append($popup);\n    } else {\n      $element.after($popup);\n    }\n\n    $scope.$on('$destroy', function() {\n      if ($scope.isOpen === true) {\n        if (!$rootScope.$$phase) {\n          $scope.$apply(function() {\n            $scope.isOpen = false;\n          });\n        }\n      }\n\n      $popup.remove();\n      $element.off('keydown', inputKeydownBind);\n      $document.off('click', documentClickBind);\n      if (scrollParentEl) {\n        scrollParentEl.off('scroll', positionPopup);\n      }\n      angular.element($window).off('resize', positionPopup);\n\n      //Clear all watch listeners on destroy\n      while (watchListeners.length) {\n        watchListeners.shift()();\n      }\n    });\n  };\n\n  $scope.getText = function(key) {\n    return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];\n  };\n\n  $scope.isDisabled = function(date) {\n    if (date === 'today') {\n      date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));\n    }\n\n    var dates = {};\n    angular.forEach(['minDate', 'maxDate'], function(key) {\n      if (!$scope.datepickerOptions[key]) {\n        dates[key] = null;\n      } else if (angular.isDate($scope.datepickerOptions[key])) {\n        dates[key] = new Date($scope.datepickerOptions[key]);\n      } else {\n        if ($datepickerPopupLiteralWarning) {\n          $log.warn('Literal date support has been deprecated, please switch to date object usage');\n        }\n\n        dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));\n      }\n    });\n\n    return $scope.datepickerOptions &&\n      dates.minDate && $scope.compare(date, dates.minDate) < 0 ||\n      dates.maxDate && $scope.compare(date, dates.maxDate) > 0;\n  };\n\n  $scope.compare = function(date1, date2) {\n    return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());\n  };\n\n  // Inner change\n  $scope.dateSelection = function(dt) {\n    $scope.date = dt;\n    var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function\n    $element.val(date);\n    ngModel.$setViewValue(date);\n\n    if (closeOnDateSelection) {\n      $scope.isOpen = false;\n      $element[0].focus();\n    }\n  };\n\n  $scope.keydown = function(evt) {\n    if (evt.which === 27) {\n      evt.stopPropagation();\n      $scope.isOpen = false;\n      $element[0].focus();\n    }\n  };\n\n  $scope.select = function(date, evt) {\n    evt.stopPropagation();\n\n    if (date === 'today') {\n      var today = new Date();\n      if (angular.isDate($scope.date)) {\n        date = new Date($scope.date);\n        date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());\n      } else {\n        date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));\n        date.setHours(0, 0, 0, 0);\n      }\n    }\n    $scope.dateSelection(date);\n  };\n\n  $scope.close = function(evt) {\n    evt.stopPropagation();\n\n    $scope.isOpen = false;\n    $element[0].focus();\n  };\n\n  $scope.disabled = angular.isDefined($attrs.disabled) || false;\n  if ($attrs.ngDisabled) {\n    watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {\n      $scope.disabled = disabled;\n    }));\n  }\n\n  $scope.$watch('isOpen', function(value) {\n    if (value) {\n      if (!$scope.disabled) {\n        $timeout(function() {\n          positionPopup();\n\n          if (onOpenFocus) {\n            $scope.$broadcast('uib:datepicker.focus');\n          }\n\n          $document.on('click', documentClickBind);\n\n          var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;\n          if (appendToBody || $position.parsePlacement(placement)[2]) {\n            scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));\n            if (scrollParentEl) {\n              scrollParentEl.on('scroll', positionPopup);\n            }\n          } else {\n            scrollParentEl = null;\n          }\n\n          angular.element($window).on('resize', positionPopup);\n        }, 0, false);\n      } else {\n        $scope.isOpen = false;\n      }\n    } else {\n      $document.off('click', documentClickBind);\n      if (scrollParentEl) {\n        scrollParentEl.off('scroll', positionPopup);\n      }\n      angular.element($window).off('resize', positionPopup);\n    }\n  });\n\n  function cameltoDash(string) {\n    return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });\n  }\n\n  function parseDateString(viewValue) {\n    var date = dateParser.parse(viewValue, dateFormat, $scope.date);\n    if (isNaN(date)) {\n      for (var i = 0; i < altInputFormats.length; i++) {\n        date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);\n        if (!isNaN(date)) {\n          return date;\n        }\n      }\n    }\n    return date;\n  }\n\n  function parseDate(viewValue) {\n    if (angular.isNumber(viewValue)) {\n      // presumably timestamp to date object\n      viewValue = new Date(viewValue);\n    }\n\n    if (!viewValue) {\n      return null;\n    }\n\n    if (angular.isDate(viewValue) && !isNaN(viewValue)) {\n      return viewValue;\n    }\n\n    if (angular.isString(viewValue)) {\n      var date = parseDateString(viewValue);\n      if (!isNaN(date)) {\n        return dateParser.toTimezone(date, ngModelOptions.getOption('timezone'));\n      }\n    }\n\n    return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined;\n  }\n\n  function validator(modelValue, viewValue) {\n    var value = modelValue || viewValue;\n\n    if (!$attrs.ngRequired && !value) {\n      return true;\n    }\n\n    if (angular.isNumber(value)) {\n      value = new Date(value);\n    }\n\n    if (!value) {\n      return true;\n    }\n\n    if (angular.isDate(value) && !isNaN(value)) {\n      return true;\n    }\n\n    if (angular.isString(value)) {\n      return !isNaN(parseDateString(value));\n    }\n\n    return false;\n  }\n\n  function documentClickBind(event) {\n    if (!$scope.isOpen && $scope.disabled) {\n      return;\n    }\n\n    var popup = $popup[0];\n    var dpContainsTarget = $element[0].contains(event.target);\n    // The popup node may not be an element node\n    // In some browsers (IE) only element nodes have the 'contains' function\n    var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);\n    if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {\n      $scope.$apply(function() {\n        $scope.isOpen = false;\n      });\n    }\n  }\n\n  function inputKeydownBind(evt) {\n    if (evt.which === 27 && $scope.isOpen) {\n      evt.preventDefault();\n      evt.stopPropagation();\n      $scope.$apply(function() {\n        $scope.isOpen = false;\n      });\n      $element[0].focus();\n    } else if (evt.which === 40 && !$scope.isOpen) {\n      evt.preventDefault();\n      evt.stopPropagation();\n      $scope.$apply(function() {\n        $scope.isOpen = true;\n      });\n    }\n  }\n\n  function positionPopup() {\n    if ($scope.isOpen) {\n      var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));\n      var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;\n      var position = $position.positionElements($element, dpElement, placement, appendToBody);\n      dpElement.css({top: position.top + 'px', left: position.left + 'px'});\n      if (dpElement.hasClass('uib-position-measure')) {\n        dpElement.removeClass('uib-position-measure');\n      }\n    }\n  }\n\n  function extractOptions(ngModelCtrl) {\n    var ngModelOptions;\n\n    if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing\n      // guarantee a value\n      ngModelOptions = angular.isObject(ngModelCtrl.$options) ?\n        ngModelCtrl.$options :\n        {\n          timezone: null\n        };\n\n      // mimic 1.6+ api\n      ngModelOptions.getOption = function (key) {\n        return ngModelOptions[key];\n      };\n    } else { // in angular >=1.6 $options is always present\n      ngModelOptions = ngModelCtrl.$options;\n    }\n\n    return ngModelOptions;\n  }\n\n  $scope.$on('uib:datepicker.mode', function() {\n    $timeout(positionPopup, 0, false);\n  });\n}])\n\n.directive('uibDatepickerPopup', function() {\n  return {\n    require: ['ngModel', 'uibDatepickerPopup'],\n    controller: 'UibDatepickerPopupController',\n    scope: {\n      datepickerOptions: '=?',\n      isOpen: '=?',\n      currentText: '@',\n      clearText: '@',\n      closeText: '@'\n    },\n    link: function(scope, element, attrs, ctrls) {\n      var ngModel = ctrls[0],\n        ctrl = ctrls[1];\n\n      ctrl.init(ngModel);\n    }\n  };\n})\n\n.directive('uibDatepickerPopupWrap', function() {\n  return {\n    restrict: 'A',\n    transclude: true,\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';\n    }\n  };\n});\n"
  },
  {
    "path": "src/datepickerPopup/test/popup.spec.js",
    "content": "describe('datepicker popup', function() {\n  var inputEl, dropdownEl, $compile, $document, $rootScope, $sniffer,\n    $templateCache, $timeout;\n  beforeEach(module('ui.bootstrap.datepickerPopup'));\n  beforeEach(module('uib/template/datepicker/datepicker.html'));\n  beforeEach(module('uib/template/datepicker/day.html'));\n  beforeEach(module('uib/template/datepicker/month.html'));\n  beforeEach(module('uib/template/datepicker/year.html'));\n  beforeEach(module('uib/template/datepickerPopup/popup.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $rootScope.date = new Date('September 30, 2010 15:30:00');\n    $templateCache = _$templateCache_;\n  }));\n\n  function getTitleButton() {\n    return element.find('th').eq(1).find('button').first();\n  }\n\n  function getTitle() {\n    return getTitleButton().text();\n  }\n\n  function clickTitleButton() {\n    getTitleButton().click();\n  }\n\n  function getLabelsRow() {\n    return element.find('thead').find('tr').eq(1);\n  }\n\n  function getLabels(dayMode) {\n    var els = getLabelsRow().find('th'),\n        labels = [];\n    for (var i = dayMode ? 1 : 0, n = els.length; i < n; i++) {\n      labels.push(els.eq(i).text());\n    }\n    return labels;\n  }\n\n  function getOptions(dayMode) {\n    var tr = element.find('tbody').find('tr');\n    var rows = [];\n\n    for (var j = 0, numRows = tr.length; j < numRows; j++) {\n      var cols = tr.eq(j).find('td'), days = [];\n      for (var i = dayMode ? 1 : 0, n = cols.length; i < n; i++) {\n        days.push(cols.eq(i).find('button').text());\n      }\n      rows.push(days);\n    }\n    return rows;\n  }\n\n  function clickOption(index) {\n    getAllOptionsEl().eq(index).click();\n  }\n\n  function getAllOptionsEl(dayMode) {\n    return element.find('tbody').find('button');\n  }\n\n  function selectedElementIndex() {\n    var buttons = getAllOptionsEl();\n    for (var i = 0; i < buttons.length; i++) {\n      if (angular.element(buttons[i]).hasClass('btn-info')) {\n        return i;\n      }\n    }\n  }\n\n  function expectSelectedElement(index) {\n    var buttons = getAllOptionsEl();\n    angular.forEach( buttons, function(button, idx) {\n      expect(angular.element(button).hasClass('btn-info')).toBe(idx === index);\n    });\n  }\n\n  function getSelectedElement(index) {\n    var buttons = getAllOptionsEl();\n    var el = $.grep(buttons, function(button, idx) {\n      return angular.element(button).hasClass('btn-info');\n    })[0];\n    return angular.element(el);\n  }\n\n  function triggerKeyDown(element, key, ctrl) {\n    var keyCodes = {\n      'enter': 13,\n      'space': 32,\n      'pageup': 33,\n      'pagedown': 34,\n      'end': 35,\n      'home': 36,\n      'left': 37,\n      'up': 38,\n      'right': 39,\n      'down': 40,\n      'esc': 27\n    };\n    var e = $.Event('keydown');\n    e.which = keyCodes[key];\n    if (ctrl) {\n      e.ctrlKey = true;\n    }\n    element.trigger(e);\n  }\n\n  function assignElements(wrapElement) {\n    inputEl = wrapElement.find('input');\n    dropdownEl = wrapElement.find('ul');\n    element = dropdownEl.find('table');\n  }\n\n  function changeInputValueTo(el, value) {\n    el.val(value);\n    el.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n    $rootScope.$digest();\n  }\n\n  describe('basic', function() {\n    var wrapElement, inputEl, dropdownEl;\n\n    function assignElements(wrapElement) {\n      inputEl = wrapElement.find('input');\n      dropdownEl = wrapElement.find('ul');\n      element = dropdownEl.find('table');\n    }\n\n    beforeEach(function() {\n      $rootScope.date = new Date('September 30, 2010 15:30:00');\n      $rootScope.isopen = true;\n      wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup is-open=\"isopen\"></div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    });\n\n    it('should stop click event from bubbling from today button', function() {\n      var bubbled = false;\n      wrapElement.on('click', function() {\n        bubbled = true;\n      });\n\n      wrapElement.find('.uib-datepicker-current').trigger('click');\n\n      expect(bubbled).toBe(false);\n    });\n\n    it('should stop click event from bubbling from clear button', function() {\n      var bubbled = false;\n      wrapElement.on('click', function() {\n        bubbled = true;\n      });\n\n      wrapElement.find('.uib-clear').trigger('click');\n\n      expect(bubbled).toBe(false);\n    });\n\n    it('should stop click event from bubbling from close button', function() {\n      var bubbled = false;\n      wrapElement.on('click', function() {\n        bubbled = true;\n      });\n\n      wrapElement.find('.uib-close').trigger('click');\n\n      expect(bubbled).toBe(false);\n    });\n  });\n\n  describe('ngModelOptions allowInvalid', function() {\n    beforeEach(inject(function(_$sniffer_) {\n      $sniffer = _$sniffer_;\n\n      $rootScope.date = new Date('September 30, 2010 15:30:00');\n      $rootScope.ngModelOptions = {\n        allowInvalid: true\n      };\n      element = $compile('<div><input ng-model=\"date\" ng-model-options=\"ngModelOptions\" uib-datepicker-popup></div>')($rootScope);\n      inputEl = element.find('input');\n      $rootScope.$digest();\n    }));\n\n    function changeInputValueTo(el, value) {\n      el.val(value);\n      el.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n      $rootScope.$digest();\n    }\n\n    it('should update ng-model even if the date is invalid when allowInvalid is true', function() {\n      changeInputValueTo(inputEl, 'pizza');\n      expect($rootScope.date).toBe('pizza');\n      expect(inputEl.val()).toBe('pizza');\n    });\n  });\n\n  describe('setting datepickerPopupConfig', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibDatepickerPopupConfig) {\n      angular.extend(originalConfig, uibDatepickerPopupConfig);\n      uibDatepickerPopupConfig.datepickerPopup = 'MM-dd-yyyy';\n\n      element = $compile('<input ng-model=\"date\" uib-datepicker-popup>')($rootScope);\n      $rootScope.$digest();\n    }));\n    afterEach(inject(function(uibDatepickerPopupConfig) {\n      // return it to the original state\n      angular.extend(uibDatepickerPopupConfig, originalConfig);\n    }));\n\n    it('changes date format', function() {\n      expect(element.val()).toEqual('09-30-2010');\n    });\n  });\n\n  describe('setting datepickerPopupConfig inside ng-if', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibDatepickerPopupConfig) {\n      angular.extend(originalConfig, uibDatepickerPopupConfig);\n      uibDatepickerPopupConfig.datepickerPopup = 'MM-dd-yyyy';\n\n      element = $compile('<div><div ng-if=\"true\"><input ng-model=\"date\" uib-datepicker-popup></div></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n    afterEach(inject(function(uibDatepickerPopupConfig) {\n      // return it to the original state\n      angular.extend(uibDatepickerPopupConfig, originalConfig);\n    }));\n\n    it('changes date format', function() {\n      expect(element.find('input').val()).toEqual('09-30-2010');\n    });\n  });\n\n  describe('initially', function() {\n    beforeEach(inject(function(_$document_, _$sniffer_) {\n      $document = _$document_;\n      $sniffer = _$sniffer_;\n      $rootScope.isopen = true;\n      $rootScope.date = new Date('September 30, 2010 15:30:00');\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('does not to display datepicker initially', function() {\n      expect(dropdownEl.length).toBe(0);\n    });\n\n    it('to display the correct value in input', function() {\n      expect(inputEl.val()).toBe('2010-09-30');\n    });\n  });\n\n  describe('initially opened', function() {\n    var wrapElement;\n\n    beforeEach(inject(function(_$document_, _$sniffer_, _$timeout_) {\n      $document = _$document_;\n      $sniffer = _$sniffer_;\n      $timeout = _$timeout_;\n      $rootScope.isopen = true;\n      $rootScope.date = new Date('September 30, 2010 15:30:00');\n      wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup is-open=\"isopen\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('datepicker is displayed', function() {\n      expect(dropdownEl.length).toBe(1);\n    });\n\n    it('renders the calendar correctly', function() {\n      expect(getLabelsRow().css('display')).not.toBe('none');\n      expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);\n      expect(getOptions(true)).toEqual([\n        ['29', '30', '31', '01', '02', '03', '04'],\n        ['05', '06', '07', '08', '09', '10', '11'],\n        ['12', '13', '14', '15', '16', '17', '18'],\n        ['19', '20', '21', '22', '23', '24', '25'],\n        ['26', '27', '28', '29', '30', '01', '02'],\n        ['03', '04', '05', '06', '07', '08', '09']\n      ]);\n    });\n\n    it('updates the input when a day is clicked', function() {\n      clickOption(17);\n      expect(inputEl.val()).toBe('2010-09-15');\n      expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));\n    });\n\n    it('should mark the input field dirty when a day is clicked', function() {\n      expect(inputEl).toHaveClass('ng-pristine');\n      clickOption(17);\n      expect(inputEl).toHaveClass('ng-dirty');\n    });\n\n    it('updates the input correctly when model changes', function() {\n      $rootScope.date = new Date('January 10, 1983 10:00:00');\n      $rootScope.$digest();\n      expect(inputEl.val()).toBe('1983-01-10');\n    });\n\n    it('closes the dropdown when a day is clicked', function() {\n      expect(dropdownEl.length).toBe(1);\n\n      clickOption(17);\n      assignElements(wrapElement);\n      expect(dropdownEl.length).toBe(0);\n    });\n\n    it('updates the model & calendar when input value changes', function() {\n      changeInputValueTo(inputEl, '2010-09-15');\n\n      expect($rootScope.date.getFullYear()).toEqual(2010);\n      expect($rootScope.date.getMonth()).toEqual(8);\n      expect($rootScope.date.getDate()).toEqual(15);\n\n      expect(getOptions(true)).toEqual([\n        ['29', '30', '31', '01', '02', '03', '04'],\n        ['05', '06', '07', '08', '09', '10', '11'],\n        ['12', '13', '14', '15', '16', '17', '18'],\n        ['19', '20', '21', '22', '23', '24', '25'],\n        ['26', '27', '28', '29', '30', '01', '02'],\n        ['03', '04', '05', '06', '07', '08', '09']\n      ]);\n      expectSelectedElement(17);\n    });\n\n    it('closes when click outside of calendar', function() {\n      expect(dropdownEl.length).toBe(1);\n\n      $timeout.flush(0);\n      $document.find('body').click();\n      assignElements(wrapElement);\n      expect(dropdownEl.length).toBe(0);\n    });\n\n    it('sets `ng-invalid` for invalid input', function() {\n      changeInputValueTo(inputEl, 'pizza');\n\n      expect(inputEl).toHaveClass('ng-invalid');\n      expect(inputEl).toHaveClass('ng-invalid-date');\n      expect($rootScope.date).toBeUndefined();\n      expect(inputEl.val()).toBe('pizza');\n    });\n\n    it('unsets `ng-invalid` for valid input', function() {\n      changeInputValueTo(inputEl, 'pizza');\n      expect(inputEl).toHaveClass('ng-invalid-date');\n\n      $rootScope.date = new Date('August 11, 2013');\n      $rootScope.$digest();\n      expect(inputEl).not.toHaveClass('ng-invalid');\n      expect(inputEl).not.toHaveClass('ng-invalid-date');\n    });\n\n    describe('focus', function () {\n      beforeEach(function() {\n        var body = $document.find('body');\n        body.append(inputEl);\n        body.append(dropdownEl);\n      });\n\n      afterEach(function() {\n        inputEl.remove();\n        dropdownEl.remove();\n      });\n\n      it('returns to the input when ESC key is pressed in the popup and closes', function() {\n        expect(dropdownEl.length).toBe(1);\n\n        dropdownEl.find('button').eq(0).focus();\n        expect(document.activeElement.tagName).toBe('BUTTON');\n\n        triggerKeyDown(dropdownEl, 'esc');\n        assignElements(wrapElement);\n        expect(dropdownEl.length).toBe(0);\n        expect(document.activeElement.tagName).toBe('INPUT');\n      });\n\n      it('returns to the input when ESC key is pressed in the input and closes', function() {\n        expect(dropdownEl.length).toBe(1);\n\n        dropdownEl.find('button').eq(0).focus();\n        expect(document.activeElement.tagName).toBe('BUTTON');\n\n        triggerKeyDown(inputEl, 'esc');\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        expect(dropdownEl.length).toBe(0);\n        expect(document.activeElement.tagName).toBe('INPUT');\n      });\n\n      it('stops the ESC key from propagating if the dropdown is open, but not when closed', function() {\n        var documentKey = -1;\n        var getKey = function(evt) { documentKey = evt.which; };\n        $document.on('keydown', getKey);\n\n        triggerKeyDown(inputEl, 'esc');\n        expect(documentKey).toBe(-1);\n\n        triggerKeyDown(inputEl, 'esc');\n        expect(documentKey).toBe(27);\n\n        $document.off('keydown', getKey);\n      });\n    });\n\n    describe('works with HTML5 date input types', function() {\n      var date2 = new Date('October 1, 2010 12:34:56.789');\n      beforeEach(inject(function(_$document_) {\n        $document = _$document_;\n        $rootScope.isopen = true;\n        $rootScope.date = new Date('September 30, 2010 15:30:00');\n      }));\n\n      it('works as date', function() {\n        setupInputWithType('date');\n        expect(dropdownEl.length).toBe(1);\n        expect(inputEl.val()).toBe('2010-09-30');\n\n        changeInputValueTo(inputEl, '1980-03-05');\n\n        expect($rootScope.date.getFullYear()).toEqual(1980);\n        expect($rootScope.date.getMonth()).toEqual(2);\n        expect($rootScope.date.getDate()).toEqual(5);\n\n        expect(getOptions(true)).toEqual([\n          ['24', '25', '26', '27', '28', '29', '01'],\n          ['02', '03', '04', '05', '06', '07', '08'],\n          ['09', '10', '11', '12', '13', '14', '15'],\n          ['16', '17', '18', '19', '20', '21', '22'],\n          ['23', '24', '25', '26', '27', '28', '29'],\n          ['30', '31', '01', '02', '03', '04', '05']\n        ]);\n        expect(selectedElementIndex()).toEqual(10);\n      });\n\n      it('works as datetime-local', function() {\n        setupInputWithType('datetime-local');\n        expect(inputEl.val()).toBe('2010-09-30T15:30:00.000');\n\n        changeInputValueTo(inputEl, '1980-03-05T12:34:56.000');\n\n        expect($rootScope.date.getFullYear()).toEqual(1980);\n        expect($rootScope.date.getMonth()).toEqual(2);\n        expect($rootScope.date.getDate()).toEqual(5);\n\n        expect(getOptions(true)).toEqual([\n          ['24', '25', '26', '27', '28', '29', '01'],\n          ['02', '03', '04', '05', '06', '07', '08'],\n          ['09', '10', '11', '12', '13', '14', '15'],\n          ['16', '17', '18', '19', '20', '21', '22'],\n          ['23', '24', '25', '26', '27', '28', '29'],\n          ['30', '31', '01', '02', '03', '04', '05']\n        ]);\n        expect(selectedElementIndex()).toEqual(10);\n      });\n\n      it('works as month', function() {\n        setupInputWithType('month');\n        expect(inputEl.val()).toBe('2010-09');\n\n        changeInputValueTo(inputEl, '1980-03');\n\n        expect($rootScope.date.getFullYear()).toEqual(1980);\n        expect($rootScope.date.getMonth()).toEqual(2);\n        expect($rootScope.date.getDate()).toEqual(30);\n\n        expect(getOptions()).toEqual([\n          ['January', 'February', 'March'],\n          ['April', 'May', 'June'],\n          ['July', 'August', 'September'],\n          ['October', 'November', 'December']\n        ]);\n        expect(selectedElementIndex()).toEqual(2);\n      });\n\n      function setupInputWithType(type) {\n        var wrapElement = $compile('<div><input type=\"' +\n          type + '\" ng-model=\"date\" uib-datepicker-popup is-open=\"isopen\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      }\n    });\n  });\n\n  describe('works with ngModelOptions', function() {\n    var $timeout;\n\n    beforeEach(inject(function(_$document_, _$sniffer_, _$timeout_) {\n      $document = _$document_;\n      $timeout = _$timeout_;\n      $rootScope.isopen = true;\n      $rootScope.date = new Date('September 30, 2010 15:30:00');\n      var wrapElement = $compile('<div><input ng-model=\"date\" ' +\n        'ng-model-options=\"{ debounce: 10000 }\" ' +\n        'uib-datepicker-popup is-open=\"isopen\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('should change model and update calendar after debounce timeout', function() {\n      changeInputValueTo(inputEl, '1980-03-05');\n\n      expect($rootScope.date.getFullYear()).toEqual(2010);\n      expect($rootScope.date.getMonth()).toEqual(9 - 1);\n      expect($rootScope.date.getDate()).toEqual(30);\n\n      expect(getOptions(true)).toEqual([\n        ['29', '30', '31', '01', '02', '03', '04'],\n        ['05', '06', '07', '08', '09', '10', '11'],\n        ['12', '13', '14', '15', '16', '17', '18'],\n        ['19', '20', '21', '22', '23', '24', '25'],\n        ['26', '27', '28', '29', '30', '01', '02'],\n        ['03', '04', '05', '06', '07', '08', '09']\n      ]);\n\n      // No changes yet\n      $timeout.flush(2000);\n      expect($rootScope.date.getFullYear()).toEqual(2010);\n      expect($rootScope.date.getMonth()).toEqual(9 - 1);\n      expect($rootScope.date.getDate()).toEqual(30);\n\n      expect(getOptions(true)).toEqual([\n        ['29', '30', '31', '01', '02', '03', '04'],\n        ['05', '06', '07', '08', '09', '10', '11'],\n        ['12', '13', '14', '15', '16', '17', '18'],\n        ['19', '20', '21', '22', '23', '24', '25'],\n        ['26', '27', '28', '29', '30', '01', '02'],\n        ['03', '04', '05', '06', '07', '08', '09']\n      ]);\n\n      $timeout.flush(10000);\n      expect($rootScope.date.getFullYear()).toEqual(1980);\n      expect($rootScope.date.getMonth()).toEqual(2);\n      expect($rootScope.date.getDate()).toEqual(5);\n\n      expect(getOptions(true)).toEqual([\n        ['24', '25', '26', '27', '28', '29', '01'],\n        ['02', '03', '04', '05', '06', '07', '08'],\n        ['09', '10', '11', '12', '13', '14', '15'],\n        ['16', '17', '18', '19', '20', '21', '22'],\n        ['23', '24', '25', '26', '27', '28', '29'],\n        ['30', '31', '01', '02', '03', '04', '05']\n      ]);\n      expectSelectedElement( 10 );\n    });\n  });\n\n  describe('works with ngModelOptions updateOn : \"default\"', function() {\n    var $timeout, wrapElement;\n\n    beforeEach(inject(function(_$document_, _$sniffer_, _$timeout_) {\n      $document = _$document_;\n      $timeout = _$timeout_;\n      $rootScope.isopen = true;\n      $rootScope.date = new Date('2010-09-30T10:00:00.000Z');\n      $rootScope.options = {\n        ngModelOptions: {\n          updateOn: 'default'\n        }\n      };\n      wrapElement = $compile('<div><input ng-model=\"date\" ' +\n        'datepicker-options=\"options\" ' +\n        'uib-datepicker-popup is-open=\"isopen\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('should close the popup and update the input when a day is clicked', function() {\n      clickOption(17);\n      assignElements(wrapElement);\n      expect(dropdownEl.length).toBe(0);\n      expect(inputEl.val()).toBe('2010-09-15');\n      expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z'));\n    });\n  });\n\n  describe('attribute `datepickerOptions`', function() {\n    describe('show-weeks', function() {\n      beforeEach(function() {\n        $rootScope.opts = {\n          showWeeks: false\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      });\n\n      it('hides week numbers based on variable', function() {\n        expect(getLabelsRow().find('th').length).toEqual(7);\n        var tr = element.find('tbody').find('tr');\n        for (var i = 0; i < 5; i++) {\n          expect(tr.eq(i).find('td').length).toEqual(7);\n        }\n      });\n    });\n\n    describe('init-date', function(){\n      beforeEach(function() {\n        $rootScope.date = null;\n        $rootScope.opts = {\n          initDate: new Date('November 9, 1980')\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      });\n\n      it('does not alter the model', function() {\n        expect($rootScope.date).toBe(null);\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('November 1980');\n      });\n    });\n\n    describe('min-date', function() {\n      it('should be able to specify a min-date through options', function() {\n        $rootScope.opts = {\n          minDate: new Date('September 12, 2010'),\n          shortcutPropagation: 'dog'\n        };\n\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        var buttons = getAllOptionsEl();\n        angular.forEach(buttons, function(button, index) {\n          expect(angular.element(button).prop('disabled')).toBe(index < 14);\n        });\n\n        $rootScope.opts.minDate = new Date('September 13, 2010');\n        $rootScope.$digest();\n        buttons = getAllOptionsEl();\n        angular.forEach(buttons, function(button, index) {\n          expect(angular.element(button).prop('disabled')).toBe(index < 15);\n        });\n      });\n    });\n\n    describe('max-date', function() {\n      it('should be able to specify a max-date through options', function() {\n        $rootScope.opts = {\n          maxDate: new Date('September 25, 2010')\n        };\n\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        var buttons = getAllOptionsEl();\n        angular.forEach(buttons, function(button, index) {\n          expect(angular.element(button).prop('disabled')).toBe(index > 27);\n        });\n\n        $rootScope.opts.maxDate = new Date('September 15, 2010');\n        $rootScope.$digest();\n        buttons = getAllOptionsEl();\n        angular.forEach(buttons, function(button, index) {\n          expect(angular.element(button).prop('disabled')).toBe(index > 17);\n        });\n      });\n    });\n\n    describe('min-mode', function() {\n      it('should be able to specify min-mode through options', function() {\n        $rootScope.opts = {\n          minMode: 'month'\n        };\n\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        expect(getTitle()).toBe('2010');\n      });\n    });\n\n    describe('max-mode', function() {\n      it('should be able to specify max-mode through options', function() {\n        $rootScope.opts = {\n          maxMode: 'month'\n        };\n\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        expect(getTitle()).toBe('September 2010');\n        clickTitleButton();\n        assignElements(wrapElement);\n        expect(getTitle()).toBe('2010');\n        clickTitleButton();\n        assignElements(wrapElement);\n        expect(getTitle()).toBe('2010');\n      });\n    });\n\n    describe('datepicker-mode', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = new Date('August 11, 2013');\n        $rootScope.opts = {\n          datepickerMode: 'month'\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"opts\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      }));\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('2013');\n      });\n\n      it('updates binding', function() {\n        clickTitleButton();\n        expect($rootScope.opts.datepickerMode).toBe('year');\n      });\n    });\n  });\n\n  describe('option `init-date`', function() {\n    beforeEach(function() {\n      $rootScope.date = null;\n      $rootScope.options = {\n        initDate: new Date('November 9, 1980')\n      };\n    });\n\n    describe('when initially set', function() {\n      beforeEach(function() {\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      });\n\n      it('does not alter the model', function() {\n        expect($rootScope.date).toBe(null);\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('November 1980');\n      });\n    });\n\n    describe('when modified before date selected.', function() {\n      beforeEach(function() {\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        $rootScope.options.initDate = new Date('December 20, 1981');\n        $rootScope.$digest();\n      });\n\n      it('does not alter the model', function() {\n        expect($rootScope.date).toBe(null);\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('December 1981');\n      });\n    });\n\n    describe('when modified after date selected.', function() {\n      beforeEach(function() {\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        $rootScope.date = new Date('April 1, 1982');\n        $rootScope.options.initDate = new Date('December 20, 1981');\n        $rootScope.$digest();\n      });\n\n      it('does not alter the model', function() {\n        expect($rootScope.date).toEqual(new Date('April 1, 1982'));\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('April 1982');\n      });\n    });\n  });\n\n  describe('toggles programatically by `open` attribute', function() {\n    var wrapElement;\n\n    beforeEach(inject(function() {\n      $rootScope.open = true;\n      wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup is-open=\"open\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('to display initially', function() {\n      expect(dropdownEl.length).toBe(1);\n    });\n\n    it('to close / open from scope variable', function() {\n      expect(dropdownEl.length).toBe(1);\n      $rootScope.open = false;\n      $rootScope.$digest();\n      assignElements(wrapElement);\n      expect(dropdownEl.length).toBe(0);\n\n      $rootScope.open = true;\n      $rootScope.$digest();\n      assignElements(wrapElement);\n      expect(dropdownEl.length).toBe(1);\n    });\n  });\n\n  describe('custom format', function() {\n    beforeEach(inject(function() {\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"dd-MMMM-yyyy\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('to display the correct value in input', function() {\n      expect(inputEl.val()).toBe('30-September-2010');\n    });\n\n    it('updates the input when a day is clicked', function() {\n      clickOption(17);\n      expect(inputEl.val()).toBe('15-September-2010');\n      expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));\n    });\n\n    it('updates the input correctly when model changes', function() {\n      $rootScope.date = new Date('January 10, 1983 10:00:00');\n      $rootScope.$digest();\n      expect(inputEl.val()).toBe('10-January-1983');\n    });\n  });\n\n  describe('custom format with time', function() {\n    beforeEach(inject(function() {\n      var wrapElement = $compile('<div><input type=\"text\" ng-model=\"date\" uib-datepicker-popup=\"MMM-d-yyyy h:mm a\" is-open=\"false\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('updates the model correctly when the input value changes', function() {\n      $rootScope.date = new Date(2015, 10, 24, 10, 0);\n      $rootScope.$digest();\n      expect(inputEl.val()).toBe('Nov-24-2015 10:00 AM');\n\n      inputEl.val('Nov-24-2015 11:00 AM').trigger('input');\n      $rootScope.$digest();\n      expect($rootScope.date).toEqual(new Date(2015, 10, 24, 11, 0));\n    });\n  });\n\n  describe('custom format with optional leading zeroes', function() {\n    beforeEach(inject(function() {\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"d!-M!-yyyy\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('to display the correct value in input', function() {\n      expect(inputEl.val()).toBe('30-09-2010');\n    });\n\n    it('updates the input when a day is clicked', function() {\n      clickOption(10);\n      expect(inputEl.val()).toBe('08-09-2010');\n      expect($rootScope.date).toEqual(new Date('September 8, 2010 15:30:00'));\n    });\n\n    it('updates the input correctly when model changes', function() {\n      $rootScope.date = new Date('December 25, 1983 10:00:00');\n      $rootScope.$digest();\n      expect(inputEl.val()).toBe('25-12-1983');\n    });\n  });\n\n  describe('dynamic custom format', function() {\n    beforeEach(inject(function() {\n      $rootScope.format = 'dd-MMMM-yyyy';\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"{{format}}\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('to display the correct value in input', function() {\n      expect(inputEl.val()).toBe('30-September-2010');\n    });\n\n    it('updates the input when a day is clicked', function() {\n      clickOption(17);\n      expect(inputEl.val()).toBe('15-September-2010');\n      expect($rootScope.date).toEqual(new Date('September 15, 2010 15:30:00'));\n    });\n\n    it('updates the input correctly when model changes', function() {\n      $rootScope.date = new Date('August 11, 2013 09:09:00');\n      $rootScope.$digest();\n      expect(inputEl.val()).toBe('11-August-2013');\n    });\n\n    it('updates the input correctly when format changes', function() {\n      $rootScope.format = 'dd/MM/yyyy';\n      $rootScope.$digest();\n      expect(inputEl.val()).toBe('30/09/2010');\n    });\n  });\n\n  describe('format errors', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibDatepickerPopupConfig) {\n      angular.extend(originalConfig, uibDatepickerPopupConfig);\n      uibDatepickerPopupConfig.datepickerPopup = null;\n    }));\n    afterEach(inject(function(uibDatepickerPopupConfig) {\n      // return it to the original state\n      angular.extend(uibDatepickerPopupConfig, originalConfig);\n    }));\n\n    it('should throw an error if there is no format', function() {\n      expect(function() {\n        $compile('<div><input ng-model=\"date\" uib-datepicker-popup><div>')($rootScope);\n      }).toThrow(new Error('uibDatepickerPopup must have a date format specified.'));\n    });\n\n    it('should throw an error if the format changes to null without fallback', function() {\n      $rootScope.format = 'dd-MMMM-yyyy';\n      $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"{{format}}\"><div>')($rootScope);\n      $rootScope.$digest();\n\n      expect(function() {\n        $rootScope.format = null;\n        $rootScope.$digest();\n      }).toThrow(new Error('uibDatepickerPopup must have a date format specified.'));\n    });\n\n    it('should thrown an error on date inputs with custom formats', function() {\n      expect(function() {\n        $compile('<div><input type=\"date\" ng-model=\"date\" uib-datepicker-popup=\"dd-yyyy-MMM\"><div>')($rootScope);\n      }).toThrow(new Error('HTML5 date input types do not support custom formats.'));\n    });\n  });\n\n  describe('european format', function() {\n    it('dd.MM.yyyy', function() {\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"dd.MM.yyyy\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n\n      changeInputValueTo(inputEl, '11.08.2013');\n      expect($rootScope.date.getFullYear()).toEqual(2013);\n      expect($rootScope.date.getMonth()).toEqual(7);\n      expect($rootScope.date.getDate()).toEqual(11);\n    });\n  });\n\n  describe('`close-on-date-selection` attribute', function() {\n    var wrapElement;\n    beforeEach(inject(function() {\n      $rootScope.close = false;\n      wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup close-on-date-selection=\"close\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('does not close the dropdown when a day is clicked', function() {\n      clickOption(17);\n      assignElements(wrapElement);\n      expect(dropdownEl.length).toBe(1);\n    });\n  });\n\n  describe('button bar', function() {\n    var buttons, buttonBarElement;\n\n    function assignButtonBar() {\n      buttonBarElement = dropdownEl.find('li').eq(-1);\n      buttons = buttonBarElement.find('button');\n    }\n\n    describe('', function() {\n      var wrapElement;\n\n      beforeEach(inject(function() {\n        $rootScope.isopen = true;\n        wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup is-open=\"isopen\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n      }));\n\n      it('should exist', function() {\n        expect(dropdownEl.length).toBe(1);\n        expect(dropdownEl.find('li').length).toBe(2);\n      });\n\n      it('should have three buttons', function() {\n        expect(buttons.length).toBe(3);\n\n        expect(buttons.eq(0).text()).toBe('Today');\n        expect(buttons.eq(1).text()).toBe('Clear');\n        expect(buttons.eq(2).text()).toBe('Done');\n      });\n\n      it('should have a button to set today date without altering time part', function() {\n        var today = new Date();\n        buttons.eq(0).click();\n        expect($rootScope.date.getFullYear()).toBe(today.getFullYear());\n        expect($rootScope.date.getMonth()).toBe(today.getMonth());\n        expect($rootScope.date.getDate()).toBe(today.getDate());\n\n        expect($rootScope.date.getHours()).toBe(15);\n        expect($rootScope.date.getMinutes()).toBe(30);\n        expect($rootScope.date.getSeconds()).toBe(0);\n      });\n\n      it('should have a button to set today date if blank', function() {\n        $rootScope.date = null;\n        $rootScope.$digest();\n\n        var today = new Date();\n        buttons.eq(0).click();\n        expect($rootScope.date.getFullYear()).toBe(today.getFullYear());\n        expect($rootScope.date.getMonth()).toBe(today.getMonth());\n        expect($rootScope.date.getDate()).toBe(today.getDate());\n\n        expect($rootScope.date.getHours()).toBe(0);\n        expect($rootScope.date.getMinutes()).toBe(0);\n        expect($rootScope.date.getSeconds()).toBe(0);\n      });\n\n      it('should have a button to clear value', function() {\n        buttons.eq(1).click();\n        expect($rootScope.date).toBe(null);\n      });\n\n      it('should clear the previously selected date', function() {\n         $rootScope.date = new Date();\n         $rootScope.$digest();\n         buttons.eq(1).click();\n         expect($rootScope.date).toBe(null);\n      });\n\n      it('should have a button to close calendar', function() {\n        buttons.eq(2).click();\n        assignElements(wrapElement);\n        expect(dropdownEl.length).toBe(0);\n      });\n    });\n\n    describe('customization', function() {\n      it('should change text from attributes', function() {\n        $rootScope.clearText = 'Null it!';\n        $rootScope.close = 'Close';\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup current-text=\"Now\" clear-text=\"{{clearText}}\" close-text=\"{{close}}ME\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n\n        expect(buttons.eq(0).text()).toBe('Now');\n        expect(buttons.eq(1).text()).toBe('Null it!');\n        expect(buttons.eq(2).text()).toBe('CloseME');\n      });\n\n      it('should disable today button if before min date', function() {\n        var date = new Date();\n        date.setDate(new Date().getDate() + 1);\n        $rootScope.options = {\n          minDate: date\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n\n        expect(buttons.eq(0).prop('disabled')).toBe(true);\n      });\n\n      it('should disable today button if before min date, yyyy-MM-dd case', inject(function(dateFilter) {\n        var date = new Date();\n        date.setDate(new Date().getDate() + 1);\n        var literalMinDate = dateFilter(date, 'yyyy-MM-dd');\n        $rootScope.options = {\n          minDate: literalMinDate\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"yyyy-MM-dd\" datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n\n        expect(buttons.eq(0).prop('disabled')).toBe(true);\n      }));\n\n      it('should not disable any button if min date is null', function() {\n        $rootScope.options = {\n          minDate: null\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n\n        for (var i = 0; i < buttons.length; i++) {\n          expect(buttons.eq(i).prop('disabled')).toBe(false);\n        }\n      });\n\n      it('should disable today button if after max date', function() {\n        var date = new Date();\n        date.setDate(new Date().getDate() - 2);\n        $rootScope.options = {\n          maxDate: date\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n\n        expect(buttons.eq(0).prop('disabled')).toBe(true);\n      });\n\n      it('should not disable any button if max date is null', function() {\n        $rootScope.options = {\n          maxDate: null\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n\n        for (var i = 0; i < buttons.length; i++) {\n          expect(buttons.eq(i).prop('disabled')).toBe(false);\n        }\n      });\n\n      it('should remove bar', function() {\n        $rootScope.showBar = false;\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup show-button-bar=\"showBar\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        expect(dropdownEl.find('li').length).toBe(1);\n      });\n\n      it('should hide weeks column on popup', function() {\n        $rootScope.options = {\n          showWeeks: false\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        expect(getLabelsRow().find('th').length).toEqual(7);\n        var tr = element.find('tbody').find('tr');\n        for (var i = 0; i < 5; i++) {\n          expect(tr.eq(i).find('td').length).toEqual(7);\n        }\n      });\n\n      it('should show weeks column on popup', function() {\n        $rootScope.options = {\n          showWeeks: true\n        };\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n\n        expect(getLabelsRow().find('th').eq(0)).not.toBeHidden();\n        var tr = element.find('tbody').find('tr');\n        for (var i = 0; i < 5; i++) {\n          expect(tr.eq(i).find('td').eq(0)).not.toBeHidden();\n        }\n      });\n    });\n\n    describe('`ng-change`', function() {\n      beforeEach(inject(function() {\n        $rootScope.changeHandler = jasmine.createSpy('changeHandler');\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup ng-change=\"changeHandler()\" is-open=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n        assignButtonBar();\n      }));\n\n      it('should be called when `today` is clicked', function() {\n        buttons.eq(0).click();\n        expect($rootScope.changeHandler).toHaveBeenCalled();\n      });\n\n      it('should be called when `clear` is clicked', function() {\n        buttons.eq(1).click();\n        expect($rootScope.changeHandler).toHaveBeenCalled();\n      });\n\n      it('should not be called when `close` is clicked', function() {\n        buttons.eq(2).click();\n        expect($rootScope.changeHandler).not.toHaveBeenCalled();\n      });\n    });\n  });\n\n  describe('use with `ng-required` directive', function() {\n    describe('`ng-required is true`', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = '';\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup ng-required=\"true\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      }));\n\n      it('should be invalid initially and when no date', function() {\n        expect(inputEl.hasClass('ng-invalid')).toBeTruthy();\n      });\n\n      it('should be valid if model has been specified', function() {\n        $rootScope.date = new Date();\n        $rootScope.$digest();\n        expect(inputEl.hasClass('ng-valid')).toBeTruthy();\n      });\n\n      it('should be valid if model value is a valid timestamp', function() {\n        $rootScope.date = Date.now();\n        $rootScope.$digest();\n        expect(inputEl.hasClass('ng-valid')).toBeTruthy();\n      });\n    });\n\n    describe('`ng-required is false`', function() {\n      beforeEach(inject(function() {\n        $rootScope.date = '';\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup ng-required=\"false\"><div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      }));\n\n      it('should be valid initially and when no date', function() {\n        expect(inputEl.hasClass('ng-valid')).toBeTruthy();\n      });\n    });\n  });\n\n  describe('use with `ng-change` directive', function() {\n    beforeEach(inject(function() {\n      $rootScope.changeHandler = jasmine.createSpy('changeHandler');\n      $rootScope.date = new Date('09/16/2010');\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup ng-required=\"true\" ng-change=\"changeHandler()\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('should not be called initially', function() {\n      expect($rootScope.changeHandler).not.toHaveBeenCalled();\n    });\n\n    it('should be called when a day is clicked', function() {\n      clickOption(17);\n      expect($rootScope.changeHandler).toHaveBeenCalled();\n    });\n\n    it('should not be called when model changes programatically', function() {\n      $rootScope.date = new Date();\n      $rootScope.$digest();\n      expect($rootScope.changeHandler).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('with disabled', function() {\n    var wrapElement;\n\n    beforeEach(function() {\n      $rootScope.isOpen = false;\n      wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup ng-required=\"true\" ng-change=\"changeHandler()\" is-open=\"isOpen\" disabled><div>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('should not open the popup', function() {\n      $rootScope.isOpen = true;\n      $rootScope.$digest();\n\n      expect($rootScope.isOpen).toBe(false);\n      expect(wrapElement.find('ul').length).toBe(0);\n    });\n  });\n\n  describe('with ng-disabled', function() {\n    var wrapElement;\n\n    beforeEach(function() {\n      $rootScope.disabled = false;\n      $rootScope.isOpen = false;\n      wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup ng-required=\"true\" ng-change=\"changeHandler()\" is-open=\"isOpen\" ng-disabled=\"disabled\"><div>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('should not open the popup when disabled', function() {\n      $rootScope.isOpen = true;\n      $rootScope.$digest();\n\n      expect($rootScope.isOpen).toBe(true);\n      expect(wrapElement.find('ul').length).toBe(1);\n\n      $rootScope.isOpen = false;\n      $rootScope.$digest();\n\n      expect($rootScope.isOpen).toBe(false);\n      expect(wrapElement.find('ul').length).toBe(0);\n\n      $rootScope.disabled = true;\n      $rootScope.isOpen = true;\n      $rootScope.$digest();\n\n      expect($rootScope.isOpen).toBe(false);\n      expect(wrapElement.find('ul').length).toBe(0);\n\n      $rootScope.disabled = false;\n      $rootScope.isOpen = true;\n      $rootScope.$digest();\n\n      expect($rootScope.isOpen).toBe(true);\n      expect(wrapElement.find('ul').length).toBe(1);\n    });\n  });\n\n  describe('with datepicker-popup-template-url', function() {\n    beforeEach(function() {\n      $rootScope.date = new Date();\n    });\n\n    afterEach(function () {\n      $document.find('body').find('.dropdown-menu').remove();\n    });\n\n    it('should allow custom templates for the popup', function() {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      var elm = angular.element('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-popup-template-url=\"foo/bar.html\" is-open=\"true\"></div>');\n\n      $compile(elm)($rootScope);\n      $rootScope.$digest();\n\n      expect(elm.children().eq(1).html()).toBe('<div>baz</div>');\n    });\n  });\n\n  describe('with datepicker-template-url', function() {\n    beforeEach(function() {\n      $rootScope.date = new Date();\n    });\n\n    afterEach(function() {\n      $document.find('body').find('.dropdown-menu').remove();\n    });\n\n    it('should allow custom templates for the datepicker', function() {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      var elm = angular.element('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-template-url=\"foo/bar.html\" is-open=\"true\"></div>');\n\n      $compile(elm)($rootScope);\n      $rootScope.$digest();\n\n      var datepicker = elm.find('[uib-datepicker]');\n\n      expect(datepicker.html()).toBe('<div>baz</div>');\n    });\n  });\n\n  describe('with an append-to-body attribute', function() {\n    beforeEach(function() {\n      $rootScope.date = new Date();\n    });\n\n    afterEach(function() {\n      $document.find('body').children().remove();\n    });\n\n    it('should append to the body', function() {\n      var $body = $document.find('body'),\n          bodyLength = $body.children().length,\n          elm = angular.element(\n            '<div><input uib-datepicker-popup ng-model=\"date\" datepicker-append-to-body=\"true\" is-open=\"true\" /></div>'\n          );\n      $compile(elm)($rootScope);\n      $rootScope.$digest();\n\n      expect($body.children().length).toEqual(bodyLength + 1);\n      expect(elm.children().length).toEqual(1);\n    });\n\n    it('should be removed on scope destroy', function() {\n      var $body = $document.find('body'),\n          bodyLength = $body.children().length,\n          isolatedScope = $rootScope.$new(),\n          elm = angular.element(\n            '<input uib-datepicker-popup ng-model=\"date\" datepicker-append-to-body=\"true\" is-open=\"true\" />'\n          );\n      $compile(elm)(isolatedScope);\n      isolatedScope.$digest();\n      expect($body.children().length).toEqual(bodyLength + 1);\n      isolatedScope.$destroy();\n      expect($body.children().length).toEqual(bodyLength);\n    });\n  });\n\n  describe('with setting datepickerConfig.showWeeks to false', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibDatepickerConfig) {\n      angular.extend(originalConfig, uibDatepickerConfig);\n      uibDatepickerConfig.showWeeks = false;\n\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n    afterEach(inject(function(uibDatepickerConfig) {\n      // return it to the original state\n      angular.extend(uibDatepickerConfig, originalConfig);\n    }));\n\n    it('changes initial visibility for weeks', function() {\n      expect(getLabelsRow().find('th').length).toEqual(7);\n      var tr = element.find('tbody').find('tr');\n      for (var i = 0; i < 5; i++) {\n        expect(tr.eq(i).find('td').length).toEqual(7);\n      }\n    });\n  });\n\n  describe('`datepicker-mode`', function() {\n    beforeEach(inject(function() {\n      $rootScope.date = new Date('August 11, 2013');\n      $rootScope.options = {\n        datepickerMode: 'month'\n      };\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"true\"></div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    }));\n\n    it('shows the correct title', function() {\n      expect(getTitle()).toBe('2013');\n    });\n\n    it('updates binding', function() {\n      clickTitleButton();\n      expect($rootScope.options.datepickerMode).toBe('year');\n    });\n  });\n\n  describe('attribute `onOpenFocus`', function() {\n    beforeEach(function() {\n      $rootScope.date = null;\n      $rootScope.isopen = false;\n      var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup on-open-focus=\"false\" is-open=\"isopen\"></div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapElement);\n    });\n\n    it('should remain focused on the input', function() {\n      var focused = true;\n      expect(dropdownEl.length).toBe(0);\n\n      inputEl[0].focus();\n      inputEl.on('blur', function() {\n        focused = false;\n      });\n      $rootScope.isopen = true;\n      $rootScope.$digest();\n\n      expect(inputEl.parent().find('.dropdown-menu').length).toBe(1);\n      expect(focused).toBe(true);\n    });\n  });\n\n  describe('altInputFormats', function() {\n    describe('datepickerPopupConfig.altInputFormats', function() {\n      var originalConfig = {};\n      beforeEach(inject(function(uibDatepickerPopupConfig) {\n        $rootScope.date = new Date('November 9, 1980');\n        angular.extend(originalConfig, uibDatepickerPopupConfig);\n        uibDatepickerPopupConfig.datepickerPopup = 'MM-dd-yyyy';\n        uibDatepickerPopupConfig.altInputFormats = ['M!/d!/yyyy'];\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      }));\n\n      afterEach(inject(function(uibDatepickerPopupConfig) {\n        // return it to the original state\n        angular.extend(uibDatepickerPopupConfig, originalConfig);\n      }));\n\n      it('changes date format', function() {\n        changeInputValueTo(inputEl, '11/8/1980');\n\n        expect($rootScope.date.getFullYear()).toEqual(1980);\n        expect($rootScope.date.getMonth()).toEqual(10);\n        expect($rootScope.date.getDate()).toEqual(8);\n      });\n\n      it('changes the datepicker', function() {\n        expect(selectedElementIndex()).toEqual(14);\n        changeInputValueTo(inputEl, '11/8/1980');\n        expect(selectedElementIndex()).toEqual(13);\n      });\n    });\n\n    describe('attribute `alt-input-formats`', function() {\n      beforeEach(function() {\n        $rootScope.date = new Date('November 9, 1980');\n        var wrapElement = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"MMMM d yyyy\" alt-input-formats=\"[\\'M!/d!/yyyy\\']\" is-open=\"true\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapElement);\n      });\n\n      it('should accept alternate input formats', function() {\n        changeInputValueTo(inputEl, '11/8/1980');\n\n        expect($rootScope.date.getFullYear()).toEqual(1980);\n        expect($rootScope.date.getMonth()).toEqual(10);\n        expect($rootScope.date.getDate()).toEqual(8);\n      });\n\n      it('changes the datepicker', function() {\n        expect(selectedElementIndex()).toEqual(14);\n        changeInputValueTo(inputEl, '11/8/1980');\n        expect(selectedElementIndex()).toEqual(13);\n      });\n    });\n  });\n\n  describe('uibDatepickerConfig ngModelOptions', function() {\n    var inputEl, dropdownEl;\n\n    function assignElements(wrapElement) {\n      inputEl = wrapElement.find('input');\n      dropdownEl = wrapElement.find('ul');\n      element = dropdownEl.find('table');\n    }\n\n    beforeEach(inject(function(uibDatepickerConfig) {\n      uibDatepickerConfig.ngModelOptions = { timezone: '+600' };\n      $rootScope.date = new Date('2010-09-30T10:00:00.000Z');\n      $rootScope.isopen = true;\n    }));\n\n    afterEach(inject(function(uibDatepickerConfig) {\n      uibDatepickerConfig.ngModelOptions = {};\n    }));\n\n    describe('timezone', function() {\n      beforeEach(inject(function(uibDatepickerConfig) {\n        var wrapper = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"MM/dd/yyyy\" is-open=\"isopen\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapper);\n      }));\n\n      it('interprets the date appropriately', function() {\n        expect(inputEl.val()).toBe('09/30/2010');\n      });\n\n      it('updates the input when a day is clicked', function() {\n        clickOption(17);\n        expect(inputEl.val()).toBe('09/15/2010');\n        expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z'));\n      });\n\n      it('shows the correct title', function() {\n        expect(getTitle()).toBe('September 2010');\n      });\n    });\n\n    it('timezone interprets init date appropriately', function() {\n      $rootScope.options = {\n        initDate: new Date('2010-09-30T23:00:00.000Z')\n      };\n      $rootScope.date = null;\n      var wrapper = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"yyyy-MM-dd\" datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapper);\n\n      expect(getTitle()).toBe('October 2010');\n    });\n\n    it('timezone interprets min date appropriately', function() {\n      $rootScope.options = {\n        minDate: new Date('2010-10-01T00:00:00.000Z')\n      };\n      var wrapper = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"yyyy-MM-dd\" datepicker-options=\"options\" is-open=\"true\"><div>')($rootScope);\n      $rootScope.$digest();\n      assignElements(wrapper);\n\n      expect(getSelectedElement().prop('disabled')).toBe(true);\n    });\n  });\n\n  describe('ng-model-options', function() {\n    describe('timezone', function() {\n      var inputEl, dropdownEl, $document, $sniffer, $timeout;\n\n      function assignElements(wrapElement) {\n        inputEl = wrapElement.find('input');\n        dropdownEl = wrapElement.find('ul');\n        element = dropdownEl.find('table');\n      }\n\n      beforeEach(function() {\n        $rootScope.date = new Date('2010-09-30T10:00:00.000Z');\n        $rootScope.options = {\n          ngModelOptions: {\n            timezone: '+600'\n          }\n        };\n        $rootScope.isopen = true;\n        var wrapper = $compile('<div><input ng-model=\"date\" uib-datepicker-popup=\"MM/dd/yyyy\" datepicker-options=\"options\" is-open=\"isopen\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapper);\n      });\n\n      it('interprets the date appropriately', function() {\n        expect(inputEl.val()).toBe('09/30/2010');\n      });\n\n      it('has `selected` only the correct day', function() {\n        expectSelectedElement(32);\n      });\n\n      it('updates the input when a day is clicked', function() {\n        clickOption(17);\n        expect(inputEl.val()).toBe('09/15/2010');\n        expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z'));\n      });\n    });\n\n    describe('timezone HTML5 date input', function() {\n      var inputEl, dropdownEl, $document, $sniffer, $timeout;\n\n      function assignElements(wrapElement) {\n        inputEl = wrapElement.find('input');\n        dropdownEl = wrapElement.find('ul');\n        element = dropdownEl.find('table');\n      }\n\n      beforeEach(function() {\n        $rootScope.date = new Date('2010-09-30T10:00:00.000Z');\n        $rootScope.options = {\n          ngModelOptions: {\n            timezone: '+600'\n          }\n        };\n        $rootScope.isopen = true;\n        var wrapper = $compile('<div><input type=\"date\" ng-model=\"date\" uib-datepicker-popup datepicker-options=\"options\" is-open=\"isopen\"></div>')($rootScope);\n        $rootScope.$digest();\n        assignElements(wrapper);\n      });\n\n      it('interprets the date appropriately', function() {\n        expect(inputEl.val()).toBe('2010-09-30');\n      });\n\n      it('has `selected` only the correct day', function() {\n        expectSelectedElement(32);\n      });\n\n      it('updates the input when a day is clicked', function() {\n        clickOption(17);\n        expect(inputEl.val()).toBe('2010-09-15');\n        expect($rootScope.date).toEqual(new Date('2010-09-15T10:00:00.000Z'));\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/debounce/debounce.js",
    "content": "angular.module('ui.bootstrap.debounce', [])\n/**\n * A helper, internal service that debounces a function\n */\n  .factory('$$debounce', ['$timeout', function($timeout) {\n    return function(callback, debounceTime) {\n      var timeoutPromise;\n\n      return function() {\n        var self = this;\n        var args = Array.prototype.slice.call(arguments);\n        if (timeoutPromise) {\n          $timeout.cancel(timeoutPromise);\n        }\n\n        timeoutPromise = $timeout(function() {\n          callback.apply(self, args);\n        }, debounceTime);\n      };\n    };\n  }]);\n"
  },
  {
    "path": "src/debounce/index.js",
    "content": "require('./debounce');\n\nvar MODULE_NAME = 'ui.bootstrap.module.debounce';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.debounce']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/debounce/test/debounce.spec.js",
    "content": "describe('$$debounce', function() {\n  var $$debounce, $timeout, debouncedFunction, i, args;\n\n  beforeEach(module('ui.bootstrap.debounce'));\n  beforeEach(inject(function(_$$debounce_, _$timeout_) {\n    $$debounce = _$$debounce_;\n    $timeout = _$timeout_;\n    i = 0;\n    debouncedFunction = $$debounce(function() {\n      args = Array.prototype.slice.call(arguments);\n      i++;\n    }, 100);\n  }));\n\n  it('should function like a $timeout when called once during timeout', function() {\n    debouncedFunction();\n    $timeout.flush(50);\n\n    expect(i).toBe(0);\n\n    $timeout.flush(50);\n\n    expect(i).toBe(1);\n  });\n\n  it('should only execute 100ms after last call when called twice', function() {\n    debouncedFunction();\n    $timeout.flush(50);\n\n    expect(i).toBe(0);\n\n    debouncedFunction();\n    $timeout.flush(50);\n\n    expect(i).toBe(0);\n\n    $timeout.flush(50);\n\n    expect(i).toBe(1);\n  });\n\n  it('should properly pass arguments to debounced function', function() {\n    debouncedFunction(1, 2, 3);\n    $timeout.flush(100);\n\n    expect(args).toEqual([1, 2, 3]);\n  });\n});\n"
  },
  {
    "path": "src/dropdown/docs/demo.html",
    "content": "\n<div ng-controller=\"DropdownCtrl\">\n    <!-- Simple dropdown -->\n    <span uib-dropdown on-toggle=\"toggled(open)\">\n      <a href id=\"simple-dropdown\" uib-dropdown-toggle>\n        Click me for a dropdown, yo!\n      </a>\n      <ul class=\"dropdown-menu\" uib-dropdown-menu aria-labelledby=\"simple-dropdown\">\n        <li ng-repeat=\"choice in items\">\n          <a href>{{choice}}</a>\n        </li>\n      </ul>\n    </span>\n\n    <!-- Single button -->\n    <div class=\"btn-group\" uib-dropdown is-open=\"status.isopen\">\n      <button id=\"single-button\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle ng-disabled=\"disabled\">\n        Button dropdown <span class=\"caret\"></span>\n      </button>\n      <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"single-button\">\n        <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n        <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n        <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n        <li class=\"divider\"></li>\n        <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n      </ul>\n    </div>\n\n    <!-- Split button -->\n    <div class=\"btn-group\" uib-dropdown>\n      <button id=\"split-button\" type=\"button\" class=\"btn btn-danger\">Action</button>\n      <button type=\"button\" class=\"btn btn-danger\" uib-dropdown-toggle>\n        <span class=\"caret\"></span>\n        <span class=\"sr-only\">Split button!</span>\n      </button>\n      <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"split-button\">\n        <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n        <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n        <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n        <li class=\"divider\"></li>\n        <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n      </ul>\n    </div>\n\n    <!-- Single button using append-to-body -->\n    <div class=\"btn-group\" uib-dropdown dropdown-append-to-body>\n      <button id=\"btn-append-to-body\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle>\n        Dropdown on Body <span class=\"caret\"></span>\n      </button>\n      <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"btn-append-to-body\">\n        <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n        <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n        <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n        <li class=\"divider\"></li>\n        <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n      </ul>\n    </div>\n\n    <!-- Single button using template-url -->\n    <div class=\"btn-group\" uib-dropdown>\n      <button id=\"button-template-url\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle ng-disabled=\"disabled\">\n        Dropdown using template <span class=\"caret\"></span>\n      </button>\n      <ul class=\"dropdown-menu\" uib-dropdown-menu template-url=\"dropdown.html\" aria-labelledby=\"button-template-url\">\n      </ul>\n    </div>\n\n    <hr />\n    <p>\n        <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleDropdown($event)\">Toggle button dropdown</button>\n        <button type=\"button\" class=\"btn btn-warning btn-sm\" ng-click=\"disabled = !disabled\">Enable/Disable</button>\n    </p>\n\n    <hr>\n    <!-- Single button with keyboard nav -->\n    <div class=\"btn-group\" uib-dropdown keyboard-nav>\n        <button id=\"simple-btn-keyboard-nav\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle>\n            Dropdown with keyboard navigation <span class=\"caret\"></span>\n        </button>\n        <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"simple-btn-keyboard-nav\">\n            <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n            <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n            <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n            <li class=\"divider\"></li>\n            <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n        </ul>\n    </div>\n\n    <hr>\n    <!-- AppendTo use case -->\n    <h4>append-to vs. append-to-body vs. inline example</h4>\n    <div id=\"dropdown-scrollable-container\" style=\"height: 15em; overflow: auto;\">\n      <div id=\"dropdown-long-content\">\n        <div id=\"dropdown-hidden-container\">\n          <div class=\"btn-group\" uib-dropdown keyboard-nav dropdown-append-to=\"appendToEl\">\n            <button id=\"btn-append-to\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle>\n              Dropdown in Container <span class=\"caret\"></span>\n            </button>\n            <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"btn-append-to\">\n              <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n              <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n              <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n              <li class=\"divider\"></li>\n              <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n          <div class=\"btn-group\" uib-dropdown dropdown-append-to-body>\n            <button id=\"btn-append-to-to-body\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle>\n              Dropdown on Body <span class=\"caret\"></span>\n            </button>\n            <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"btn-append-to-to-body\">\n              <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n              <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n              <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n              <li class=\"divider\"></li>\n              <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n          <div class=\"btn-group\" uib-dropdown>\n            <button id=\"btn-append-to-single-button\" type=\"button\" class=\"btn btn-primary\" uib-dropdown-toggle>\n              Inline Dropdown <span class=\"caret\"></span>\n            </button>\n            <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"btn-append-to-single-button\">\n              <li role=\"menuitem\"><a href=\"#\">Action</a></li>\n              <li role=\"menuitem\"><a href=\"#\">Another action</a></li>\n              <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n              <li class=\"divider\"></li>\n              <li role=\"menuitem\"><a href=\"#\">Separated link</a></li>\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <script type=\"text/ng-template\" id=\"dropdown.html\">\n        <ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"button-template-url\">\n          <li role=\"menuitem\"><a href=\"#\">Action in Template</a></li>\n          <li role=\"menuitem\"><a href=\"#\">Another action in Template</a></li>\n          <li role=\"menuitem\"><a href=\"#\">Something else here</a></li>\n          <li class=\"divider\"></li>\n          <li role=\"menuitem\"><a href=\"#\">Separated link in Template</a></li>\n        </ul>\n    </script>\n</div>\n"
  },
  {
    "path": "src/dropdown/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('DropdownCtrl', function ($scope, $log) {\n  $scope.items = [\n    'The first choice!',\n    'And another choice for you.',\n    'but wait! A third!'\n  ];\n\n  $scope.status = {\n    isopen: false\n  };\n\n  $scope.toggled = function(open) {\n    $log.log('Dropdown is now: ', open);\n  };\n\n  $scope.toggleDropdown = function($event) {\n    $event.preventDefault();\n    $event.stopPropagation();\n    $scope.status.isopen = !$scope.status.isopen;\n  };\n\n  $scope.appendToEl = angular.element(document.querySelector('#dropdown-long-content'));\n});\n"
  },
  {
    "path": "src/dropdown/docs/readme.md",
    "content": "Dropdown is a simple directive which will toggle a dropdown menu on click or programmatically.\n\nThis directive is composed by three parts:\n\n* `uib-dropdown` which transforms a node into a dropdown.\n* `uib-dropdown-toggle` which allows the dropdown to be toggled via click. This directive is optional.\n* `uib-dropdown-menu` which transforms a node into the popup menu.\n\nEach of these parts need to be used as attribute directives.\n\n### uib-dropdown settings\n\n* `auto-close`\n  _(Default: `always`)_ -\n  Controls the behavior of the menu when clicked.\n  * `always` - Automatically closes the dropdown when any of its elements is clicked.\n  * `disabled` - Disables the auto close. You can control it manually with `is-open`. It still gets closed if the toggle is clicked, `esc` is pressed or another dropdown is open.\n  * `outsideClick` - Closes the dropdown automatically only when the user clicks any element outside the dropdown.\n\n* `dropdown-append-to`\n  <small class=\"badge\">$</small>\n  _(Default: `null`)_ -\n  Appends the inner dropdown-menu to an arbitrary DOM element.\n\n* `dropdown-append-to-body`\n  <small class=\"badge\">B</small>\n  _(Default: `false`)_ -\n  Appends the inner dropdown-menu to the body element if the attribute is present without a value, or with a non `false` value.\n\n* `is-open`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Defines whether or not the dropdown-menu is open. The `uib-dropdown-toggle` will toggle this attribute on click.\n\n* `keyboard-nav`:\n  <small class=\"badge\">B</small>\n  _(Default: `false`)_ -\n  Enables navigation of dropdown list elements with the arrow keys.\n\n* `on-toggle(open)`\n  <small class=\"badge\">$</small> -\n  An optional expression called when the dropdown menu is opened or closed.\n\n### uib-dropdown-menu settings\n\n* `template-url`\n  _(Default: `none`)_ -\n  You may specify a template for the dropdown menu. Check the demos for an example.\n\n### Additional settings `uibDropdownConfig`\n\n* `appendToOpenClass`\n  _(Default: `uib-dropdown-open`)_ -\n  Class to apply when the dropdown is open and appended to a different DOM element.\n\n* `openClass`\n  _(Default: `open`)_ -\n  Class to apply when the dropdown is open.\n\n### Known issues\n\nFor usage with ngTouch, it is recommended to use the programmatic `is-open` trigger with ng-click - this is due to ngTouch decorating ng-click to prevent propagation of the event.\n"
  },
  {
    "path": "src/dropdown/dropdown.js",
    "content": "angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])\n\n.constant('uibDropdownConfig', {\n  appendToOpenClass: 'uib-dropdown-open',\n  openClass: 'open'\n})\n\n.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {\n  var openScope = null;\n  var openedContainers = $$multiMap.createNew();\n\n  this.isOnlyOpen = function(dropdownScope, appendTo) {\n    var openedDropdowns = openedContainers.get(appendTo);\n    if (openedDropdowns) {\n      var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {\n        if (dropdown.scope === dropdownScope) {\n          return dropdown;\n        }\n\n        return toClose;\n      }, {});\n      if (openDropdown) {\n        return openedDropdowns.length === 1;\n      }\n    }\n\n    return false;\n  };\n\n  this.open = function(dropdownScope, element, appendTo) {\n    if (!openScope) {\n      $document.on('click', closeDropdown);\n    }\n\n    if (openScope && openScope !== dropdownScope) {\n      openScope.isOpen = false;\n    }\n\n    openScope = dropdownScope;\n\n    if (!appendTo) {\n      return;\n    }\n\n    var openedDropdowns = openedContainers.get(appendTo);\n    if (openedDropdowns) {\n      var openedScopes = openedDropdowns.map(function(dropdown) {\n        return dropdown.scope;\n      });\n      if (openedScopes.indexOf(dropdownScope) === -1) {\n        openedContainers.put(appendTo, {\n          scope: dropdownScope\n        });\n      }\n    } else {\n      openedContainers.put(appendTo, {\n        scope: dropdownScope\n      });\n    }\n  };\n\n  this.close = function(dropdownScope, element, appendTo) {\n    if (openScope === dropdownScope) {\n      $document.off('click', closeDropdown);\n      $document.off('keydown', this.keybindFilter);\n      openScope = null;\n    }\n\n    if (!appendTo) {\n      return;\n    }\n\n    var openedDropdowns = openedContainers.get(appendTo);\n    if (openedDropdowns) {\n      var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {\n        if (dropdown.scope === dropdownScope) {\n          return dropdown;\n        }\n\n        return toClose;\n      }, {});\n      if (dropdownToClose) {\n        openedContainers.remove(appendTo, dropdownToClose);\n      }\n    }\n  };\n\n  var closeDropdown = function(evt) {\n    // This method may still be called during the same mouse event that\n    // unbound this event handler. So check openScope before proceeding.\n    if (!openScope || !openScope.isOpen) { return; }\n\n    if (evt && openScope.getAutoClose() === 'disabled') { return; }\n\n    if (evt && evt.which === 3) { return; }\n\n    var toggleElement = openScope.getToggleElement();\n    if (evt && toggleElement && toggleElement[0].contains(evt.target)) {\n      return;\n    }\n\n    var dropdownElement = openScope.getDropdownElement();\n    if (evt && openScope.getAutoClose() === 'outsideClick' &&\n      dropdownElement && dropdownElement[0].contains(evt.target)) {\n      return;\n    }\n\n    openScope.focusToggleElement();\n    openScope.isOpen = false;\n\n    if (!$rootScope.$$phase) {\n      openScope.$apply();\n    }\n  };\n\n  this.keybindFilter = function(evt) {\n    if (!openScope) {\n      // see this.close as ESC could have been pressed which kills the scope so we can not proceed\n      return;\n    }\n\n    var dropdownElement = openScope.getDropdownElement();\n    var toggleElement = openScope.getToggleElement();\n    var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);\n    var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);\n    if (evt.which === 27) {\n      evt.stopPropagation();\n      openScope.focusToggleElement();\n      closeDropdown();\n    } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {\n      evt.preventDefault();\n      evt.stopPropagation();\n      openScope.focusDropdownEntry(evt.which);\n    }\n  };\n}])\n\n.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {\n  var self = this,\n    scope = $scope.$new(), // create a child scope so we are not polluting original one\n    templateScope,\n    appendToOpenClass = dropdownConfig.appendToOpenClass,\n    openClass = dropdownConfig.openClass,\n    getIsOpen,\n    setIsOpen = angular.noop,\n    toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,\n    keynavEnabled = false,\n    selectedOption = null,\n    body = $document.find('body');\n\n  $element.addClass('dropdown');\n\n  this.init = function() {\n    if ($attrs.isOpen) {\n      getIsOpen = $parse($attrs.isOpen);\n      setIsOpen = getIsOpen.assign;\n\n      $scope.$watch(getIsOpen, function(value) {\n        scope.isOpen = !!value;\n      });\n    }\n\n    keynavEnabled = angular.isDefined($attrs.keyboardNav);\n  };\n\n  this.toggle = function(open) {\n    scope.isOpen = arguments.length ? !!open : !scope.isOpen;\n    if (angular.isFunction(setIsOpen)) {\n      setIsOpen(scope, scope.isOpen);\n    }\n\n    return scope.isOpen;\n  };\n\n  // Allow other directives to watch status\n  this.isOpen = function() {\n    return scope.isOpen;\n  };\n\n  scope.getToggleElement = function() {\n    return self.toggleElement;\n  };\n\n  scope.getAutoClose = function() {\n    return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'\n  };\n\n  scope.getElement = function() {\n    return $element;\n  };\n\n  scope.isKeynavEnabled = function() {\n    return keynavEnabled;\n  };\n\n  scope.focusDropdownEntry = function(keyCode) {\n    var elems = self.dropdownMenu ? //If append to body is used.\n      angular.element(self.dropdownMenu).find('a') :\n      $element.find('ul').eq(0).find('a');\n\n    switch (keyCode) {\n      case 40: {\n        if (!angular.isNumber(self.selectedOption)) {\n          self.selectedOption = 0;\n        } else {\n          self.selectedOption = self.selectedOption === elems.length - 1 ?\n            self.selectedOption :\n            self.selectedOption + 1;\n        }\n        break;\n      }\n      case 38: {\n        if (!angular.isNumber(self.selectedOption)) {\n          self.selectedOption = elems.length - 1;\n        } else {\n          self.selectedOption = self.selectedOption === 0 ?\n            0 : self.selectedOption - 1;\n        }\n        break;\n      }\n    }\n    elems[self.selectedOption].focus();\n  };\n\n  scope.getDropdownElement = function() {\n    return self.dropdownMenu;\n  };\n\n  scope.focusToggleElement = function() {\n    if (self.toggleElement) {\n      self.toggleElement[0].focus();\n    }\n  };\n\n  function removeDropdownMenu() {\n    $element.append(self.dropdownMenu);\n  }\n\n  scope.$watch('isOpen', function(isOpen, wasOpen) {\n    var appendTo = null,\n      appendToBody = false;\n\n    if (angular.isDefined($attrs.dropdownAppendTo)) {\n      var appendToEl = $parse($attrs.dropdownAppendTo)(scope);\n      if (appendToEl) {\n        appendTo = angular.element(appendToEl);\n      }\n    }\n\n    if (angular.isDefined($attrs.dropdownAppendToBody)) {\n      var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope);\n      if (appendToBodyValue !== false) {\n        appendToBody = true;\n      }\n    }\n\n    if (appendToBody && !appendTo) {\n      appendTo = body;\n    }\n\n    if (appendTo && self.dropdownMenu) {\n      if (isOpen) {\n        appendTo.append(self.dropdownMenu);\n        $element.on('$destroy', removeDropdownMenu);\n      } else {\n        $element.off('$destroy', removeDropdownMenu);\n        removeDropdownMenu();\n      }\n    }\n\n    if (appendTo && self.dropdownMenu) {\n      var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),\n        css,\n        rightalign,\n        scrollbarPadding,\n        scrollbarWidth = 0;\n\n      css = {\n        top: pos.top + 'px',\n        display: isOpen ? 'block' : 'none'\n      };\n\n      rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');\n      if (!rightalign) {\n        css.left = pos.left + 'px';\n        css.right = 'auto';\n      } else {\n        css.left = 'auto';\n        scrollbarPadding = $position.scrollbarPadding(appendTo);\n\n        if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {\n          scrollbarWidth = scrollbarPadding.scrollbarWidth;\n        }\n\n        css.right = window.innerWidth - scrollbarWidth -\n          (pos.left + $element.prop('offsetWidth')) + 'px';\n      }\n\n      // Need to adjust our positioning to be relative to the appendTo container\n      // if it's not the body element\n      if (!appendToBody) {\n        var appendOffset = $position.offset(appendTo);\n\n        css.top = pos.top - appendOffset.top + 'px';\n\n        if (!rightalign) {\n          css.left = pos.left - appendOffset.left + 'px';\n        } else {\n          css.right = window.innerWidth -\n            (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';\n        }\n      }\n\n      self.dropdownMenu.css(css);\n    }\n\n    var openContainer = appendTo ? appendTo : $element;\n    var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;\n    var hasOpenClass = openContainer.hasClass(dropdownOpenClass);\n    var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);\n\n    if (hasOpenClass === !isOpen) {\n      var toggleClass;\n      if (appendTo) {\n        toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';\n      } else {\n        toggleClass = isOpen ? 'addClass' : 'removeClass';\n      }\n      $animate[toggleClass](openContainer, dropdownOpenClass).then(function() {\n        if (angular.isDefined(isOpen) && isOpen !== wasOpen) {\n          toggleInvoker($scope, { open: !!isOpen });\n        }\n      });\n    }\n\n    if (isOpen) {\n      if (self.dropdownMenuTemplateUrl) {\n        $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {\n          templateScope = scope.$new();\n          $compile(tplContent.trim())(templateScope, function(dropdownElement) {\n            var newEl = dropdownElement;\n            self.dropdownMenu.replaceWith(newEl);\n            self.dropdownMenu = newEl;\n            $document.on('keydown', uibDropdownService.keybindFilter);\n          });\n        });\n      } else {\n        $document.on('keydown', uibDropdownService.keybindFilter);\n      }\n\n      scope.focusToggleElement();\n      uibDropdownService.open(scope, $element, appendTo);\n    } else {\n      uibDropdownService.close(scope, $element, appendTo);\n      if (self.dropdownMenuTemplateUrl) {\n        if (templateScope) {\n          templateScope.$destroy();\n        }\n        var newEl = angular.element('<ul class=\"dropdown-menu\"></ul>');\n        self.dropdownMenu.replaceWith(newEl);\n        self.dropdownMenu = newEl;\n      }\n\n      self.selectedOption = null;\n    }\n\n    if (angular.isFunction(setIsOpen)) {\n      setIsOpen($scope, isOpen);\n    }\n  });\n}])\n\n.directive('uibDropdown', function() {\n  return {\n    controller: 'UibDropdownController',\n    link: function(scope, element, attrs, dropdownCtrl) {\n      dropdownCtrl.init();\n    }\n  };\n})\n\n.directive('uibDropdownMenu', function() {\n  return {\n    restrict: 'A',\n    require: '?^uibDropdown',\n    link: function(scope, element, attrs, dropdownCtrl) {\n      if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {\n        return;\n      }\n\n      element.addClass('dropdown-menu');\n\n      var tplUrl = attrs.templateUrl;\n      if (tplUrl) {\n        dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;\n      }\n\n      if (!dropdownCtrl.dropdownMenu) {\n        dropdownCtrl.dropdownMenu = element;\n      }\n    }\n  };\n})\n\n.directive('uibDropdownToggle', function() {\n  return {\n    require: '?^uibDropdown',\n    link: function(scope, element, attrs, dropdownCtrl) {\n      if (!dropdownCtrl) {\n        return;\n      }\n\n      element.addClass('dropdown-toggle');\n\n      dropdownCtrl.toggleElement = element;\n\n      var toggleDropdown = function(event) {\n        event.preventDefault();\n\n        if (!element.hasClass('disabled') && !attrs.disabled) {\n          scope.$apply(function() {\n            dropdownCtrl.toggle();\n          });\n        }\n      };\n\n      element.on('click', toggleDropdown);\n\n      // WAI-ARIA\n      element.attr({ 'aria-haspopup': true, 'aria-expanded': false });\n      scope.$watch(dropdownCtrl.isOpen, function(isOpen) {\n        element.attr('aria-expanded', !!isOpen);\n      });\n\n      scope.$on('$destroy', function() {\n        element.off('click', toggleDropdown);\n      });\n    }\n  };\n});\n"
  },
  {
    "path": "src/dropdown/index-nocss.js",
    "content": "require('../multiMap');\nrequire('../position/index-nocss.js');\nrequire('./dropdown');\n\nvar MODULE_NAME = 'ui.bootstrap.module.dropdown';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.dropdown']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/dropdown/index.js",
    "content": "require('../position/position.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/dropdown/test/dropdown.spec.js",
    "content": "describe('uib-dropdown', function() {\n  var $animate, $compile, $rootScope, $document, $templateCache, dropdownConfig, element, $browser, $log;\n\n  beforeEach(module('ngAnimateMock'));\n  beforeEach(module('ui.bootstrap.dropdown'));\n\n  beforeEach(inject(function(_$animate_, _$compile_, _$rootScope_, _$document_, _$templateCache_, uibDropdownConfig, _$browser_, _$log_) {\n    $animate = _$animate_;\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $document = _$document_;\n    $templateCache = _$templateCache_;\n    dropdownConfig = uibDropdownConfig;\n    $browser = _$browser_;\n    $log = _$log_;\n  }));\n\n  afterEach(function() {\n    element.remove();\n  });\n\n  var clickDropdownToggle = function(elm) {\n    elm = elm || element;\n    elm.find('a[uib-dropdown-toggle]').click();\n  };\n\n  var triggerKeyDown = function (element, keyCode) {\n    var e = $.Event('keydown');\n    spyOn(e, 'stopPropagation');\n    e.stopPropagation.and.callThrough();\n    e.which = keyCode;\n    element.trigger(e);\n    return e;\n  };\n\n  describe('basic', function() {\n    function dropdown() {\n      return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li></ul></li>')($rootScope);\n    }\n\n    beforeEach(function() {\n      element = dropdown();\n    });\n\n    it('should toggle on `a` click', function() {\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n      clickDropdownToggle();\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      clickDropdownToggle();\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should toggle when an option is clicked', function() {\n      $document.find('body').append(element);\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n      clickDropdownToggle();\n      expect(element).toHaveClass(dropdownConfig.openClass);\n\n      var optionEl = element.find('ul > li').eq(0).find('a').eq(0);\n      optionEl.click();\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should close on document click', function() {\n      clickDropdownToggle();\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      $document.click();\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should close on escape key & focus toggle element', function() {\n      var dropdownMenu = element.find('[uib-dropdown-menu]');\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      var event = triggerKeyDown(dropdownMenu, 27);\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n      expect(element.find('a')).toHaveFocus();\n      expect(event.stopPropagation).toHaveBeenCalled();\n    });\n\n    it('should not close on backspace key', function() {\n      clickDropdownToggle();\n      triggerKeyDown(element, 8);\n      expect(element).toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should not close on right click', function() {\n      clickDropdownToggle();\n      element.find('ul a').trigger({\n        type: 'mousedown',\n        which: 3\n      });\n      expect(element).toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should only allow one dropdown to be open at once', function() {\n      var elm1 = dropdown();\n      var elm2 = dropdown();\n      expect(elm1).not.toHaveClass(dropdownConfig.openClass);\n      expect(elm2).not.toHaveClass(dropdownConfig.openClass);\n\n      clickDropdownToggle(elm1);\n      expect(elm1).toHaveClass(dropdownConfig.openClass);\n      expect(elm2).not.toHaveClass(dropdownConfig.openClass);\n\n      clickDropdownToggle(elm2);\n      expect(elm1).not.toHaveClass(dropdownConfig.openClass);\n      expect(elm2).toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should not toggle if the element has `disabled` class', function() {\n      var elm = $compile('<li uib-dropdown><a class=\"disabled\" uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n      clickDropdownToggle( elm );\n      expect(elm).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should not toggle if the element is disabled', function() {\n      var elm = $compile('<li uib-dropdown><button disabled=\"disabled\" uib-dropdown-toggle></button><ul><li>Hello</li></ul></li>')($rootScope);\n      elm.find('button').click();\n      expect(elm).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should not toggle if the element has `ng-disabled` as true', function() {\n      $rootScope.isdisabled = true;\n      var elm = $compile('<li uib-dropdown><div ng-disabled=\"isdisabled\" uib-dropdown-toggle></div><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n      $rootScope.$digest();\n      elm.find('div').click();\n      expect(elm).not.toHaveClass(dropdownConfig.openClass);\n\n      $rootScope.isdisabled = false;\n      $rootScope.$digest();\n      elm.find('div').click();\n      expect(elm).toHaveClass(dropdownConfig.openClass);\n    });\n\n    it('should unbind events on scope destroy', function() {\n      var $scope = $rootScope.$new();\n      var elm = $compile('<li uib-dropdown><button ng-disabled=\"isdisabled\" uib-dropdown-toggle></button><ul uib-dropdown-menu><li>Hello</li></ul></li>')($scope);\n      $scope.$digest();\n\n      var buttonEl = elm.find('button');\n      buttonEl.click();\n      expect(elm).toHaveClass(dropdownConfig.openClass);\n      buttonEl.click();\n      expect(elm).not.toHaveClass(dropdownConfig.openClass);\n\n      $scope.$destroy();\n      buttonEl.click();\n      expect(elm).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    // issue 270\n    it('executes other document click events normally', function() {\n      var checkboxEl = $compile('<input type=\"checkbox\" ng-click=\"clicked = true\" />')($rootScope);\n      $rootScope.$digest();\n\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n      expect($rootScope.clicked).toBeFalsy();\n\n      clickDropdownToggle();\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      expect($rootScope.clicked).toBeFalsy();\n\n      checkboxEl.click();\n      expect($rootScope.clicked).toBeTruthy();\n    });\n\n    // WAI-ARIA\n    it('should aria markup to the `dropdown-toggle`', function() {\n      var toggleEl = element.find('a');\n      expect(toggleEl.attr('aria-haspopup')).toBe('true');\n      expect(toggleEl.attr('aria-expanded')).toBe('false');\n\n      clickDropdownToggle();\n      expect(toggleEl.attr('aria-expanded')).toBe('true');\n      clickDropdownToggle();\n      expect(toggleEl.attr('aria-expanded')).toBe('false');\n    });\n\n    // pr/issue 3274\n    it('should not raise $digest:inprog if dismissed during a digest cycle', function() {\n      clickDropdownToggle();\n      expect(element).toHaveClass(dropdownConfig.openClass);\n\n      $rootScope.$apply(function() {\n        $document.click();\n      });\n\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n    });\n  });\n\n  describe('using dropdownMenuTemplate', function() {\n    function dropdown() {\n      $templateCache.put('custom.html', '<ul class=\"uib-dropdown-menu\"><li>Item 1</li></ul>');\n\n      return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul uib-dropdown-menu template-url=\"custom.html\"></ul></li>')($rootScope);\n    }\n\n    beforeEach(function() {\n      element = dropdown();\n    });\n\n    it('should apply custom template for dropdown menu', function() {\n      element.find('a').click();\n      expect(element.find('ul.uib-dropdown-menu').eq(0).find('li').eq(0).text()).toEqual('Item 1');\n    });\n\n    it('should clear ul when dropdown menu is closed', function() {\n      element.find('a').click();\n      expect(element.find('ul.uib-dropdown-menu').eq(0).find('li').eq(0).text()).toEqual('Item 1');\n      element.find('a').click();\n      expect(element.find('ul.uib-dropdown-menu').eq(0).find('li').length).toEqual(0);\n    });\n  });\n\n  describe('using dropdown-append-to-body', function() {\n    describe('with no value', function() {\n      function dropdown() {\n        return $compile('<li uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);\n      }\n\n      beforeEach(function() {\n        element = dropdown();\n        $document.find('body').append(element);\n      });\n\n      afterEach(function() {\n        element.remove();\n      });\n\n      it('does not add the menu to the body', function() {\n        expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n      });\n\n      describe('when toggled open', function() {\n        var toggle;\n        beforeEach(function() {\n          toggle = element.find('[uib-dropdown-toggle]');\n          toggle.trigger('click');\n        });\n        it('adds the menu to the body', function() {\n          expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);\n        });\n\n        describe('when toggled closed', function() {\n          beforeEach(function() {\n            toggle.trigger('click');\n          });\n          it('removes the menu from body', function() {\n            expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n          });\n        });\n\n        describe('when closed by clicking on menu', function() {\n          var menu;\n          beforeEach(function() {\n            menu = $document.find('#dropdown-menu a');\n            menu.focus();\n            menu.trigger('click');\n          });\n          it('focuses the dropdown element on close', function() {\n            expect(document.activeElement).toBe(toggle[0]);\n          });\n          it('removes the menu from body', function() {\n            expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n          });\n        });\n        describe('when the dropdown is removed', function() {\n          beforeEach(function() {\n            element.remove();\n            $rootScope.$digest();\n          });\n          it('removes the menu from body', function() {\n            expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n          });\n        });\n      });\n    });\n\n    describe('with a value', function() {\n      function dropdown() {\n        return $compile('<li uib-dropdown dropdown-append-to-body=\"appendToBody\"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);\n      }\n      describe('that is not false', function() {\n        beforeEach(function() {\n          $rootScope.appendToBody = 'sure';\n\n          element = dropdown();\n          $document.find('body').append(element);\n        });\n\n        afterEach(function() {\n          element.remove();\n        });\n        it('does not add the menu to the body', function() {\n          expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n        });\n\n        describe('when toggled open', function() {\n          var toggle;\n          beforeEach(function() {\n            toggle = element.find('[uib-dropdown-toggle]');\n            toggle.trigger('click');\n          });\n          it('adds the menu to the body', function() {\n            expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);\n          });\n\n          describe('when toggled closed', function() {\n            beforeEach(function() {\n              toggle.trigger('click');\n            });\n            it('removes the menu from body', function() {\n              expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n            });\n          });\n\n          describe('when closed by clicking on menu', function() {\n            var menu;\n            beforeEach(function() {\n              menu = $document.find('#dropdown-menu a');\n              menu.focus();\n              menu.trigger('click');\n            });\n            it('focuses the dropdown element on close', function() {\n              expect(document.activeElement).toBe(toggle[0]);\n            });\n            it('removes the menu from body', function() {\n              expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n            });\n          });\n          describe('when the dropdown is removed', function() {\n            beforeEach(function() {\n              element.remove();\n              $rootScope.$digest();\n            });\n            it('removes the menu from body', function() {\n              expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n            });\n          });\n        });\n      });\n\n      describe('that is false', function() {\n        beforeEach(function() {\n          $rootScope.appendToBody = false;\n\n          element = dropdown();\n          $document.find('body').append(element);\n        });\n\n        afterEach(function() {\n          element.remove();\n        });\n\n        it('does not add the menu to the body', function() {\n          expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n        });\n\n        describe('when toggled open', function() {\n          var toggle;\n          beforeEach(function() {\n            toggle = element.find('[uib-dropdown-toggle]');\n            toggle.trigger('click');\n          });\n          it('does not add the menu to the body', function() {\n            expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n          });\n\n          describe('when toggled closed', function() {\n            beforeEach(function() {\n              toggle.trigger('click');\n            });\n            it('does not remove the menu', function() {\n              expect($document.find('#dropdown-menu').length).not.toEqual(0);\n            });\n          });\n\n          describe('when closed by clicking on menu', function() {\n            var menu;\n            beforeEach(function() {\n              menu = $document.find('#dropdown-menu a');\n              menu.focus();\n              menu.trigger('click');\n            });\n            it('focuses the dropdown element on close', function() {\n              expect(document.activeElement).toBe(toggle[0]);\n            });\n            it('does not removes the menu from body', function() {\n              expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n            });\n          });\n          describe('when the dropdown is removed', function() {\n            beforeEach(function() {\n              element.remove();\n              $rootScope.$digest();\n            });\n            it('removes the menu from body', function() {\n              expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n            });\n          });\n        });\n      });\n    });\n  });\n\n  describe('using dropdown-append-to', function() {\n    var initialPage, container;\n\n    function dropdown() {\n      return $compile('<li uib-dropdown dropdown-append-to=\"appendTo\"><a href uib-dropdown-toggle></a><ul class=\"dropdown-menu\" uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Container</a></li></ul></li>')($rootScope);\n    }\n\n    beforeEach(function() {\n      $document.find('body').append(angular.element('<div id=\"dropdown-container\"></div>'));\n\n      $rootScope.appendTo = container = $document.find('#dropdown-container');\n\n      element = dropdown();\n      $document.find('body').append(element);\n    });\n\n    afterEach(function() {\n      // Cleanup the extra elements we appended\n      $document.find('#dropdown-container').remove();\n    });\n\n    it('does not add the menu to the container', function() {\n      expect($document.find('#dropdown-menu').parent()[0]).not.toBe(container[0]);\n    });\n    it('does not add open class on container', function() {\n      expect(container).not.toHaveClass('uib-dropdown-open');\n    });\n\n    describe('when toggled open', function() {\n      var toggle;\n      beforeEach(function() {\n        toggle = element.find('[uib-dropdown-toggle]');\n        toggle.trigger('click');\n      });\n      it('adds the menu to the container', function() {\n        expect($document.find('#dropdown-menu').parent()[0]).toBe(container[0]);\n      });\n      it('adds open class on container', function() {\n        expect(container).toHaveClass('uib-dropdown-open');\n      });\n\n      describe('when toggled closed', function() {\n        beforeEach(function() {\n          toggle.trigger('click');\n        });\n        it('removes the menu from the container', function() {\n          expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n        });\n        it('removes open class from container', function() {\n          expect(container).not.toHaveClass('uib-dropdown-open');\n        });\n      });\n\n      describe('when closed by clicking on menu', function() {\n        var menu;\n        beforeEach(function() {\n          menu = $document.find('#dropdown-menu a');\n          menu.focus();\n          menu.trigger('click');\n        });\n        it('focuses the dropdown element on close', function() {\n          expect(document.activeElement).toBe(toggle[0]);\n        });\n        it('removes the menu from the container', function() {\n          expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n        });\n        it('removes open class from container', function() {\n          expect(container).not.toHaveClass('uib-dropdown-open');\n        });\n      });\n      describe('when the dropdown is removed', function() {\n        beforeEach(function() {\n          element.remove();\n          $rootScope.$digest();\n        });\n        it('removes the menu from the container', function() {\n          expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);\n        });\n      });\n    });\n  });\n\n  describe('using dropdown-append-to with two dropdowns', function() {\n    function dropdown() {\n      return $compile('<div><div class=\"dropdown1\" uib-dropdown dropdown-append-to=\"appendTo\" on-toggle=\"log(1, open)\"><a href uib-dropdown-toggle></a><ul class=\"dropdown-menu\" uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Container</a></li></ul></div><div class=\"dropdown2\" uib-dropdown dropdown-append-to=\"appendTo\" on-toggle=\"log(2, open)\"><a href uib-dropdown-toggle></a><ul class=\"dropdown-menu\" uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Container</a></li></ul></div></div>')($rootScope);\n    }\n\n    beforeEach(function() {\n      $document.find('body').append(angular.element('<div id=\"dropdown-container\"></div>'));\n\n      $rootScope.appendTo = $document.find('#dropdown-container');\n      $rootScope.log = jasmine.createSpy('log');\n\n      element = dropdown();\n      $document.find('body').append(element);\n    });\n\n    afterEach(function() {\n      // Cleanup the extra elements we appended\n      $document.find('#dropdown-container').remove();\n    });\n\n    it('should keep the class when toggling from one dropdown to another with the same container', function() {\n      var container = $document.find('#dropdown-container');\n\n      expect(container).not.toHaveClass('uib-dropdown-open');\n      element.find('.dropdown1 [uib-dropdown-toggle]').click();\n      expect(container).toHaveClass('uib-dropdown-open');\n      element.find('.dropdown2 [uib-dropdown-toggle]').click();\n      expect(container).toHaveClass('uib-dropdown-open');\n    });\n  });\n\n  describe('using is-open', function() {\n    describe('with uib-dropdown-toggle', function() {\n      beforeEach(function() {\n        $rootScope.isopen = true;\n        element = $compile('<li uib-dropdown is-open=\"isopen\"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should be open initially', function() {\n        expect(element).toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('should change `is-open` binding when toggles', function() {\n        clickDropdownToggle();\n        expect($rootScope.isopen).toBe(false);\n      });\n\n      it('should toggle when `is-open` changes', function() {\n        $rootScope.isopen = false;\n        $rootScope.$digest();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('focus toggle element when opening', function() {\n        $document.find('body').append(element);\n        clickDropdownToggle();\n        $rootScope.isopen = false;\n        $rootScope.$digest();\n        expect(element.find('a')).not.toHaveFocus();\n        $rootScope.isopen = true;\n        $rootScope.$digest();\n        expect(element.find('a')).toHaveFocus();\n      });\n    });\n\n    describe('without uib-dropdown-toggle', function() {\n      beforeEach(function() {\n        $rootScope.isopen = true;\n        element = $compile('<li uib-dropdown is-open=\"isopen\"><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should be open initially', function() {\n        expect(element).toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('should toggle when `is-open` changes', function() {\n        $rootScope.isopen = false;\n        $rootScope.$digest();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n    });\n  });\n\n  describe('using on-toggle', function() {\n    describe('with is-open to false', function() {\n      beforeEach(function() {\n        $rootScope.toggleHandler = jasmine.createSpy('toggleHandler');\n        $rootScope.isopen = false;\n        element = $compile('<li uib-dropdown on-toggle=\"toggleHandler(open)\" is-open=\"isopen\"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should not have been called initially', function() {\n        expect($rootScope.toggleHandler).not.toHaveBeenCalled();\n      });\n\n      it('should call it correctly when toggles', function() {\n        $rootScope.isopen = true;\n        $rootScope.$digest();\n\n        $animate.flush();\n        $rootScope.$digest();\n        expect($rootScope.toggleHandler).toHaveBeenCalledWith(true);\n\n        clickDropdownToggle();\n        $animate.flush();\n        $rootScope.$digest();\n        expect($rootScope.toggleHandler).toHaveBeenCalledWith(false);\n      });\n    });\n\n    describe('with is-open to true', function() {\n      beforeEach(function() {\n        $rootScope.toggleHandler = jasmine.createSpy('toggleHandler');\n        $rootScope.isopen = true;\n        element = $compile('<li uib-dropdown on-toggle=\"toggleHandler(open)\" is-open=\"isopen\"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should not have been called initially', function() {\n        expect($rootScope.toggleHandler).not.toHaveBeenCalled();\n      });\n\n      it('should call it correctly when toggles', function() {\n        $rootScope.isopen = false;\n        $rootScope.$digest();\n\n        $animate.flush();\n        $rootScope.$digest();\n        expect($rootScope.toggleHandler).toHaveBeenCalledWith(false);\n\n        $rootScope.isopen = true;\n        $rootScope.$digest();\n\n        $animate.flush();\n        $rootScope.$digest();\n        expect($rootScope.toggleHandler).toHaveBeenCalledWith(true);\n      });\n    });\n\n    describe('without is-open', function() {\n      beforeEach(function() {\n        $rootScope.toggleHandler = jasmine.createSpy('toggleHandler');\n        element = $compile('<li uib-dropdown on-toggle=\"toggleHandler(open)\"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n        $rootScope.$digest();\n      });\n\n      it('should not have been called initially', function() {\n        expect($rootScope.toggleHandler).not.toHaveBeenCalled();\n      });\n\n      it('should call it when clicked', function() {\n        clickDropdownToggle();\n\n        $animate.flush();\n        $rootScope.$digest();\n        expect($rootScope.toggleHandler).toHaveBeenCalledWith(true);\n\n        clickDropdownToggle();\n\n        $animate.flush();\n        $rootScope.$digest();\n        expect($rootScope.toggleHandler).toHaveBeenCalledWith(false);\n      });\n    });\n  });\n\n  describe('using auto-close', function() {\n    function dropdown(autoClose) {\n      return $compile('<li uib-dropdown ' +\n        (autoClose === undefined ? '' : 'auto-close=\"' + autoClose + '\"') +\n        '><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li></ul></li>')($rootScope);\n    }\n\n    describe('always', function() {\n      it('should close on document click if no auto-close is specified', function() {\n        element = dropdown();\n        clickDropdownToggle();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        $document.click();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('should close on document click if empty auto-close is specified', function() {\n        element = dropdown('');\n        clickDropdownToggle();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        $document.click();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n    });\n\n    describe('disabled', function() {\n      it('auto-close=\"disabled\"', function() {\n        element = dropdown('disabled');\n        clickDropdownToggle();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        $document.click();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('control with is-open', function() {\n        $rootScope.isopen = true;\n        element = $compile('<li uib-dropdown is-open=\"isopen\" auto-close=\"disabled\"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);\n        $rootScope.$digest();\n\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        //should remain open\n        $document.click();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        //now should close\n        $rootScope.isopen = false;\n        $rootScope.$digest();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('should close anyway if toggle is clicked', function() {\n        element = dropdown('disabled');\n        clickDropdownToggle();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        clickDropdownToggle();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('should close anyway if esc is pressed', function() {\n        element = dropdown('disabled');\n        var dropdownMenu = element.find('[uib-dropdown-menu]');\n        $document.find('body').append(element);\n        clickDropdownToggle();\n        triggerKeyDown(dropdownMenu, 27);\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n        expect(element.find('a')).toHaveFocus();\n      });\n\n      it('should close anyway if another dropdown is opened', function() {\n        var elm1 = dropdown('disabled');\n        var elm2 = dropdown();\n        expect(elm1).not.toHaveClass(dropdownConfig.openClass);\n        expect(elm2).not.toHaveClass(dropdownConfig.openClass);\n        clickDropdownToggle(elm1);\n        expect(elm1).toHaveClass(dropdownConfig.openClass);\n        expect(elm2).not.toHaveClass(dropdownConfig.openClass);\n        clickDropdownToggle(elm2);\n        expect(elm1).not.toHaveClass(dropdownConfig.openClass);\n        expect(elm2).toHaveClass(dropdownConfig.openClass);\n      });\n    });\n\n    describe('outsideClick', function() {\n      it('should close only on a click outside of the dropdown menu', function() {\n        element = dropdown('outsideClick');\n        clickDropdownToggle();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        element.find('ul li a').click();\n        expect(element).toHaveClass(dropdownConfig.openClass);\n        $document.click();\n        expect(element).not.toHaveClass(dropdownConfig.openClass);\n      });\n\n      it('should work with dropdown-append-to-body', function() {\n        element = $compile('<li uib-dropdown dropdown-append-to-body auto-close=\"outsideClick\"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);\n        clickDropdownToggle();\n        var dropdownMenu = $document.find('#dropdown-menu');\n        expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);\n        dropdownMenu.find('li').eq(0).trigger('click');\n        expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);\n        $document.click();\n        expect(dropdownMenu.parent()).not.toHaveClass(dropdownConfig.appendToOpenClass);\n      });\n    });\n  });\n\n  describe('using keyboard-nav', function() {\n    function dropdown() {\n      return $compile('<li uib-dropdown keyboard-nav><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);\n    }\n    function getFocusedElement() {\n      return angular.element(document.activeElement);\n    }\n    beforeEach(function() {\n      element = dropdown();\n    });\n\n    it('should focus first list element when down arrow pressed', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      triggerKeyDown(getFocusedElement(), 40);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var optionEl = element.find('ul').eq(0).find('a').eq(0);\n      expect(optionEl).toHaveFocus();\n    });\n\n    it('should not focus first list element when down arrow pressed if closed', function() {\n      $document.find('body').append(element);\n      triggerKeyDown(getFocusedElement(), 40);\n\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a').eq(0);\n      expect(focusEl).not.toHaveFocus();\n    });\n\n    it('should focus second list element when down arrow pressed twice', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      triggerKeyDown(getFocusedElement(), 40);\n      triggerKeyDown(getFocusedElement(), 40);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a').eq(1);\n      expect(focusEl).toHaveFocus();\n    });\n\n    it('should not focus first list element when up arrow pressed after dropdown toggled', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      expect(element).toHaveClass(dropdownConfig.openClass);\n\n      triggerKeyDown(getFocusedElement(), 38);\n      var focusEl = element.find('ul').eq(0).find('a').eq(0);\n      expect(focusEl).not.toHaveFocus();\n    });\n\n    it('should focus last list element when up arrow pressed after dropdown toggled', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      triggerKeyDown(getFocusedElement(), 38);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a').eq(1);\n      expect(focusEl).toHaveFocus();\n    });\n\n    it('should not change focus when other keys are pressed', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      triggerKeyDown(getFocusedElement(), 37);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a');\n      expect(focusEl[0]).not.toHaveFocus();\n      expect(focusEl[1]).not.toHaveFocus();\n    });\n\n    it('should focus first list element when down arrow pressed 2x and up pressed 1x', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      triggerKeyDown(getFocusedElement(), 40);\n      triggerKeyDown(getFocusedElement(), 40);\n\n      triggerKeyDown(getFocusedElement(), 38);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a').eq(0);\n      expect(focusEl).toHaveFocus();\n    });\n\n    it('should stay focused on final list element if down pressed at list end', function() {\n      $document.find('body').append(element);\n      clickDropdownToggle();\n      triggerKeyDown(getFocusedElement(), 40);\n      triggerKeyDown(getFocusedElement(), 40);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a').eq(1);\n      expect(focusEl).toHaveFocus();\n\n      triggerKeyDown(element, 40);\n      expect(focusEl).toHaveFocus();\n    });\n\n    it('should close if esc is pressed while focused', function() {\n      element = dropdown('disabled');\n      $document.find('body').append(element);\n      clickDropdownToggle();\n\n      triggerKeyDown(getFocusedElement(), 40);\n\n      expect(element).toHaveClass(dropdownConfig.openClass);\n      var focusEl = element.find('ul').eq(0).find('a').eq(0);\n      expect(focusEl).toHaveFocus();\n\n      triggerKeyDown(getFocusedElement(), 27);\n      expect(element).not.toHaveClass(dropdownConfig.openClass);\n    });\n\n    describe('with dropdown-append-to-body', function() {\n      function dropdown() {\n        return $compile('<li uib-dropdown dropdown-append-to-body keyboard-nav><a href uib-dropdown-toggle>foo</a><ul uib-dropdown-menu id=\"dropdown-menu\"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);\n      }\n\n      beforeEach(function() {\n        element = dropdown();\n      });\n\n      it('should focus first list element when down arrow pressed', function() {\n        $document.find('body').append(element);\n        clickDropdownToggle();\n\n        var dropdownMenu = $document.find('#dropdown-menu');\n\n        triggerKeyDown(getFocusedElement(), 40);\n\n        expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);\n        var focusEl = $document.find('ul').eq(0).find('a');\n        expect(focusEl).toHaveFocus();\n      });\n\n      it('should focus second list element when down arrow pressed twice', function() {\n        $document.find('body').append(element);\n        clickDropdownToggle();\n        var dropdownMenu = $document.find('#dropdown-menu');\n        triggerKeyDown(getFocusedElement(), 40);\n        triggerKeyDown(getFocusedElement(), 40);\n        triggerKeyDown(getFocusedElement(), 40);\n\n        expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);\n        var elem1 = $document.find('ul');\n        var elem2 = elem1.find('a');\n        var focusEl = $document.find('ul').eq(0).find('a').eq(1);\n        expect(focusEl).toHaveFocus();\n      });\n    });\n  });\n\n  // issue #5942\n  describe('using dropdown-append-to-body with dropdown-menu-right class', function() {\n    function dropdown() {\n      return $compile('<li style=\"float: right;\" uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle>Toggle menu</a><ul uib-dropdown-menu class=\"dropdown-menu-right\" id=\"dropdown-menu\"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);\n    }\n\n    beforeEach(function() {\n      element = dropdown();\n      $document.find('body').append(element);\n\n      var menu = $document.find('#dropdown-menu');\n      menu.css('position', 'absolute');\n    });\n\n    afterEach(function() {\n      element.remove();\n    });\n\n    it('should align the menu correctly when the body has no vertical scrollbar', function() {\n      var toggle = element.find('[uib-dropdown-toggle]');\n      var menu = $document.find('#dropdown-menu');\n      toggle.trigger('click');\n\n      // Get the offsets of the rightmost position of both the toggle and the menu (offset from the left of the window)\n      var toggleRight = Math.round(toggle.offset().left + toggle.outerWidth());\n      var menuRight = Math.round(menu.offset().left + menu.outerWidth());\n      expect(menuRight).toBe(toggleRight);\n    });\n  });\n});\n"
  },
  {
    "path": "src/isClass/index.js",
    "content": "require('./isClass');\n\nvar MODULE_NAME = 'ui.bootstrap.module.isClass';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.isClass']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/isClass/isClass.js",
    "content": "// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to\n// at most one element.\nangular.module('ui.bootstrap.isClass', [])\n.directive('uibIsClass', [\n         '$animate',\nfunction ($animate) {\n  //                    11111111          22222222\n  var ON_REGEXP = /^\\s*([\\s\\S]+?)\\s+on\\s+([\\s\\S]+?)\\s*$/;\n  //                    11111111           22222222\n  var IS_REGEXP = /^\\s*([\\s\\S]+?)\\s+for\\s+([\\s\\S]+?)\\s*$/;\n\n  var dataPerTracked = {};\n\n  return {\n    restrict: 'A',\n    compile: function(tElement, tAttrs) {\n      var linkedScopes = [];\n      var instances = [];\n      var expToData = {};\n      var lastActivated = null;\n      var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);\n      var onExp = onExpMatches[2];\n      var expsStr = onExpMatches[1];\n      var exps = expsStr.split(',');\n\n      return linkFn;\n\n      function linkFn(scope, element, attrs) {\n        linkedScopes.push(scope);\n        instances.push({\n          scope: scope,\n          element: element\n        });\n\n        exps.forEach(function(exp, k) {\n          addForExp(exp, scope);\n        });\n\n        scope.$on('$destroy', removeScope);\n      }\n\n      function addForExp(exp, scope) {\n        var matches = exp.match(IS_REGEXP);\n        var clazz = scope.$eval(matches[1]);\n        var compareWithExp = matches[2];\n        var data = expToData[exp];\n        if (!data) {\n          var watchFn = function(compareWithVal) {\n            var newActivated = null;\n            instances.some(function(instance) {\n              var thisVal = instance.scope.$eval(onExp);\n              if (thisVal === compareWithVal) {\n                newActivated = instance;\n                return true;\n              }\n            });\n            if (data.lastActivated !== newActivated) {\n              if (data.lastActivated) {\n                $animate.removeClass(data.lastActivated.element, clazz);\n              }\n              if (newActivated) {\n                $animate.addClass(newActivated.element, clazz);\n              }\n              data.lastActivated = newActivated;\n            }\n          };\n          expToData[exp] = data = {\n            lastActivated: null,\n            scope: scope,\n            watchFn: watchFn,\n            compareWithExp: compareWithExp,\n            watcher: scope.$watch(compareWithExp, watchFn)\n          };\n        }\n        data.watchFn(scope.$eval(compareWithExp));\n      }\n\n      function removeScope(e) {\n        var removedScope = e.targetScope;\n        var index = linkedScopes.indexOf(removedScope);\n        linkedScopes.splice(index, 1);\n        instances.splice(index, 1);\n        if (linkedScopes.length) {\n          var newWatchScope = linkedScopes[0];\n          angular.forEach(expToData, function(data) {\n            if (data.scope === removedScope) {\n              data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);\n              data.scope = newWatchScope;\n            }\n          });\n        } else {\n          expToData = {};\n        }\n      }\n    }\n  };\n}]);"
  },
  {
    "path": "src/isClass/test/isClass.spec.js",
    "content": "describe('uibIsClass', function() {\n  var $rootScope;\n\n  beforeEach(module('ui.bootstrap.isClass'));\n  beforeEach(inject(function($compile, _$rootScope_) {\n    $rootScope = _$rootScope_;\n    $rootScope.activeClass = 'active';\n    $rootScope.items = [1, 2, 3];\n    element = $compile('<div><div ng-repeat=\"item in items\" ' +\n      'uib-is-class=\"activeClass for activeItem on item\">{{ item }}</div></div>')($rootScope);\n    $rootScope.$digest();\n  }));\n\n  it('initializes classes correctly', function() {\n    expect(element.find('.active').length).toEqual(0);\n  });\n\n  it('sets classes correctly', function() {\n    $rootScope.activeItem = 2;\n    $rootScope.$digest();\n    expect(element.find('.active').text()).toEqual('2');\n\n    $rootScope.items.splice(1, 1);\n    $rootScope.$digest();\n    expect(element.find('.active').length).toEqual(0);\n  });\n\n  it('handles removal of items correctly', function() {\n    $rootScope.activeItem = 2;\n    $rootScope.$digest();\n    expect(element.find('.active').text()).toEqual('2');\n\n    $rootScope.items.splice(1, 1);\n    $rootScope.$digest();\n    expect(element.find('.active').length).toEqual(0);\n\n    $rootScope.activeItem = 1;\n    $rootScope.$digest();\n    expect(element.find('.active').text()).toEqual('1');\n  });\n\n  it('handles moving of items', function() {\n    $rootScope.activeItem = 2;\n    $rootScope.items = [2, 1, 3];\n    $rootScope.$digest();\n    expect(element.find('.active').text()).toEqual('2');\n    expect(element.find('.active').length).toEqual(1);\n    expect(element.find('.active').index()).toEqual(0);\n\n    $rootScope.items = [4, 3, 2];\n    $rootScope.$digest();\n    expect(element.find('.active').text()).toEqual('2');\n    expect(element.find('.active').length).toEqual(1);\n    expect(element.find('.active').index()).toEqual(2);\n  });\n\n  it('handles emptying and re-adding the items', function() {\n    $rootScope.activeItem = 2;\n    $rootScope.items = [];\n    $rootScope.$digest();\n    expect(element.find('.active').length).toEqual(0);\n\n    $rootScope.items = [4, 3, 2];\n    $rootScope.$digest();\n    expect(element.find('.active').text()).toEqual('2');\n    expect(element.find('.active').index()).toEqual(2);\n  });\n\n  it('handles undefined items', function() {\n    $rootScope.activeItem = undefined;\n    $rootScope.items = [];\n    $rootScope.$digest();\n    expect(element.find('.active').length).toEqual(0);\n\n    $rootScope.items = [4, 3, undefined];\n    $rootScope.$digest();\n    expect(element.find('.active').length).toEqual(1);\n    expect(element.find('.active').text()).toEqual('');\n  });\n});"
  },
  {
    "path": "src/modal/docs/demo.html",
    "content": "<div ng-controller=\"ModalDemoCtrl as $ctrl\" class=\"modal-demo\">\n    <script type=\"text/ng-template\" id=\"myModalContent.html\">\n        <div class=\"modal-header\">\n            <h3 class=\"modal-title\" id=\"modal-title\">I'm a modal!</h3>\n        </div>\n        <div class=\"modal-body\" id=\"modal-body\">\n            <ul>\n                <li ng-repeat=\"item in $ctrl.items\">\n                    <a href=\"#\" ng-click=\"$event.preventDefault(); $ctrl.selected.item = item\">{{ item }}</a>\n                </li>\n            </ul>\n            Selected: <b>{{ $ctrl.selected.item }}</b>\n        </div>\n        <div class=\"modal-footer\">\n            <button class=\"btn btn-primary\" type=\"button\" ng-click=\"$ctrl.ok()\">OK</button>\n            <button class=\"btn btn-warning\" type=\"button\" ng-click=\"$ctrl.cancel()\">Cancel</button>\n        </div>\n    </script>\n    <script type=\"text/ng-template\" id=\"stackedModal.html\">\n        <div class=\"modal-header\">\n            <h3 class=\"modal-title\" id=\"modal-title-{{name}}\">The {{name}} modal!</h3>\n        </div>\n        <div class=\"modal-body\" id=\"modal-body-{{name}}\">\n            Having multiple modals open at once is probably bad UX but it's technically possible.\n        </div>\n    </script>\n\n    <button type=\"button\" class=\"btn btn-default\" ng-click=\"$ctrl.open()\">Open me!</button>\n    <button type=\"button\" class=\"btn btn-default\" ng-click=\"$ctrl.open('lg')\">Large modal</button>\n    <button type=\"button\" class=\"btn btn-default\" ng-click=\"$ctrl.open('sm')\">Small modal</button>\n    <button type=\"button\" \n        class=\"btn btn-default\" \n        ng-click=\"$ctrl.open('sm', '.modal-parent')\">\n            Modal appended to a custom parent\n    </button>\n    <button type=\"button\" class=\"btn btn-default\" ng-click=\"$ctrl.toggleAnimation()\">Toggle Animation ({{ $ctrl.animationsEnabled }})</button>\n    <button type=\"button\" class=\"btn btn-default\" ng-click=\"$ctrl.openComponentModal()\">Open a component modal!</button>\n    <button type=\"button\" class=\"btn btn-default\" ng-click=\"$ctrl.openMultipleModals()\">\n        Open multiple modals at once \n    </button>\n    <div ng-show=\"$ctrl.selected\">Selection from a modal: {{ $ctrl.selected }}</div>\n    <div class=\"modal-parent\">\n    </div>\n</div>\n"
  },
  {
    "path": "src/modal/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($uibModal, $log, $document) {\n  var $ctrl = this;\n  $ctrl.items = ['item1', 'item2', 'item3'];\n\n  $ctrl.animationsEnabled = true;\n\n  $ctrl.open = function (size, parentSelector) {\n    var parentElem = parentSelector ? \n      angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;\n    var modalInstance = $uibModal.open({\n      animation: $ctrl.animationsEnabled,\n      ariaLabelledBy: 'modal-title',\n      ariaDescribedBy: 'modal-body',\n      templateUrl: 'myModalContent.html',\n      controller: 'ModalInstanceCtrl',\n      controllerAs: '$ctrl',\n      size: size,\n      appendTo: parentElem,\n      resolve: {\n        items: function () {\n          return $ctrl.items;\n        }\n      }\n    });\n\n    modalInstance.result.then(function (selectedItem) {\n      $ctrl.selected = selectedItem;\n    }, function () {\n      $log.info('Modal dismissed at: ' + new Date());\n    });\n  };\n\n  $ctrl.openComponentModal = function () {\n    var modalInstance = $uibModal.open({\n      animation: $ctrl.animationsEnabled,\n      component: 'modalComponent',\n      resolve: {\n        items: function () {\n          return $ctrl.items;\n        }\n      }\n    });\n\n    modalInstance.result.then(function (selectedItem) {\n      $ctrl.selected = selectedItem;\n    }, function () {\n      $log.info('modal-component dismissed at: ' + new Date());\n    });\n  };\n\n  $ctrl.openMultipleModals = function () {\n    $uibModal.open({\n      animation: $ctrl.animationsEnabled,\n      ariaLabelledBy: 'modal-title-bottom',\n      ariaDescribedBy: 'modal-body-bottom',\n      templateUrl: 'stackedModal.html',\n      size: 'sm',\n      controller: function($scope) {\n        $scope.name = 'bottom';  \n      }\n    });\n\n    $uibModal.open({\n      animation: $ctrl.animationsEnabled,\n      ariaLabelledBy: 'modal-title-top',\n      ariaDescribedBy: 'modal-body-top',\n      templateUrl: 'stackedModal.html',\n      size: 'sm',\n      controller: function($scope) {\n        $scope.name = 'top';  \n      }\n    });\n  };\n\n  $ctrl.toggleAnimation = function () {\n    $ctrl.animationsEnabled = !$ctrl.animationsEnabled;\n  };\n});\n\n// Please note that $uibModalInstance represents a modal window (instance) dependency.\n// It is not the same as the $uibModal service used above.\n\nangular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($uibModalInstance, items) {\n  var $ctrl = this;\n  $ctrl.items = items;\n  $ctrl.selected = {\n    item: $ctrl.items[0]\n  };\n\n  $ctrl.ok = function () {\n    $uibModalInstance.close($ctrl.selected.item);\n  };\n\n  $ctrl.cancel = function () {\n    $uibModalInstance.dismiss('cancel');\n  };\n});\n\n// Please note that the close and dismiss bindings are from $uibModalInstance.\n\nangular.module('ui.bootstrap.demo').component('modalComponent', {\n  templateUrl: 'myModalContent.html',\n  bindings: {\n    resolve: '<',\n    close: '&',\n    dismiss: '&'\n  },\n  controller: function () {\n    var $ctrl = this;\n\n    $ctrl.$onInit = function () {\n      $ctrl.items = $ctrl.resolve.items;\n      $ctrl.selected = {\n        item: $ctrl.items[0]\n      };\n    };\n\n    $ctrl.ok = function () {\n      $ctrl.close({$value: $ctrl.selected.item});\n    };\n\n    $ctrl.cancel = function () {\n      $ctrl.dismiss({$value: 'cancel'});\n    };\n  }\n});\n"
  },
  {
    "path": "src/modal/docs/readme.md",
    "content": "`$uibModal` is a service to create modal windows.\nCreating modals is straightforward: create a template and controller, and reference them when using `$uibModal`.\n\nThe `$uibModal` service has only one method: `open(options)`.\n\n### $uibModal's open function\n\n#### options parameter\n\n* `animation`\n  _(Type: `boolean`, Default: `true`)_ -\n  Set to false to disable animations on new modal/backdrop. Does not toggle animations for modals/backdrops that are already displayed.\n\n* `appendTo`\n  _(Type: `angular.element`, Default: `body`: Example: `$document.find('aside').eq(0)`)_ -\n  Appends the modal to a specific element.\n\n* `ariaDescribedBy`\n  _(Type: `string`, `my-modal-description`)_ -\n  Sets the [`aria-describedby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-describedby) property on the modal. The value should be an id (without the leading `#`) pointing to the element that describes your modal. Typically, this will be the text on your modal, but does not include something the user would interact with, like buttons or a form. Omitting this option will not impact sighted users but will weaken your accessibility support.\n\n* `ariaLabelledBy`\n  _(Type: `string`, `my-modal-title`)_ -\n  Sets the [`aria-labelledby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-labelledby) property on the modal. The value should be an id (without the leading `#`) pointing to the element that labels your modal. Typically, this will be a header element. Omitting this option will not impact sighted users but will weaken your accessibility support.\n\n* `backdrop`\n  _(Type: `boolean|string`, Default: `true`)_ -\n  Controls presence of a backdrop. Allowed values: `true` (default), `false` (no backdrop), `'static'` (disables modal closing by click on the backdrop).\n\n* `backdropClass`\n  _(Type: `string`)_ -\n  Additional CSS class(es) to be added to a modal backdrop template.\n\n* `bindToController`\n  _(Type: `boolean`, Default: `false`)_ -\n  When used with `controllerAs` & set to `true`, it will bind the $scope properties onto the controller.\n\n* `component`\n  _(Type: `string`, Example: `myComponent`)_ -\n  A string reference to the component to be rendered that is registered with Angular's compiler. If using a directive, the directive must have `restrict: 'E'` and a template or templateUrl set.\n\n  It supports these bindings:\n\n  * `close` - A method that can be used to close a modal, passing a result. The result must be passed in this format: `{$value: myResult}`\n\n  * `dismiss` - A method that can be used to dismiss a modal, passing a result. The result must be passed in this format: `{$value: myRejectedResult}`\n\n  * `modalInstance` - The modal instance. This is the same `$uibModalInstance` injectable found when using `controller`.\n\n  * `resolve` - An object of the modal resolve values. See [UI Router resolves](#ui-router-resolves) for details.\n\n* `controller`\n  _(Type: `function|string|array`, Example: `MyModalController`)_ -\n  A controller for the modal instance, either a controller name as a string, or an inline controller function, optionally wrapped in array notation for dependency injection. Allows the controller-as syntax. Has a special `$uibModalInstance` injectable to access the modal instance.\n\n* `controllerAs`\n  _(Type: `string`, Example: `ctrl`)_ -\n  An alternative to the controller-as syntax. Requires the `controller` option to be provided as well.\n\n* `keyboard` -\n  _(Type: `boolean`, Default: `true`)_ -\n  Indicates whether the dialog should be closable by hitting the ESC key.\n\n* `openedClass`\n  _(Type: `string`, Default: `modal-open`)_ -\n  Class added to the `body` element when the modal is opened.\n\n* `resolve`\n  _(Type: `Object`)_ -\n  Members that will be resolved and passed to the controller as locals; it is equivalent of the `resolve` property in the router.\n\n* `scope`\n  _(Type: `$scope`)_ -\n  The parent scope instance to be used for the modal's content. Defaults to `$rootScope`.\n\n* `size`\n  _(Type: `string`, Example: `lg`)_ -\n  Optional suffix of modal window class. The value used is appended to the `modal-` class, i.e. a value of `sm` gives `modal-sm`.\n\n* `template`\n  _(Type: `string`)_ -\n  Inline template representing the modal's content.\n\n* `templateUrl`\n  _(Type: `string`)_ -\n  A path to a template representing modal's content. You need either a `template` or `templateUrl`.\n\n* `windowClass`\n  _(Type: `string`)_ -\n  Additional CSS class(es) to be added to a modal window template.\n\n* `windowTemplateUrl`\n  _(Type: `string`, Default: `uib/template/modal/window.html`)_ -\n  A path to a template overriding modal's window template.\n\n* `windowTopClass`\n  _(Type: `string`)_ -\n  CSS class(es) to be added to the top modal window.\n\nGlobal defaults may be set for `$uibModal` via `$uibModalProvider.options`.\n\n#### return\n\nThe `open` method returns a modal instance, an object with the following properties:\n\n* `close(result)`\n  _(Type: `function`)_ -\n  Can be used to close a modal, passing a result.\n\n* `dismiss(reason)`\n  _(Type: `function`)_ -\n  Can be used to dismiss a modal, passing a reason.\n\n* `result`\n  _(Type: `promise`)_ -\n  Is resolved when a modal is closed and rejected when a modal is dismissed.\n\n* `opened`\n  _(Type: `promise`)_ -\n  Is resolved when a modal gets opened after downloading content's template and resolving all variables.\n\n* `closed`\n  _(Type: `promise`)_ -\n  Is resolved when a modal is closed and the animation completes.\n\n* `rendered`\n  _(Type: `promise`)_ -\n  Is resolved when a modal is rendered.\n\n---\n\nThe scope associated with modal's content is augmented with:\n\n* `$close(result)`\n  _(Type: `function`)_ -\n  A method that can be used to close a modal, passing a result.\n\n* `$dismiss(reason)`\n  _(Type: `function`)_ -\n  A method that can be used to dismiss a modal, passing a reason.\n\nThose methods make it easy to close a modal window without a need to create a dedicated controller.\n\nAlso, when using `bindToController`, you can define an `$onInit` method in the controller that will fire upon initialization.\n\n---\n\nEvents fired:\n\n* `$uibUnscheduledDestruction` -\n  This event is fired if the $scope is destroyed via unexpected mechanism, such as it being passed in the modal options and a $route/$state transition occurs. The modal will also be dismissed.\n\n* `modal.closing` -\n  This event is broadcast to the modal scope before the modal closes. If the listener calls preventDefault() on the event, then the modal will remain open.\n  Also, the `$close` and `$dismiss` methods returns true if the event was executed. This event also includes a parameter for the result/reason and a boolean that indicates whether the modal is being closed (true) or dismissed.\n\n##### UI Router resolves\n\nIf one wants to have the modal resolve using [UI Router's](https://github.com/angular-ui/ui-router) pre-1.0 resolve mechanism, one can call `$uibResolve.setResolver('$resolve')` in the configuration phase of the application. One can also provide a custom resolver as well, as long as the signature conforms to UI Router's [$resolve](http://angular-ui.github.io/ui-router/site/#/api/ui.router.util.$resolve).\n\nWhen the modal is opened with a controller, a `$resolve` object is exposed on the template with the resolved values from the resolve object. If using the component option, see details on how to access this object in component section of the modal documentation.\n"
  },
  {
    "path": "src/modal/index-nocss.js",
    "content": "require('../multiMap');\nrequire('../position/index-nocss.js');\nrequire('../stackedMap');\nrequire('../../template/modal/window.html.js');\nrequire('./modal');\n\nvar MODULE_NAME = 'ui.bootstrap.module.modal';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.modal', 'uib/template/modal/window.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/modal/index.js",
    "content": "require('../position/position.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/modal/modal.js",
    "content": "angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])\n/**\n * Pluggable resolve mechanism for the modal resolve resolution\n * Supports UI Router's $resolve service\n */\n  .provider('$uibResolve', function() {\n    var resolve = this;\n    this.resolver = null;\n\n    this.setResolver = function(resolver) {\n      this.resolver = resolver;\n    };\n\n    this.$get = ['$injector', '$q', function($injector, $q) {\n      var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;\n      return {\n        resolve: function(invocables, locals, parent, self) {\n          if (resolver) {\n            return resolver.resolve(invocables, locals, parent, self);\n          }\n\n          var promises = [];\n\n          angular.forEach(invocables, function(value) {\n            if (angular.isFunction(value) || angular.isArray(value)) {\n              promises.push($q.resolve($injector.invoke(value)));\n            } else if (angular.isString(value)) {\n              promises.push($q.resolve($injector.get(value)));\n            } else {\n              promises.push($q.resolve(value));\n            }\n          });\n\n          return $q.all(promises).then(function(resolves) {\n            var resolveObj = {};\n            var resolveIter = 0;\n            angular.forEach(invocables, function(value, key) {\n              resolveObj[key] = resolves[resolveIter++];\n            });\n\n            return resolveObj;\n          });\n        }\n      };\n    }];\n  })\n\n/**\n * A helper directive for the $modal service. It creates a backdrop element.\n */\n  .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',\n  function($animate, $injector, $modalStack) {\n    return {\n      restrict: 'A',\n      compile: function(tElement, tAttrs) {\n        tElement.addClass(tAttrs.backdropClass);\n        return linkFn;\n      }\n    };\n\n    function linkFn(scope, element, attrs) {\n      if (attrs.modalInClass) {\n        $animate.addClass(element, attrs.modalInClass);\n\n        scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {\n          var done = setIsAsync();\n          if (scope.modalOptions.animation) {\n            $animate.removeClass(element, attrs.modalInClass).then(done);\n          } else {\n            done();\n          }\n        });\n      }\n    }\n  }])\n\n  .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',\n  function($modalStack, $q, $animateCss, $document) {\n    return {\n      scope: {\n        index: '@'\n      },\n      restrict: 'A',\n      transclude: true,\n      templateUrl: function(tElement, tAttrs) {\n        return tAttrs.templateUrl || 'uib/template/modal/window.html';\n      },\n      link: function(scope, element, attrs) {\n        element.addClass(attrs.windowTopClass || '');\n        scope.size = attrs.size;\n\n        scope.close = function(evt) {\n          var modal = $modalStack.getTop();\n          if (modal && modal.value.backdrop &&\n            modal.value.backdrop !== 'static' &&\n            evt.target === evt.currentTarget) {\n            evt.preventDefault();\n            evt.stopPropagation();\n            $modalStack.dismiss(modal.key, 'backdrop click');\n          }\n        };\n\n        // moved from template to fix issue #2280\n        element.on('click', scope.close);\n\n        // This property is only added to the scope for the purpose of detecting when this directive is rendered.\n        // We can detect that by using this property in the template associated with this directive and then use\n        // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.\n        scope.$isRendered = true;\n\n        // Deferred object that will be resolved when this modal is rendered.\n        var modalRenderDeferObj = $q.defer();\n        // Resolve render promise post-digest\n        scope.$$postDigest(function() {\n          modalRenderDeferObj.resolve();\n        });\n\n        modalRenderDeferObj.promise.then(function() {\n          var animationPromise = null;\n\n          if (attrs.modalInClass) {\n            animationPromise = $animateCss(element, {\n              addClass: attrs.modalInClass\n            }).start();\n\n            scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {\n              var done = setIsAsync();\n              $animateCss(element, {\n                removeClass: attrs.modalInClass\n              }).start().then(done);\n            });\n          }\n\n\n          $q.when(animationPromise).then(function() {\n            // Notify {@link $modalStack} that modal is rendered.\n            var modal = $modalStack.getTop();\n            if (modal) {\n              $modalStack.modalRendered(modal.key);\n            }\n\n            /**\n             * If something within the freshly-opened modal already has focus (perhaps via a\n             * directive that causes focus) then there's no need to try to focus anything.\n             */\n            if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {\n              var inputWithAutofocus = element[0].querySelector('[autofocus]');\n              /**\n               * Auto-focusing of a freshly-opened modal element causes any child elements\n               * with the autofocus attribute to lose focus. This is an issue on touch\n               * based devices which will show and then hide the onscreen keyboard.\n               * Attempts to refocus the autofocus element via JavaScript will not reopen\n               * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus\n               * the modal element if the modal does not contain an autofocus element.\n               */\n              if (inputWithAutofocus) {\n                inputWithAutofocus.focus();\n              } else {\n                element[0].focus();\n              }\n            }\n          });\n        });\n      }\n    };\n  }])\n\n  .directive('uibModalAnimationClass', function() {\n    return {\n      compile: function(tElement, tAttrs) {\n        if (tAttrs.modalAnimation) {\n          tElement.addClass(tAttrs.uibModalAnimationClass);\n        }\n      }\n    };\n  })\n\n  .directive('uibModalTransclude', ['$animate', function($animate) {\n    return {\n      link: function(scope, element, attrs, controller, transclude) {\n        transclude(scope.$parent, function(clone) {\n          element.empty();\n          $animate.enter(clone, element);\n        });\n      }\n    };\n  }])\n\n  .factory('$uibModalStack', ['$animate', '$animateCss', '$document',\n    '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',\n    function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {\n      var OPENED_MODAL_CLASS = 'modal-open';\n\n      var backdropDomEl, backdropScope;\n      var openedWindows = $$stackedMap.createNew();\n      var openedClasses = $$multiMap.createNew();\n      var $modalStack = {\n        NOW_CLOSING_EVENT: 'modal.stack.now-closing'\n      };\n      var topModalIndex = 0;\n      var previousTopOpenedModal = null;\n      var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count';\n\n      //Modal focus behavior\n      var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\\'-1\\']), ' +\n        'button:not([disabled]):not([tabindex=\\'-1\\']),select:not([disabled]):not([tabindex=\\'-1\\']), textarea:not([disabled]):not([tabindex=\\'-1\\']), ' +\n        'iframe, object, embed, *[tabindex]:not([tabindex=\\'-1\\']), *[contenteditable=true]';\n      var scrollbarPadding;\n      var SNAKE_CASE_REGEXP = /[A-Z]/g;\n\n      // TODO: extract into common dependency with tooltip\n      function snake_case(name) {\n        var separator = '-';\n        return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {\n          return (pos ? separator : '') + letter.toLowerCase();\n        });\n      }\n\n      function isVisible(element) {\n        return !!(element.offsetWidth ||\n          element.offsetHeight ||\n          element.getClientRects().length);\n      }\n\n      function backdropIndex() {\n        var topBackdropIndex = -1;\n        var opened = openedWindows.keys();\n        for (var i = 0; i < opened.length; i++) {\n          if (openedWindows.get(opened[i]).value.backdrop) {\n            topBackdropIndex = i;\n          }\n        }\n\n        // If any backdrop exist, ensure that it's index is always\n        // right below the top modal\n        if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {\n          topBackdropIndex = topModalIndex;\n        }\n        return topBackdropIndex;\n      }\n\n      $rootScope.$watch(backdropIndex, function(newBackdropIndex) {\n        if (backdropScope) {\n          backdropScope.index = newBackdropIndex;\n        }\n      });\n\n      function removeModalWindow(modalInstance, elementToReceiveFocus) {\n        var modalWindow = openedWindows.get(modalInstance).value;\n        var appendToElement = modalWindow.appendTo;\n\n        //clean up the stack\n        openedWindows.remove(modalInstance);\n        previousTopOpenedModal = openedWindows.top();\n        if (previousTopOpenedModal) {\n          topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);\n        }\n\n        removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {\n          var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;\n          openedClasses.remove(modalBodyClass, modalInstance);\n          var areAnyOpen = openedClasses.hasKey(modalBodyClass);\n          appendToElement.toggleClass(modalBodyClass, areAnyOpen);\n          if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {\n            if (scrollbarPadding.originalRight) {\n              appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});\n            } else {\n              appendToElement.css({paddingRight: ''});\n            }\n            scrollbarPadding = null;\n          }\n          toggleTopWindowClass(true);\n        }, modalWindow.closedDeferred);\n        checkRemoveBackdrop();\n\n        //move focus to specified element if available, or else to body\n        if (elementToReceiveFocus && elementToReceiveFocus.focus) {\n          elementToReceiveFocus.focus();\n        } else if (appendToElement.focus) {\n          appendToElement.focus();\n        }\n      }\n\n      // Add or remove \"windowTopClass\" from the top window in the stack\n      function toggleTopWindowClass(toggleSwitch) {\n        var modalWindow;\n\n        if (openedWindows.length() > 0) {\n          modalWindow = openedWindows.top().value;\n          modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);\n        }\n      }\n\n      function checkRemoveBackdrop() {\n        //remove backdrop if no longer needed\n        if (backdropDomEl && backdropIndex() === -1) {\n          var backdropScopeRef = backdropScope;\n          removeAfterAnimate(backdropDomEl, backdropScope, function() {\n            backdropScopeRef = null;\n          });\n          backdropDomEl = undefined;\n          backdropScope = undefined;\n        }\n      }\n\n      function removeAfterAnimate(domEl, scope, done, closedDeferred) {\n        var asyncDeferred;\n        var asyncPromise = null;\n        var setIsAsync = function() {\n          if (!asyncDeferred) {\n            asyncDeferred = $q.defer();\n            asyncPromise = asyncDeferred.promise;\n          }\n\n          return function asyncDone() {\n            asyncDeferred.resolve();\n          };\n        };\n        scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);\n\n        // Note that it's intentional that asyncPromise might be null.\n        // That's when setIsAsync has not been called during the\n        // NOW_CLOSING_EVENT broadcast.\n        return $q.when(asyncPromise).then(afterAnimating);\n\n        function afterAnimating() {\n          if (afterAnimating.done) {\n            return;\n          }\n          afterAnimating.done = true;\n\n          $animate.leave(domEl).then(function() {\n            if (done) {\n              done();\n            }\n\n            domEl.remove();\n            if (closedDeferred) {\n              closedDeferred.resolve();\n            }\n          });\n\n          scope.$destroy();\n        }\n      }\n\n      $document.on('keydown', keydownListener);\n\n      $rootScope.$on('$destroy', function() {\n        $document.off('keydown', keydownListener);\n      });\n\n      function keydownListener(evt) {\n        if (evt.isDefaultPrevented()) {\n          return evt;\n        }\n\n        var modal = openedWindows.top();\n        if (modal) {\n          switch (evt.which) {\n            case 27: {\n              if (modal.value.keyboard) {\n                evt.preventDefault();\n                $rootScope.$apply(function() {\n                  $modalStack.dismiss(modal.key, 'escape key press');\n                });\n              }\n              break;\n            }\n            case 9: {\n              var list = $modalStack.loadFocusElementList(modal);\n              var focusChanged = false;\n              if (evt.shiftKey) {\n                if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {\n                  focusChanged = $modalStack.focusLastFocusableElement(list);\n                }\n              } else {\n                if ($modalStack.isFocusInLastItem(evt, list)) {\n                  focusChanged = $modalStack.focusFirstFocusableElement(list);\n                }\n              }\n\n              if (focusChanged) {\n                evt.preventDefault();\n                evt.stopPropagation();\n              }\n\n              break;\n            }\n          }\n        }\n      }\n\n      $modalStack.open = function(modalInstance, modal) {\n        var modalOpener = $document[0].activeElement,\n          modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;\n\n        toggleTopWindowClass(false);\n\n        // Store the current top first, to determine what index we ought to use\n        // for the current top modal\n        previousTopOpenedModal = openedWindows.top();\n\n        openedWindows.add(modalInstance, {\n          deferred: modal.deferred,\n          renderDeferred: modal.renderDeferred,\n          closedDeferred: modal.closedDeferred,\n          modalScope: modal.scope,\n          backdrop: modal.backdrop,\n          keyboard: modal.keyboard,\n          openedClass: modal.openedClass,\n          windowTopClass: modal.windowTopClass,\n          animation: modal.animation,\n          appendTo: modal.appendTo\n        });\n\n        openedClasses.put(modalBodyClass, modalInstance);\n\n        var appendToElement = modal.appendTo,\n            currBackdropIndex = backdropIndex();\n\n        if (currBackdropIndex >= 0 && !backdropDomEl) {\n          backdropScope = $rootScope.$new(true);\n          backdropScope.modalOptions = modal;\n          backdropScope.index = currBackdropIndex;\n          backdropDomEl = angular.element('<div uib-modal-backdrop=\"modal-backdrop\"></div>');\n          backdropDomEl.attr({\n            'class': 'modal-backdrop',\n            'ng-style': '{\\'z-index\\': 1040 + (index && 1 || 0) + index*10}',\n            'uib-modal-animation-class': 'fade',\n            'modal-in-class': 'in'\n          });\n          if (modal.backdropClass) {\n            backdropDomEl.addClass(modal.backdropClass);\n          }\n\n          if (modal.animation) {\n            backdropDomEl.attr('modal-animation', 'true');\n          }\n          $compile(backdropDomEl)(backdropScope);\n          $animate.enter(backdropDomEl, appendToElement);\n          if ($uibPosition.isScrollable(appendToElement)) {\n            scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);\n            if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {\n              appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});\n            }\n          }\n        }\n\n        var content;\n        if (modal.component) {\n          content = document.createElement(snake_case(modal.component.name));\n          content = angular.element(content);\n          content.attr({\n            resolve: '$resolve',\n            'modal-instance': '$uibModalInstance',\n            close: '$close($value)',\n            dismiss: '$dismiss($value)'\n          });\n        } else {\n          content = modal.content;\n        }\n\n        // Set the top modal index based on the index of the previous top modal\n        topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;\n        var angularDomEl = angular.element('<div uib-modal-window=\"modal-window\"></div>');\n        angularDomEl.attr({\n          'class': 'modal',\n          'template-url': modal.windowTemplateUrl,\n          'window-top-class': modal.windowTopClass,\n          'role': 'dialog',\n          'aria-labelledby': modal.ariaLabelledBy,\n          'aria-describedby': modal.ariaDescribedBy,\n          'size': modal.size,\n          'index': topModalIndex,\n          'animate': 'animate',\n          'ng-style': '{\\'z-index\\': 1050 + $$topModalIndex*10, display: \\'block\\'}',\n          'tabindex': -1,\n          'uib-modal-animation-class': 'fade',\n          'modal-in-class': 'in'\n        }).append(content);\n        if (modal.windowClass) {\n          angularDomEl.addClass(modal.windowClass);\n        }\n\n        if (modal.animation) {\n          angularDomEl.attr('modal-animation', 'true');\n        }\n\n        appendToElement.addClass(modalBodyClass);\n        if (modal.scope) {\n          // we need to explicitly add the modal index to the modal scope\n          // because it is needed by ngStyle to compute the zIndex property.\n          modal.scope.$$topModalIndex = topModalIndex;\n        }\n        $animate.enter($compile(angularDomEl)(modal.scope), appendToElement);\n\n        openedWindows.top().value.modalDomEl = angularDomEl;\n        openedWindows.top().value.modalOpener = modalOpener;\n\n        applyAriaHidden(angularDomEl);\n\n        function applyAriaHidden(el) {\n          if (!el || el[0].tagName === 'BODY') {\n            return;\n          }\n\n          getSiblings(el).forEach(function(sibling) {\n            var elemIsAlreadyHidden = sibling.getAttribute('aria-hidden') === 'true',\n              ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);\n\n            if (!ariaHiddenCount) {\n              ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;\n            }\n\n            sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);\n            sibling.setAttribute('aria-hidden', 'true');\n          });\n\n          return applyAriaHidden(el.parent());\n\n          function getSiblings(el) {\n            var children = el.parent() ? el.parent().children() : [];\n\n            return Array.prototype.filter.call(children, function(child) {\n              return child !== el[0];\n            });\n          }\n        }\n      };\n\n      function broadcastClosing(modalWindow, resultOrReason, closing) {\n        return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;\n      }\n\n      function unhideBackgroundElements() {\n        Array.prototype.forEach.call(\n          document.querySelectorAll('[' + ARIA_HIDDEN_ATTRIBUTE_NAME + ']'),\n          function(hiddenEl) {\n            var ariaHiddenCount = parseInt(hiddenEl.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10),\n              newHiddenCount = ariaHiddenCount - 1;\n            hiddenEl.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, newHiddenCount);\n\n            if (!newHiddenCount) {\n              hiddenEl.removeAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME);\n              hiddenEl.removeAttribute('aria-hidden');\n            }\n          }\n        );\n      }\n\n      $modalStack.close = function(modalInstance, result) {\n        var modalWindow = openedWindows.get(modalInstance);\n        unhideBackgroundElements();\n        if (modalWindow && broadcastClosing(modalWindow, result, true)) {\n          modalWindow.value.modalScope.$$uibDestructionScheduled = true;\n          modalWindow.value.deferred.resolve(result);\n          removeModalWindow(modalInstance, modalWindow.value.modalOpener);\n          return true;\n        }\n\n        return !modalWindow;\n      };\n\n      $modalStack.dismiss = function(modalInstance, reason) {\n        var modalWindow = openedWindows.get(modalInstance);\n        unhideBackgroundElements();\n        if (modalWindow && broadcastClosing(modalWindow, reason, false)) {\n          modalWindow.value.modalScope.$$uibDestructionScheduled = true;\n          modalWindow.value.deferred.reject(reason);\n          removeModalWindow(modalInstance, modalWindow.value.modalOpener);\n          return true;\n        }\n        return !modalWindow;\n      };\n\n      $modalStack.dismissAll = function(reason) {\n        var topModal = this.getTop();\n        while (topModal && this.dismiss(topModal.key, reason)) {\n          topModal = this.getTop();\n        }\n      };\n\n      $modalStack.getTop = function() {\n        return openedWindows.top();\n      };\n\n      $modalStack.modalRendered = function(modalInstance) {\n        var modalWindow = openedWindows.get(modalInstance);\n        if (modalWindow) {\n          modalWindow.value.renderDeferred.resolve();\n        }\n      };\n\n      $modalStack.focusFirstFocusableElement = function(list) {\n        if (list.length > 0) {\n          list[0].focus();\n          return true;\n        }\n        return false;\n      };\n\n      $modalStack.focusLastFocusableElement = function(list) {\n        if (list.length > 0) {\n          list[list.length - 1].focus();\n          return true;\n        }\n        return false;\n      };\n\n      $modalStack.isModalFocused = function(evt, modalWindow) {\n        if (evt && modalWindow) {\n          var modalDomEl = modalWindow.value.modalDomEl;\n          if (modalDomEl && modalDomEl.length) {\n            return (evt.target || evt.srcElement) === modalDomEl[0];\n          }\n        }\n        return false;\n      };\n\n      $modalStack.isFocusInFirstItem = function(evt, list) {\n        if (list.length > 0) {\n          return (evt.target || evt.srcElement) === list[0];\n        }\n        return false;\n      };\n\n      $modalStack.isFocusInLastItem = function(evt, list) {\n        if (list.length > 0) {\n          return (evt.target || evt.srcElement) === list[list.length - 1];\n        }\n        return false;\n      };\n\n      $modalStack.loadFocusElementList = function(modalWindow) {\n        if (modalWindow) {\n          var modalDomE1 = modalWindow.value.modalDomEl;\n          if (modalDomE1 && modalDomE1.length) {\n            var elements = modalDomE1[0].querySelectorAll(tabbableSelector);\n            return elements ?\n              Array.prototype.filter.call(elements, function(element) {\n                return isVisible(element);\n              }) : elements;\n          }\n        }\n      };\n\n      return $modalStack;\n    }])\n\n  .provider('$uibModal', function() {\n    var $modalProvider = {\n      options: {\n        animation: true,\n        backdrop: true, //can also be false or 'static'\n        keyboard: true\n      },\n      $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',\n        function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {\n          var $modal = {};\n\n          function getTemplatePromise(options) {\n            return options.template ? $q.when(options.template) :\n              $templateRequest(angular.isFunction(options.templateUrl) ?\n                options.templateUrl() : options.templateUrl);\n          }\n\n          var promiseChain = null;\n          $modal.getPromiseChain = function() {\n            return promiseChain;\n          };\n\n          $modal.open = function(modalOptions) {\n            var modalResultDeferred = $q.defer();\n            var modalOpenedDeferred = $q.defer();\n            var modalClosedDeferred = $q.defer();\n            var modalRenderDeferred = $q.defer();\n\n            //prepare an instance of a modal to be injected into controllers and returned to a caller\n            var modalInstance = {\n              result: modalResultDeferred.promise,\n              opened: modalOpenedDeferred.promise,\n              closed: modalClosedDeferred.promise,\n              rendered: modalRenderDeferred.promise,\n              close: function (result) {\n                return $modalStack.close(modalInstance, result);\n              },\n              dismiss: function (reason) {\n                return $modalStack.dismiss(modalInstance, reason);\n              }\n            };\n\n            //merge and clean up options\n            modalOptions = angular.extend({}, $modalProvider.options, modalOptions);\n            modalOptions.resolve = modalOptions.resolve || {};\n            modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);\n\n            if (!modalOptions.appendTo.length) {\n              throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');\n            }\n\n            //verify options\n            if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {\n              throw new Error('One of component or template or templateUrl options is required.');\n            }\n\n            var templateAndResolvePromise;\n            if (modalOptions.component) {\n              templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null));\n            } else {\n              templateAndResolvePromise =\n                $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);\n            }\n\n            function resolveWithTemplate() {\n              return templateAndResolvePromise;\n            }\n\n            // Wait for the resolution of the existing promise chain.\n            // Then switch to our own combined promise dependency (regardless of how the previous modal fared).\n            // Then add to $modalStack and resolve opened.\n            // Finally clean up the chain variable if no subsequent modal has overwritten it.\n            var samePromise;\n            samePromise = promiseChain = $q.all([promiseChain])\n              .then(resolveWithTemplate, resolveWithTemplate)\n              .then(function resolveSuccess(tplAndVars) {\n                var providedScope = modalOptions.scope || $rootScope;\n\n                var modalScope = providedScope.$new();\n                modalScope.$close = modalInstance.close;\n                modalScope.$dismiss = modalInstance.dismiss;\n\n                modalScope.$on('$destroy', function() {\n                  if (!modalScope.$$uibDestructionScheduled) {\n                    modalScope.$dismiss('$uibUnscheduledDestruction');\n                  }\n                });\n\n                var modal = {\n                  scope: modalScope,\n                  deferred: modalResultDeferred,\n                  renderDeferred: modalRenderDeferred,\n                  closedDeferred: modalClosedDeferred,\n                  animation: modalOptions.animation,\n                  backdrop: modalOptions.backdrop,\n                  keyboard: modalOptions.keyboard,\n                  backdropClass: modalOptions.backdropClass,\n                  windowTopClass: modalOptions.windowTopClass,\n                  windowClass: modalOptions.windowClass,\n                  windowTemplateUrl: modalOptions.windowTemplateUrl,\n                  ariaLabelledBy: modalOptions.ariaLabelledBy,\n                  ariaDescribedBy: modalOptions.ariaDescribedBy,\n                  size: modalOptions.size,\n                  openedClass: modalOptions.openedClass,\n                  appendTo: modalOptions.appendTo\n                };\n\n                var component = {};\n                var ctrlInstance, ctrlInstantiate, ctrlLocals = {};\n\n                if (modalOptions.component) {\n                  constructLocals(component, false, true, false);\n                  component.name = modalOptions.component;\n                  modal.component = component;\n                } else if (modalOptions.controller) {\n                  constructLocals(ctrlLocals, true, false, true);\n\n                  // the third param will make the controller instantiate later,private api\n                  // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126\n                  ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);\n                  if (modalOptions.controllerAs && modalOptions.bindToController) {\n                    ctrlInstance = ctrlInstantiate.instance;\n                    ctrlInstance.$close = modalScope.$close;\n                    ctrlInstance.$dismiss = modalScope.$dismiss;\n                    angular.extend(ctrlInstance, {\n                      $resolve: ctrlLocals.$scope.$resolve\n                    }, providedScope);\n                  }\n\n                  ctrlInstance = ctrlInstantiate();\n\n                  if (angular.isFunction(ctrlInstance.$onInit)) {\n                    ctrlInstance.$onInit();\n                  }\n                }\n\n                if (!modalOptions.component) {\n                  modal.content = tplAndVars[0];\n                }\n\n                $modalStack.open(modalInstance, modal);\n                modalOpenedDeferred.resolve(true);\n\n                function constructLocals(obj, template, instanceOnScope, injectable) {\n                  obj.$scope = modalScope;\n                  obj.$scope.$resolve = {};\n                  if (instanceOnScope) {\n                    obj.$scope.$uibModalInstance = modalInstance;\n                  } else {\n                    obj.$uibModalInstance = modalInstance;\n                  }\n\n                  var resolves = template ? tplAndVars[1] : tplAndVars;\n                  angular.forEach(resolves, function(value, key) {\n                    if (injectable) {\n                      obj[key] = value;\n                    }\n\n                    obj.$scope.$resolve[key] = value;\n                  });\n                }\n            }, function resolveError(reason) {\n              modalOpenedDeferred.reject(reason);\n              modalResultDeferred.reject(reason);\n            })['finally'](function() {\n              if (promiseChain === samePromise) {\n                promiseChain = null;\n              }\n            });\n\n            return modalInstance;\n          };\n\n          return $modal;\n        }\n      ]\n    };\n\n    return $modalProvider;\n  });\n"
  },
  {
    "path": "src/modal/test/modal.spec.js",
    "content": "describe('$uibResolve', function() {\n  beforeEach(module('ui.bootstrap.modal'));\n\n  it('should resolve invocables and return promise with object of resolutions', function() {\n    module(function($provide) {\n      $provide.factory('bar', function() {\n        return 'bar';\n      });\n    });\n\n    inject(function($q, $rootScope, $uibResolve) {\n      $uibResolve.resolve({\n        foo: 'bar',\n        bar: $q.resolve('baz'),\n        baz: function() {\n          return 'boo';\n        }\n      }).then(function(resolves) {\n          expect(resolves).toEqual({\n            foo: 'bar',\n            bar: 'baz',\n            baz: 'boo'\n          });\n        });\n\n      $rootScope.$digest();\n    });\n  });\n\n  describe('with custom resolver', function() {\n    beforeEach(module(function($provide, $uibResolveProvider) {\n      $provide.factory('$resolve', function() {\n        return {\n          resolve: jasmine.createSpy()\n        };\n      });\n\n      $uibResolveProvider.setResolver('$resolve');\n    }));\n\n    it('should call $resolve.resolve', inject(function($uibResolve, $resolve) {\n      $uibResolve.resolve({foo: 'bar'}, {}, null, null);\n\n      expect($resolve.resolve).toHaveBeenCalledWith({foo: 'bar'}, {}, null, null);\n    }));\n  });\n});\n\ndescribe('uibModalTransclude', function() {\n  var uibModalTranscludeDDO,\n    $animate;\n\n  beforeEach(module('ui.bootstrap.modal'));\n  beforeEach(module(function($provide) {\n    $animate = jasmine.createSpyObj('$animate', ['enter']);\n    $provide.value('$animate', $animate);\n  }));\n\n  beforeEach(inject(function(uibModalTranscludeDirective) {\n    uibModalTranscludeDDO = uibModalTranscludeDirective[0];\n  }));\n\n  describe('when initialised', function() {\n    var scope,\n      element,\n      transcludeSpy,\n      transcludeFn;\n\n    beforeEach(function() {\n      scope = {\n        $parent: 'parentScope'\n      };\n\n      element = jasmine.createSpyObj('containerElement', ['empty']);\n      transcludeSpy = jasmine.createSpy('transcludeSpy').and.callFake(function(scope, fn) {\n        transcludeFn = fn;\n      });\n\n      uibModalTranscludeDDO.link(scope, element, {}, {}, transcludeSpy);\n    });\n\n    it('should call the transclusion function', function() {\n      expect(transcludeSpy).toHaveBeenCalledWith(scope.$parent, jasmine.any(Function));\n    });\n\n    describe('transclusion callback', function() {\n      var transcludedContent;\n\n      beforeEach(function() {\n        transcludedContent = 'my transcluded content';\n        transcludeFn(transcludedContent);\n      });\n\n      it('should empty the element', function() {\n        expect(element.empty).toHaveBeenCalledWith();\n      });\n\n      it('should append the transcluded content', function() {\n        expect($animate.enter).toHaveBeenCalledWith(transcludedContent, element);\n      });\n    });\n  });\n});\n\ndescribe('$uibModal', function() {\n  var $animate, $controllerProvider, $rootScope, $document, $compile, $templateCache, $timeout, $q;\n  var $uibModal, $uibModalStack, $uibModalProvider;\n\n  beforeEach(module('ngAnimateMock'));\n  beforeEach(module('ui.bootstrap.modal'));\n  beforeEach(module('uib/template/modal/window.html'));\n  beforeEach(module(function(_$controllerProvider_, _$uibModalProvider_, $compileProvider) {\n    $controllerProvider = _$controllerProvider_;\n    $uibModalProvider = _$uibModalProvider_;\n    $compileProvider.directive('parentDirective', function() {\n      return {\n        controller: function() {\n          this.text = 'foo';\n        }\n      };\n    }).directive('childDirective', function() {\n      return {\n        require: '^parentDirective',\n        link: function(scope, elem, attrs, ctrl) {\n          scope.text = ctrl.text;\n        }\n      };\n    }).directive('focusMe', function() {\n      return {\n        link: function(scope, elem, attrs) {\n          elem.focus();\n        }\n      };\n    }).component('fooBar', {\n      bindings: {\n        resolve: '<',\n        modalInstance: '<',\n        close: '&',\n        dismiss: '&'\n      },\n      controller: angular.noop,\n      controllerAs: 'foobar',\n      template: '<div>Foo Bar</div>'\n    });\n  }));\n\n  beforeEach(inject(function(_$animate_, _$rootScope_, _$document_, _$compile_, _$templateCache_, _$timeout_, _$q_, _$uibModal_, _$uibModalStack_) {\n    $animate = _$animate_;\n    $rootScope = _$rootScope_;\n    $document = _$document_;\n    $compile = _$compile_;\n    $templateCache = _$templateCache_;\n    $timeout = _$timeout_;\n    $q = _$q_;\n    $uibModal = _$uibModal_;\n    $uibModalStack = _$uibModalStack_;\n  }));\n\n  beforeEach(function() {\n    jasmine.addMatchers({\n      toBeResolvedWith: function(util, customEqualityTesters) {\n        return {\n          compare: function(promise, expected) {\n            var called = false;\n            promise.then(function(result) {\n              expect(result).toEqual(expected);\n\n              if (result === expected) {\n                result.message = 'Expected \"' + angular.mock.dump(result) + '\" not to be resolved with \"' + expected + '\".';\n              } else {\n                result.message = 'Expected \"' + angular.mock.dump(result) + '\" to be resolved with \"' + expected + '\".';\n              }\n            }, function(result) {\n              fail('Expected \"' + angular.mock.dump(result) + '\" to be resolved with \"' + expected + '\".');\n            })['finally'](function() {\n              called = true;\n            });\n\n            $rootScope.$digest();\n\n            if (!called) {\n              fail('Expected \"' + angular.mock.dump(result) + '\" to be resolved with \"' + expected + '\".');\n            }\n\n            return {pass: true};\n          }\n        };\n      },\n      toBeRejectedWith: function(util, customEqualityTesters) {\n        return {\n          compare: function(promise, expected) {\n            var result = {};\n            var called = false;\n\n            promise.then(function(result) {\n              fail('Expected \"' + angular.mock.dump(result) + '\" to be rejected with \"' + expected + '\".');\n            }, function(result) {\n              expect(result).toEqual(expected);\n\n              if (result === expected) {\n                result.message = 'Expected \"' + angular.mock.dump(result) + '\" not to be rejected with \"' + expected + '\".';\n              } else {\n                result.message = 'Expected \"' + angular.mock.dump(result) + '\" to be rejected with \"' + expected + '\".';\n              }\n            })['finally'](function() {\n              called = true;\n            });\n\n            $rootScope.$digest();\n\n            if (!called) {\n              fail('Expected \"' + angular.mock.dump(result) + '\" to be rejected with \"' + expected + '\".');\n            }\n\n            return {pass: true};\n          }\n        };\n      },\n      toHaveModalOpenWithContent: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, content, selector) {\n            var contentToCompare, modalDomEls = actual.find('body > div.modal > div.modal-dialog > div.modal-content');\n\n            contentToCompare = selector ? modalDomEls.find(selector) : modalDomEls;\n\n            var result = {\n              pass: modalDomEls.css('display') === 'block' && contentToCompare.html() === content\n            };\n\n            if (result.pass) {\n              result.message = '\"Expected \"' + angular.mock.dump(modalDomEls) + '\" not to be open with \"' + content + '\".';\n            } else {\n              result.message = '\"Expected \"' + angular.mock.dump(modalDomEls) + '\" to be open with \"' + content + '\".';\n            }\n\n            return result;\n          }\n        };\n      },\n      toHaveModalsOpen: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, expected) {\n            var modalDomEls = actual.find('body > div.modal');\n\n            var result = {\n              pass: util.equals(modalDomEls.length, expected, customEqualityTesters)\n            };\n\n            if (result.pass) {\n              result.message = 'Expected \"' + angular.mock.dump(modalDomEls) + '\" not to have \"' + expected + '\" modals opened.';\n            } else {\n              result.message = 'Expected \"' + angular.mock.dump(modalDomEls) + '\" to have \"' + expected + '\" modals opened.';\n            }\n\n            return result;\n          }\n        };\n      },\n      toHaveBackdrop: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, expected) {\n            var backdropDomEls = actual.find('body > div.modal-backdrop');\n\n            var result = {\n              pass: util.equals(backdropDomEls.length, 1, customEqualityTesters)\n            };\n\n            if (result.pass) {\n              result.message = 'Expected \"' + angular.mock.dump(backdropDomEls) + '\" not to be a backdrop element\".';\n            } else {\n              result.message = 'Expected \"' + angular.mock.dump(backdropDomEls) + '\" to be a backdrop element\".';\n            }\n\n            return result;\n          }\n        };\n      }\n    });\n  });\n\n  afterEach(function () {\n    var body = $document.find('body');\n    body.find('div.modal').remove();\n    body.find('div.modal-backdrop').remove();\n    body.removeClass('modal-open');\n    $document.off('keydown');\n  });\n\n  function triggerKeyDown(element, keyCode, shiftKey) {\n    var e = $.Event('keydown');\n    e.srcElement = element[0];\n    e.which = keyCode;\n    e.shiftKey = shiftKey;\n    element.trigger(e);\n  }\n\n  function open(modalOptions, noFlush, noDigest) {\n    var modal = $uibModal.open(modalOptions);\n    modal.opened['catch'](angular.noop);\n    modal.result['catch'](angular.noop);\n\n    if (!noDigest) {\n      $rootScope.$digest();\n      if (!noFlush) {\n        $animate.flush();\n      }\n    }\n\n    return modal;\n  }\n\n  function close(modal, result, noFlush) {\n    var closed = modal.close(result);\n    $rootScope.$digest();\n    if (!noFlush) {\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n    }\n    return closed;\n  }\n\n  function dismiss(modal, reason, noFlush) {\n    var closed = modal.dismiss(reason);\n    $rootScope.$digest();\n    if (!noFlush) {\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n    }\n    return closed;\n  }\n\n  describe('basic scenarios with default options', function() {\n    it('should open and dismiss a modal with a minimal set of options', function() {\n      var modal = open({template: '<div>Content</div>'});\n\n      expect($document).toHaveModalsOpen(1);\n      expect($document).toHaveModalOpenWithContent('Content', 'div');\n      expect($document).toHaveBackdrop();\n\n      dismiss(modal, 'closing in test');\n\n      expect($document).toHaveModalsOpen(0);\n\n      expect($document).not.toHaveBackdrop();\n    });\n\n    it('should compile modal before inserting into DOM', function() {\n      var topModal;\n      var modalInstance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function (result) {\n          return $uibModalStack.close(modalInstance, result);\n        },\n        dismiss: function (reason) {\n          return $uibModalStack.dismiss(modalInstance, reason);\n        }\n      };\n      var expectedText = 'test';\n\n      $uibModalStack.open(modalInstance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modalInstance.result,\n        renderDeferred: modalInstance.rendered,\n        closedDeferred: modalInstance.closed,\n        content: '<div id=\"test\">{{\\'' + expectedText + '\\'}}</div>'\n      });\n\n      topModal = $uibModalStack.getTop();\n\n      expect(topModal.value.modalDomEl.find('#test').length).toEqual(0);\n      expect(angular.element('#test').length).toEqual(0);\n\n      $rootScope.$digest();\n\n      expect(topModal.value.modalDomEl.find('#test').text()).toEqual(expectedText);\n      expect(angular.element('#test').text()).toEqual(expectedText);\n\n      $animate.flush();\n\n      close(modalInstance, 'closing in test', true);\n    });\n\n    it('should resolve rendered promise when animation is complete', function() {\n      var modalInstance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function (result) {\n          return $uibModalStack.close(modalInstance, result);\n        },\n        dismiss: function (reason) {\n          return $uibModalStack.dismiss(modalInstance, reason);\n        }\n      };\n      var rendered = false;\n      modalInstance.rendered.promise.then(function() {\n        rendered = true;\n      });\n\n      $uibModalStack.open(modalInstance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modalInstance.result,\n        renderDeferred: modalInstance.rendered,\n        closedDeferred: modalInstance.closed,\n        content: '<div id=\"test\">test</div>'\n      });\n\n      $rootScope.$digest();\n\n      expect(rendered).toBe(false);\n\n      $animate.flush();\n\n      expect(rendered).toBe(true);\n    });\n\n    it('should not throw an exception on a second dismiss', function() {\n      var modal = open({template: '<div>Content</div>'});\n\n      expect($document).toHaveModalsOpen(1);\n      expect($document).toHaveModalOpenWithContent('Content', 'div');\n      expect($document).toHaveBackdrop();\n\n      dismiss(modal, 'closing in test');\n\n      expect($document).toHaveModalsOpen(0);\n\n      dismiss(modal, 'closing in test', true);\n    });\n\n    it('should not throw an exception on a second close', function() {\n      var modal = open({template: '<div>Content</div>'});\n\n      expect($document).toHaveModalsOpen(1);\n      expect($document).toHaveModalOpenWithContent('Content', 'div');\n      expect($document).toHaveBackdrop();\n\n      close(modal, 'closing in test');\n\n      expect($document).toHaveModalsOpen(0);\n\n      close(modal, 'closing in test', true);\n    });\n\n    it('should open a modal from templateUrl', function() {\n      $templateCache.put('content.html', '<div>URL Content</div>');\n      var modal = open({templateUrl: 'content.html'});\n\n      expect($document).toHaveModalsOpen(1);\n      expect($document).toHaveModalOpenWithContent('URL Content', 'div');\n      expect($document).toHaveBackdrop();\n\n      dismiss(modal, 'closing in test');\n\n      expect($document).toHaveModalsOpen(0);\n\n      expect($document).not.toHaveBackdrop();\n    });\n\n    it('should support closing on ESC', function() {\n      var modal = open({template: '<div>Content</div>'});\n      expect($document).toHaveModalsOpen(1);\n\n      triggerKeyDown($document, 27);\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n\n      expect($document).toHaveModalsOpen(0);\n    });\n\n    it('should not close on ESC if event.preventDefault() was issued', function() {\n      var modal = open({template: '<div><button>x</button></div>' });\n      expect($document).toHaveModalsOpen(1);\n\n      var button = angular.element('button').on('keydown', preventKeyDown);\n\n      triggerKeyDown(button, 27);\n      $rootScope.$digest();\n\n      expect($document).toHaveModalsOpen(1);\n\n      button.off('keydown', preventKeyDown);\n\n      triggerKeyDown(button, 27);\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n\n      expect($document).toHaveModalsOpen(0);\n\n      function preventKeyDown(evt) {\n        evt.preventDefault();\n      }\n    });\n\n    it('should support closing on backdrop click', function() {\n      var modal = open({template: '<div>Content</div>'});\n      expect($document).toHaveModalsOpen(1);\n\n      $document.find('body > div.modal').click();\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n\n      expect($document).toHaveModalsOpen(0);\n    });\n\n    it('should return to the element which had focus before the dialog was invoked', function() {\n      var link = '<a href>Link</a>';\n      var element = angular.element(link);\n      angular.element(document.body).append(element);\n      element.focus();\n      expect(document.activeElement.tagName).toBe('A');\n\n      var modal = open({template: '<div>Content<button>inside modal</button></div>'});\n      $rootScope.$digest();\n      expect(document.activeElement.className.split(' ')).toContain('modal');\n      expect($document).toHaveModalsOpen(1);\n\n      triggerKeyDown($document, 27);\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n\n      expect(document.activeElement.tagName).toBe('A');\n      expect($document).toHaveModalsOpen(0);\n\n      element.remove();\n    });\n\n    it('should return to document.body if element which had focus before the dialog was invoked is gone, or is missing focus function', function() {\n      var link = '<a href>Link</a>';\n      var element = angular.element(link);\n      angular.element(document.body).append(element);\n      element.focus();\n      expect(document.activeElement.tagName).toBe('A');\n\n      var modal = open({template: '<div>Content</div>'});\n      $rootScope.$digest();\n      expect(document.activeElement.tagName).toBe('DIV');\n      expect($document).toHaveModalsOpen(1);\n\n      // Fake undefined focus function, happening in IE in certain\n      // iframe conditions. See issue 3639\n      element[0].focus = undefined;\n      triggerKeyDown($document, 27);\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n\n      expect(document.activeElement.tagName).toBe('BODY');\n      expect($document).toHaveModalsOpen(0);\n      element.remove();\n    });\n\n    it('should resolve returned promise on close', function() {\n      var modal = open({template: '<div>Content</div>'});\n      close(modal, 'closed ok');\n\n      expect(modal.result).toBeResolvedWith('closed ok');\n    });\n\n    it('should reject returned promise on dismiss', function() {\n      var modal = open({template: '<div>Content</div>'});\n      dismiss(modal, 'esc');\n\n      expect(modal.result).toBeRejectedWith('esc');\n    });\n\n    it('should reject returned promise on unexpected closure', function() {\n      var scope = $rootScope.$new();\n      var modal = open({template: '<div>Content</div>', scope: scope});\n      scope.$destroy();\n\n      expect(modal.result).toBeRejectedWith('$uibUnscheduledDestruction');\n\n      $animate.flush();\n      $rootScope.$digest();\n      $animate.flush();\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(0);\n    });\n\n    it('should resolve the closed promise when modal is closed', function() {\n      var modal = open({template: '<div>Content</div>'});\n      var closed = false;\n      close(modal, 'closed ok');\n\n      modal.closed.then(function() {\n        closed = true;\n      });\n\n      $rootScope.$digest();\n\n      expect(closed).toBe(true);\n    });\n\n    it('should resolve the closed promise when modal is dismissed', function() {\n      var modal = open({template: '<div>Content</div>'});\n      var closed = false;\n      dismiss(modal, 'esc');\n\n      modal.closed.then(function() {\n        closed = true;\n      });\n\n      $rootScope.$digest();\n\n      expect(closed).toBe(true);\n    });\n\n    it('should expose a promise linked to the templateUrl / resolve promises', function() {\n      var modal = open({template: '<div>Content</div>', resolve: {\n          ok: function() {return $q.when('ok');}\n        }}\n      );\n      expect(modal.opened).toBeResolvedWith(true);\n    });\n\n    it('should expose a promise linked to the templateUrl / resolve promises and reject it if needed', function() {\n      var modal = open({template: '<div>Content</div>', resolve: {\n        ok: function() {return $q.reject('ko');}\n      }}, true);\n      expect(modal.opened).toBeRejectedWith('ko');\n    });\n\n    it('should focus on the element that has autofocus attribute when the modal is open/reopen and the animations have finished', function() {\n      function openAndCloseModalWithAutofocusElement() {\n        var modal = open({template: '<div><input type=\"text\" id=\"auto-focus-element\" autofocus></div>'});\n        $rootScope.$digest();\n        expect(angular.element('#auto-focus-element')).toHaveFocus();\n\n        close(modal, 'closed ok');\n\n        expect(modal.result).toBeResolvedWith('closed ok');\n      }\n\n      openAndCloseModalWithAutofocusElement();\n      openAndCloseModalWithAutofocusElement();\n    });\n\n    it('should not focus on the element that has autofocus attribute when the modal is opened and something in the modal already has focus and the animations have finished', function() {\n      function openAndCloseModalWithAutofocusElement() {\n\n        var modal = open({template: '<div><input type=\"text\" id=\"pre-focus-element\" focus-me><input type=\"text\" id=\"auto-focus-element\" autofocus></div>'});\n        $rootScope.$digest();\n        expect(angular.element('#auto-focus-element')).not.toHaveFocus();\n        expect(angular.element('#pre-focus-element')).toHaveFocus();\n\n        close(modal, 'closed ok');\n\n        expect(modal.result).toBeResolvedWith('closed ok');\n      }\n\n      openAndCloseModalWithAutofocusElement();\n      openAndCloseModalWithAutofocusElement();\n    });\n\n    it('should wait until the in animation is finished before attempting to focus the modal or autofocus element', function() {\n      function openAndCloseModalWithAutofocusElement() {\n        var modal = open({template: '<div><input type=\"text\" id=\"auto-focus-element\" autofocus></div>'}, true, true);\n        expect(angular.element('#auto-focus-element')).not.toHaveFocus();\n\n        $rootScope.$digest();\n        $animate.flush();\n\n        expect(angular.element('#auto-focus-element')).toHaveFocus();\n\n        close(modal, 'closed ok');\n\n        expect(modal.result).toBeResolvedWith('closed ok');\n      }\n\n      function openAndCloseModalWithOutAutofocusElement() {\n        var link = '<a href>Link</a>';\n        var element = angular.element(link);\n        angular.element(document.body).append(element);\n        element.focus();\n        expect(document.activeElement.tagName).toBe('A');\n\n        var modal = open({template: '<div><input type=\"text\"></div>'}, true, true);\n        expect(document.activeElement.tagName).toBe('A');\n\n        $rootScope.$digest();\n        $animate.flush();\n\n        expect(document.activeElement.className.split(' ')).toContain('modal');\n\n        close(modal, 'closed ok');\n\n        expect(modal.result).toBeResolvedWith('closed ok');\n\n        element.remove();\n      }\n\n      openAndCloseModalWithAutofocusElement();\n      openAndCloseModalWithOutAutofocusElement();\n    });\n\n    it('should change focus to first element when tab key was pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link\"><input type=\"text\" id=\"tab-focus-input1\"/><input type=\"text\" id=\"tab-focus-input2\"/>' +\n        '<button id=\"tab-focus-button\">Open me!</button>'\n      });\n      expect($document).toHaveModalsOpen(1);\n\n      var lastElement = angular.element(document.getElementById('tab-focus-button'));\n      lastElement.focus();\n      triggerKeyDown(lastElement, 9);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to last element when shift+tab key is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link\"><input type=\"text\" id=\"tab-focus-input1\"/><input type=\"text\" id=\"tab-focus-input2\"/>' +\n        '<button id=\"tab-focus-button\">Open me!</button>'\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');\n\n      var lastElement = angular.element(document.getElementById('tab-focus-link'));\n      lastElement.focus();\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to first element when tab key is pressed when keyboard is false', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link\"><input type=\"text\" id=\"tab-focus-input1\"/><input type=\"text\" id=\"tab-focus-input2\"/>' +\n        '<button id=\"tab-focus-button\">Open me!</button>',\n        keyboard: false\n      });\n      expect($document).toHaveModalsOpen(1);\n\n      var lastElement = angular.element(document.getElementById('tab-focus-button'));\n      lastElement.focus();\n      triggerKeyDown(lastElement, 9);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to last element when shift+tab keys are pressed when keyboard is false', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link\"><input type=\"text\" id=\"tab-focus-input1\"/><input type=\"text\" id=\"tab-focus-input2\"/>' +\n        '<button id=\"tab-focus-button\">Open me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');\n\n      var lastElement = angular.element(document.getElementById('tab-focus-link'));\n      lastElement.focus();\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to next proper element when DOM changes and tab is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link1\">a</a><a href=\"#\" id=\"tab-focus-link2\">b</a><a href=\"#\" id=\"tab-focus-link3\">c</a>' +\n        '<button id=\"tab-focus-button\">Open me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      $('#tab-focus-link3').focus();\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');\n\n      $('#tab-focus-button').remove();\n      triggerKeyDown(angular.element(document.activeElement), 9, false);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link1\">a</a><a href=\"#\" id=\"tab-focus-link2\">b</a><a href=\"#\" id=\"tab-focus-link3\">c</a>' +\n        '<button id=\"tab-focus-button\">Open me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      $('#tab-focus-link1').focus();\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');\n\n      $('#tab-focus-button').remove();\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to next non-hidden element when tab is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link1\">a</a><a href=\"#\" id=\"tab-focus-link2\">b</a><a href=\"#\" id=\"tab-focus-link3\">c</a>' +\n        '<button id=\"tab-focus-button\">Open me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      $('#tab-focus-link3').focus();\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');\n\n      $('#tab-focus-button').css('display', 'none');\n      triggerKeyDown(angular.element(document.activeElement), 9, false);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to previous non-hidden element when shift+tab is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<a href=\"#\" id=\"tab-focus-link1\">a</a><a href=\"#\" id=\"tab-focus-link2\">b</a><a href=\"#\" id=\"tab-focus-link3\">c</a>' +\n        '<button id=\"tab-focus-button\">Open me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      $('#tab-focus-link1').focus();\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');\n\n      $('#tab-focus-button').css('display', 'none');\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to next tabbable element when tab is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<button id=\"tab-focus-button1\" tabindex=\"-1\">Skip me!</button><a href=\"#\" id=\"tab-focus-link1\">a</a>' +\n        '<a href=\"#\" id=\"tab-focus-link2\">b</a><a href=\"#\" id=\"tab-focus-link3\">c</a>' +\n        '<button id=\"tab-focus-button2\" tabindex=\"-1\">Skip me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      $('#tab-focus-link3').focus();\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');\n\n      triggerKeyDown(angular.element(document.activeElement), 9, false);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');\n\n      initialPage.remove();\n    });\n\n    it('should change focus to previous tabbable element when shift+tab is pressed', function() {\n      var initialPage = angular.element('<a href=\"#\" id=\"cannot-get-focus-from-modal\">Outland link</a>');\n      angular.element(document.body).append(initialPage);\n      initialPage.focus();\n\n      open({\n        template:'<button id=\"tab-focus-button1\" tabindex=\"-1\">Skip me!</button><a href=\"#\" id=\"tab-focus-link1\">a</a>' +\n        '<a href=\"#\" id=\"tab-focus-link2\">b</a><a href=\"#\" id=\"tab-focus-link3\">c</a>' +\n        '<button id=\"tab-focus-button2\" tabindex=\"-1\">Skip me!</button>',\n        keyboard: false\n      });\n      $rootScope.$digest();\n      expect($document).toHaveModalsOpen(1);\n\n      $('#tab-focus-link1').focus();\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');\n\n      triggerKeyDown(angular.element(document.activeElement), 9, true);\n      expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');\n\n      initialPage.remove();\n    });\n  });\n\n  describe('default options can be changed in a provider', function() {\n    it('should allow overriding default options in a provider', function() {\n      $uibModalProvider.options.backdrop = false;\n      var modal = open({template: '<div>Content</div>'});\n\n      expect($document).toHaveModalOpenWithContent('Content', 'div');\n      expect($document).not.toHaveBackdrop();\n    });\n\n    it('should accept new objects with default options in a provider', function() {\n      $uibModalProvider.options = {\n        backdrop: false\n      };\n      var modal = open({template: '<div>Content</div>'});\n\n      expect($document).toHaveModalOpenWithContent('Content', 'div');\n      expect($document).not.toHaveBackdrop();\n    });\n  });\n\n  describe('option by option', function() {\n    describe('component', function() {\n      function getModalComponent($document) {\n        return $document.find('body > div.modal > div.modal-dialog > div.modal-content foo-bar');\n      }\n\n      it('should use as modal content', function() {\n        open({\n          component: 'fooBar'\n        });\n\n        var component = getModalComponent($document);\n        expect(component.html()).toBe('<div>Foo Bar</div>');\n      });\n\n      it('should bind expected values', function() {\n        var modal = open({\n          component: 'fooBar',\n          resolve: {\n            foo: function() {\n              return 'bar';\n            }\n          }\n        });\n\n        var component = getModalComponent($document);\n        var componentScope = component.isolateScope();\n\n        expect(componentScope.foobar.resolve.foo).toBe('bar');\n        expect(componentScope.foobar.modalInstance).toBe(modal);\n        expect(componentScope.foobar.close).toEqual(jasmine.any(Function));\n        expect(componentScope.foobar.dismiss).toEqual(jasmine.any(Function));\n      });\n\n      it('should close the modal', function() {\n        var modal = open({\n          component: 'fooBar',\n          resolve: {\n            foo: function() {\n              return 'bar';\n            }\n          }\n        });\n\n        var component = getModalComponent($document);\n        var componentScope = component.isolateScope();\n\n        componentScope.foobar.close({\n          $value: 'baz'\n        });\n\n        expect(modal.result).toBeResolvedWith('baz');\n      });\n\n      it('should dismiss the modal', function() {\n        var modal = open({\n          component: 'fooBar',\n          resolve: {\n            foo: function() {\n              return 'bar';\n            }\n          }\n        });\n\n        var component = getModalComponent($document);\n        var componentScope = component.isolateScope();\n\n        componentScope.foobar.dismiss({\n          $value: 'baz'\n        });\n\n        expect(modal.result).toBeRejectedWith('baz');\n      });\n    });\n\n    describe('template and templateUrl', function() {\n      it('should throw an error if none of component, template and templateUrl are provided', function() {\n        expect(function(){\n          var modal = open({});\n        }).toThrow(new Error('One of component or template or templateUrl options is required.'));\n      });\n\n      it('should not fail if a templateUrl contains leading / trailing white spaces', function() {\n        $templateCache.put('whitespace.html', '  <div>Whitespaces</div>  ');\n        open({templateUrl: 'whitespace.html'});\n        expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');\n      });\n\n      it('should accept template as a function', function() {\n        open({template: function() {\n          return '<div>From a function</div>';\n        }});\n\n        expect($document).toHaveModalOpenWithContent('From a function', 'div');\n      });\n\n      it('should not fail if a templateUrl as a function', function() {\n\n        $templateCache.put('whitespace.html', '  <div>Whitespaces</div>  ');\n        open({templateUrl: function() {\n          return 'whitespace.html';\n        }});\n        expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');\n      });\n    });\n\n    describe('controller', function() {\n      it('should accept controllers and inject modal instances', function() {\n        var TestCtrl = function($scope, $uibModalInstance) {\n          $scope.fromCtrl = 'Content from ctrl';\n          $scope.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);\n        };\n\n        open({template: '<div>{{fromCtrl}} {{isModalInstance}}</div>', controller: TestCtrl});\n        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');\n      });\n\n      it('should accept controllerAs alias', function() {\n        $controllerProvider.register('TestCtrl', function($uibModalInstance) {\n          this.fromCtrl = 'Content from ctrl';\n          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);\n        });\n\n        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: 'TestCtrl as test'});\n        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');\n      });\n\n      it('should respect the controllerAs property as an alternative for the controller-as syntax', function() {\n        $controllerProvider.register('TestCtrl', function($uibModalInstance) {\n          this.fromCtrl = 'Content from ctrl';\n          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);\n        });\n\n        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: 'TestCtrl', controllerAs: 'test'});\n        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');\n      });\n\n      it('should allow defining in-place controller-as controllers', function() {\n        open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: function($uibModalInstance) {\n          this.fromCtrl = 'Content from ctrl';\n          this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);\n        }, controllerAs: 'test'});\n        expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');\n      });\n\n      it('should allow usage of bindToController', function() {\n        var $scope = $rootScope.$new(true);\n        $scope.foo = 'bar';\n        open({\n          template: '<div>{{test.fromCtrl}} {{test.closeDismissPresent()}} {{test.foo}}</div>',\n          controller: function($uibModalInstance) {\n            expect(this.foo).toEqual($scope.foo);\n            this.fromCtrl = 'Content from ctrl';\n            this.closeDismissPresent = function() {\n              return angular.isFunction(this.$close) && angular.isFunction(this.$dismiss);\n            };\n          },\n          controllerAs: 'test',\n          bindToController: true,\n          scope: $scope\n        });\n        expect($document).toHaveModalOpenWithContent('Content from ctrl true bar', 'div');\n      });\n\n      it('should have $onInit called', function() {\n        var $scope = $rootScope.$new(true);\n        var $onInit = jasmine.createSpy('$onInit');\n        $scope.foo = 'bar';\n        open({\n          template: '<div>{{test.fromCtrl}} {{test.closeDismissPresent()}} {{test.foo}}</div>',\n          controller: function($uibModalInstance) {\n            this.$onInit = $onInit;\n            this.fromCtrl = 'Content from ctrl';\n            this.closeDismissPresent = function() {\n              return angular.isFunction(this.$close) && angular.isFunction(this.$dismiss);\n            };\n          },\n          controllerAs: 'test',\n          bindToController: true,\n          scope: $scope\n        });\n        expect($document).toHaveModalOpenWithContent('Content from ctrl true bar', 'div');\n        expect($onInit).toHaveBeenCalled();\n      });\n    });\n\n    describe('resolve', function() {\n      var ExposeCtrl = function($scope, value) {\n        $scope.value = value;\n      };\n\n      function modalDefinition(template, resolve) {\n        return {\n          template: template,\n          controller: ExposeCtrl,\n          resolve: resolve\n        };\n      }\n\n      it('should resolve simple values', function() {\n        open(modalDefinition('<div>{{value}}</div>', {\n          value: function() {\n            return 'Content from resolve';\n          }\n        }));\n\n        expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');\n      });\n\n      it('should resolve string references to injectables', function() {\n        open({\n          controller: function($scope, $foo) {\n            $scope.value = 'Content from resolve';\n            expect($foo).toBe($uibModal);\n          },\n          resolve: {\n            $foo: '$uibModal'\n          },\n          template: '<div>{{value}}</div>'\n        });\n\n        expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');\n      });\n\n      it('should resolve promises as promises', function() {\n        open({\n          controller: function($scope, $foo) {\n            $scope.value = 'Content from resolve';\n            expect($foo).toBe('bar');\n          },\n          resolve: {\n            $foo: $q.when('bar')\n          },\n          template: '<div>{{value}}</div>'\n        });\n      });\n\n      it('should delay showing modal if one of the resolves is a promise', function() {\n        open(modalDefinition('<div>{{value}}</div>', {\n          value: function() {\n            return $timeout(function() { return 'Promise'; }, 100);\n          }\n        }), true);\n        expect($document).toHaveModalsOpen(0);\n\n        $timeout.flush();\n        expect($document).toHaveModalOpenWithContent('Promise', 'div');\n      });\n\n      it('should not open dialog (and reject returned promise) if one of resolve fails', function() {\n        var deferred = $q.defer();\n\n        var modal = open(modalDefinition('<div>{{value}}</div>', {\n          value: function() {\n            return deferred.promise;\n          }\n        }), true);\n        expect($document).toHaveModalsOpen(0);\n\n        deferred.reject('error in test');\n        $rootScope.$digest();\n\n        expect($document).toHaveModalsOpen(0);\n        expect(modal.result).toBeRejectedWith('error in test');\n      });\n\n      it('should support injection with minification-safe syntax in resolve functions', function() {\n        open(modalDefinition('<div>{{value.id}}</div>', {\n          value: ['$locale', function(e) {\n            return e;\n          }]\n        }));\n\n        expect($document).toHaveModalOpenWithContent('en-us', 'div');\n      });\n    });\n\n    describe('scope', function() {\n      it('should use custom scope if provided', function() {\n        var $scope = $rootScope.$new();\n        $scope.fromScope = 'Content from custom scope';\n        open({\n          template: '<div>{{fromScope}}</div>',\n          scope: $scope\n        });\n        expect($document).toHaveModalOpenWithContent('Content from custom scope', 'div');\n      });\n\n      it('should create and use child of $rootScope if custom scope not provided', function() {\n        var scopeTailBefore = $rootScope.$$childTail;\n\n        $rootScope.fromScope = 'Content from root scope';\n        open({\n          template: '<div>{{fromScope}}</div>'\n        });\n        expect($document).toHaveModalOpenWithContent('Content from root scope', 'div');\n      });\n\n      it('should expose $resolve in template', function() {\n        open({\n          controller: function($scope) {},\n          resolve: {\n            $foo: function() {\n              return 'Content from resolve';\n            }\n          },\n          template: '<div>{{$resolve.$foo}}</div>'\n        });\n\n        expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');\n      });\n    });\n\n    describe('keyboard', function () {\n      it('should not close modals if keyboard option is set to false', function() {\n        open({\n          template: '<div>No keyboard</div>',\n          keyboard: false\n        });\n\n        expect($document).toHaveModalsOpen(1);\n\n        triggerKeyDown($document, 27);\n        $rootScope.$digest();\n\n        expect($document).toHaveModalsOpen(1);\n      });\n    });\n\n    describe('backdrop', function() {\n      it('should not have any backdrop element if backdrop set to false', function() {\n        var modal = open({\n          template: '<div>No backdrop</div>',\n          backdrop: false\n        });\n        expect($document).toHaveModalOpenWithContent('No backdrop', 'div');\n        expect($document).not.toHaveBackdrop();\n\n        dismiss(modal);\n        expect($document).toHaveModalsOpen(0);\n      });\n\n      it('should not close modal on backdrop click if backdrop is specified as \"static\"', function() {\n        open({\n          template: '<div>Static backdrop</div>',\n          backdrop: 'static'\n        });\n\n        $document.find('body > div.modal-backdrop').click();\n        $rootScope.$digest();\n\n        expect($document).toHaveModalOpenWithContent('Static backdrop', 'div');\n        expect($document).toHaveBackdrop();\n      });\n\n      it('should contain backdrop in classes on each modal opening', function() {\n        var modal = open({ template: '<div>With backdrop</div>' });\n        var backdropEl = $document.find('body > div.modal-backdrop');\n        expect(backdropEl).toHaveClass('in');\n\n        dismiss(modal);\n\n        modal = open({ template: '<div>With backdrop</div>' });\n        backdropEl = $document.find('body > div.modal-backdrop');\n        expect(backdropEl).toHaveClass('in');\n\n      });\n\n      describe('custom backdrop classes', function () {\n        it('should support additional backdrop class as string', function() {\n          open({\n            template: '<div>With custom backdrop class</div>',\n            backdropClass: 'additional'\n          });\n\n          expect($document.find('div.modal-backdrop')).toHaveClass('additional');\n        });\n      });\n    });\n\n    describe('custom window classes', function() {\n      it('should support additional window class as string', function() {\n        open({\n          template: '<div>With custom window class</div>',\n          windowClass: 'additional'\n        });\n\n        expect($document.find('div.modal')).toHaveClass('additional');\n      });\n    });\n\n    describe('top window class', function () {\n      it('should support top class option', function () {\n        open({\n          template: '<div>With custom window top class</div>',\n          windowTopClass: 'top-class'\n        });\n\n        expect($document.find('div.modal')).toHaveClass('top-class');\n      });\n    });\n\n    describe('size', function() {\n      it('should support creating small modal dialogs', function() {\n        open({\n          template: '<div>Small modal dialog</div>',\n          size: 'sm'\n        });\n\n        expect($document.find('div.modal-dialog')).toHaveClass('modal-sm');\n      });\n\n      it('should support creating large modal dialogs', function() {\n        open({\n          template: '<div>Large modal dialog</div>',\n          size: 'lg'\n        });\n\n        expect($document.find('div.modal-dialog')).toHaveClass('modal-lg');\n      });\n\n      it('should support custom size modal dialogs', function() {\n        open({\n          template: '<div>Large modal dialog</div>',\n          size: 'custom'\n        });\n\n        expect($document.find('div.modal-dialog')).toHaveClass('modal-custom');\n      });\n    });\n\n    describe('animation', function() {\n      it('should have animation fade classes by default', function() {\n        open({\n          template: '<div>Small modal dialog</div>'\n        });\n\n        expect($document.find('.modal')).toHaveClass('fade');\n        expect($document.find('.modal-backdrop')).toHaveClass('fade');\n      });\n\n      it('should not have fade classes if animation false', function() {\n        open({\n          template: '<div>Small modal dialog</div>',\n          animation: false\n        });\n\n        expect($document.find('.modal')).not.toHaveClass('fade');\n        expect($document.find('.modal-backdrop')).not.toHaveClass('fade');\n      });\n    });\n\n    describe('appendTo', function() {\n      it('should be added to body by default', function() {\n        var modal = open({template: '<div>Content</div>'});\n\n        expect($document).toHaveModalsOpen(1);\n        expect($document).toHaveModalOpenWithContent('Content', 'div');\n      });\n\n      it('should not be added to body if appendTo is passed', function() {\n        var element = angular.element('<section>Some content</section>');\n        angular.element(document.body).append(element);\n\n        var modal = open({template: '<div>Content</div>', appendTo: element});\n\n        expect($document).not.toHaveModalOpenWithContent('Content', 'div');\n\n        element.remove();\n      });\n\n      it('should be added to appendTo element if appendTo is passed', function() {\n        var element = angular.element('<section>Some content</section>');\n        angular.element(document.body).append(element);\n\n        expect($document.find('section').children('div.modal').length).toBe(0);\n        open({template: '<div>Content</div>', appendTo: element});\n        expect($document.find('section').children('div.modal').length).toBe(1);\n\n        element.remove();\n      });\n\n      it('should throw error if appendTo element is not found', function() {\n        expect(function(){\n          open({template: '<div>Content</div>', appendTo: $document.find('aside')});\n        }).toThrow(new Error('appendTo element not found. Make sure that the element passed is in DOM.'));\n      });\n\n      it('should be removed from appendTo element when dismissed', function() {\n        var modal = open({template: '<div>Content</div>'});\n\n        expect($document).toHaveModalsOpen(1);\n\n        dismiss(modal);\n        expect($document).toHaveModalsOpen(0);\n      });\n\n      it('should allow requiring parent directive from appendTo target', function() {\n        var element = $compile('<section parent-directive>Some content</section>')($rootScope);\n        angular.element(document.body).append(element);\n\n        open({template: '<div child-directive>{{text}}</div>', appendTo: element});\n        expect($document.find('[child-directive]').text()).toBe('foo');\n\n        element.remove();\n      });\n    });\n\n    describe('openedClass', function() {\n      var body;\n\n      beforeEach(function() {\n        body = $document.find('body');\n      });\n\n      it('should add the modal-open class to the body element by default', function() {\n        open({\n          template: '<div>dummy modal</div>'\n        });\n\n        expect(body).toHaveClass('modal-open');\n      });\n\n      it('should add the custom class to the body element', function() {\n        open({\n          template: '<div>dummy modal</div>',\n          openedClass: 'foo'\n        });\n\n        expect(body).toHaveClass('foo');\n        expect(body).not.toHaveClass('modal-open');\n      });\n\n      it('should remove the custom class on closing of modal after animations have completed', function() {\n        var modal = open({\n          template: '<div>dummy modal</div>',\n          openedClass: 'foo'\n        });\n\n        expect(body).toHaveClass('foo');\n\n        close(modal, null, true);\n        expect(body).toHaveClass('foo');\n\n        $animate.flush();\n        $rootScope.$digest();\n        $animate.flush();\n        $rootScope.$digest();\n\n        expect(body).not.toHaveClass('foo');\n      });\n\n      it('should add multiple custom classes to the body element and remove appropriately', function() {\n        var modal1 = open({\n          template: '<div>dummy modal</div>',\n          openedClass: 'foo'\n        });\n\n        expect(body).toHaveClass('foo');\n        expect(body).not.toHaveClass('modal-open');\n\n        var modal2 = open({\n          template: '<div>dummy modal</div>',\n          openedClass: 'bar'\n        });\n\n        expect(body).toHaveClass('foo');\n        expect(body).toHaveClass('bar');\n        expect(body).not.toHaveClass('modal-open');\n\n        var modal3 = open({\n          template: '<div>dummy modal</div>',\n          openedClass: 'foo'\n        });\n\n        expect(body).toHaveClass('foo');\n        expect(body).toHaveClass('bar');\n        expect(body).not.toHaveClass('modal-open');\n\n        close(modal1);\n\n        expect(body).toHaveClass('foo');\n        expect(body).toHaveClass('bar');\n        expect(body).not.toHaveClass('modal-open');\n\n        close(modal2);\n\n        expect(body).toHaveClass('foo');\n        expect(body).not.toHaveClass('bar');\n        expect(body).not.toHaveClass('modal-open');\n\n        close(modal3);\n\n        expect(body).not.toHaveClass('foo');\n        expect(body).not.toHaveClass('bar');\n        expect(body).not.toHaveClass('modal-open');\n      });\n\n      it('should not add the modal-open class if modal is closed before animation', function() {\n        var modal = open({\n          template: '<div>dummy modal</div>'\n        }, true);\n\n        close(modal);\n\n        expect(body).not.toHaveClass('modal-open');\n      });\n    });\n\n    describe('ariaLabelledBy', function() {\n      it('should add the aria-labelledby property to the modal', function() {\n        open({\n          template: '<div><h3 id=\"modal-label\">Modal Label</h3><p id=\"modal-description\">Modal description</p></div>',\n          ariaLabelledBy: 'modal-label'\n        });\n\n        expect($document.find('.modal').attr('aria-labelledby')).toEqual('modal-label');\n      });\n    });\n\n    describe('ariaDescribedBy', function() {\n      it('should add the aria-describedby property to the modal', function() {\n        open({\n          template: '<div><h3 id=\"modal-label\">Modal Label</h3><p id=\"modal-description\">Modal description</p></div>',\n          ariaDescribedBy: 'modal-description'\n        });\n\n        expect($document.find('.modal').attr('aria-describedby')).toEqual('modal-description');\n      });\n    });\n  });\n\n  describe('modal window', function() {\n    it('should not use transclusion scope for modals content - issue 2110', function() {\n      $rootScope.animate = false;\n      $compile('<div uib-modal-window animate=\"animate\"><span ng-init=\"foo=true\"></span></div>')($rootScope);\n      $rootScope.$digest();\n\n      expect($rootScope.foo).toBeTruthy();\n    });\n\n    it('should support window top class', function () {\n      $rootScope.animate = false;\n      var windowEl = $compile('<div uib-modal-window animate=\"animate\" window-top-class=\"test foo\">content</div>')($rootScope);\n      $rootScope.$digest();\n\n      expect(windowEl).toHaveClass('test');\n      expect(windowEl).toHaveClass('foo');\n    });\n\n    it('should support custom template url', inject(function($templateCache) {\n      $templateCache.put('window.html', '<div ng-transclude></div>');\n\n      var windowEl = $compile('<div uib-modal-window template-url=\"window.html\">content</div>')($rootScope);\n      $rootScope.$digest();\n\n      expect(windowEl.html()).toBe('<div ng-transclude=\"\">content</div>');\n    }));\n  });\n\n  describe('multiple modals', function() {\n    it('should allow opening of multiple modals', function() {\n      var modal1 = open({template: '<div>Modal1</div>'});\n      var modal2 = open({template: '<div>Modal2</div>'});\n      expect($document).toHaveModalsOpen(2);\n\n      dismiss(modal2);\n      expect($document).toHaveModalsOpen(1);\n      expect($document).toHaveModalOpenWithContent('Modal1', 'div');\n\n      dismiss(modal1);\n      expect($document).toHaveModalsOpen(0);\n    });\n\n    it('should be able to dismiss all modals at once', function() {\n      var modal1 = open({template: '<div>Modal1</div>'});\n      var modal2 = open({template: '<div>Modal2</div>'});\n      expect($document).toHaveModalsOpen(2);\n\n      $uibModalStack.dismissAll();\n      $animate.flush();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(0);\n    });\n\n    it('should not close any modals on ESC if the topmost one does not allow it', function() {\n      var modal1 = open({template: '<div>Modal1</div>'});\n      var modal2 = open({template: '<div>Modal2</div>', keyboard: false});\n\n      triggerKeyDown($document, 27);\n      $rootScope.$digest();\n\n      expect($document).toHaveModalsOpen(2);\n    });\n\n    it('should not close any modals on click if a topmost modal does not have backdrop', function() {\n      var modal1 = open({template: '<div>Modal1</div>'});\n      var modal2 = open({template: '<div>Modal2</div>', backdrop: false});\n\n      $document.find('body > div.modal-backdrop').click();\n      $rootScope.$digest();\n\n      expect($document).toHaveModalsOpen(2);\n    });\n\n    it('should not interfere with default options', function() {\n      var modal1 = open({template: '<div>Modal1</div>', backdrop: false});\n      var modal2 = open({template: '<div>Modal2</div>'});\n      $rootScope.$digest();\n\n      expect($document).toHaveBackdrop();\n    });\n\n    it('should add \"modal-open\" class when a modal gets opened', function() {\n      var body = $document.find('body');\n      expect(body).not.toHaveClass('modal-open');\n\n      var modal1 = open({template: '<div>Content1</div>'});\n      expect(body).toHaveClass('modal-open');\n\n      var modal2 = open({template: '<div>Content1</div>'});\n      expect(body).toHaveClass('modal-open');\n\n      dismiss(modal1);\n      expect(body).toHaveClass('modal-open');\n\n      dismiss(modal2);\n      expect(body).not.toHaveClass('modal-open');\n    });\n\n    it('should return to the element which had focus before the dialog is invoked', function() {\n      var link = '<a href>Link</a>';\n      var element = angular.element(link);\n      angular.element(document.body).append(element);\n      element.focus();\n      expect(document.activeElement.tagName).toBe('A');\n\n      var modal1 = open({template: '<div>Modal1<button id=\"focus\">inside modal1</button></div>'});\n      $rootScope.$digest();\n      document.getElementById('focus').focus();\n      expect(document.activeElement.tagName).toBe('BUTTON');\n      expect($document).toHaveModalsOpen(1);\n\n      var modal2 = open({template: '<div>Modal2</div>'});\n      $rootScope.$digest();\n      expect(document.activeElement.tagName).toBe('DIV');\n      expect($document).toHaveModalsOpen(2);\n\n      dismiss(modal2);\n      expect(document.activeElement.tagName).toBe('BUTTON');\n      expect($document).toHaveModalsOpen(1);\n\n      dismiss(modal1);\n      expect(document.activeElement.tagName).toBe('A');\n      expect($document).toHaveModalsOpen(0);\n\n      element.remove();\n    });\n\n    it('should open modals and resolve the opened promises in order', function() {\n      // Opens a modal for each element in array order.\n      // Order is an array of non-repeating integers from 0..length-1 representing when to resolve that modal's promise.\n      // For example [1,2,0] would resolve the 3rd modal's promise first and the 2nd modal's promise last.\n      // Tests that the modals are added to $uibModalStack and that each resolves its \"opened\" promise sequentially.\n      // If an element is {reject:n} then n is still the order, but the corresponding promise is rejected.\n      // A rejection earlier in the open sequence should not affect modals opened later.\n      function test(order) {\n        var ds = []; // {index, deferred, reject}\n        var expected = ''; // 0..length-1\n        var actual = '';\n        angular.forEach(order, function(x, i) {\n          var reject = x.reject !== undefined;\n          if (reject) {\n            x = x.reject;\n          } else {\n            expected += i;\n          }\n          ds[x] = {index: i, deferred: $q.defer(), reject: reject};\n\n          var scope = $rootScope.$new();\n          var failed = false;\n          scope.index = i;\n          open({\n            template: '<div>' + i + '</div>',\n            scope: scope,\n            resolve: {\n              x: function() { return ds[x].deferred.promise['catch'](function () {\n                failed = true;\n              }); }\n            }\n          }, true).opened.then(function() {\n            expect($uibModalStack.getTop().value.modalScope.index).toEqual(i);\n            if (!failed) { actual += i; }\n          });\n        });\n\n        angular.forEach(ds, function(d, i) {\n          if (d.reject) {\n            d.deferred.reject('rejected:' + d.index);\n          } else {\n            d.deferred.resolve('resolved:' + d.index);\n          }\n          $rootScope.$digest();\n        });\n\n        expect(actual).toEqual(expected);\n        expect($uibModal.getPromiseChain()).toEqual(null);\n      }\n\n      // Calls emit n! times on arrays of length n containing all non-repeating permutations of the integers 0..n-1.\n      function permute(n, emit) {\n        if (n < 1 || typeof emit !== 'function') {\n          return;\n        }\n        var a = [];\n        function _permute(depth) {\n          index: for (var i = 0; i < n; i++) {\n            for (var j = 0; j < depth; j++) {\n              if (a[j] === i) {\n                continue index; // already used\n              }\n            }\n\n            a[depth] = i;\n            if (depth + 1 === n) {\n              emit(angular.copy(a));\n            } else {\n              _permute(depth + 1);\n            }\n          }\n        }\n        _permute(0);\n      }\n\n      permute(2, function(a) {\n        test(a);\n      });\n      permute(2, function(a) {\n        test(a.map(function(x, i) {\n          return {reject:x};\n        }));\n      });\n      permute(2, function(a) {\n        test(a.map(function(x, i) {\n          return i === 0 ? {reject: x} : x;\n        }));\n      });\n      permute(3, function(a) {\n        test(a);\n      });\n      permute(3, function(a) {\n        test(a.map(function(x, i) {\n          return {reject: x};\n        }));\n      });\n      permute(3, function(a) {\n        test(a.map(function(x, i) {\n          return i === 0 ? {reject: x} : x;\n        }));\n      });\n      permute(3, function(a) {\n        test(a.map(function(x, i) {\n          return i === 1 ? {reject: x} : x;\n        }));\n      });\n\n      $animate.flush();\n    });\n\n    it('should have top class only on top window', function () {\n      var modal1 = open({template: '<div>Content1</div>', windowClass: 'modal1', windowTopClass: 'modal-top'});\n      expect($document.find('div.modal1')).toHaveClass('modal-top');\n      expect($document).toHaveModalsOpen(1);\n\n      var modal2 = open({template: '<div>Content1</div>', windowClass: 'modal2', windowTopClass: 'modal-top'});\n      expect($document.find('div.modal1')).not.toHaveClass('modal-top');\n      expect($document.find('div.modal2')).toHaveClass('modal-top');\n      expect($document).toHaveModalsOpen(2);\n\n      var modal3 = open({template: '<div>Content1</div>', windowClass: 'modal3', windowTopClass: 'modal-top'});\n      expect($document.find('div.modal1')).not.toHaveClass('modal-top');\n      expect($document.find('div.modal2')).not.toHaveClass('modal-top');\n      expect($document.find('div.modal3')).toHaveClass('modal-top');\n      expect($document).toHaveModalsOpen(3);\n\n      dismiss(modal2);\n      expect($document.find('div.modal1')).not.toHaveClass('modal-top');\n      expect($document.find('div.modal3')).toHaveClass('modal-top');\n      expect($document).toHaveModalsOpen(2);\n\n      close(modal3);\n      expect($document.find('div.modal1')).toHaveClass('modal-top');\n      expect($document).toHaveModalsOpen(1);\n    });\n\n    it('should have top modal with highest index', function() {\n      var modal2Index = null;\n      var modal3Index = null;\n\n      var modal1Instance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function(result) {\n          return $uibModalStack.close(modal1Instance, result);\n        },\n        dismiss: function(reason) {\n          return $uibModalStack.dismiss(modal1Instance, reason);\n        }\n      };\n      var modal2Instance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function(result) {\n          return $uibModalStack.close(modal2Instance, result);\n        },\n        dismiss: function(reason) {\n          return $uibModalStack.dismiss(modal2Instance, reason);\n        }\n      };\n      var modal3Instance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function(result) {\n          return $uibModalStack.close(modal13nstance, result);\n        },\n        dismiss: function(reason) {\n          return $uibModalStack.dismiss(modal3Instance, reason);\n        }\n      };\n\n      var modal1 = $uibModalStack.open(modal1Instance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modal1Instance.result,\n        renderDeferred: modal1Instance.rendered,\n        closedDeferred: modal1Instance.closed,\n        content: '<div>Modal1</div>'\n      });\n\n      $rootScope.$digest();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(1);\n\n      expect(parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10)).toEqual(0);\n\n      var modal2 = $uibModalStack.open(modal2Instance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modal2Instance.result,\n        renderDeferred: modal2Instance.rendered,\n        closedDeferred: modal2Instance.closed,\n        content: '<div>Modal2</div>'\n      });\n\n      modal2Instance.rendered.promise.then(function() {\n        modal2Index = parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10);\n      });\n\n      $rootScope.$digest();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(2);\n\n      expect(modal2Index).toEqual(1);\n      close(modal1Instance);\n      expect($document).toHaveModalsOpen(1);\n\n      var modal3 = $uibModalStack.open(modal3Instance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modal3Instance.result,\n        renderDeferred: modal3Instance.rendered,\n        closedDeferred: modal3Instance.closed,\n        content: '<div>Modal3</div>'\n      });\n\n      modal3Instance.rendered.promise.then(function() {\n        modal3Index = parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10);\n      });\n\n      $rootScope.$digest();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(2);\n\n      expect(modal3Index).toEqual(2);\n      expect(modal2Index).toBeLessThan(modal3Index);\n    });\n\n    it('should have top modal with highest z-index', function() {\n      var modal2zIndex = null;\n      var modal3zIndex = null;\n\n      var modal1Instance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function(result) {\n          return $uibModalStack.close(modal1Instance, result);\n        },\n        dismiss: function(reason) {\n          return $uibModalStack.dismiss(modal1Instance, reason);\n        }\n      };\n      var modal2Instance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function(result) {\n          return $uibModalStack.close(modal2Instance, result);\n        },\n        dismiss: function(reason) {\n          return $uibModalStack.dismiss(modal2Instance, reason);\n        }\n      };\n      var modal3Instance = {\n        result: $q.defer(),\n        opened: $q.defer(),\n        closed: $q.defer(),\n        rendered: $q.defer(),\n        close: function(result) {\n          return $uibModalStack.close(modal3Instance, result);\n        },\n        dismiss: function(reason) {\n          return $uibModalStack.dismiss(modal3Instance, reason);\n        }\n      };\n\n      var modal1 = $uibModalStack.open(modal1Instance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modal1Instance.result,\n        renderDeferred: modal1Instance.rendered,\n        closedDeferred: modal1Instance.closed,\n        content: '<div>Modal1</div>'\n      });\n\n      $rootScope.$digest();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(1);\n\n      expect(+$uibModalStack.getTop().value.modalDomEl[0].style.zIndex).toBe(1050);\n\n      var modal2 = $uibModalStack.open(modal2Instance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modal2Instance.result,\n        renderDeferred: modal2Instance.rendered,\n        closedDeferred: modal2Instance.closed,\n        content: '<div>Modal2</div>'\n      });\n\n      modal2Instance.rendered.promise.then(function() {\n        modal2zIndex = +$uibModalStack.getTop().value.modalDomEl[0].style.zIndex;\n      });\n\n      $rootScope.$digest();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(2);\n\n      expect(modal2zIndex).toBe(1060);\n      close(modal1Instance);\n      expect($document).toHaveModalsOpen(1);\n\n      var modal3 = $uibModalStack.open(modal3Instance, {\n        appendTo: angular.element(document.body),\n        scope: $rootScope.$new(),\n        deferred: modal3Instance.result,\n        renderDeferred: modal3Instance.rendered,\n        closedDeferred: modal3Instance.closed,\n        content: '<div>Modal3</div>'\n      });\n\n      modal3Instance.rendered.promise.then(function() {\n        modal3zIndex = +$uibModalStack.getTop().value.modalDomEl[0].style.zIndex;\n      });\n\n      $rootScope.$digest();\n      $animate.flush();\n      expect($document).toHaveModalsOpen(2);\n\n      expect(modal3zIndex).toBe(1070);\n      expect(modal2zIndex).toBeLessThan(modal3zIndex);\n    });\n  });\n\n  describe('modal.closing event', function() {\n    it('should close the modal contingent on the modal.closing event and return whether the modal closed', function() {\n      var preventDefault;\n      var modal;\n      var template = '<div>content</div>';\n\n      function TestCtrl($scope) {\n        $scope.$on('modal.closing', function(event, resultOrReason, closing) {\n          if (preventDefault) {\n            event.preventDefault();\n          }\n        });\n      }\n\n      modal = open({template: template, controller: TestCtrl});\n\n      preventDefault = true;\n      expect(close(modal, 'result', true)).toBeFalsy();\n      expect($document).toHaveModalsOpen(1);\n\n      preventDefault = false;\n      expect(close(modal, 'result')).toBeTruthy();\n      expect($document).toHaveModalsOpen(0);\n\n      modal = open({template: template, controller: TestCtrl});\n\n      preventDefault = true;\n      expect(dismiss(modal, 'result', true)).toBeFalsy();\n      expect($document).toHaveModalsOpen(1);\n\n      preventDefault = false;\n      expect(dismiss(modal, 'result')).toBeTruthy();\n      expect($document).toHaveModalsOpen(0);\n    });\n\n    it('should trigger modal.closing and pass result/reason and closing parameters to the event', function() {\n      var called;\n\n      called = false;\n\n      close(open({\n        template: '<div>content</div>',\n        controller: function($scope) {\n          $scope.$on('modal.closing', function(event, resultOrReason, closing) {\n            called = true;\n            expect(resultOrReason).toBe('result');\n            expect(closing).toBeTruthy();\n          });\n        }\n      }), 'result');\n      expect(called).toBeTruthy();\n\n      called = false;\n      dismiss(open({\n        template: '<div>content</div>',\n        controller: function($scope) {\n          $scope.$on('modal.closing', function(event, resultOrReason, closing) {\n            called = true;\n            expect(resultOrReason).toBe('reason');\n            expect(closing).toBeFalsy();\n          });\n        }\n      }), 'reason');\n      expect(called).toBeTruthy();\n    });\n  });\n});\n"
  },
  {
    "path": "src/multiMap/index.js",
    "content": "require('./multiMap.js');\n"
  },
  {
    "path": "src/multiMap/multiMap.js",
    "content": "angular.module('ui.bootstrap.multiMap', [])\n/**\n * A helper, internal data structure that stores all references attached to key\n */\n  .factory('$$multiMap', function() {\n    return {\n      createNew: function() {\n        var map = {};\n\n        return {\n          entries: function() {\n            return Object.keys(map).map(function(key) {\n              return {\n                key: key,\n                value: map[key]\n              };\n            });\n          },\n          get: function(key) {\n            return map[key];\n          },\n          hasKey: function(key) {\n            return !!map[key];\n          },\n          keys: function() {\n            return Object.keys(map);\n          },\n          put: function(key, value) {\n            if (!map[key]) {\n              map[key] = [];\n            }\n\n            map[key].push(value);\n          },\n          remove: function(key, value) {\n            var values = map[key];\n\n            if (!values) {\n              return;\n            }\n\n            var idx = values.indexOf(value);\n\n            if (idx !== -1) {\n              values.splice(idx, 1);\n            }\n\n            if (!values.length) {\n              delete map[key];\n            }\n          }\n        };\n      }\n    };\n  });\n"
  },
  {
    "path": "src/multiMap/test/multiMap.spec.js",
    "content": "describe('multi map', function() {\n  var multiMap;\n\n  beforeEach(module('ui.bootstrap.multiMap'));\n  beforeEach(inject(function($$multiMap) {\n    multiMap = $$multiMap.createNew();\n  }));\n\n  it('should add and remove objects by key', function() {\n    multiMap.put('foo', 'bar');\n\n    expect(multiMap.get('foo')).toEqual(['bar']);\n\n    multiMap.put('foo', 'baz');\n\n    expect(multiMap.get('foo')).toEqual(['bar', 'baz']);\n\n    multiMap.remove('foo', 'bar');\n\n    expect(multiMap.get('foo')).toEqual(['baz']);\n\n    multiMap.remove('foo', 'baz');\n\n    expect(multiMap.hasKey('foo')).toBe(false);\n  });\n\n  it('should support getting the keys', function() {\n    multiMap.put('foo', 'bar');\n    multiMap.put('baz', 'boo');\n\n    expect(multiMap.keys()).toEqual(['foo', 'baz']);\n  });\n\n  it('should return all entries', function() {\n    multiMap.put('foo', 'bar');\n    multiMap.put('foo', 'bar2');\n    multiMap.put('baz', 'boo');\n\n    expect(multiMap.entries()).toEqual([\n      {\n        key: 'foo',\n        value: ['bar', 'bar2']\n      },\n      {\n        key: 'baz',\n        value: ['boo']\n      }\n    ]);\n  });\n\n  it('should preserve semantic of an empty key', function() {\n    expect(multiMap.get('key')).toBeUndefined();\n  });\n\n  it('should respect removal of non-existing elements', function() {\n    expect(multiMap.remove('foo', 'bar')).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "src/pager/docs/demo.html",
    "content": "<div ng-controller=\"PagerDemoCtrl\">\n  <h4>Pager</h4>\n  <pre>You are currently on page {{currentPage}}</pre>\n  <ul uib-pager total-items=\"totalItems\" ng-model=\"currentPage\"></ul>\n</div>\n"
  },
  {
    "path": "src/pager/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('PagerDemoCtrl', function($scope) {\n  $scope.totalItems = 64;\n  $scope.currentPage = 4;\n});\n"
  },
  {
    "path": "src/pager/docs/readme.md",
    "content": "A lightweight pager directive that is focused on providing previous/next paging functionality\n\n### uib-pager settings\n\n* `align`\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether to align each link to the sides.\n  \n* `items-per-page`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `10`)_ -\n  Maximum number of items per page. A value less than one indicates all items on one page.\n  \n* `next-text`\n  <small class=\"badge\">C</small>\n  _(Default: `Next »`)_ -\n  Text for Next button.\n  \n* `ng-disabled`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Used to disable the pager component.\n  \n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Current page number. First page is 1.\n  \n* `num-pages`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">readonly</small>\n  _(Default: `angular.noop`)_ -\n  An optional expression assigned the total number of pages to display.\n\n* `previous-text`\n  <small class=\"badge\">C</small>\n  _(Default: `« Previous`)_ -\n  Text for Previous button.\n\n* `template-url`\n  _(Default: `uib/template/pager/pager.html`)_ -\n  Override the template for the component with a custom provided template.\n  \n* `total-items`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Total number of items in all pages.\n"
  },
  {
    "path": "src/pager/index.js",
    "content": "require('../paging');\nrequire('../tabindex');\nrequire('../../template/pager/pager.html.js');\nrequire('./pager');\n\nvar MODULE_NAME = 'ui.bootstrap.module.pager';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.pager', 'uib/template/pager/pager.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/pager/pager.js",
    "content": "angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])\n\n.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {\n  $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;\n\n  uibPaging.create(this, $scope, $attrs);\n}])\n\n.constant('uibPagerConfig', {\n  itemsPerPage: 10,\n  previousText: '« Previous',\n  nextText: 'Next »',\n  align: true\n})\n\n.directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {\n  return {\n    scope: {\n      totalItems: '=',\n      previousText: '@',\n      nextText: '@',\n      ngDisabled: '='\n    },\n    require: ['uibPager', '?ngModel'],\n    restrict: 'A',\n    controller: 'UibPagerController',\n    controllerAs: 'pager',\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/pager/pager.html';\n    },\n    link: function(scope, element, attrs, ctrls) {\n      element.addClass('pager');\n      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n\n      if (!ngModelCtrl) {\n        return; // do nothing if no ng-model\n      }\n\n      paginationCtrl.init(ngModelCtrl, uibPagerConfig);\n    }\n  };\n}]);\n"
  },
  {
    "path": "src/pager/test/pager.spec.js",
    "content": "describe('pager directive', function() {\n  var $compile, $rootScope, $document, $templateCache, body, element;\n  beforeEach(module('ui.bootstrap.pager'));\n  beforeEach(module('uib/template/pager/pager.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _$templateCache_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $rootScope.total = 47; // 5 pages\n    $rootScope.currentPage = 3;\n    $document = _$document_;\n    $templateCache = _$templateCache_;\n    body = $document.find('body');\n    element = $compile('<ul uib-pager total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n    $rootScope.$digest();\n  }));\n\n  function getPaginationBarSize() {\n    return element.find('li').length;\n  }\n\n  function getPaginationEl(index) {\n    return element.find('li').eq(index);\n  }\n\n  function clickPaginationEl(index) {\n    getPaginationEl(index).find('a').click();\n  }\n\n  function getPaginationLinkEl(elem, index) {\n    return elem.find('li').eq(index).find('a');\n  }\n\n  function updateCurrentPage(value) {\n    $rootScope.currentPage = value;\n    $rootScope.$digest();\n  }\n\n  it('has a \"pager\" css class', function() {\n    expect(element.hasClass('pager')).toBe(true);\n  });\n\n  it('contains 2 li elements', function() {\n    expect(getPaginationBarSize()).toBe(2);\n    expect(getPaginationEl(0).text()).toBe('« Previous');\n    expect(getPaginationEl(-1).text()).toBe('Next »');\n  });\n\n  it('aligns previous & next page', function() {\n    expect(getPaginationEl(0)).toHaveClass('previous');\n    expect(getPaginationEl(0)).not.toHaveClass('next');\n\n    expect(getPaginationEl(-1)).not.toHaveClass('previous');\n    expect(getPaginationEl(-1)).toHaveClass('next');\n  });\n\n  it('exposes the controller on the template', function() {\n    $templateCache.put('uib/template/pager/pager.html', '<div>{{pager.text}}</div>');\n\n    element = $compile('<ul uib-pager></ul uib-pager>')($rootScope);\n    $rootScope.$digest();\n\n    var ctrl = element.controller('uibPager');\n    expect(ctrl).toBeDefined();\n\n    ctrl.text = 'foo';\n    $rootScope.$digest();\n\n    expect(element.html()).toBe('<div class=\"ng-binding\">foo</div>');\n  });\n\n  it('disables the \"previous\" link if current page is 1', function() {\n    updateCurrentPage(1);\n    expect(getPaginationEl(0)).toHaveClass('disabled');\n  });\n\n  it('disables the \"next\" link if current page is num-pages', function() {\n    updateCurrentPage(5);\n    expect(getPaginationEl(-1)).toHaveClass('disabled');\n  });\n\n  it('changes currentPage if the \"previous\" link is clicked', function() {\n    clickPaginationEl(0);\n    expect($rootScope.currentPage).toBe(2);\n  });\n\n  it('changes currentPage if the \"next\" link is clicked', function() {\n    clickPaginationEl(-1);\n    expect($rootScope.currentPage).toBe(4);\n  });\n\n  it('does not change the current page on \"previous\" click if already at first page', function() {\n    updateCurrentPage(1);\n    clickPaginationEl(0);\n    expect($rootScope.currentPage).toBe(1);\n  });\n\n  it('does not change the current page on \"next\" click if already at last page', function() {\n    updateCurrentPage(5);\n    clickPaginationEl(-1);\n    expect($rootScope.currentPage).toBe(5);\n  });\n\n  it('executes the `ng-change` expression when an element is clicked', function() {\n    $rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');\n    element = $compile('<ul uib-pager total-items=\"total\" ng-model=\"currentPage\" ng-change=\"selectPageHandler()\"></ul>')($rootScope);\n    $rootScope.$digest();\n\n    clickPaginationEl(-1);\n    expect($rootScope.selectPageHandler).toHaveBeenCalled();\n  });\n\n  it('does not changes the number of pages when `total-items` changes', function() {\n    $rootScope.total = 73; // 8 pages\n    $rootScope.$digest();\n\n    expect(getPaginationBarSize()).toBe(2);\n    expect(getPaginationEl(0).text()).toBe('« Previous');\n    expect(getPaginationEl(-1).text()).toBe('Next »');\n  });\n\n  it('should blur the \"next\" link after it has been clicked', function() {\n    body.append(element);\n    var linkEl = getPaginationLinkEl(element, -1);\n\n    linkEl.focus();\n    expect(linkEl).toHaveFocus();\n\n    linkEl.click();\n    expect(linkEl).not.toHaveFocus();\n\n    element.remove();\n  });\n\n  it('should blur the \"prev\" link after it has been clicked', function() {\n    body.append(element);\n    var linkEl = getPaginationLinkEl(element, -1);\n\n    linkEl.focus();\n    expect(linkEl).toHaveFocus();\n\n    linkEl.click();\n    expect(linkEl).not.toHaveFocus();\n\n    element.remove();\n  });\n\n  it('allows custom templates', function() {\n    $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n    element = $compile('<ul uib-pager template-url=\"foo/bar.html\"></ul>')($rootScope);\n    $rootScope.$digest();\n\n    expect(element.html()).toBe('<div>baz</div>');\n  });\n\n  describe('`items-per-page`', function() {\n    beforeEach(function() {\n      $rootScope.perpage = 5;\n      element = $compile('<ul uib-pager total-items=\"total\" items-per-page=\"perpage\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('does not change the number of pages', function() {\n      expect(getPaginationBarSize()).toBe(2);\n      expect(getPaginationEl(0).text()).toBe('« Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next »');\n    });\n\n    it('selects the last page when it is too big', function() {\n      $rootScope.perpage = 30;\n      $rootScope.$digest();\n\n      expect($rootScope.currentPage).toBe(2);\n      expect(getPaginationBarSize()).toBe(2);\n      expect(getPaginationEl(0)).not.toHaveClass('disabled');\n      expect(getPaginationEl(-1)).toHaveClass('disabled');\n    });\n  });\n\n  describe('when `page` is not a number', function() {\n    it('handles string', function() {\n      updateCurrentPage('1');\n      expect(getPaginationEl(0)).toHaveClass('disabled');\n\n      updateCurrentPage('05');\n      expect(getPaginationEl(-1)).toHaveClass('disabled');\n    });\n  });\n\n  describe('`num-pages`', function() {\n    beforeEach(function() {\n      $rootScope.numpg = null;\n      element = $compile('<ul uib-pager total-items=\"total\" ng-model=\"currentPage\" num-pages=\"numpg\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('equals to total number of pages', function() {\n      expect($rootScope.numpg).toBe(5);\n    });\n  });\n\n  describe('setting `pagerConfig`', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibPagerConfig) {\n      angular.extend(originalConfig, uibPagerConfig);\n      uibPagerConfig.previousText = 'PR';\n      uibPagerConfig.nextText = 'NE';\n      uibPagerConfig.align = false;\n      element = $compile('<ul uib-pager total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    }));\n    afterEach(inject(function(uibPagerConfig) {\n      // return it to the original state\n      angular.extend(uibPagerConfig, originalConfig);\n    }));\n\n    it('should change paging text', function() {\n      expect(getPaginationEl(0).text()).toBe('PR');\n      expect(getPaginationEl(-1).text()).toBe('NE');\n    });\n\n    it('should not align previous & next page link', function() {\n      expect(getPaginationEl(0)).not.toHaveClass('previous');\n      expect(getPaginationEl(-1)).not.toHaveClass('next');\n    });\n  });\n\n  describe('override configuration from attributes', function() {\n    beforeEach(function() {\n      element = $compile('<ul uib-pager align=\"false\" previous-text=\"<\" next-text=\">\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains 2 li elements', function() {\n      expect(getPaginationBarSize()).toBe(2);\n    });\n\n    it('should change paging text from attributes', function() {\n      expect(getPaginationEl(0).text()).toBe('<');\n      expect(getPaginationEl(-1).text()).toBe('>');\n    });\n\n    it('should not align previous & next page link', function() {\n      expect(getPaginationEl(0)).not.toHaveClass('previous');\n      expect(getPaginationEl(-1)).not.toHaveClass('next');\n    });\n\n    it('changes \"previous\" & \"next\" text from interpolated attributes', function() {\n      $rootScope.previousText = '<<';\n      $rootScope.nextText = '>>';\n      element = $compile('<ul uib-pager align=\"false\" previous-text=\"{{previousText}}\" next-text=\"{{nextText}}\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationEl(0).text()).toBe('<<');\n      expect(getPaginationEl(-1).text()).toBe('>>');\n    });\n  });\n\n  it('disables the component when ng-disabled is true', function() {\n    $rootScope.disable = true;\n\n    element = $compile('<ul uib-pager total-items=\"total\" ng-disabled=\"disable\" ng-model=\"currentPage\"></ul>')($rootScope);\n    $rootScope.$digest();\n    updateCurrentPage(2);\n\n    expect(getPaginationEl(0)).toHaveClass('disabled');\n    expect(getPaginationEl(-1)).toHaveClass('disabled');\n\n    $rootScope.disable = false;\n    $rootScope.$digest();\n\n    expect(getPaginationEl(0)).not.toHaveClass('disabled');\n    expect(getPaginationEl(-1)).not.toHaveClass('disabled');\n\n    $rootScope.disable = true;\n    $rootScope.$digest();\n\n    expect(getPaginationEl(0)).toHaveClass('disabled');\n    expect(getPaginationEl(-1)).toHaveClass('disabled');\n  });\n});\n"
  },
  {
    "path": "src/pagination/docs/demo.html",
    "content": "<div ng-controller=\"PaginationDemoCtrl\">\n    <h4>Default</h4>\n    <ul uib-pagination total-items=\"totalItems\" ng-model=\"currentPage\" ng-change=\"pageChanged()\"></ul>\n    <ul uib-pagination boundary-links=\"true\" total-items=\"totalItems\" ng-model=\"currentPage\" class=\"pagination-sm\" previous-text=\"&lsaquo;\" next-text=\"&rsaquo;\" first-text=\"&laquo;\" last-text=\"&raquo;\"></ul>\n    <ul uib-pagination direction-links=\"false\" boundary-links=\"true\" total-items=\"totalItems\" ng-model=\"currentPage\"></ul>\n    <ul uib-pagination direction-links=\"false\" total-items=\"totalItems\" ng-model=\"currentPage\" num-pages=\"smallnumPages\"></ul>\n    <pre>The selected page no: {{currentPage}}</pre>\n    <button type=\"button\" class=\"btn btn-info\" ng-click=\"setPage(3)\">Set current page to: 3</button>\n\n    <hr />\n    <h4>Limit the maximum visible buttons</h4>\n    <h6><code>rotate</code> defaulted to <code>true</code>:</h6>\n    <ul uib-pagination total-items=\"bigTotalItems\" ng-model=\"bigCurrentPage\" max-size=\"maxSize\" class=\"pagination-sm\" boundary-links=\"true\" num-pages=\"numPages\"></ul>\n    <h6><code>rotate</code> defaulted to <code>true</code> and <code>force-ellipses</code> set to <code>true</code>:</h6>\n    <ul uib-pagination total-items=\"bigTotalItems\" ng-model=\"bigCurrentPage\" max-size=\"maxSize\" class=\"pagination-sm\" boundary-links=\"true\" force-ellipses=\"true\"></ul>\n    <h6><code>rotate</code> set to <code>false</code>:</h6>\n    <ul uib-pagination total-items=\"bigTotalItems\" ng-model=\"bigCurrentPage\" max-size=\"maxSize\" class=\"pagination-sm\" boundary-links=\"true\" rotate=\"false\"></ul>\n    <h6><code>boundary-link-numbers</code> set to <code>true</code> and <code>rotate</code> defaulted to <code>true</code>:</h6>\n    <ul uib-pagination total-items=\"bigTotalItems\" ng-model=\"bigCurrentPage\" max-size=\"maxSize\" class=\"pagination-sm\" boundary-link-numbers=\"true\"></ul>\n    <h6><code>boundary-link-numbers</code> set to <code>true</code> and <code>rotate</code> set to <code>false</code>:</h6>\n    <ul uib-pagination total-items=\"bigTotalItems\" ng-model=\"bigCurrentPage\" max-size=\"maxSize\" class=\"pagination-sm\" boundary-link-numbers=\"true\" rotate=\"false\"></ul>\n    <pre>Page: {{bigCurrentPage}} / {{numPages}}</pre>\n\n</div>\n"
  },
  {
    "path": "src/pagination/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('PaginationDemoCtrl', function ($scope, $log) {\n  $scope.totalItems = 64;\n  $scope.currentPage = 4;\n\n  $scope.setPage = function (pageNo) {\n    $scope.currentPage = pageNo;\n  };\n\n  $scope.pageChanged = function() {\n    $log.log('Page changed to: ' + $scope.currentPage);\n  };\n\n  $scope.maxSize = 5;\n  $scope.bigTotalItems = 175;\n  $scope.bigCurrentPage = 1;\n});\n"
  },
  {
    "path": "src/pagination/docs/readme.md",
    "content": "A lightweight pagination directive that is focused on ... providing pagination & will take care of visualising a pagination bar and enable / disable buttons correctly!\n\n### uib-pagination settings\n\n* `boundary-links`\n  <small class=\"badge\">C</small>\n  _(Default: `false`)_ -\n  Whether to display First / Last buttons.\n\n* `boundary-link-numbers`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `false`)_ -\n  Whether to always display the first and last page numbers. If `max-size` is smaller than the number of pages, then the  first and last page numbers are still shown with ellipses in-between as necessary. NOTE: `max-size` refers to the center of the range. This option may add up to 2 more numbers on each side of the displayed range for the end value and what would be an ellipsis but is replaced by a number because it is sequential.\n\n* `direction-links`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether to display Previous / Next buttons.\n\n* `first-text`\n  <small class=\"badge\">C</small>\n  _(Default: `First`)_ -\n  Text for First button.\n\n* `force-ellipses`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `false`)_ -\n  Also displays ellipses when `rotate` is true and `max-size` is smaller than the number of pages.\n\n* `items-per-page`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `10`)_ -\n  Maximum number of items per page. A value less than one indicates all items on one page.\n\n* `last-text`\n  <small class=\"badge\">C</small>\n  _(Default: `Last`)_ -\n  Text for Last button.\n\n* `max-size`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `null`)_ -\n  Limit number for pagination size.\n\n* `next-text`\n  <small class=\"badge\">C</small>\n  _(Default: `Next`)_ -\n  Text for Next button.\n\n* `ng-change`\n  <small class=\"badge\">$</small> -\n  This can be used to call a function whenever the page changes.\n\n* `ng-disabled`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Used to disable the pagination component.\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Current page number. First page is 1.\n\n* `num-pages`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">readonly</small>\n  _(Default: `angular.noop`)_ -\n  An optional expression assigned the total number of pages to display.\n\n* `page-label`\n  _(Default: `angular.identity`)_ -\n  An optional expression to override the page label based on passing the current page indexes. Supports page number with `$page` in the template.\n\n* `previous-text`\n  <small class=\"badge\">C</small>\n  _(Default: `Previous`)_ -\n  Text for Previous button.\n\n* `rotate`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether to keep current page in the middle of the visible ones.\n\n* `template-url`\n  _(Default: `uib/template/pagination/pagination.html`)_ -\n  Override the template for the component with a custom provided template\n\n* `total-items`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Total number of items in all pages.\n"
  },
  {
    "path": "src/pagination/index.js",
    "content": "require('../paging');\nrequire('../tabindex');\nrequire('../../template/pagination/pagination.html.js');\nrequire('./pagination');\n\nvar MODULE_NAME = 'ui.bootstrap.module.pagination';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.pagination', 'uib/template/pagination/pagination.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/pagination/pagination.js",
    "content": "angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])\n.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {\n  var ctrl = this;\n  // Setup configuration parameters\n  var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,\n    rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,\n    forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,\n    boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,\n    pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;\n  $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;\n  $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;\n  $attrs.$set('role', 'menu');\n\n  uibPaging.create(this, $scope, $attrs);\n\n  if ($attrs.maxSize) {\n    ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {\n      maxSize = parseInt(value, 10);\n      ctrl.render();\n    }));\n  }\n\n  // Create page object used in template\n  function makePage(number, text, isActive) {\n    return {\n      number: number,\n      text: text,\n      active: isActive\n    };\n  }\n\n  function getPages(currentPage, totalPages) {\n    var pages = [];\n\n    // Default page limits\n    var startPage = 1, endPage = totalPages;\n    var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;\n\n    // recompute if maxSize\n    if (isMaxSized) {\n      if (rotate) {\n        // Current page is displayed in the middle of the visible ones\n        startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);\n        endPage = startPage + maxSize - 1;\n\n        // Adjust if limit is exceeded\n        if (endPage > totalPages) {\n          endPage = totalPages;\n          startPage = endPage - maxSize + 1;\n        }\n      } else {\n        // Visible pages are paginated with maxSize\n        startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;\n\n        // Adjust last page if limit is exceeded\n        endPage = Math.min(startPage + maxSize - 1, totalPages);\n      }\n    }\n\n    // Add page number links\n    for (var number = startPage; number <= endPage; number++) {\n      var page = makePage(number, pageLabel(number), number === currentPage);\n      pages.push(page);\n    }\n\n    // Add links to move between page sets\n    if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {\n      if (startPage > 1) {\n        if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning\n        var previousPageSet = makePage(startPage - 1, '...', false);\n        pages.unshift(previousPageSet);\n      }\n        if (boundaryLinkNumbers) {\n          if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential\n            var secondPageLink = makePage(2, '2', false);\n            pages.unshift(secondPageLink);\n          }\n          //add the first page\n          var firstPageLink = makePage(1, '1', false);\n          pages.unshift(firstPageLink);\n        }\n      }\n\n      if (endPage < totalPages) {\n        if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end\n        var nextPageSet = makePage(endPage + 1, '...', false);\n        pages.push(nextPageSet);\n      }\n        if (boundaryLinkNumbers) {\n          if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential\n            var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);\n            pages.push(secondToLastPageLink);\n          }\n          //add the last page\n          var lastPageLink = makePage(totalPages, totalPages, false);\n          pages.push(lastPageLink);\n        }\n      }\n    }\n    return pages;\n  }\n\n  var originalRender = this.render;\n  this.render = function() {\n    originalRender();\n    if ($scope.page > 0 && $scope.page <= $scope.totalPages) {\n      $scope.pages = getPages($scope.page, $scope.totalPages);\n    }\n  };\n}])\n\n.constant('uibPaginationConfig', {\n  itemsPerPage: 10,\n  boundaryLinks: false,\n  boundaryLinkNumbers: false,\n  directionLinks: true,\n  firstText: 'First',\n  previousText: 'Previous',\n  nextText: 'Next',\n  lastText: 'Last',\n  rotate: true,\n  forceEllipses: false\n})\n\n.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {\n  return {\n    scope: {\n      totalItems: '=',\n      firstText: '@',\n      previousText: '@',\n      nextText: '@',\n      lastText: '@',\n      ngDisabled:'='\n    },\n    require: ['uibPagination', '?ngModel'],\n    restrict: 'A',\n    controller: 'UibPaginationController',\n    controllerAs: 'pagination',\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/pagination/pagination.html';\n    },\n    link: function(scope, element, attrs, ctrls) {\n      element.addClass('pagination');\n      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n\n      if (!ngModelCtrl) {\n         return; // do nothing if no ng-model\n      }\n\n      paginationCtrl.init(ngModelCtrl, uibPaginationConfig);\n    }\n  };\n}]);\n"
  },
  {
    "path": "src/pagination/test/pagination.spec.js",
    "content": "describe('pagination directive', function() {\n  var $compile, $rootScope, $document, $templateCache, body, element;\n  beforeEach(module('ui.bootstrap.pagination'));\n  beforeEach(module('uib/template/pagination/pagination.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _$templateCache_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $rootScope.total = 47; // 5 pages\n    $rootScope.currentPage = 3;\n    $rootScope.disabled = false;\n    $document = _$document_;\n    $templateCache = _$templateCache_;\n    body = $document.find('body');\n    element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n    $rootScope.$digest();\n  }));\n\n  function getPaginationBarSize() {\n    return element.find('li').length;\n  }\n\n  function getPaginationEl(index) {\n    return element.find('li').eq(index);\n  }\n\n  // Returns a comma-separated string that represents the pager, like: \"Prev, 1, 2, 3, Next\"\n  function getPaginationAsText() {\n    var len = getPaginationBarSize(), outItems = [];\n    for (var i = 0; i < len; i++) {\n      outItems.push(getPaginationEl(i).text());\n    }\n    return outItems.join(', ');\n  }\n\n  function clickPaginationEl(index) {\n    getPaginationEl(index).find('a').click();\n  }\n\n  function getPaginationLinkEl(elem, index) {\n    return elem.find('li').eq(index).find('a');\n  }\n\n  function updateCurrentPage(value) {\n    $rootScope.currentPage = value;\n    $rootScope.$digest();\n  }\n\n  function setDisabled(value) {\n    $rootScope.disabled = value;\n    $rootScope.$digest();\n  }\n\n  it('has a \"pagination\" css class', function() {\n    expect(element.hasClass('pagination')).toBe(true);\n  });\n\n  it('has accessibility attributes', function() {\n    expect(element.attr('role')).toEqual('menu');\n\n    var li = element.find('li');\n    for (var i = 0; i < li.length; i++) {\n      expect(li.eq(i).attr('role')).toEqual('menuitem');\n    }\n  });\n\n  it('exposes the controller to the template', function() {\n    $templateCache.put('uib/template/pagination/pagination.html', '<div>{{pagination.randomText}}</div>');\n    var scope = $rootScope.$new();\n\n    element = $compile('<ul uib-pagination></ul>')(scope);\n    $rootScope.$digest();\n\n    var ctrl = element.controller('uibPagination');\n\n    expect(ctrl).toBeDefined();\n\n    ctrl.randomText = 'foo';\n    $rootScope.$digest();\n\n    expect(element.html()).toBe('<div class=\"ng-binding\">foo</div>');\n  });\n\n  it('allows custom templates', function() {\n    $templateCache.put('foo/bar.html', '<div>baz</div>');\n    var scope = $rootScope.$new();\n\n    element = $compile('<ul uib-pagination template-url=\"foo/bar.html\"></ul>')(scope);\n    $rootScope.$digest();\n\n    expect(element.html()).toBe('<div>baz</div>');\n  });\n\n  it('contains num-pages + 2 li elements', function() {\n    expect(getPaginationBarSize()).toBe(7);\n    expect(getPaginationEl(0).text()).toBe('Previous');\n    expect(getPaginationEl(-1).text()).toBe('Next');\n  });\n\n  it('has the number of the page as text in each page item', function() {\n    for (var i = 1; i <= 5; i++) {\n      expect(getPaginationEl(i).text()).toEqual(''+i);\n    }\n  });\n\n  it('sets the current page to be active', function() {\n    expect(getPaginationEl($rootScope.currentPage).hasClass('active')).toBe(true);\n  });\n\n  it('disables the \"previous\" link if current page is 1', function() {\n    updateCurrentPage(1);\n    expect(getPaginationEl(0).hasClass('disabled')).toBe(true);\n  });\n\n  it('disables the \"next\" link if current page is last', function() {\n    updateCurrentPage(5);\n    expect(getPaginationEl(-1).hasClass('disabled')).toBe(true);\n  });\n\n  it('changes currentPage if a page link is clicked', function() {\n    clickPaginationEl(2);\n    expect($rootScope.currentPage).toBe(2);\n  });\n\n  it('changes currentPage if the \"previous\" link is clicked', function() {\n    clickPaginationEl(0);\n    expect($rootScope.currentPage).toBe(2);\n  });\n\n  it('changes currentPage if the \"next\" link is clicked', function() {\n    clickPaginationEl(-1);\n    expect($rootScope.currentPage).toBe(4);\n  });\n\n  it('does not change the current page on \"previous\" click if already at first page', function() {\n    updateCurrentPage(1);\n    clickPaginationEl(0);\n    expect($rootScope.currentPage).toBe(1);\n  });\n\n  it('does not change the current page on \"next\" click if already at last page', function() {\n    updateCurrentPage(5);\n    clickPaginationEl(-1);\n    expect($rootScope.currentPage).toBe(5);\n  });\n\n  it('changes the number of pages when `total-items` changes', function() {\n    $rootScope.total = 78; // 8 pages\n    $rootScope.$digest();\n\n    expect(getPaginationBarSize()).toBe(10);\n    expect(getPaginationEl(0).text()).toBe('Previous');\n    expect(getPaginationEl(-1).text()).toBe('Next');\n  });\n\n  it('does not \"break\" when `total-items` is undefined', function() {\n    $rootScope.total = undefined;\n    $rootScope.$digest();\n\n    expect(getPaginationBarSize()).toBe(3); // Previous, 1, Next\n    expect(getPaginationEl(0)).toHaveClass('disabled');\n    expect(getPaginationEl(1)).toHaveClass('active');\n    expect(getPaginationEl(2)).toHaveClass('disabled');\n  });\n\n  it('does not \"break\" when `total-items` is negative', function() {\n    $rootScope.total = -1;\n    $rootScope.$digest();\n\n    expect(getPaginationBarSize()).toBe(3); // Previous, 1, Next\n    expect(getPaginationEl(0)).toHaveClass('disabled');\n    expect(getPaginationEl(1)).toHaveClass('active');\n    expect(getPaginationEl(2)).toHaveClass('disabled');\n  });\n\n  it('does not change the current page when `total-items` changes but is valid', function() {\n    $rootScope.currentPage = 1;\n    $rootScope.total = 18; // 2 pages\n    $rootScope.$digest();\n\n    expect($rootScope.currentPage).toBe(1);\n  });\n\n  it('should blur a page link after it has been clicked', function() {\n    body.append(element);\n    var linkEl = getPaginationLinkEl(element, 2);\n\n    linkEl.focus();\n    expect(linkEl).toHaveFocus();\n\n    linkEl.click();\n    expect(linkEl).not.toHaveFocus();\n\n    element.remove();\n  });\n\n  it('should blur the \"next\" link after it has been clicked', function() {\n    body.append(element);\n    var linkEl = getPaginationLinkEl(element, -1);\n\n    linkEl.focus();\n    expect(linkEl).toHaveFocus();\n\n    linkEl.click();\n    expect(linkEl).not.toHaveFocus();\n\n    element.remove();\n  });\n\n  it('should blur the \"prev\" link after it has been clicked', function() {\n    body.append(element);\n    var linkEl = getPaginationLinkEl(element, 0);\n\n    linkEl.focus();\n    expect(linkEl).toHaveFocus();\n\n    linkEl.click();\n    expect(linkEl).not.toHaveFocus();\n\n    element.remove();\n  });\n\n  describe('`items-per-page`', function() {\n    beforeEach(function() {\n      $rootScope.perpage = 5;\n      element = $compile('<ul uib-pagination total-items=\"total\" items-per-page=\"perpage\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('changes the number of pages', function() {\n      expect(getPaginationBarSize()).toBe(12);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n\n    it('changes the number of pages when changes', function() {\n      $rootScope.perpage = 20;\n      $rootScope.$digest();\n\n      expect(getPaginationBarSize()).toBe(5);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n\n    it('selects the last page when current page is too big', function() {\n      $rootScope.perpage = 30;\n      $rootScope.$digest();\n\n      expect($rootScope.currentPage).toBe(2);\n      expect(getPaginationBarSize()).toBe(4);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n\n    it('displays a single page when it is negative', function() {\n      $rootScope.perpage = -1;\n      $rootScope.$digest();\n\n      expect(getPaginationBarSize()).toBe(3);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(1).text()).toBe('1');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n  });\n\n  describe('executes `ng-change` expression', function() {\n    beforeEach(function() {\n      $rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" ng-change=\"selectPageHandler()\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('when an element is clicked', function() {\n      clickPaginationEl(2);\n      expect($rootScope.selectPageHandler).toHaveBeenCalled();\n    });\n  });\n\n  describe('when `page` is not a number', function() {\n    it('handles numerical string', function() {\n      updateCurrentPage('2');\n      expect(getPaginationEl(2)).toHaveClass('active');\n\n      updateCurrentPage('04');\n      expect(getPaginationEl(4)).toHaveClass('active');\n    });\n\n    it('defaults to 1 if non-numeric', function() {\n      updateCurrentPage('pizza');\n      expect(getPaginationEl(1)).toHaveClass('active');\n    });\n  });\n\n  describe('with `max-size` option', function() {\n    beforeEach(function() {\n      $rootScope.total = 98; // 10 pages\n      $rootScope.currentPage = 3;\n      $rootScope.maxSize = 5;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" max-size=\"maxSize\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains maxsize + 2 li elements', function() {\n      expect(getPaginationBarSize()).toBe($rootScope.maxSize + 2);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n\n    it('shows the page number even if it can\\'t be shown in the middle', function() {\n      updateCurrentPage(1);\n      expect(getPaginationEl(1)).toHaveClass('active');\n\n      updateCurrentPage(10);\n      expect(getPaginationEl(-2)).toHaveClass('active');\n    });\n\n    it('shows the page number in middle after the next link is clicked', function() {\n      updateCurrentPage(6);\n      clickPaginationEl(-1);\n\n      expect($rootScope.currentPage).toBe(7);\n      expect(getPaginationEl(3)).toHaveClass('active');\n      expect(getPaginationEl(3).text()).toBe(''+$rootScope.currentPage);\n    });\n\n    it('shows the page number in middle after the prev link is clicked', function() {\n      updateCurrentPage(7);\n      clickPaginationEl(0);\n\n      expect($rootScope.currentPage).toBe(6);\n      expect(getPaginationEl(3)).toHaveClass('active');\n      expect(getPaginationEl(3).text()).toBe(''+$rootScope.currentPage);\n    });\n\n    it('changes pagination bar size when max-size value changed', function() {\n      $rootScope.maxSize = 7;\n      $rootScope.$digest();\n      expect(getPaginationBarSize()).toBe(9);\n    });\n\n    it('sets the pagination bar size to num-pages, if max-size is greater than num-pages ', function() {\n      $rootScope.maxSize = 15;\n      $rootScope.$digest();\n      expect(getPaginationBarSize()).toBe(12);\n    });\n\n    it('should not change value of max-size expression, if max-size is greater than num-pages ', function() {\n      $rootScope.maxSize = 15;\n      $rootScope.$digest();\n      expect($rootScope.maxSize).toBe(15);\n    });\n\n    it('should not display page numbers, if max-size is zero', function() {\n      $rootScope.maxSize = 0;\n      $rootScope.$digest();\n      expect(getPaginationBarSize()).toBe(2);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n\n    it('should blur page link when visible range changes', function () {\n      body.append(element);\n      var linkEl = getPaginationLinkEl(element, 4);\n\n      linkEl.focus();\n      expect(linkEl).toHaveFocus();\n\n      linkEl.click();\n      expect(linkEl).not.toHaveFocus();\n\n      element.remove();\n    });\n  });\n\n  describe('with `force-ellipses` option', function() {\n    beforeEach(function() {\n      $rootScope.total = 98; // 10 pages\n      $rootScope.currentPage = 3;\n      $rootScope.maxSize = 5;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" max-size=\"maxSize\" force-ellipses=\"true\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains maxsize + 3 li elements', function() {\n      expect(getPaginationBarSize()).toBe($rootScope.maxSize + 3);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n      expect(getPaginationEl(-2).text()).toBe('...');\n    });\n\n    it('shows the page number in middle after the next link is clicked', function() {\n      updateCurrentPage(6);\n      clickPaginationEl(-1);\n\n      expect($rootScope.currentPage).toBe(7);\n      expect(getPaginationEl(4)).toHaveClass('active');\n      expect(getPaginationEl(4).text()).toBe(''+$rootScope.currentPage);\n    });\n\n    it('shows the page number in middle after the prev link is clicked', function() {\n      updateCurrentPage(7);\n      clickPaginationEl(0);\n\n      expect($rootScope.currentPage).toBe(6);\n      expect(getPaginationEl(4)).toHaveClass('active');\n      expect(getPaginationEl(4).text()).toBe(''+$rootScope.currentPage);\n    });\n\n    it('changes pagination bar size when max-size value changed', function() {\n      $rootScope.maxSize = 7;\n      $rootScope.$digest();\n      expect(getPaginationBarSize()).toBe(10);\n    });\n\n    it('should display an ellipsis on the right if the last displayed page\\'s number is less than the last page', function() {\n      updateCurrentPage(1);\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, ..., Next');\n    });\n\n    it('should display an ellipsis on the left if the first displayed page\\'s number is greater than 1', function() {\n      updateCurrentPage(10);\n      expect(getPaginationAsText()).toBe('Previous, ..., 6, 7, 8, 9, 10, Next');\n    });\n\n    it('should display both ellipsis\\' if the displayed range is in the middle', function() {\n      updateCurrentPage(5);\n      expect(getPaginationAsText()).toBe('Previous, ..., 3, 4, 5, 6, 7, ..., Next');\n    });\n\n    it('should not display any ellipses if the number of pages >= maxsize', function() {\n      $rootScope.maxSize = 10;\n      $rootScope.$digest();\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Next');\n    });\n  });\n\n  describe('with `boundary-link-numbers` option', function() {\n    beforeEach(function() {\n      $rootScope.total = 98; // 10 pages\n      $rootScope.currentPage = 3;\n      $rootScope.maxSize = 5;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" max-size=\"maxSize\" boundary-link-numbers=\"true\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains maxsize + 4 li elements', function() {\n      expect(getPaginationBarSize()).toBe($rootScope.maxSize + 4);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n      expect(getPaginationEl(-2).text()).toBe('10');\n      expect(getPaginationEl(-3).text()).toBe('...');\n    });\n\n    it('shows the page number in middle after the next link is clicked', function() {\n      updateCurrentPage(6);\n      clickPaginationEl(-1);\n\n      expect($rootScope.currentPage).toBe(7);\n      expect(getPaginationEl(5)).toHaveClass('active');\n      expect(getPaginationEl(5).text()).toBe(''+$rootScope.currentPage);\n    });\n\n    it('shows the page number in middle after the prev link is clicked', function() {\n      updateCurrentPage(7);\n      clickPaginationEl(0);\n\n      expect($rootScope.currentPage).toBe(6);\n      expect(getPaginationEl(5)).toHaveClass('active');\n      expect(getPaginationEl(5).text()).toBe(''+$rootScope.currentPage);\n    });\n\n    it('changes pagination bar size when max-size value changed', function() {\n      $rootScope.maxSize = 7;\n      $rootScope.$digest();\n      expect(getPaginationBarSize()).toBe(11);\n    });\n\n    it('should display an ellipsis on the right if the last displayed page\\'s number is less than the last page', function() {\n      updateCurrentPage(1);\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, ..., 10, Next');\n    });\n\n    it('should display an ellipsis on the left if the first displayed page\\'s number is greater than 1', function() {\n      updateCurrentPage(10);\n      expect(getPaginationAsText()).toBe('Previous, 1, ..., 6, 7, 8, 9, 10, Next');\n    });\n\n    it('should display both ellipses if the displayed range is in the middle', function() {\n      $rootScope.maxSize = 3;\n      $rootScope.$digest();\n      updateCurrentPage(6);\n      expect(getPaginationAsText()).toBe('Previous, 1, ..., 5, 6, 7, ..., 10, Next');\n    });\n\n    it('should not display any ellipses if the number of pages >= maxsize', function() {\n      $rootScope.maxSize = 10;\n      $rootScope.$digest();\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Next');\n    });\n\n    it('should not display an ellipsis on the left if the start page is 2', function() {\n      updateCurrentPage(4);\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, 6, ..., 10, Next');\n    });\n\n    it('should not display an ellipsis on the left if the start page is 3', function() {\n      updateCurrentPage(5);\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, 6, 7, ..., 10, Next');\n    });\n\n    it('should not display an ellipsis on the right if the end page is totalPages - 1', function() {\n      updateCurrentPage(7);\n      expect(getPaginationAsText()).toBe('Previous, 1, ..., 5, 6, 7, 8, 9, 10, Next');\n    });\n\n    it('should not display an ellipsis on the right if the end page is totalPages - 2', function() {\n      updateCurrentPage(6);\n      expect(getPaginationAsText()).toBe('Previous, 1, ..., 4, 5, 6, 7, 8, 9, 10, Next');\n    });\n\n    it('should not display any ellipses if the number of pages <= maxsize + 4 and current page is in center', function() {\n      $rootScope.total = 88; // 9 pages\n      $rootScope.$digest();\n      updateCurrentPage(5);\n      expect(getPaginationAsText()).toBe('Previous, 1, 2, 3, 4, 5, 6, 7, 8, 9, Next');\n    });\n  });\n\n  describe('with `max-size` option & no `rotate`', function() {\n    beforeEach(function() {\n      $rootScope.total = 115; // 12 pages\n      $rootScope.currentPage = 7;\n      $rootScope.maxSize = 5;\n      $rootScope.rotate = false;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" max-size=\"maxSize\" rotate=\"rotate\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains maxsize + 4 elements', function() {\n      expect(getPaginationBarSize()).toBe($rootScope.maxSize + 4);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(1).text()).toBe('...');\n      expect(getPaginationEl(2).text()).toBe('6');\n      expect(getPaginationEl(-3).text()).toBe('10');\n      expect(getPaginationEl(-2).text()).toBe('...');\n      expect(getPaginationEl(-1).text()).toBe('Next');\n    });\n\n    it('shows only the next ellipsis element on first page set', function() {\n      updateCurrentPage(3);\n      expect(getPaginationEl(1).text()).toBe('1');\n      expect(getPaginationEl(-3).text()).toBe('5');\n      expect(getPaginationEl(-2).text()).toBe('...');\n    });\n\n    it('shows only the previous ellipsis element on last page set', function() {\n      updateCurrentPage(12);\n      expect(getPaginationBarSize()).toBe(5);\n      expect(getPaginationEl(1).text()).toBe('...');\n      expect(getPaginationEl(2).text()).toBe('11');\n      expect(getPaginationEl(-2).text()).toBe('12');\n    });\n\n    it('moves to the previous set when first ellipsis is clicked', function() {\n      expect(getPaginationEl(1).text()).toBe('...');\n\n      clickPaginationEl(1);\n\n      expect($rootScope.currentPage).toBe(5);\n      expect(getPaginationEl(-3)).toHaveClass('active');\n    });\n\n    it('moves to the next set when last ellipsis is clicked', function() {\n      expect(getPaginationEl(-2).text()).toBe('...');\n\n      clickPaginationEl(-2);\n\n      expect($rootScope.currentPage).toBe(11);\n      expect(getPaginationEl(2)).toHaveClass('active');\n    });\n\n    it('should not display page numbers, if max-size is zero', function() {\n      $rootScope.maxSize = 0;\n      $rootScope.$digest();\n\n      expect(getPaginationBarSize()).toBe(2);\n      expect(getPaginationEl(0).text()).toBe('Previous');\n      expect(getPaginationEl(1).text()).toBe('Next');\n    });\n  });\n\n  describe('pagination directive with `boundary-links`', function() {\n    beforeEach(function() {\n      element = $compile('<ul uib-pagination boundary-links=\"true\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains num-pages + 4 li elements', function() {\n      expect(getPaginationBarSize()).toBe(9);\n      expect(getPaginationEl(0).text()).toBe('First');\n      expect(getPaginationEl(1).text()).toBe('Previous');\n      expect(getPaginationEl(-2).text()).toBe('Next');\n      expect(getPaginationEl(-1).text()).toBe('Last');\n    });\n\n    it('has first and last li elements visible', function() {\n      expect(getPaginationEl(0).css('display')).not.toBe('none');\n      expect(getPaginationEl(-1).css('display')).not.toBe('none');\n    });\n\n\n    it('disables the \"first\" & \"previous\" link if current page is 1', function() {\n      updateCurrentPage(1);\n\n      expect(getPaginationEl(0)).toHaveClass('disabled');\n      expect(getPaginationEl(1)).toHaveClass('disabled');\n    });\n\n    it('disables the \"last\" & \"next\" link if current page is num-pages', function() {\n      updateCurrentPage(5);\n\n      expect(getPaginationEl(-2)).toHaveClass('disabled');\n      expect(getPaginationEl(-1)).toHaveClass('disabled');\n    });\n\n    it('changes currentPage if the \"first\" link is clicked', function() {\n      clickPaginationEl(0);\n      expect($rootScope.currentPage).toBe(1);\n    });\n\n    it('changes currentPage if the \"last\" link is clicked', function() {\n      clickPaginationEl(-1);\n      expect($rootScope.currentPage).toBe(5);\n    });\n\n    it('does not change the current page on \"first\" click if already at first page', function() {\n      updateCurrentPage(1);\n      clickPaginationEl(0);\n      expect($rootScope.currentPage).toBe(1);\n    });\n\n    it('does not change the current page on \"last\" click if already at last page', function() {\n      updateCurrentPage(5);\n      clickPaginationEl(-1);\n      expect($rootScope.currentPage).toBe(5);\n    });\n\n    it('changes \"first\" & \"last\" text from attributes', function() {\n      element = $compile('<ul uib-pagination boundary-links=\"true\" first-text=\"<<<\" last-text=\">>>\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationEl(0).text()).toBe('<<<');\n      expect(getPaginationEl(-1).text()).toBe('>>>');\n    });\n\n    it('changes \"previous\" & \"next\" text from attributes', function() {\n      element = $compile('<ul uib-pagination boundary-links=\"true\" previous-text=\"<<\" next-text=\">>\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationEl(1).text()).toBe('<<');\n      expect(getPaginationEl(-2).text()).toBe('>>');\n    });\n\n    it('changes \"first\" & \"last\" text from interpolated attributes', function() {\n      $rootScope.myfirstText = '<<<';\n      $rootScope.mylastText = '>>>';\n      element = $compile('<ul uib-pagination boundary-links=\"true\" first-text=\"{{myfirstText}}\" last-text=\"{{mylastText}}\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationEl(0).text()).toBe('<<<');\n      expect(getPaginationEl(-1).text()).toBe('>>>');\n    });\n\n    it('changes \"previous\" & \"next\" text from interpolated attributes', function() {\n      $rootScope.previousText = '<<';\n      $rootScope.nextText = '>>';\n      element = $compile('<ul uib-pagination boundary-links=\"true\" previous-text=\"{{previousText}}\" next-text=\"{{nextText}}\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationEl(1).text()).toBe('<<');\n      expect(getPaginationEl(-2).text()).toBe('>>');\n    });\n\n    it('should blur the \"first\" link after it has been clicked', function() {\n      body.append(element);\n      var linkEl = getPaginationLinkEl(element, 0);\n\n      linkEl.focus();\n      expect(linkEl).toHaveFocus();\n\n      linkEl.click();\n      expect(linkEl).not.toHaveFocus();\n\n      element.remove();\n    });\n\n    it('should blur the \"last\" link after it has been clicked', function() {\n      body.append(element);\n      var linkEl = getPaginationLinkEl(element, -1);\n\n      linkEl.focus();\n      expect(linkEl).toHaveFocus();\n\n      linkEl.click();\n      expect(linkEl).not.toHaveFocus();\n\n      element.remove();\n    });\n  });\n\n  describe('pagination directive with just number links', function() {\n    beforeEach(function() {\n      element = $compile('<ul uib-pagination direction-links=\"false\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains num-pages li elements', function() {\n      expect(getPaginationBarSize()).toBe(5);\n      expect(getPaginationEl(0).text()).toBe('1');\n      expect(getPaginationEl(-1).text()).toBe('5');\n    });\n\n    it('has the number of the page as text in each page item', function() {\n      for(var i = 0; i < 5; i++) {\n        expect(getPaginationEl(i).text()).toEqual(''+(i+1));\n      }\n    });\n\n    it('sets the current page to be active', function() {\n      expect(getPaginationEl(2)).toHaveClass('active');\n    });\n\n    it('does not disable the \"1\" link if current page is 1', function() {\n      updateCurrentPage(1);\n\n      expect(getPaginationEl(0)).not.toHaveClass('disabled');\n      expect(getPaginationEl(0)).toHaveClass('active');\n    });\n\n    it('does not disable the \"last\" link if current page is last page', function() {\n      updateCurrentPage(5);\n\n      expect(getPaginationEl(-1)).not.toHaveClass('disabled');\n      expect(getPaginationEl(-1)).toHaveClass('active');\n    });\n\n    it('changes currentPage if a page link is clicked', function() {\n      clickPaginationEl(1);\n      expect($rootScope.currentPage).toBe(2);\n    });\n\n    it('changes the number of items when total items changes', function() {\n      $rootScope.total = 73; // 8 pages\n      $rootScope.$digest();\n\n      expect(getPaginationBarSize()).toBe(8);\n      expect(getPaginationEl(0).text()).toBe('1');\n      expect(getPaginationEl(-1).text()).toBe('8');\n    });\n  });\n\n  describe('with just boundary & number links', function() {\n    beforeEach(function() {\n      $rootScope.directions = false;\n      element = $compile('<ul uib-pagination boundary-links=\"true\" direction-links=\"directions\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains number of pages + 2 li elements', function() {\n      expect(getPaginationBarSize()).toBe(7);\n      expect(getPaginationEl(0).text()).toBe('First');\n      expect(getPaginationEl(1).text()).toBe('1');\n      expect(getPaginationEl(-2).text()).toBe('5');\n      expect(getPaginationEl(-1).text()).toBe('Last');\n    });\n\n    it('disables the \"first\" & activates \"1\" link if current page is 1', function() {\n      updateCurrentPage(1);\n\n      expect(getPaginationEl(0)).toHaveClass('disabled');\n      expect(getPaginationEl(1)).not.toHaveClass('disabled');\n      expect(getPaginationEl(1)).toHaveClass('active');\n    });\n\n    it('disables the \"last\" & \"next\" link if current page is num-pages', function() {\n      updateCurrentPage(5);\n\n      expect(getPaginationEl(-2)).toHaveClass('active');\n      expect(getPaginationEl(-2)).not.toHaveClass('disabled');\n      expect(getPaginationEl(-1)).toHaveClass('disabled');\n    });\n  });\n\n  describe('`num-pages`', function() {\n    beforeEach(function() {\n      $rootScope.numpg = null;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" num-pages=\"numpg\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('equals to total number of pages', function() {\n      expect($rootScope.numpg).toBe(5);\n    });\n\n    it('changes when total number of pages change', function() {\n      $rootScope.total = 73; // 8 pages\n      $rootScope.$digest();\n      expect($rootScope.numpg).toBe(8);\n    });\n\n    it('shows minimun one page if total items are not defined and does not break binding', function() {\n      $rootScope.total = undefined;\n      $rootScope.$digest();\n      expect($rootScope.numpg).toBe(1);\n\n      $rootScope.total = 73; // 8 pages\n      $rootScope.$digest();\n      expect($rootScope.numpg).toBe(8);\n    });\n  });\n\n  describe('setting `paginationConfig`', function() {\n    var originalConfig, paginationConfig;\n    beforeEach(inject(function(_uibPaginationConfig_) {\n      originalConfig = angular.copy(_uibPaginationConfig_);\n      paginationConfig = _uibPaginationConfig_;\n    }));\n    afterEach(inject(function(_uibPaginationConfig_) {\n      // return it to the original stat\n      angular.copy(originalConfig, _uibPaginationConfig_);\n    }));\n\n    it('should change paging text', function() {\n      paginationConfig.boundaryLinks = true;\n      paginationConfig.directionLinks = true;\n      paginationConfig.firstText = 'FI';\n      paginationConfig.previousText = 'PR';\n      paginationConfig.nextText = 'NE';\n      paginationConfig.lastText = 'LA';\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationEl(0).text()).toBe('FI');\n      expect(getPaginationEl(1).text()).toBe('PR');\n      expect(getPaginationEl(-2).text()).toBe('NE');\n      expect(getPaginationEl(-1).text()).toBe('LA');\n    });\n\n    it('contains number of pages + 2 li elements', function() {\n      paginationConfig.itemsPerPage = 5;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationBarSize()).toBe(12);\n    });\n\n    it('should take maxSize defaults into account', function() {\n      paginationConfig.maxSize = 2;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getPaginationBarSize()).toBe(4);\n    });\n\n    it('should take forceEllipses defaults into account', function () {\n      paginationConfig.forceEllipses = true;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" max-size=\"2\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      // Should contain 2 nav buttons, 2 pages, and 2 ellipsis since the currentPage defaults to 3, which is in the middle\n      expect(getPaginationBarSize()).toBe(6);\n    });\n\n    it('should take boundaryLinkNumbers defaults into account', function () {\n      paginationConfig.boundaryLinkNumbers = true;\n      $rootScope.total = 88; // 9 pages\n      $rootScope.currentPage = 5;\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" max-size=\"3\"></ul>')($rootScope);\n      $rootScope.$digest();\n\n      // Should contain 2 nav buttons, 2 pages, 2 ellipsis, and 2 extra end numbers since the currentPage is in the middle\n      expect(getPaginationBarSize()).toBe(9);\n      expect(getPaginationAsText()).toBe('Previous, 1, ..., 4, 5, 6, ..., 9, Next');\n    });\n  });\n\n  describe('override configuration from attributes', function() {\n    beforeEach(function() {\n      $rootScope.pageLabel = function(id) {\n          return 'test_'+ id;\n      };\n      element = $compile('<ul uib-pagination boundary-links=\"true\" page-label=\"pageLabel($page)\" first-text=\"<<\" previous-text=\"<\" next-text=\">\" last-text=\">>\" total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('contains number of pages + 4 li elements', function() {\n      expect(getPaginationBarSize()).toBe(9);\n    });\n\n    it('should change paging text from attribute', function() {\n      expect(getPaginationEl(0).text()).toBe('<<');\n      expect(getPaginationEl(1).text()).toBe('<');\n      expect(getPaginationEl(-2).text()).toBe('>');\n      expect(getPaginationEl(-1).text()).toBe('>>');\n    });\n\n    it('has the label of the page as text in each page item', function() {\n      for (var i = 1; i <= 5; i++) {\n        // +1 because the first element is a <\n        expect(getPaginationEl(i+1).text()).toEqual('test_'+i);\n      }\n    });\n  });\n\n  describe('disabled with ngDisable', function() {\n    beforeEach(function() {\n      element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\" ng-disabled=\"disabled\"></ul>')($rootScope);\n      $rootScope.currentPage = 3;\n      $rootScope.$digest();\n    });\n\n    it('should not respond to clicking', function() {\n      setDisabled(true);\n      clickPaginationEl(2);\n      expect($rootScope.currentPage).toBe(3);\n      setDisabled(false);\n      clickPaginationEl(2);\n      expect($rootScope.currentPage).toBe(2);\n    });\n\n    it('should change the class of all buttons except selected one', function() {\n      setDisabled(false);\n      expect(getPaginationEl(3).hasClass('active')).toBe(true);\n      expect(getPaginationEl(4).hasClass('active')).toBe(false);\n      setDisabled(true);\n      expect(getPaginationEl(3).hasClass('disabled')).toBe(false);\n      expect(getPaginationEl(4).hasClass('disabled')).toBe(true);\n    });\n  });\n});\n\ndescribe('pagination directive', function() {\n  var $compile, $rootScope, element;\n  beforeEach(module('ui.bootstrap.pagination'));\n  beforeEach(module('uib/template/pagination/pagination.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n  }));\n\n  it('should retain the model value when total-items starts as undefined', function() {\n    $rootScope.currentPage = 5;\n    $rootScope.total = undefined;\n    element = $compile('<ul uib-pagination total-items=\"total\" ng-model=\"currentPage\"></ul>')($rootScope);\n    $rootScope.$digest();\n\n    expect($rootScope.currentPage).toBe(5);\n\n    $rootScope.total = 100;\n    $rootScope.$digest();\n\n    expect($rootScope.currentPage).toBe(5);\n  });\n});\n"
  },
  {
    "path": "src/paging/index.js",
    "content": "require('./paging');\n\nvar MODULE_NAME = 'ui.bootstrap.module.paging';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.paging']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/paging/paging.js",
    "content": "angular.module('ui.bootstrap.paging', [])\n/**\n * Helper internal service for generating common controller code between the\n * pager and pagination components\n */\n.factory('uibPaging', ['$parse', function($parse) {\n  return {\n    create: function(ctrl, $scope, $attrs) {\n      ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;\n      ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl\n      ctrl._watchers = [];\n\n      ctrl.init = function(ngModelCtrl, config) {\n        ctrl.ngModelCtrl = ngModelCtrl;\n        ctrl.config = config;\n\n        ngModelCtrl.$render = function() {\n          ctrl.render();\n        };\n\n        if ($attrs.itemsPerPage) {\n          ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {\n            ctrl.itemsPerPage = parseInt(value, 10);\n            $scope.totalPages = ctrl.calculateTotalPages();\n            ctrl.updatePage();\n          }));\n        } else {\n          ctrl.itemsPerPage = config.itemsPerPage;\n        }\n\n        $scope.$watch('totalItems', function(newTotal, oldTotal) {\n          if (angular.isDefined(newTotal) || newTotal !== oldTotal) {\n            $scope.totalPages = ctrl.calculateTotalPages();\n            ctrl.updatePage();\n          }\n        });\n      };\n\n      ctrl.calculateTotalPages = function() {\n        var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);\n        return Math.max(totalPages || 0, 1);\n      };\n\n      ctrl.render = function() {\n        $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;\n      };\n\n      $scope.selectPage = function(page, evt) {\n        if (evt) {\n          evt.preventDefault();\n        }\n\n        var clickAllowed = !$scope.ngDisabled || !evt;\n        if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {\n          if (evt && evt.target) {\n            evt.target.blur();\n          }\n          ctrl.ngModelCtrl.$setViewValue(page);\n          ctrl.ngModelCtrl.$render();\n        }\n      };\n\n      $scope.getText = function(key) {\n        return $scope[key + 'Text'] || ctrl.config[key + 'Text'];\n      };\n\n      $scope.noPrevious = function() {\n        return $scope.page === 1;\n      };\n\n      $scope.noNext = function() {\n        return $scope.page === $scope.totalPages;\n      };\n\n      ctrl.updatePage = function() {\n        ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable\n\n        if ($scope.page > $scope.totalPages) {\n          $scope.selectPage($scope.totalPages);\n        } else {\n          ctrl.ngModelCtrl.$render();\n        }\n      };\n\n      $scope.$on('$destroy', function() {\n        while (ctrl._watchers.length) {\n          ctrl._watchers.shift()();\n        }\n      });\n    }\n  };\n}]);\n"
  },
  {
    "path": "src/paging/test/paging.spec.js",
    "content": "describe('paging factory', function() {\n  var $rootScope, $scope, ctrl, attrs;\n\n  beforeEach(module('ui.bootstrap.paging'));\n  beforeEach(inject(function(_$rootScope_, uibPaging) {\n    $rootScope = _$rootScope_;\n    $scope = $rootScope.$new();\n    ctrl = {};\n    attrs = {};\n\n    uibPaging.create(ctrl, $scope, attrs);\n  }));\n\n  describe('init', function() {\n    var ngModelCtrl, config;\n\n    beforeEach(function() {\n      ngModelCtrl = {};\n      config = {\n        foo: 'bar',\n        itemsPerPage: 12\n      };\n    });\n\n    describe('without itemsPerPage', function() {\n      beforeEach(function() {\n        ctrl.init(ngModelCtrl, config);\n      });\n\n      it('should set the ngModel and config', function() {\n        expect(ctrl.ngModelCtrl).toBe(ngModelCtrl);\n        expect(ctrl.config).toBe(config);\n      });\n\n      it('should properly render the model', function() {\n        spyOn(ctrl, 'render');\n\n        ngModelCtrl.$render();\n\n        expect(ctrl.render).toHaveBeenCalled();\n      });\n\n      it('should set to default itemsPerPage', function() {\n        expect(ctrl.itemsPerPage).toBe(12);\n      });\n\n      it('should update the page when total items changes', function() {\n        spyOn(ctrl, 'calculateTotalPages').and.returnValue(5);\n        spyOn(ctrl, 'updatePage');\n        $rootScope.$digest();\n\n        expect(ctrl.calculateTotalPages.calls.count()).toBe(0);\n        expect(ctrl.updatePage.calls.count()).toBe(0);\n\n        $scope.totalItems = 10;\n        $rootScope.$digest();\n\n        expect(ctrl.calculateTotalPages.calls.count()).toBe(1);\n        expect(ctrl.updatePage.calls.count()).toBe(1);\n        expect($scope.totalPages).toBe(5);\n\n        $scope.totalItems = undefined;\n        $scope.totalPages = 2;\n        $rootScope.$digest();\n\n        expect(ctrl.calculateTotalPages.calls.count()).toBe(2);\n        expect(ctrl.updatePage.calls.count()).toBe(2);\n        expect($scope.totalPages).toBe(5);\n      });\n    });\n\n    describe('with itemsPerPage', function() {\n      beforeEach(function() {\n        attrs.itemsPerPage = 'abc';\n        $rootScope.abc = 10;\n\n        ctrl.init(ngModelCtrl, config);\n      });\n\n      it('should update the page when itemsPerPage changes', function() {\n        spyOn(ctrl, 'calculateTotalPages').and.returnValue(5);\n        spyOn(ctrl, 'updatePage');\n        $rootScope.$digest();\n\n        expect(ctrl.itemsPerPage).toBe(10);\n        expect($scope.totalPages).toBe(5);\n        expect(ctrl.updatePage).toHaveBeenCalled();\n      });\n    });\n  });\n\n  describe('calculate totalPages', function() {\n    it('when itemsPerPage is less than 1', function() {\n      ctrl.itemsPerPage = 0;\n      $scope.totalItems = 101;\n      expect(ctrl.calculateTotalPages()).toBe(1);\n    });\n\n    it('when itemsPerPage is greater than 1', function() {\n      ctrl.itemsPerPage = 10;\n      $scope.totalItems = 101;\n      expect(ctrl.calculateTotalPages()).toBe(11);\n    });\n  });\n\n  describe('render', function() {\n    it('should set page to 1 when invalid', function() {\n      ctrl.ngModelCtrl.$viewValue = 'abcd';\n      $scope.page = 10;\n\n      ctrl.render();\n\n      expect($scope.page).toBe(1);\n    });\n\n    it('should set page to view value when valid', function() {\n      ctrl.ngModelCtrl.$viewValue = '3';\n      $scope.page = 10;\n\n      ctrl.render();\n\n      expect($scope.page).toBe(3);\n    });\n  });\n\n  describe('select page', function() {\n    beforeEach(function() {\n      spyOn(ctrl.ngModelCtrl, '$setViewValue');\n      ctrl.ngModelCtrl.$render = jasmine.createSpy('ctrl.ngModelCtrl.$render');\n      $scope.page = 5;\n      $scope.totalPages = 20;\n    });\n\n    it('should change the page', function() {\n      $scope.selectPage(12);\n\n      expect(ctrl.ngModelCtrl.$setViewValue).toHaveBeenCalledWith(12);\n      expect(ctrl.ngModelCtrl.$render).toHaveBeenCalled();\n    });\n\n    it('should not change the page to one out of range', function() {\n      $scope.selectPage(-1);\n\n      expect(ctrl.ngModelCtrl.$setViewValue).not.toHaveBeenCalled();\n      expect(ctrl.ngModelCtrl.$render).not.toHaveBeenCalled();\n\n      $scope.selectPage(21);\n\n      expect(ctrl.ngModelCtrl.$setViewValue).not.toHaveBeenCalled();\n      expect(ctrl.ngModelCtrl.$render).not.toHaveBeenCalled();\n    });\n\n    describe('on click', function() {\n      var evt;\n\n      beforeEach(function() {\n        evt = {\n          preventDefault: jasmine.createSpy('evt.preventDefault'),\n          target: {\n            blur: jasmine.createSpy('evt.target.blur')\n          }\n        };\n      });\n\n      it('should prevent default behavior', function() {\n        $scope.selectPage(12, evt);\n\n        expect(evt.preventDefault).toHaveBeenCalled();\n      });\n\n      it('should not change the page if disabled and from an event', function() {\n        $scope.ngDisabled = true;\n\n        $scope.selectPage(12, evt);\n\n        expect(ctrl.ngModelCtrl.$setViewValue).not.toHaveBeenCalled();\n        expect(ctrl.ngModelCtrl.$render).not.toHaveBeenCalled();\n      });\n\n      it('should blur the element clicked', function() {\n        $scope.selectPage(12, evt);\n\n        expect(evt.target.blur).toHaveBeenCalled();\n      });\n    });\n  });\n\n  it('should get the text', function() {\n    $scope.fooText = 'bar';\n\n    expect($scope.getText('foo')).toBe('bar');\n  });\n\n  it('should get the default text', function() {\n    ctrl.config = {\n      fooText: 'bar'\n    };\n\n    expect($scope.getText('foo')).toBe('bar');\n  });\n\n  it('should disable previous button', function() {\n    $scope.page = 1;\n\n    expect($scope.noPrevious()).toBe(true);\n  });\n\n  it('should enable previous button', function() {\n    $scope.page = 2;\n\n    expect($scope.noPrevious()).toBe(false);\n  });\n\n  it('should disable next button', function() {\n    $scope.page = 10;\n    $scope.totalPages = 10;\n\n    expect($scope.noNext()).toBe(true);\n  });\n\n  it('should enable next button', function() {\n    $scope.page = 9;\n    $scope.totalPages = 10;\n\n    expect($scope.noNext()).toBe(false);\n  });\n\n  describe('update page', function() {\n    beforeEach(function() {\n      spyOn($scope, 'selectPage');\n      ctrl.ngModelCtrl.$render = jasmine.createSpy('ctrl.ngModelCtrl.$render');\n      ctrl.setNumPages = jasmine.createSpy('ctrl.setNumPages');\n      $scope.totalPages = 10;\n    });\n\n    it('should select the last page if page is above total', function() {\n      $scope.page = 12;\n\n      ctrl.updatePage();\n\n      expect(ctrl.setNumPages).toHaveBeenCalledWith($rootScope, 10);\n      expect($scope.selectPage).toHaveBeenCalledWith(10);\n      expect(ctrl.ngModelCtrl.$render).not.toHaveBeenCalled();\n    });\n\n    it('should execute render if page is within range', function() {\n      $scope.page = 5;\n\n      ctrl.updatePage();\n\n      expect(ctrl.setNumPages).toHaveBeenCalledWith($rootScope, 10);\n      expect($scope.selectPage).not.toHaveBeenCalled();\n      expect(ctrl.ngModelCtrl.$render).toHaveBeenCalled();\n    });\n  });\n\n  describe('gc', function() {\n    it('should clear watchers', function() {\n      var watcher1 = jasmine.createSpy('watcher1'),\n        watcher2 = jasmine.createSpy('watcher2');\n      ctrl._watchers = [watcher1, watcher2];\n\n      $scope.$destroy();\n\n      expect(ctrl._watchers.length).toBe(0);\n      expect(watcher1).toHaveBeenCalled();\n      expect(watcher2).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "src/popover/docs/demo.html",
    "content": "<div ng-controller=\"PopoverDemoCtrl\">\n    <h4>Dynamic</h4>\n    <div class=\"form-group\">\n      <label>Popup Text:</label>\n      <input type=\"text\" ng-model=\"dynamicPopover.content\" class=\"form-control\">\n    </div>\n    <div class=\"form-group\">\n      <label>Popup Title:</label>\n      <input type=\"text\" ng-model=\"dynamicPopover.title\" class=\"form-control\">\n    </div>\n    <div class=\"form-group\">\n      <label>Popup Template:</label>\n      <input type=\"text\" ng-model=\"dynamicPopover.templateUrl\" class=\"form-control\">\n    </div>\n    <button uib-popover=\"{{dynamicPopover.content}}\" popover-title=\"{{dynamicPopover.title}}\" type=\"button\" class=\"btn btn-default\">Dynamic Popover</button>\n\n    <button uib-popover-template=\"dynamicPopover.templateUrl\" popover-title=\"{{dynamicPopover.title}}\" type=\"button\" class=\"btn btn-default\">Popover With Template</button>\n\n    <script type=\"text/ng-template\" id=\"myPopoverTemplate.html\">\n        <div>{{dynamicPopover.content}}</div>\n        <div class=\"form-group\">\n          <label>Popup Title:</label>\n          <input type=\"text\" ng-model=\"dynamicPopover.title\" class=\"form-control\">\n        </div>\n    </script>\n    <hr />\n    <h4>Positional</h4>\n    <div class=\"form-group\">\n      <label>Popover placement</label>\n      <select class=\"form-control\" ng-model=\"placement.selected\" ng-options=\"o as o for o in placement.options\"></select>\n    </div>\n    <button popover-placement=\"{{placement.selected}}\" uib-popover=\"On the {{placement.selected}}\" type=\"button\" class=\"btn btn-default\">Popover {{placement.selected}}</button>\n\n    <hr />\n    <h4>Triggers</h4>\n    <p>\n      <button uib-popover=\"I appeared on mouse enter!\" popover-trigger=\"'mouseenter'\" type=\"button\" class=\"btn btn-default\">Mouseenter</button>\n    </p>\n    <input type=\"text\" value=\"Click me!\" uib-popover=\"I appeared on focus! Click away and I'll vanish...\"  popover-trigger=\"'focus'\" class=\"form-control\">\n\n    <hr />\n    <h4>Other</h4>\n    <button popover-animation=\"true\" uib-popover=\"I fade in and out!\" type=\"button\" class=\"btn btn-default\">fading</button>\n    <button uib-popover=\"I have a title!\" popover-title=\"The title.\" type=\"button\" class=\"btn btn-default\">title</button>\n    <button uib-popover=\"I am activated manually\" popover-is-open=\"popoverIsOpen\" ng-click=\"popoverIsOpen = !popoverIsOpen\" type=\"button\" class=\"btn btn-default\">Toggle popover</button>\n    <button uib-popover-html=\"htmlPopover\" class=\"btn btn-default\">HTML Popover</button>\n    <button uib-popover-html=\"'<b>HTML</b>, <i>inline</i>'\" class=\"btn btn-default\">HTML Popover (inline)</button>\n</div>\n"
  },
  {
    "path": "src/popover/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope, $sce) {\n  $scope.dynamicPopover = {\n    content: 'Hello, World!',\n    templateUrl: 'myPopoverTemplate.html',\n    title: 'Title'\n  };\n\n  $scope.placement = {\n    options: [\n      'top',\n      'top-left',\n      'top-right',\n      'bottom',\n      'bottom-left',\n      'bottom-right',\n      'left',\n      'left-top',\n      'left-bottom',\n      'right',\n      'right-top',\n      'right-bottom'\n    ],\n    selected: 'top'\n  };\n  \n  $scope.htmlPopover = $sce.trustAsHtml('<b style=\"color: red\">I can</b> have <div class=\"label label-success\">HTML</div> content');\n});\n"
  },
  {
    "path": "src/popover/docs/readme.md",
    "content": "A lightweight, extensible directive for fancy popover creation. The popover\ndirective supports multiple placements, optional transition animation, and more.\n\nLike the Bootstrap jQuery plugin, the popover **requires** the tooltip\nmodule.\n\n__Note to mobile developers__:  Please note that while popovers may work correctly on mobile devices (including tablets),\n  we have made the decision to not officially support such a use-case because it does not make sense from a UX perspective.\n\nThere are three versions of the popover: `uib-popover` and `uib-popover-template`, and `uib-popover-html`:\n\n* `uib-popover` -\n  Takes text only and will escape any HTML provided for the popover body.\n* `uib-popover-html`\n  <small class=\"badge\">$</small> -\n  Takes an expression that evaluates to an HTML string. Note that this HTML is not compiled. If compilation is required, please use the `uib-popover-template` attribute option instead.  *The user is responsible for ensuring the content is safe to put into the DOM!*\n* `uib-popover-template`\n  <small class=\"badge\">$</small> -\n  A URL representing the location of a template to use for the popover body. Note that the contents of this template need to be wrapped in a tag, e.g., `<div></div>`.\n\n### uib-popover-* settings\n\nAll these settings are available for the three types of popovers.\n\n* `popover-animation`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`, Config: `animation`)_ -\n  Should it fade in and out?\n\n* `popover-append-to-body`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `false`, Config: `appendToBody`)_ -\n  Should the popover be appended to '$body' instead of the parent element?\n\n* `popover-class` -\n  Custom class to be applied to the popover.\n\n* `popover-enable`\n  <small class=\"badge\">$</small>\n  _(Default: `true`)_ -\n  Is it enabled? It will enable or disable the configured popover-trigger.\n\n* `popover-is-open`\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether to show the popover.\n\n* `popover-placement`\n  <small class=\"badge\">C</small>\n  _(Default: `top`, Config: `placement`)_ -\n  Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: \"auto bottom-left\". The popover will attempt to position where it fits in the closest scrollable ancestor. Accepts:\n\n   * `top` - popover on top, horizontally centered on host element.\n   * `top-left` - popover on top, left edge aligned with host element left edge.\n   * `top-right` - popover on top, right edge aligned with host element right edge.\n   * `bottom` - popover on bottom, horizontally centered on host element.\n   * `bottom-left` - popover on bottom, left edge aligned with host element left edge.\n   * `bottom-right` - popover on bottom, right edge aligned with host element right edge.\n   * `left` - popover on left, vertically centered on host element.\n   * `left-top` - popover on left, top edge aligned with host element top edge.\n   * `left-bottom` - popover on left, bottom edge aligned with host element bottom edge.\n   * `right` - popover on right, vertically centered on host element.\n   * `right-top` - popover on right, top edge aligned with host element top edge.\n   * `right-bottom` - popover on right, bottom edge aligned with host element bottom edge.\n\n* `popover-popup-close-delay`\n  <small class=\"badge\">C</small>\n  _(Default: `0`, Config: `popupCloseDelay`)_ -\n  For how long should the popover remain open after the close trigger event?\n\n* `popover-popup-delay`\n  <small class=\"badge\">C</small>\n  _(Default: `0`, Config: `popupDelay`)_ -\n  Popup delay in milliseconds until it opens.\n\n* `popover-title` -\n   A string to display as a fancy title.\n\n* `popover-trigger`\n  <small class=\"badge\">$</small>\n  _(Default: `'click'`)_ -\n  What should trigger a show of the popover? Supports a space separated list of event names, or objects (see below).\n\n**Note:** To configure the tooltips, you need to do it on `$uibTooltipProvider` (also see below).\n\n### Triggers\n\nThe following show triggers are supported out of the box, along with their provided hide triggers:\n\n- `mouseenter`: `mouseleave`\n- `click`: `click`\n- `outsideClick`: `outsideClick`\n- `focus`: `blur`\n- `none`\n\nThe `outsideClick` trigger will cause the popover to toggle on click, and hide when anything else is clicked.\n\nFor any non-supported value, the trigger will be used to both show and hide the\npopover. Using the 'none' trigger will disable the internal trigger(s), one can\nthen use the `popover-is-open` attribute exclusively to show and hide the popover.\n\n### $uibTooltipProvider\n\nThrough the `$uibTooltipProvider`, you can change the way tooltips and popovers\nbehave by default; the attributes above always take precedence. The following\nmethods are available:\n\n* `setTriggers(obj)`\n  _(Example: `{ 'openTrigger': 'closeTrigger' }`)_ -\n  Extends the default trigger mappings mentioned above with mappings of your own.\n\n* `options(obj)` -\n  Provide a set of defaults for certain tooltip and popover attributes. Currently supports the ones with the <small class=\"badge\">C</small> badge.\n\n### Known issues\n\nFor Safari 7+ support, if you want to use **focus** `popover-trigger`, you need to use an anchor tag with a tab index. For example:\n\n```\n<a tabindex=\"0\" uib-popover=\"Test\" popover-trigger=\"focus\" class=\"btn btn-default\">\n  Click Me\n</a>\n```\n"
  },
  {
    "path": "src/popover/index-nocss.js",
    "content": "require('../tooltip/index-nocss.js');\nrequire('../../template/popover/popover.html.js');\nrequire('../../template/popover/popover-html.html.js');\nrequire('../../template/popover/popover-template.html.js');\nrequire('./popover');\n\nvar MODULE_NAME = 'ui.bootstrap.module.popover';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.popover', 'uib/template/popover/popover.html', 'uib/template/popover/popover-html.html', 'uib/template/popover/popover-template.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/popover/index.js",
    "content": "require('../tooltip/tooltip.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/popover/popover.js",
    "content": "/**\n * The following features are still outstanding: popup delay, animation as a\n * function, placement as a function, inside, support for more triggers than\n * just mouse enter/leave, and selector delegatation.\n */\nangular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])\n\n.directive('uibPopoverTemplatePopup', function() {\n  return {\n    restrict: 'A',\n    scope: { uibTitle: '@', contentExp: '&', originScope: '&' },\n    templateUrl: 'uib/template/popover/popover-template.html'\n  };\n})\n\n.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {\n  return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {\n    useContentExp: true\n  });\n}])\n\n.directive('uibPopoverHtmlPopup', function() {\n  return {\n    restrict: 'A',\n    scope: { contentExp: '&', uibTitle: '@' },\n    templateUrl: 'uib/template/popover/popover-html.html'\n  };\n})\n\n.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {\n  return $uibTooltip('uibPopoverHtml', 'popover', 'click', {\n    useContentExp: true\n  });\n}])\n\n.directive('uibPopoverPopup', function() {\n  return {\n    restrict: 'A',\n    scope: { uibTitle: '@', content: '@' },\n    templateUrl: 'uib/template/popover/popover.html'\n  };\n})\n\n.directive('uibPopover', ['$uibTooltip', function($uibTooltip) {\n  return $uibTooltip('uibPopover', 'popover', 'click');\n}]);\n"
  },
  {
    "path": "src/popover/test/popover-html.spec.js",
    "content": "describe('popover', function() {\n  var elm,\n      elmBody,\n      scope,\n      elmScope,\n      tooltipScope;\n\n  // load the popover code\n  beforeEach(module('ui.bootstrap.popover'));\n\n  // load the template\n  beforeEach(module('uib/template/popover/popover-html.html'));\n\n  beforeEach(inject(function($rootScope, $compile, $sce, _$document_) {\n    $document = _$document_;\n    elmBody = angular.element(\n      '<div><span uib-popover-html=\"template\">Selector Text</span></div>'\n    );\n\n    scope = $rootScope;\n    scope.template = $sce.trustAsHtml('<span>My template</span>');\n    $compile(elmBody)(scope);\n    scope.$digest();\n    $document.find('body').append(elmBody);\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  afterEach(function() {\n    $document.off('keypress');\n  });\n\n  it('should not be open initially', inject(function() {\n    expect(tooltipScope.isOpen).toBe(false);\n\n    // We can only test *that* the popover-popup element wasn't created as the\n    // implementation is templated and replaced.\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should open on click', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    // We can only test *that* the popover-popup element was created as the\n    // implementation is templated and replaced.\n    expect(elmBody.children().length).toBe(2);\n  }));\n\n  it('should close on second click', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(false);\n  }));\n\n  it('should not open on click if template is empty', inject(function() {\n    scope.template = null;\n    scope.$digest();\n\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(false);\n\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should show updated text', inject(function($sce) {\n    scope.template = $sce.trustAsHtml('<span>My template</span>');\n    scope.$digest();\n\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    expect(elmBody.children().eq(1).text().trim()).toBe('My template');\n\n    scope.template = $sce.trustAsHtml('<span>Another template</span>');\n    scope.$digest();\n\n    expect(elmBody.children().eq(1).text().trim()).toBe('Another template');\n  }));\n\n  it('should hide popover when template becomes empty', inject(function($timeout) {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    $timeout.flush(0);\n    expect(tooltipScope.isOpen).toBe(true);\n\n    scope.template = '';\n    scope.$digest();\n\n    expect(tooltipScope.isOpen).toBe(false);\n\n    $timeout.flush();\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n\n  it('should not unbind event handlers created by other directives - issue 456', inject(function($compile) {\n    scope.click = function() {\n      scope.clicked = !scope.clicked;\n    };\n\n    elmBody = angular.element(\n      '<div><input uib-popover-html=\"template\" ng-click=\"click()\" popover-trigger=\"mouseenter\"/></div>'\n    );\n    $compile(elmBody)(scope);\n    scope.$digest();\n\n    elm = elmBody.find('input');\n\n    elm.trigger('mouseenter');\n    tooltipScope.$digest();\n    elm.trigger('mouseleave');\n    tooltipScope.$digest();\n    expect(scope.clicked).toBeFalsy();\n\n    elm.click();\n    expect(scope.clicked).toBeTruthy();\n  }));\n\n  it('should popup with animate class by default', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    expect(elmBody.children().eq(1)).toHaveClass('fade');\n  }));\n\n  it('should popup without animate class when animation disabled', inject(function($compile) {\n    elmBody = angular.element(\n      '<div><span uib-popover-html=\"template\" popover-animation=\"false\">Selector Text</span></div>'\n    );\n\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n    expect(elmBody.children().eq(1)).not.toHaveClass('fade');\n  }));\n\n  it ('should display the title', inject(function($compile) {\n    elmBody = angular.element(\n      '<div><span uib-popover-html=\"template\" popover-title=\"popover title\">Selector Text</span></div>'\n    );\n\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n\n    elm.trigger('click');\n    scope.$digest();\n\n    var titleEl = elmBody.find('.popover-title');\n    expect(titleEl.text()).toBe('popover title');\n  }));\n\n  describe('supports options', function() {\n    describe('placement', function() {\n      it('can specify an alternative, valid placement', inject(function($compile) {\n        elmBody = angular.element(\n          '<div><span uib-popover-html=\"template\" popover-placement=\"left\">Trigger here</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n\n        expect(elmBody.children().length).toBe(2);\n        var ttipElement = elmBody.find('div.popover');\n        expect(ttipElement).toHaveClass('left');\n      }));\n\n    });\n\n    describe('class', function() {\n      it('can specify a custom class', inject(function($compile) {\n        elmBody = angular.element(\n          '<div><span uib-popover-html=\"template\" popover-class=\"custom\">Trigger here</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n\n        expect(elmBody.children().length).toBe(2);\n        var ttipElement = elmBody.find('div.popover');\n        expect(ttipElement).toHaveClass('custom');\n      }));\n    });\n  });\n});\n"
  },
  {
    "path": "src/popover/test/popover-template.spec.js",
    "content": "describe('popover template', function() {\n  var elm,\n      elmBody,\n      scope,\n      elmScope,\n      tooltipScope,\n      $document;\n\n  // load the popover code\n  beforeEach(module('ui.bootstrap.popover'));\n\n  // load the template\n  beforeEach(module('uib/template/popover/popover.html'));\n  beforeEach(module('uib/template/popover/popover-template.html'));\n\n  beforeEach(inject(function($templateCache) {\n    $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);\n  }));\n\n  beforeEach(inject(function($rootScope, $compile, _$document_) {\n    $document = _$document_;\n    elmBody = angular.element(\n      '<div><span uib-popover-template=\"templateUrl\">Selector Text</span></div>'\n    );\n\n    scope = $rootScope;\n    $compile(elmBody)(scope);\n    $document.find('body').append(elmBody);\n    scope.templateUrl = 'myUrl';\n\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  afterEach(function() {\n    $document.off('keypress');\n    elmBody.remove();\n  });\n\n  it('should open on click', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    expect(elmBody.children().length ).toBe(2);\n  }));\n\n  it('should not open on click if templateUrl is empty', inject(function() {\n    scope.templateUrl = null;\n    scope.$digest();\n\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(false);\n\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should show updated text', inject(function() {\n    scope.myTemplateText = 'some text';\n\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    scope.$digest();\n    expect(elmBody.children().eq(1).text().trim()).toBe('some text');\n\n    scope.myTemplateText = 'new text';\n    scope.$digest();\n\n    expect(elmBody.children().eq(1).text().trim()).toBe('new text');\n  }));\n\n  it('should hide popover when template becomes empty', inject(function($timeout) {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    $timeout.flush(0);\n    expect(tooltipScope.isOpen).toBe(true);\n\n    scope.templateUrl = '';\n    scope.$digest();\n\n    expect(tooltipScope.isOpen).toBe(false);\n\n    $timeout.flush();\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it ('should display the title', inject(function($compile) {\n    elmBody = angular.element(\n      '<div><span uib-popover-template=\"templateUrl\" popover-title=\"popover title\">Selector Text</span></div>'\n    );\n\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n\n    elm.trigger('click');\n    scope.$digest();\n\n    var titleEl = elmBody.find('.popover-title');\n    expect(titleEl.text()).toBe('popover title');\n  }));\n\n  describe('supports options', function() {\n    describe('placement', function() {\n      it('can specify an alternative, valid placement', inject(function($compile) {\n        elmBody = angular.element(\n          '<div><span uib-popover-template=\"templateUrl\" popover-placement=\"left\">Trigger</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n\n        expect(elmBody.children().length).toBe(2);\n        var ttipElement = elmBody.find('div.popover');\n        expect(ttipElement).toHaveClass('left');\n      }));\n\n    });\n\n    describe('class', function() {\n      it('can specify a custom class', inject(function($compile) {\n        elmBody = angular.element(\n          '<div><span uib-popover-template=\"templateUrl\" popover-class=\"custom\">Trigger</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n\n        expect(elmBody.children().length).toBe(2);\n        var ttipElement = elmBody.find('div.popover');\n        expect(ttipElement).toHaveClass('custom');\n      }));\n    });\n  });\n});\n"
  },
  {
    "path": "src/popover/test/popover.spec.js",
    "content": "describe('popover', function() {\n  var elm,\n      elmBody,\n      scope,\n      elmScope,\n      tooltipScope,\n      $document;\n\n  // load the popover code\n  beforeEach(module('ui.bootstrap.popover'));\n\n  // load the template\n  beforeEach(module('uib/template/popover/popover.html'));\n\n  beforeEach(inject(function($rootScope, $compile, _$document_) {\n    $document = _$document_;\n    elmBody = angular.element(\n      '<div><span uib-popover=\"popover text\">Selector Text</span></div>'\n    );\n\n    scope = $rootScope;\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  afterEach(function() {\n    $document.off('keypress');\n  });\n\n  it('should not be open initially', inject(function() {\n    expect(tooltipScope.isOpen).toBe(false);\n\n    // We can only test *that* the popover-popup element wasn't created as the\n    // implementation is templated and replaced.\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should open on click', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    // We can only test *that* the popover-popup element was created as the\n    // implementation is templated and replaced.\n    expect(elmBody.children().length).toBe(2);\n  }));\n\n  it('should close on second click', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(false);\n  }));\n\n  it('should not unbind event handlers created by other directives - issue 456', inject(function($compile) {\n    scope.click = function() {\n      scope.clicked = !scope.clicked;\n    };\n\n    elmBody = angular.element(\n      '<div><input uib-popover=\"Hello!\" ng-click=\"click()\" popover-trigger=\"mouseenter\"/></div>'\n    );\n    $compile(elmBody)(scope);\n    scope.$digest();\n\n    elm = elmBody.find('input');\n\n    elm.trigger('mouseenter');\n    elm.trigger('mouseleave');\n    expect(scope.clicked).toBeFalsy();\n\n    elm.click();\n    expect(scope.clicked).toBeTruthy();\n  }));\n\n  it('should popup with animate class by default', inject(function() {\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n\n    expect(elmBody.children().eq(1)).toHaveClass('fade');\n  }));\n\n  it('should popup without animate class when animation disabled', inject(function($compile) {\n    elmBody = angular.element(\n      '<div><span uib-popover=\"popover text\" popover-animation=\"false\">Selector Text</span></div>'\n    );\n\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n\n    elm.trigger('click');\n    tooltipScope.$digest();\n    expect(tooltipScope.isOpen).toBe(true);\n    expect(elmBody.children().eq(1)).not.toHaveClass('fade');\n  }));\n\n  it ('should display the title', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><span uib-popover=\"popover text\" popover-title=\"popover title\">Trigger here</span></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$digest();\n\n      elm = elmBody.find('span');\n      elm.trigger('click');\n      scope.$digest();\n\n      var titleEl = elmBody.find('.popover-title');\n      expect(titleEl.text()).toBe('popover title');\n  }));\n\n  it ('should display the content', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><span uib-popover=\"popover text\" popover-title=\"popover title\">Trigger here</span></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$digest();\n\n      elm = elmBody.find('span');\n      elm.trigger('click');\n      scope.$digest();\n\n      var contentEl = elmBody.find('.popover-content');\n      expect(contentEl.text()).toBe('popover text');\n  }));\n\n  describe('supports options', function() {\n    describe('placement', function() {\n      it('can specify an alternative, valid placement', inject(function($compile) {\n        elmBody = angular.element(\n          '<div><span uib-popover=\"popover text\" popover-placement=\"left\">Trigger here</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n\n        expect(elmBody.children().length).toBe(2);\n        var ttipElement = elmBody.find('div.popover');\n        expect(ttipElement).toHaveClass('left');\n      }));\n    });\n\n    describe('class', function() {\n      it('can specify a custom class', inject(function($compile) {\n        elmBody = angular.element(\n          '<div><span uib-popover=\"popover text\" popover-class=\"custom\">Trigger here</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n\n        expect(elmBody.children().length).toBe(2);\n        var ttipElement = elmBody.find('div.popover');\n        expect(ttipElement).toHaveClass('custom');\n      }));\n    });\n\n    describe('is-open', function() {\n      beforeEach(inject(function ($compile) {\n        scope.isOpen = false;\n        elmBody = angular.element(\n          '<div><span uib-popover=\"popover text\" popover-placement=\"left\" popover-is-open=\"isOpen\">Trigger here</span></div>'\n        );\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n      }));\n\n      it('should show and hide with the controller value', function() {\n        expect(tooltipScope.isOpen).toBe(false);\n        elmScope.isOpen = true;\n        elmScope.$digest();\n        expect(tooltipScope.isOpen).toBe(true);\n        elmScope.isOpen = false;\n        elmScope.$digest();\n        expect(tooltipScope.isOpen).toBe(false);\n      });\n\n      it('should update the controller value', function() {\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(elmScope.isOpen).toBe(true);\n        elm.trigger('click');\n        tooltipScope.$digest();\n        expect(elmScope.isOpen).toBe(false);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/position/docs/demo.html",
    "content": "<div ng-controller=\"PositionDemoCtrl\">\n  <h4>$uibPosition service</h4>\n  <div id=\"posdemoparent\" ng-style=\"{'overflow': (parentScrollable && 'scroll'), 'position': (parentRelative && 'relative')}\" style=\"border: 1px solid #ccc; padding: 15px;\">\n    <div class=\"checkbox\">\n      <label>\n        <input type=\"checkbox\" ng-model=\"parentScrollable\"> Parent scrollable\n      </label>\n    </div>\n    <div class=\"checkbox\">\n      <label>\n        <input type=\"checkbox\" ng-model=\"parentRelative\"> Parent relative\n      </label>\n    </div>\n    <button id=\"posdemobtn\" class=\"btn btn-default\" ng-click=\"getValues()\">Get values</button>\n\n    <div id=\"posdemodiv\" style=\"width: 100px; height: 100px; margin: 15px 0; padding: 10px; background-color: #f8f8f8; border: 1px solid #ccc;\">\n      Demo element\n    </div>\n  </div>\n  <br />\n  offsetParent: {{elemVals.offsetParent}}\n  <br />\n  scrollParent: {{elemVals.scrollParent}}\n  <br />\n  scrollbarWidth: {{scrollbarWidth}}\n  <br />\n  position: {{elemVals.position}}\n  <br />\n  offset: {{elemVals.offset}}\n  <br />\n  viewportOffset: {{elemVals.viewportOffset}}\n  <br />\n  positionElements: {{elemVals.positionElements}}\n</div>"
  },
  {
    "path": "src/position/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('PositionDemoCtrl', function ($scope, $window, $uibPosition) {\n\n    $scope.elemVals = {};\n    $scope.parentScrollable = true;\n    $scope.parentRelative = true;\n\n    $scope.getValues = function() {\n      var divEl = $window.document.querySelector('#posdemodiv');\n      var btnEl = $window.document.querySelector('#posdemobtn');\n\n      var offsetParent = $uibPosition.offsetParent(divEl);\n      $scope.elemVals.offsetParent = 'type: ' + offsetParent.tagName + ', id: ' + offsetParent.id;\n\n      var scrollParent = $uibPosition.scrollParent(divEl);\n      $scope.elemVals.scrollParent = 'type: ' + scrollParent.tagName + ', id: ' + scrollParent.id;\n\n      $scope.scrollbarWidth = $uibPosition.scrollbarWidth();\n\n      $scope.elemVals.position = $uibPosition.position(divEl);\n\n      $scope.elemVals.offset = $uibPosition.offset(divEl);\n\n      $scope.elemVals.viewportOffset = $uibPosition.viewportOffset(divEl);\n\n      $scope.elemVals.positionElements = $uibPosition.positionElements(btnEl, divEl, 'auto bottom-left');\n    };\n});"
  },
  {
    "path": "src/position/docs/readme.md",
    "content": "The `$uibPosition` service provides a set of DOM utilities used internally to absolute-position an element in relation to another element (tooltips, popovers, typeaheads etc...).\n\n#### getRawNode(element)\n\nTakes a jQuery/jqLite element and converts it to a raw DOM element.\n\n##### parameters\n\n* `element`\n  _(Type: `object`)_ -\n  The element to convert.\n\n##### returns\n\n* _(Type: `element`)_ -\n  A raw DOM element.\n\n#### parseStyle(element)\n\nParses a numeric style value to a number.  Strips units and will return 0 for invalid (NaN) numbers.\n\n##### parameters\n\n* `value`\n  _(Type: `string`)_ -\n  The style value to parse.\n\n##### returns\n\n* _(Type: `number`)_ -\n  The numeric value of the style property.\n\n#### offsetParent(element)\n\nGets the closest positioned ancestor.\n\n##### parameters\n\n* `element`\n  _(Type: `element`)_ -\n  The element to get the offset parent for.\n\n##### returns\n\n* _(Type: `element`)_ -\n  The closest positioned ancestor.\n\n#### scrollbarWidth(isBody)\n\nCalculates the browser scrollbar width and caches the result for future calls.  Concept from the TWBS measureScrollbar() function in [modal.js](https://github.com/twbs/bootstrap/blob/master/js/modal.js).\n\n##### parameters\n\n* `isBody`\n  _(Type: `boolean`, Default: `false`, optional)_ - Is the requested scrollbar width for the body/html element.  IE and Edge overlay the scrollbar on the body/html element and should be considered 0.\n\n##### returns\n\n* _(Type: `number`)_ -\n  The width of the browser scrollbar.\n\n#### scrollbarPadding(element)\n\nCalculates the padding required to replace the scrollbar on an element.\n\n##### parameters\n\n* 'element' _(Type: `element`)_ - The element to calculate the padding on (should be a scrollable element).\n\n##### returns\n\nAn object with the following properties:\n\n* `scrollbarWidth`\n  _(Type: `number`)_ -\n  The width of the scrollbar.\n\n* `widthOverflow`\n  _(Type: `boolean`)_ -\n  Whether the width is overflowing.\n\n* `right`\n  _(Type: `number`)_ -\n  The total right padding required to replace the scrollbar.\n\n* `originalRight`\n  _(Type: `number`)_ -\n  The oringal right padding on the element.\n\n* `heightOverflow`\n  _(Type: `boolean`)_ -\n  Whether the height is overflowing.\n\n* `bottom`\n  _(Type: `number`)_ -\n  The total bottom padding required to replace the scrollbar.\n\n* `originalBottom`\n  _(Type: `number`)_ -\n  The oringal bottom padding on the element.\n\n#### isScrollable(element, includeHidden)\n\nDetermines if an element is scrollable.\n\n##### parameters\n\n* `element`\n  _(Type: `element`)_ -\n  The element to check.\n\n* `includeHidden`\n  _(Type: `boolean`, Default: `false`, optional)_ - Should scroll style of 'hidden' be considered.\n\n##### returns\n\n* _(Type: `boolean`)_ -\n  Whether the element is scrollable.\n\n#### scrollParent(element, includeHidden, includeSelf)\n\nGets the closest scrollable ancestor.  Concept from the jQueryUI [scrollParent.js](https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js).\n\n##### parameters\n\n* `element`\n  _(Type: `element`)_ -\n  The element to get the closest scrollable ancestor for.\n\n* `includeHidden`\n  _(Type: `boolean`, Default: `false`, optional)_ - Should scroll style of 'hidden' be considered.\n\n* `includeSelf`\n  _(Type: `boolean`, Default: `false`, optional)_ - Should the element passed in be included in the scrollable lookup.\n\n##### returns\n\n* _(Type: `element`)_ -\n  The closest scrollable ancestor.\n\n#### position(element, includeMargins)\n\nA read-only equivalent of jQuery's [position](http://api.jquery.com/position/) function, distance to closest positioned ancestor. Does not account for margins by default like jQuery's position.\n\n##### parameters\n\n* `element` _(Type: `element`)_ -\n  The element to get the position for.\n\n* `includeMargins` _(Type: `boolean`, Default: `false`, optional)_ -\n  Should margins be accounted for.\n\n##### returns\n\nAn object with the following properties:\n\n* `width`\n  _(Type: `number`)_ -\n  The width of the element.\n\n* `height`\n  _(Type: `number`)_ -\n  The height of the element.\n\n* `top`\n  _(Type: `number`)_ -\n  Distance to top edge of offset parent.\n\n* `left`\n  _(Type: `number`)_ -\n  Distance to left edge of offset parent.\n\n#### offset(element)\n\nA read-only equivalent of jQuery's [offset](http://api.jquery.com/offset/) function, distance to viewport.\n\n##### parameters\n\n* `element`\n  _(Type: `element`)_ -\n  The element to get the offset for.\n\n##### returns\n\nAn object with the following properties:\n\n* `width`\n  _(Type: `number`)_ -\n  The width of the element.\n\n* `height`\n  _(Type: `number`)_ -\n  The height of the element.\n\n* `top`\n  _(Type: `number`)_ -\n  Distance to top edge of the viewport.\n\n* `left`\n  _(Type: `number`)_ -\n  Distance to left edge of the viewport.\n\n#### viewportOffset(element, useDocument, includePadding)\n\nGets the elements available space relative to the closest scrollable ancestor.  Accounts for padding, border, and scrollbar width.\nRight and bottom dimensions represent the distance to the respective edge of the viewport element, not the top and left edge.\nIf the element edge extends beyond the viewport, a negative value will be reported.\n\n##### parameters\n\n* `element`\n  _(Type: `element`)_ -\n  The element to get the viewport offset for.\n\n* `useDocument`\n  _(Type: `boolean`, Default: `false`, optional)_ -\n  Should the viewport be the document element instead of the first scrollable element.\n\n* `includePadding`\n  _(Type: `boolean`, Default: `true`, optional)_ -\n  Should the padding on the viewport element be accounted for, default is true.\n\n##### returns\n\nAn object with the following properties:\n\n* `top`\n  _(Type: `number`)_ -\n  Distance to top content edge of the viewport.\n\n* `bottom`\n  _(Type: `number`)_ -\n  Distance to bottom content edge of the viewport.\n\n* `left`\n  _(Type: `number`)_ -\n  Distance to left content edge of the viewport.\n\n* `right`\n  _(Type: `number`)_ -\n  Distance to right content edge of the viewport.\n\n#### parsePlacement(placement)\n\nGets an array of placement values parsed from a placement string. Along with the 'auto' indicator, supported placement strings are:\n\n* top: element on top, horizontally centered on host element.\n* top-left: element on top, left edge aligned with host element left edge.\n* top-right: element on top, right edge aligned with host element right edge.\n* bottom: element on bottom, horizontally centered on host element.\n* bottom-left: element on bottom, left edge aligned with host element left edge.\n* bottom-right: element on bottom, right edge aligned with host element right edge.\n* left: element on left, vertically centered on host element.\n* left-top: element on left, top edge aligned with host element top edge.\n* left-bottom: element on left, bottom edge aligned with host element bottom edge.\n* right: element on right, vertically centered on host element.\n* right-top: element on right, top edge aligned with host element top edge.\n* right-bottom: element on right, bottom edge aligned with host element bottom edge.\n\nA placement string with an 'auto' indicator is expected to be space separated from the placement, i.e: 'auto bottom-left'.\nIf the primary and secondary placement values do not match 'top, bottom, left, right' then 'top' will be the primary placement and\n'center' will be the secondary placement.  If 'auto' is passed, true will be returned as the 3rd value of the array.\n\n##### parameters\n\n* `placement`\n  _(Type: `string`, Example: `auto top-left`)_ -\n  The placement string to parse.\n\n##### returns\n\nAn array with the following values:\n\n* `[0]`\n  _(Type: `string`)_ -\n  The primary placement.\n\n* `[1]`\n  _(Type: `string`)_ -\n  The secondary placement.\n\n* `[2]`\n  _(Type: `boolean`)_ -\n  Is auto place enabled.\n\n#### positionElements(hostElement, targetElement, placement, appendToBody)\n\nGets gets coordinates for an element to be positioned relative to another element.\n\n##### parameters\n\n* `hostElement`\n  _(Type: `element`)_ -\n  The element to position against.\n\n* `targetElement`\n  _(Type: `element`)_ -\n  The element to position.\n\n* `placement`\n  _(Type: `string`, Default: `top`, optional)_ -\n  The placement for the target element.  See the parsePlacement() function for available options.  If 'auto' placement is used, the viewportOffset() function is used to decide where the targetElement will fit.\n\n* `appendToBody`\n  _(Type: `boolean`, Default: `false`, optional)_ -\n  Should the coordinates be cacluated from the body element.\n\n##### returns\n\nAn object with the following properties:\n\n* `top`\n  _(Type: `number`)_ -\n  The targetElement top value.\n\n* `left`\n  _(Type: `number`)_ -\n  The targetElement left value.\n\n* `right`\n  _(Type: `number`)_ -\n  The resolved placement with 'auto' removed.\n\n#### positionArrow(element, placement)\n\nPositions the tooltip and popover arrow elements when using placement options beyond the standard top, left, bottom, or right.\n\n##### parameters\n\n* `element`\n  _(Type: `element`)_ -\n  The element to position the arrow element for.\n\n* `placement`\n  _(Type: `string`)_ -\n  The placement for the element.\n"
  },
  {
    "path": "src/position/index-nocss.js",
    "content": "require('./position');\n\nvar MODULE_NAME = 'ui.bootstrap.module.position';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.position']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/position/index.js",
    "content": "require('./position.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/position/position.css",
    "content": ".uib-position-measure {\n  display: block !important;\n  visibility: hidden !important;\n  position: absolute !important;\n  top: -9999px !important;\n  left: -9999px !important;\n}\n\n.uib-position-scrollbar-measure {\n  position: absolute !important;\n  top: -9999px !important;\n  width: 50px !important;\n  height: 50px !important;\n  overflow: scroll !important;\n}\n\n.uib-position-body-scrollbar-measure {\n  overflow: scroll !important;\n}"
  },
  {
    "path": "src/position/position.js",
    "content": "angular.module('ui.bootstrap.position', [])\n\n/**\n * A set of utility methods for working with the DOM.\n * It is meant to be used where we need to absolute-position elements in\n * relation to another element (this is the case for tooltips, popovers,\n * typeahead suggestions etc.).\n */\n  .factory('$uibPosition', ['$document', '$window', function($document, $window) {\n    /**\n     * Used by scrollbarWidth() function to cache scrollbar's width.\n     * Do not access this variable directly, use scrollbarWidth() instead.\n     */\n    var SCROLLBAR_WIDTH;\n    /**\n     * scrollbar on body and html element in IE and Edge overlay\n     * content and should be considered 0 width.\n     */\n    var BODY_SCROLLBAR_WIDTH;\n    var OVERFLOW_REGEX = {\n      normal: /(auto|scroll)/,\n      hidden: /(auto|scroll|hidden)/\n    };\n    var PLACEMENT_REGEX = {\n      auto: /\\s?auto?\\s?/i,\n      primary: /^(top|bottom|left|right)$/,\n      secondary: /^(top|bottom|left|right|center)$/,\n      vertical: /^(top|bottom)$/\n    };\n    var BODY_REGEX = /(HTML|BODY)/;\n\n    return {\n\n      /**\n       * Provides a raw DOM element from a jQuery/jQLite element.\n       *\n       * @param {element} elem - The element to convert.\n       *\n       * @returns {element} A HTML element.\n       */\n      getRawNode: function(elem) {\n        return elem.nodeName ? elem : elem[0] || elem;\n      },\n\n      /**\n       * Provides a parsed number for a style property.  Strips\n       * units and casts invalid numbers to 0.\n       *\n       * @param {string} value - The style value to parse.\n       *\n       * @returns {number} A valid number.\n       */\n      parseStyle: function(value) {\n        value = parseFloat(value);\n        return isFinite(value) ? value : 0;\n      },\n\n      /**\n       * Provides the closest positioned ancestor.\n       *\n       * @param {element} element - The element to get the offest parent for.\n       *\n       * @returns {element} The closest positioned ancestor.\n       */\n      offsetParent: function(elem) {\n        elem = this.getRawNode(elem);\n\n        var offsetParent = elem.offsetParent || $document[0].documentElement;\n\n        function isStaticPositioned(el) {\n          return ($window.getComputedStyle(el).position || 'static') === 'static';\n        }\n\n        while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {\n          offsetParent = offsetParent.offsetParent;\n        }\n\n        return offsetParent || $document[0].documentElement;\n      },\n\n      /**\n       * Provides the scrollbar width, concept from TWBS measureScrollbar()\n       * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js\n       * In IE and Edge, scollbar on body and html element overlay and should\n       * return a width of 0.\n       *\n       * @returns {number} The width of the browser scollbar.\n       */\n      scrollbarWidth: function(isBody) {\n        if (isBody) {\n          if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {\n            var bodyElem = $document.find('body');\n            bodyElem.addClass('uib-position-body-scrollbar-measure');\n            BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;\n            BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;\n            bodyElem.removeClass('uib-position-body-scrollbar-measure');\n          }\n          return BODY_SCROLLBAR_WIDTH;\n        }\n\n        if (angular.isUndefined(SCROLLBAR_WIDTH)) {\n          var scrollElem = angular.element('<div class=\"uib-position-scrollbar-measure\"></div>');\n          $document.find('body').append(scrollElem);\n          SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;\n          SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;\n          scrollElem.remove();\n        }\n\n        return SCROLLBAR_WIDTH;\n      },\n\n      /**\n       * Provides the padding required on an element to replace the scrollbar.\n       *\n       * @returns {object} An object with the following properties:\n       *   <ul>\n       *     <li>**scrollbarWidth**: the width of the scrollbar</li>\n       *     <li>**widthOverflow**: whether the the width is overflowing</li>\n       *     <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>\n       *     <li>**rightOriginal**: the amount of right padding currently on the element</li>\n       *     <li>**heightOverflow**: whether the the height is overflowing</li>\n       *     <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>\n       *     <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>\n       *   </ul>\n       */\n      scrollbarPadding: function(elem) {\n        elem = this.getRawNode(elem);\n\n        var elemStyle = $window.getComputedStyle(elem);\n        var paddingRight = this.parseStyle(elemStyle.paddingRight);\n        var paddingBottom = this.parseStyle(elemStyle.paddingBottom);\n        var scrollParent = this.scrollParent(elem, false, true);\n        var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName));\n\n        return {\n          scrollbarWidth: scrollbarWidth,\n          widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,\n          right: paddingRight + scrollbarWidth,\n          originalRight: paddingRight,\n          heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,\n          bottom: paddingBottom + scrollbarWidth,\n          originalBottom: paddingBottom\n         };\n      },\n\n      /**\n       * Checks to see if the element is scrollable.\n       *\n       * @param {element} elem - The element to check.\n       * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,\n       *   default is false.\n       *\n       * @returns {boolean} Whether the element is scrollable.\n       */\n      isScrollable: function(elem, includeHidden) {\n        elem = this.getRawNode(elem);\n\n        var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;\n        var elemStyle = $window.getComputedStyle(elem);\n        return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);\n      },\n\n      /**\n       * Provides the closest scrollable ancestor.\n       * A port of the jQuery UI scrollParent method:\n       * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js\n       *\n       * @param {element} elem - The element to find the scroll parent of.\n       * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,\n       *   default is false.\n       * @param {boolean=} [includeSelf=false] - Should the element being passed be\n       * included in the scrollable llokup.\n       *\n       * @returns {element} A HTML element.\n       */\n      scrollParent: function(elem, includeHidden, includeSelf) {\n        elem = this.getRawNode(elem);\n\n        var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;\n        var documentEl = $document[0].documentElement;\n        var elemStyle = $window.getComputedStyle(elem);\n        if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {\n          return elem;\n        }\n        var excludeStatic = elemStyle.position === 'absolute';\n        var scrollParent = elem.parentElement || documentEl;\n\n        if (scrollParent === documentEl || elemStyle.position === 'fixed') {\n          return documentEl;\n        }\n\n        while (scrollParent.parentElement && scrollParent !== documentEl) {\n          var spStyle = $window.getComputedStyle(scrollParent);\n          if (excludeStatic && spStyle.position !== 'static') {\n            excludeStatic = false;\n          }\n\n          if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {\n            break;\n          }\n          scrollParent = scrollParent.parentElement;\n        }\n\n        return scrollParent;\n      },\n\n      /**\n       * Provides read-only equivalent of jQuery's position function:\n       * http://api.jquery.com/position/ - distance to closest positioned\n       * ancestor.  Does not account for margins by default like jQuery position.\n       *\n       * @param {element} elem - The element to caclulate the position on.\n       * @param {boolean=} [includeMargins=false] - Should margins be accounted\n       * for, default is false.\n       *\n       * @returns {object} An object with the following properties:\n       *   <ul>\n       *     <li>**width**: the width of the element</li>\n       *     <li>**height**: the height of the element</li>\n       *     <li>**top**: distance to top edge of offset parent</li>\n       *     <li>**left**: distance to left edge of offset parent</li>\n       *   </ul>\n       */\n      position: function(elem, includeMagins) {\n        elem = this.getRawNode(elem);\n\n        var elemOffset = this.offset(elem);\n        if (includeMagins) {\n          var elemStyle = $window.getComputedStyle(elem);\n          elemOffset.top -= this.parseStyle(elemStyle.marginTop);\n          elemOffset.left -= this.parseStyle(elemStyle.marginLeft);\n        }\n        var parent = this.offsetParent(elem);\n        var parentOffset = {top: 0, left: 0};\n\n        if (parent !== $document[0].documentElement) {\n          parentOffset = this.offset(parent);\n          parentOffset.top += parent.clientTop - parent.scrollTop;\n          parentOffset.left += parent.clientLeft - parent.scrollLeft;\n        }\n\n        return {\n          width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),\n          height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),\n          top: Math.round(elemOffset.top - parentOffset.top),\n          left: Math.round(elemOffset.left - parentOffset.left)\n        };\n      },\n\n      /**\n       * Provides read-only equivalent of jQuery's offset function:\n       * http://api.jquery.com/offset/ - distance to viewport.  Does\n       * not account for borders, margins, or padding on the body\n       * element.\n       *\n       * @param {element} elem - The element to calculate the offset on.\n       *\n       * @returns {object} An object with the following properties:\n       *   <ul>\n       *     <li>**width**: the width of the element</li>\n       *     <li>**height**: the height of the element</li>\n       *     <li>**top**: distance to top edge of viewport</li>\n       *     <li>**right**: distance to bottom edge of viewport</li>\n       *   </ul>\n       */\n      offset: function(elem) {\n        elem = this.getRawNode(elem);\n\n        var elemBCR = elem.getBoundingClientRect();\n        return {\n          width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),\n          height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),\n          top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),\n          left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))\n        };\n      },\n\n      /**\n       * Provides offset distance to the closest scrollable ancestor\n       * or viewport.  Accounts for border and scrollbar width.\n       *\n       * Right and bottom dimensions represent the distance to the\n       * respective edge of the viewport element.  If the element\n       * edge extends beyond the viewport, a negative value will be\n       * reported.\n       *\n       * @param {element} elem - The element to get the viewport offset for.\n       * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead\n       * of the first scrollable element, default is false.\n       * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element\n       * be accounted for, default is true.\n       *\n       * @returns {object} An object with the following properties:\n       *   <ul>\n       *     <li>**top**: distance to the top content edge of viewport element</li>\n       *     <li>**bottom**: distance to the bottom content edge of viewport element</li>\n       *     <li>**left**: distance to the left content edge of viewport element</li>\n       *     <li>**right**: distance to the right content edge of viewport element</li>\n       *   </ul>\n       */\n      viewportOffset: function(elem, useDocument, includePadding) {\n        elem = this.getRawNode(elem);\n        includePadding = includePadding !== false ? true : false;\n\n        var elemBCR = elem.getBoundingClientRect();\n        var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};\n\n        var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);\n        var offsetParentBCR = offsetParent.getBoundingClientRect();\n\n        offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;\n        offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;\n        if (offsetParent === $document[0].documentElement) {\n          offsetBCR.top += $window.pageYOffset;\n          offsetBCR.left += $window.pageXOffset;\n        }\n        offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;\n        offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;\n\n        if (includePadding) {\n          var offsetParentStyle = $window.getComputedStyle(offsetParent);\n          offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);\n          offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);\n          offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);\n          offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);\n        }\n\n        return {\n          top: Math.round(elemBCR.top - offsetBCR.top),\n          bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),\n          left: Math.round(elemBCR.left - offsetBCR.left),\n          right: Math.round(offsetBCR.right - elemBCR.right)\n        };\n      },\n\n      /**\n       * Provides an array of placement values parsed from a placement string.\n       * Along with the 'auto' indicator, supported placement strings are:\n       *   <ul>\n       *     <li>top: element on top, horizontally centered on host element.</li>\n       *     <li>top-left: element on top, left edge aligned with host element left edge.</li>\n       *     <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>\n       *     <li>bottom: element on bottom, horizontally centered on host element.</li>\n       *     <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>\n       *     <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>\n       *     <li>left: element on left, vertically centered on host element.</li>\n       *     <li>left-top: element on left, top edge aligned with host element top edge.</li>\n       *     <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>\n       *     <li>right: element on right, vertically centered on host element.</li>\n       *     <li>right-top: element on right, top edge aligned with host element top edge.</li>\n       *     <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>\n       *   </ul>\n       * A placement string with an 'auto' indicator is expected to be\n       * space separated from the placement, i.e: 'auto bottom-left'  If\n       * the primary and secondary placement values do not match 'top,\n       * bottom, left, right' then 'top' will be the primary placement and\n       * 'center' will be the secondary placement.  If 'auto' is passed, true\n       * will be returned as the 3rd value of the array.\n       *\n       * @param {string} placement - The placement string to parse.\n       *\n       * @returns {array} An array with the following values\n       * <ul>\n       *   <li>**[0]**: The primary placement.</li>\n       *   <li>**[1]**: The secondary placement.</li>\n       *   <li>**[2]**: If auto is passed: true, else undefined.</li>\n       * </ul>\n       */\n      parsePlacement: function(placement) {\n        var autoPlace = PLACEMENT_REGEX.auto.test(placement);\n        if (autoPlace) {\n          placement = placement.replace(PLACEMENT_REGEX.auto, '');\n        }\n\n        placement = placement.split('-');\n\n        placement[0] = placement[0] || 'top';\n        if (!PLACEMENT_REGEX.primary.test(placement[0])) {\n          placement[0] = 'top';\n        }\n\n        placement[1] = placement[1] || 'center';\n        if (!PLACEMENT_REGEX.secondary.test(placement[1])) {\n          placement[1] = 'center';\n        }\n\n        if (autoPlace) {\n          placement[2] = true;\n        } else {\n          placement[2] = false;\n        }\n\n        return placement;\n      },\n\n      /**\n       * Provides coordinates for an element to be positioned relative to\n       * another element.  Passing 'auto' as part of the placement parameter\n       * will enable smart placement - where the element fits. i.e:\n       * 'auto left-top' will check to see if there is enough space to the left\n       * of the hostElem to fit the targetElem, if not place right (same for secondary\n       * top placement).  Available space is calculated using the viewportOffset\n       * function.\n       *\n       * @param {element} hostElem - The element to position against.\n       * @param {element} targetElem - The element to position.\n       * @param {string=} [placement=top] - The placement for the targetElem,\n       *   default is 'top'. 'center' is assumed as secondary placement for\n       *   'top', 'left', 'right', and 'bottom' placements.  Available placements are:\n       *   <ul>\n       *     <li>top</li>\n       *     <li>top-right</li>\n       *     <li>top-left</li>\n       *     <li>bottom</li>\n       *     <li>bottom-left</li>\n       *     <li>bottom-right</li>\n       *     <li>left</li>\n       *     <li>left-top</li>\n       *     <li>left-bottom</li>\n       *     <li>right</li>\n       *     <li>right-top</li>\n       *     <li>right-bottom</li>\n       *   </ul>\n       * @param {boolean=} [appendToBody=false] - Should the top and left values returned\n       *   be calculated from the body element, default is false.\n       *\n       * @returns {object} An object with the following properties:\n       *   <ul>\n       *     <li>**top**: Value for targetElem top.</li>\n       *     <li>**left**: Value for targetElem left.</li>\n       *     <li>**placement**: The resolved placement.</li>\n       *   </ul>\n       */\n      positionElements: function(hostElem, targetElem, placement, appendToBody) {\n        hostElem = this.getRawNode(hostElem);\n        targetElem = this.getRawNode(targetElem);\n\n        // need to read from prop to support tests.\n        var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');\n        var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');\n\n        placement = this.parsePlacement(placement);\n\n        var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);\n        var targetElemPos = {top: 0, left: 0, placement: ''};\n\n        if (placement[2]) {\n          var viewportOffset = this.viewportOffset(hostElem, appendToBody);\n\n          var targetElemStyle = $window.getComputedStyle(targetElem);\n          var adjustedSize = {\n            width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),\n            height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))\n          };\n\n          placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :\n                         placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :\n                         placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :\n                         placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :\n                         placement[0];\n\n          placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :\n                         placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :\n                         placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :\n                         placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :\n                         placement[1];\n\n          if (placement[1] === 'center') {\n            if (PLACEMENT_REGEX.vertical.test(placement[0])) {\n              var xOverflow = hostElemPos.width / 2 - targetWidth / 2;\n              if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {\n                placement[1] = 'left';\n              } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {\n                placement[1] = 'right';\n              }\n            } else {\n              var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;\n              if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {\n                placement[1] = 'top';\n              } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {\n                placement[1] = 'bottom';\n              }\n            }\n          }\n        }\n\n        switch (placement[0]) {\n          case 'top':\n            targetElemPos.top = hostElemPos.top - targetHeight;\n            break;\n          case 'bottom':\n            targetElemPos.top = hostElemPos.top + hostElemPos.height;\n            break;\n          case 'left':\n            targetElemPos.left = hostElemPos.left - targetWidth;\n            break;\n          case 'right':\n            targetElemPos.left = hostElemPos.left + hostElemPos.width;\n            break;\n        }\n\n        switch (placement[1]) {\n          case 'top':\n            targetElemPos.top = hostElemPos.top;\n            break;\n          case 'bottom':\n            targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;\n            break;\n          case 'left':\n            targetElemPos.left = hostElemPos.left;\n            break;\n          case 'right':\n            targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;\n            break;\n          case 'center':\n            if (PLACEMENT_REGEX.vertical.test(placement[0])) {\n              targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;\n            } else {\n              targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;\n            }\n            break;\n        }\n\n        targetElemPos.top = Math.round(targetElemPos.top);\n        targetElemPos.left = Math.round(targetElemPos.left);\n        targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];\n\n        return targetElemPos;\n      },\n\n      /**\n       * Provides a way to adjust the top positioning after first\n       * render to correctly align element to top after content\n       * rendering causes resized element height\n       *\n       * @param {array} placementClasses - The array of strings of classes\n       * element should have.\n       * @param {object} containerPosition - The object with container\n       * position information\n       * @param {number} initialHeight - The initial height for the elem.\n       * @param {number} currentHeight - The current height for the elem.\n       */\n      adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) {\n        if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {\n          return {\n            top: containerPosition.top - currentHeight + 'px'\n          };\n        }\n      },\n\n      /**\n       * Provides a way for positioning tooltip & dropdown\n       * arrows when using placement options beyond the standard\n       * left, right, top, or bottom.\n       *\n       * @param {element} elem - The tooltip/dropdown element.\n       * @param {string} placement - The placement for the elem.\n       */\n      positionArrow: function(elem, placement) {\n        elem = this.getRawNode(elem);\n\n        var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');\n        if (!innerElem) {\n          return;\n        }\n\n        var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');\n\n        var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');\n        if (!arrowElem) {\n          return;\n        }\n\n        var arrowCss = {\n          top: '',\n          bottom: '',\n          left: '',\n          right: ''\n        };\n\n        placement = this.parsePlacement(placement);\n        if (placement[1] === 'center') {\n          // no adjustment necessary - just reset styles\n          angular.element(arrowElem).css(arrowCss);\n          return;\n        }\n\n        var borderProp = 'border-' + placement[0] + '-width';\n        var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];\n\n        var borderRadiusProp = 'border-';\n        if (PLACEMENT_REGEX.vertical.test(placement[0])) {\n          borderRadiusProp += placement[0] + '-' + placement[1];\n        } else {\n          borderRadiusProp += placement[1] + '-' + placement[0];\n        }\n        borderRadiusProp += '-radius';\n        var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];\n\n        switch (placement[0]) {\n          case 'top':\n            arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;\n            break;\n          case 'bottom':\n            arrowCss.top = isTooltip ? '0' : '-' + borderWidth;\n            break;\n          case 'left':\n            arrowCss.right = isTooltip ? '0' : '-' + borderWidth;\n            break;\n          case 'right':\n            arrowCss.left = isTooltip ? '0' : '-' + borderWidth;\n            break;\n        }\n\n        arrowCss[placement[1]] = borderRadius;\n\n        angular.element(arrowElem).css(arrowCss);\n      }\n    };\n  }]);\n"
  },
  {
    "path": "src/position/test/position.spec.js",
    "content": "describe('$uibPosition service', function () {\n  var TargetElMock = function(width, height) {\n    this.width = width;\n    this.height = height;\n\n    this.prop = function(propName) {\n      return propName === 'offsetWidth' ? width : height;\n    };\n  };\n\n  var $document;\n  var $uibPosition;\n\n  beforeEach(module('ui.bootstrap.position'));\n\n  beforeEach(inject(function(_$document_, _$uibPosition_) {\n    $document = _$document_;\n    $uibPosition = _$uibPosition_;\n  }));\n\n  beforeEach(function () {\n    jasmine.addMatchers({\n      toBePositionedAt: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, top, left) {\n            var result = {\n              pass: util.equals(actual.top, top, customEqualityTesters) &&\n                      util.equals(actual.left, left, customEqualityTesters)\n            };\n\n            if (result.pass) {\n              result.message = 'Expected \"(' + actual.top + ', ' + actual.left + ')\" not to be positioned at (' + top + ', ' + left + ')';\n            } else {\n              result.message = 'Expected \"(' + actual.top + ', ' + actual.left + ')\" to be positioned at (' + top + ', ' + left + ')';\n            }\n\n            return result;\n          }\n        };\n      }\n    });\n  });\n\n  describe('rawnode', function() {\n    it('returns the raw DOM element from an angular element', function() {\n      var angularEl = angular.element('<div></div>');\n      var el = $uibPosition.getRawNode(angularEl);\n      expect(el.nodeName).toBe('DIV');\n    });\n\n    it('returns the raw DOM element from a select element', function() {\n      var angularEl = angular.element('<select><option value=\"value\">value</option></select>');\n      var el = $uibPosition.getRawNode(angularEl);\n      expect(el.nodeName).toBe('SELECT');\n    });\n  });\n\n  describe('offset', function() {\n    it('returns getBoundingClientRect by default', function() {\n      var el = angular.element('<div>Foo</div>');\n\n      /* getBoundingClientRect values will be based on the testing Chrome window\n       so that makes this tests very brittle if we don't mock */\n      spyOn(el[0], 'getBoundingClientRect').and.returnValue({\n        width: 100,\n        height: 100,\n        top: 2,\n        left: 2\n      });\n      $document.find('body').append(el);\n\n      var offset = $uibPosition.offset(el);\n\n      expect(offset).toEqual({\n        width: 100,\n        height: 100,\n        top: 2,\n        left: 2\n      });\n\n      el.remove();\n    });\n  });\n\n  describe('viewportOffset', function() {\n    var el;\n\n    beforeEach(function() {\n      el = angular.element('<div id=\"outer\" style=\"overflow: auto; width: 200px; height: 200px; padding: 25px; box-sizing: border-box;\"><div id=\"inner\" style=\"margin: 20px; width: 100px; height: 100px; box-sizing: border-box;\"></div></div>');\n      $document.find('body').append(el);\n    });\n\n    afterEach(function() {\n      el.remove();\n    });\n\n    it('measures the offset', function() {\n      var vpOffset = $uibPosition.viewportOffset(document.getElementById('inner'));\n      expect(vpOffset).toEqual({\n        top: 20,\n        bottom: 30,\n        left: 20,\n        right: 30\n      });\n    });\n\n    it('measures the offset without padding', function() {\n      var outerEl = document.getElementById('outer');\n      outerEl.style.paddingTop = '0px';\n      outerEl.style.paddingBottom = '0px';\n      outerEl.style.paddingLeft = '0px';\n      outerEl.style.paddingRight = '0px';\n\n      var vpOffset = $uibPosition.viewportOffset(document.getElementById('inner'));\n      expect(vpOffset).toEqual({\n        top: 20,\n        bottom: 80,\n        left: 20,\n        right: 80\n      });\n    });\n\n    it('measures the offset with borders', function() {\n      var outerEl = document.getElementById('outer');\n      outerEl.style.width = '220px';\n      outerEl.style.height = '220px';\n      outerEl.style.border = '10px solid black';\n\n      var vpOffset = $uibPosition.viewportOffset(document.getElementById('inner'));\n      expect(vpOffset).toEqual({\n        top: 20,\n        bottom: 30,\n        left: 20,\n        right: 30\n      });\n    });\n\n    it('measures the offset excluding padding', function() {\n      var vpOffset = $uibPosition.viewportOffset(document.getElementById('inner'), false, false);\n      expect(vpOffset).toEqual({\n        top: 45,\n        bottom: 55,\n        left: 45,\n        right: 55\n      });\n    });\n\n    it('measures the offset when scrolled', function() {\n      var innerEl = document.getElementById('inner');\n      innerEl.style.width = '300px';\n      innerEl.style.height = '300px';\n      var outerEl = document.getElementById('outer');\n      outerEl.scrollTop = 25;\n      outerEl.scrollLeft = 25;\n\n      var vpOffset = $uibPosition.viewportOffset(document.getElementById('inner'));\n      expect(vpOffset.top).toEqual(-5);\n      expect(vpOffset.bottom).toBeGreaterThan(-180);\n      expect(vpOffset.left).toEqual(-5);\n      expect(vpOffset.right).toBeGreaterThan(-180);\n\n      //brittle\n      // expect(vpOffset).toEqual({\n      //   top: -5,\n      //   bottom: -162,\n      //   left: -5,\n      //   right: -162\n      // });\n    });\n\n  });\n\n  describe('position', function() {\n    var el;\n\n    afterEach(function() {\n      el.remove();\n    });\n\n    it('gets position with document as the relative parent', function() {\n      el = angular.element('<div>Foo</div>');\n\n      spyOn(el[0], 'getBoundingClientRect').and.returnValue({\n        width: 100,\n        height: 100,\n        top: 2,\n        left: 2\n      });\n\n      $document.find('body').append(el);\n\n      var position = $uibPosition.position(el);\n\n      expect(position).toEqual({\n        width: 100,\n        height: 100,\n        top: 2,\n        left: 2\n      });\n    });\n\n    it('gets position with an element as the relative parent', function() {\n      el = angular.element('<div id=\"outer\" style=\"position:relative;\"><div id=\"inner\">Foo</div></div>');\n\n      $document.find('body').append(el);\n\n      var outerEl = angular.element(document.getElementById('outer'));\n      var innerEl = angular.element(document.getElementById('inner'));\n\n      spyOn(outerEl[0], 'getBoundingClientRect').and.returnValue({\n        width: 100,\n        height: 100,\n        top: 2,\n        left: 2\n      });\n      spyOn(innerEl[0], 'getBoundingClientRect').and.returnValue({\n        width: 20,\n        height: 20,\n        top: 5,\n        left: 5\n      });\n\n      var position = $uibPosition.position(innerEl);\n\n      expect(position).toEqual({\n        width: 20,\n        height: 20,\n        top: 3,\n        left: 3\n      });\n    });\n  });\n\n  describe('isScrollable', function() {\n    var el;\n\n    afterEach(function() {\n      el.remove();\n    });\n\n    it('should return true if the element is scrollable', function() {\n      el = angular.element('<div style=\"overflow: auto\"></div>');\n      $document.find('body').append(el);\n      expect($uibPosition.isScrollable(el)).toBe(true);\n    });\n\n    it('should return false if the element is scrollable', function() {\n      el = angular.element('<div></div>');\n      $document.find('body').append(el);\n      expect($uibPosition.isScrollable(el)).toBe(false);\n    });\n\n  });\n\n  describe('scrollParent', function() {\n    var el;\n\n    afterEach(function() {\n      el.remove();\n    });\n\n    it('gets the closest scrollable ancestor', function() {\n      el = angular.element('<div id=\"outer\" style=\"overflow: auto;\"><div>Foo<div id=\"inner\">Bar</div></div></div>');\n\n      $document.find('body').css({overflow: 'auto'}).append(el);\n\n      var outerEl = document.getElementById('outer');\n      var innerEl = document.getElementById('inner');\n\n      var scrollParent = $uibPosition.scrollParent(innerEl);\n      expect(scrollParent).toEqual(outerEl);\n    });\n\n    it('gets the closest scrollable ancestor with overflow-x: scroll', function() {\n      el = angular.element('<div id=\"outer\" style=\"overflow-x: scroll;\"><div>Foo<div id=\"inner\">Bar</div></div></div>');\n\n      $document.find('body').css({overflow: 'auto'}).append(el);\n\n      var outerEl = document.getElementById('outer');\n      var innerEl = document.getElementById('inner');\n\n      var scrollParent = $uibPosition.scrollParent(innerEl);\n      expect(scrollParent).toEqual(outerEl);\n    });\n\n    it('gets the closest scrollable ancestor with overflow-y: hidden', function() {\n      el = angular.element('<div id=\"outer\" style=\"overflow-y: hidden;\"><div>Foo<div id=\"inner\">Bar</div></div></div>');\n\n      $document.find('body').css({overflow: 'auto'}).append(el);\n\n      var outerEl = document.getElementById('outer');\n      var innerEl = document.getElementById('inner');\n\n      var scrollParent = $uibPosition.scrollParent(innerEl, true);\n      expect(scrollParent).toEqual(outerEl);\n    });\n\n    it('gets the document element if no scrollable ancestor exists', function() {\n      el = angular.element('<div id=\"outer\"><div>Foo<div id=\"inner\">Bar</div></div></div>');\n\n      $document.find('body').css({overflow: ''}).append(el);\n\n      var innerEl = document.getElementById('inner');\n\n      var scrollParent = $uibPosition.scrollParent(innerEl);\n      expect(scrollParent).toEqual($document[0].documentElement);\n    });\n\n    it('gets the closest scrollable ancestor after a positioned ancestor when positioned absolute', function() {\n      el = angular.element('<div id=\"outer\" style=\"overflow: auto; position: relative;\"><div style=\"overflow: auto;\">Foo<div id=\"inner\" style=\"position: absolute;\">Bar</div></div></div>');\n\n      $document.find('body').css({overflow: 'auto'}).append(el);\n\n      var outerEl = document.getElementById('outer');\n      var innerEl = document.getElementById('inner');\n\n      var scrollParent = $uibPosition.scrollParent(innerEl);\n      expect(scrollParent).toEqual(outerEl);\n    });\n  });\n\n  describe('positionElements - append-to-body: false', function() {\n    var el;\n\n    beforeEach(function() {\n      //mock position info normally queried from the DOM\n      $uibPosition.position = function() {\n        return {\n          width: 20,\n          height: 20,\n          top: 100,\n          left: 100\n        };\n      };\n    });\n\n    it('should position element on top-center by default', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'other')).toBePositionedAt(90, 105);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top')).toBePositionedAt(90, 105);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top-center')).toBePositionedAt(90, 105);\n    });\n\n    it('should position on top-left', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top-left')).toBePositionedAt(90, 100);\n    });\n\n    it('should position on top-right', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top-right')).toBePositionedAt(90, 110);\n    });\n\n    it('should position elements on bottom-center when \"bottom\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom')).toBePositionedAt(120, 105);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom-center')).toBePositionedAt(120, 105);\n    });\n\n    it('should position elements on bottom-left', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom-left')).toBePositionedAt(120, 100);\n    });\n\n    it('should position elements on bottom-right', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom-right')).toBePositionedAt(120, 110);\n    });\n\n    it('should position elements on left-center when \"left\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left')).toBePositionedAt(105, 90);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left-center')).toBePositionedAt(105, 90);\n    });\n\n    it('should position elements on left-top when \"left-top\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left-top')).toBePositionedAt(100, 90);\n    });\n\n    it('should position elements on left-bottom when \"left-bottom\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left-bottom')).toBePositionedAt(110, 90);\n    });\n\n    it('should position elements on right-center when \"right\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right')).toBePositionedAt(105, 120);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right-center')).toBePositionedAt(105, 120);\n    });\n\n    it('should position elements on right-top when \"right-top\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right-top')).toBePositionedAt(100, 120);\n    });\n\n    it('should position elements on right-top when \"right-top\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right-bottom')).toBePositionedAt(110, 120);\n    });\n  });\n\n  describe('positionElements - append-to-body: true', function() {\n    beforeEach(function() {\n      //mock offset info normally queried from the DOM\n      $uibPosition.offset = function() {\n        return {\n          width: 20,\n          height: 20,\n          top: 100,\n          left: 100\n        };\n      };\n    });\n\n    it('should position element on top-center by default', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'other', true)).toBePositionedAt(90, 105);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top', true)).toBePositionedAt(90, 105);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top-center', true)).toBePositionedAt(90, 105);\n    });\n\n    it('should position on top-left', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top-left', true)).toBePositionedAt(90, 100);\n    });\n\n    it('should position on top-right', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'top-right', true)).toBePositionedAt(90, 110);\n    });\n\n    it('should position elements on bottom-center when \"bottom\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom', true)).toBePositionedAt(120, 105);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom-center', true)).toBePositionedAt(120, 105);\n    });\n\n    it('should position elements on bottom-left', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom-left', true)).toBePositionedAt(120, 100);\n    });\n\n    it('should position elements on bottom-right', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'bottom-right', true)).toBePositionedAt(120, 110);\n    });\n\n    it('should position elements on left-center when \"left\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left', true)).toBePositionedAt(105, 90);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left-center', true)).toBePositionedAt(105, 90);\n    });\n\n    it('should position elements on left-top when \"left-top\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left-top', true)).toBePositionedAt(100, 90);\n    });\n\n    it('should position elements on left-bottom when \"left-bottom\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'left-bottom', true)).toBePositionedAt(110, 90);\n    });\n\n    it('should position elements on right-center when \"right\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right', true)).toBePositionedAt(105, 120);\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right-center', true)).toBePositionedAt(105, 120);\n    });\n\n    it('should position elements on right-top when \"right-top\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right-top', true)).toBePositionedAt(100, 120);\n    });\n\n    it('should position elements on right-bottom when \"right-bottom\" specified', function() {\n      expect($uibPosition.positionElements({}, new TargetElMock(10, 10), 'right-bottom', true)).toBePositionedAt(110, 120);\n    });\n  });\n\n  describe('smart positioning', function() {\n    var viewportOffset, el;\n\n    beforeEach(function() {\n      el = angular.element('<div></div>');\n      $document.find('body').append(el);\n\n      //mock position info normally queried from the DOM\n      $uibPosition.position = function() {\n        return {\n          width: 40,\n          height: 40,\n          top: 100,\n          left: 100\n        };\n      };\n\n      viewportOffset = {\n        width: 10,\n        height: 10,\n        top: 10,\n        bottom: 10,\n        left: 10,\n        right: 10\n      };\n\n      $uibPosition.viewportOffset = function() {\n        return viewportOffset;\n      };\n    });\n\n    afterEach(function() {\n      el.remove();\n    });\n\n    // tests primary top -> bottom\n    // tests secondary left -> right\n    it('should position element on bottom-right when top-left does not fit', function() {\n      viewportOffset.bottom = 20;\n      viewportOffset.left = 20;\n      el.css({ width: '60px', height: '20px' });\n      expect($uibPosition.positionElements({}, el, 'auto top-left')).toBePositionedAt(140, 80);\n    });\n\n    // tests primary bottom -> top\n    // tests secondary right -> left\n    it('should position element on top-left when bottom-right does not fit', function() {\n      viewportOffset.top = 20;\n      viewportOffset.right = 20;\n      el.css({ width: '60px', height: '20px' });\n      expect($uibPosition.positionElements({}, el, 'auto bottom-right')).toBePositionedAt(80, 100);\n    });\n\n    // tests primary left -> right\n    // tests secondary top -> bottom\n    it('should position element on right-bottom when left-top does not fit', function() {\n      viewportOffset.top = 20;\n      viewportOffset.right = 20;\n      el.css({ width: '20px', height: '60px' });\n      expect($uibPosition.positionElements({}, el, 'auto left-top')).toBePositionedAt(80, 140);\n    });\n\n    // tests primary right -> left\n    // tests secondary bottom -> top\n    it('should position element on left-top when right-bottom does not fit', function() {\n      viewportOffset.bottom = 20;\n      viewportOffset.left = 20;\n      el.css({ width: '20px', height: '60px' });\n      expect($uibPosition.positionElements({}, el, 'auto right-bottom')).toBePositionedAt(100, 80);\n    });\n\n    // tests vertical center -> top\n    it('should position element on left-top when left-center does not fit vetically', function() {\n      viewportOffset.bottom = 100;\n      el.css({ width: '20px', height: '120px' });\n      expect($uibPosition.positionElements({}, el, 'auto left')).toBePositionedAt(100, 80);\n    });\n\n    // tests vertical center -> bottom\n    it('should position element on left-bottom when left-center does not fit vertically', function() {\n      viewportOffset.top = 100;\n      el.css({ width: '20px', height: '120px' });\n      expect($uibPosition.positionElements({}, el, 'auto left')).toBePositionedAt(20, 80);\n    });\n\n    // tests horizontal center -> left\n    it('should position element on top-left when top-center does not fit horizontally', function() {\n      viewportOffset.right = 100;\n      el.css({ width: '120px', height: '20px' });\n      expect($uibPosition.positionElements({}, el, 'auto top')).toBePositionedAt(80, 100);\n    });\n\n    // tests horizontal center -> right\n    it('should position element on top-right when top-center does not fit horizontally', function() {\n      viewportOffset.left = 100;\n      el.css({ width: '120px', height: '20px' });\n      expect($uibPosition.positionElements({}, el, 'auto top')).toBePositionedAt(80, 20);\n    });\n  });\n});\n"
  },
  {
    "path": "src/position/test/test.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" ng-app=\"position\">\n<head>\n    <meta charset=\"utf-8\">\n    <script src=\"../../../node_modules/angular/angular.js\"></script>\n    <script src=\"../position.js\"></script>\n    <style type=\"text/css\">\n        .container {\n            border: 1px solid red;\n        }\n\n        .container-relative {\n            border: 1px solid red;\n            position: relative;\n        }\n\n        .content {\n            border: 5px solid #808080;\n            background-color: dodgerblue;\n            width: 200px;\n        }\n\n        .positioned {\n            border: 5px solid #808080;\n            background-color: green;\n            position: absolute;\n        }\n    </style>\n    <script type=\"text/javascript\">\n        angular.module('position', ['ui.bootstrap.position']).directive('position', function ($compile, $position) {\n            return {\n                link: function (scope, element, attrs) {\n\n                    var positionedEl = angular.element('<div class=\"positioned\">Positioned</div>');\n                    var elPosition = $position.position(element);\n                    elPosition.left += elPosition.width;\n\n                    positionedEl.css({left: elPosition.left + 'px', top: elPosition.top + 'px'});\n\n                    if (attrs['position'] === 'body') {\n                        angular.element(document.getElementsByTagName('body')[0]).after($compile(positionedEl)(scope));\n                    } else {\n                        element.after($compile(positionedEl)(scope));\n                    }\n                }\n            };\n        });\n    </script>\n</head>\n<body class=\"container\">\n<h3>Within body</h3>\n\n<div class=\"content\" position>Content</div>\n\n<h3>Within statically positioned DIV</h3>\n\n<div class=\"container\">\n    <div class=\"content\" position>Content</div>\n</div>\n\n<h3>Within relative-positioned DIV - position specified in CSS</h3>\n\n<div class=\"container-relative\">\n    <div class=\"content\" position>Content</div>\n</div>\n\n<h3>Within relative-positioned DIV</h3>\n\n<div style=\"position: relative; left: 200px\" class=\"container\">\n    <div class=\"content\" position>Content</div>\n</div>\n\n<h3>Within absolute-positioned DIV</h3>\n\n<div style=\"position: absolute; left: 400px; top: 400px\" class=\"container\">\n    <div class=\"content\" position>Content - absolute</div>\n</div>\n\n<h3>Within overflowing absolute-positioned DIV</h3>\n<div class=\"container\" style=\"height: 50px; overflow: scroll;overflow-x: hidden; position: absolute;\">\n    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non velit nulla. Suspendisse sit amet tempus diam. Sed at ultricies neque. Suspendisse id felis a sem placerat ornare. Donec auctor, purus at molestie tempor, arcu enim molestie lacus, ac imperdiet massa urna eu massa. Praesent velit tellus, scelerisque a fermentum ut, ornare in diam. Phasellus egestas molestie feugiat. Vivamus sit amet viverra metus.\n    <div class=\"content\" position>Content absolute overflow</div>\n</div>\n\n<br>\n<br>\n<br>\n\n<h3>Next to a float element</h3>\n\n<div class=\"container\">\n    <div class=\"content\" style=\"float: right\" position>Content</div>\n</div>\n\n<h3>Within a table</h3>\n<table class=\"container\">\n    <tr>\n        <td>Some other content</td>\n        <td>\n            <div class=\"content\" position>Content</div>\n        </td>\n    </tr>\n</table>\n\n<h3>Within a table that is inside a relative-positioned DIV</h3>\n\n<div style=\"position: relative; left: 200px\" class=\"container\">\n    <table class=\"container\">\n        <tr>\n            <td>Some other content</td>\n            <td>\n                <div class=\"content\" position>Content</div>\n            </td>\n        </tr>\n    </table>\n</div>\n\n<h3>Inside svg</h3>\n\n<svg height=\"300px\" width=\"300px\">\n  <rect x=\"0\" y=\"0\" height=\"300\" width=\"300\" fill=\"aliceblue\"></rect>\n  <rect x=\"50\" y=\"50\" height=\"200\" width=\"200\" position=\"body\" fill=\"white\" stroke=\"red\">\n  </rect>\n</svg>\n\n\n<h3>Inside looong text</h3>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non velit nulla. Suspendisse sit amet tempus diam. Sed at ultricies neque. Suspendisse id felis a sem placerat ornare. Donec auctor, purus at molestie tempor, arcu enim molestie lacus, ac imperdiet massa urna eu massa. Praesent velit tellus, scelerisque a fermentum ut, ornare in diam. Phasellus egestas molestie feugiat. Vivamus sit amet viverra metus.</p>\n<p>Etiam ultricies odio commodo erat ullamcorper sodales. Nullam ac dui ac libero dictum mollis. Quisque convallis adipiscing facilisis. In nec nisi velit, id auctor lectus. Cras interdum urna non felis lacinia vulputate. Integer dignissim, mi aliquam gravida auctor, massa odio cursus lorem, eu ultrices eros nisl tempus diam. Maecenas tristique pellentesque nisi sed adipiscing. Aenean hendrerit sapien quis arcu lobortis vitae pulvinar ante volutpat. Morbi consectetur erat eu lacus facilisis eu ullamcorper orci euismod. Quisque diam dui, interdum in suscipit et, fringilla non justo. Pellentesque non nibh odio. Proin sit amet massa sem.</p>\n<p>Nam in urna erat, at congue nisi. Donec eu tellus lorem, sed facilisis tellus. Aliquam suscipit faucibus ipsum, at hendrerit metus interdum at. Integer et eros ac lacus vulputate sagittis quis quis erat. Suspendisse consectetur vehicula purus vitae imperdiet. Suspendisse in augue magna, quis imperdiet enim. Nullam non diam ac erat auctor bibendum. Praesent ante mauris, egestas sit amet molestie sed, tristique at lorem. Nam at mi ac nisl venenatis semper nec eget mi. Pellentesque a lectus ac leo feugiat suscipit. Quisque tristique dui nec urna placerat a viverra mi iaculis. Ut et tellus et turpis sagittis iaculis nec eu magna. Sed quis nunc non arcu tincidunt ultricies viverra id mauris.</p>\n<p>Curabitur luctus rutrum ultricies. Aenean ut rutrum orci. Sed molestie lorem in leo cursus id feugiat nisi scelerisque. Maecenas pulvinar neque nec lacus feugiat dictum. Donec viverra felis nec nisi mollis feugiat. Phasellus vehicula, ligula at mattis porttitor, sapien urna hendrerit quam, at fringilla nisl quam vel elit. In eu lacus ligula. Praesent eget gravida nisl. Suspendisse velit diam, pellentesque a tempus quis, vestibulum vel leo.</p>\n<p>Maecenas feugiat ultrices laoreet. Sed congue posuere diam ac faucibus. Pellentesque eget leo ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed nec quam eu tellus sagittis cursus a sit amet eros. Mauris sit amet orci at orci vulputate commodo ut ut nunc. Etiam sagittis erat ut nisi ultricies feugiat. Morbi sed eros nisi. Cras vitae augue in risus aliquet commodo non id est.</p>\n<div class=\"content\" position>HERE</div>\n<p>Maecenas laoreet nisi pretium elit bibendum eget tempor nunc aliquet. Vivamus interdum nisi sit amet tortor fermentum congue. Suspendisse at posuere erat. Aliquam hendrerit ultricies nunc non adipiscing. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis molestie viverra nulla a aliquet. Nullam non eros vel sem vehicula suscipit. Ut sit amet arcu ac tortor dignissim viverra in a ligula.</p>\n\n\n<div style=\"position: fixed; bottom: 0\" class=\"container\">\n    <h3>Within fixed div</h3>\n    <div class=\"content\" position>Content</div>\n</div>\n\n\n</body>\n</html>\n"
  },
  {
    "path": "src/progressbar/docs/demo.html",
    "content": "<div ng-controller=\"ProgressDemoCtrl\">\n\n    <h3>Static</h3>\n    <div class=\"row\">\n        <div class=\"col-sm-4\"><uib-progressbar value=\"55\"></uib-progressbar></div>\n        <div class=\"col-sm-4\"><uib-progressbar class=\"progress-striped\" value=\"22\" type=\"warning\">22%</uib-progressbar></div>\n        <div class=\"col-sm-4\"><uib-progressbar class=\"progress-striped active\" max=\"200\" value=\"166\" type=\"danger\"><i>166 / 200</i></uib-progressbar></div>\n    </div>\n\n    <hr />\n    <h3>Dynamic <button type=\"button\" class=\"btn btn-sm btn-primary\" ng-click=\"random()\">Randomize</button></h3>\n    <uib-progressbar max=\"max\" value=\"dynamic\"><span style=\"color:white; white-space:nowrap;\">{{dynamic}} / {{max}}</span></uib-progressbar>\n    \n    <small><em>No animation</em></small>\n    <uib-progressbar animate=\"false\" value=\"dynamic\" type=\"success\"><b>{{dynamic}}%</b></uib-progressbar>\n\n    <small><em>Object (changes type based on value)</em></small>\n    <uib-progressbar class=\"progress-striped active\" value=\"dynamic\" type=\"{{type}}\">{{type}} <i ng-show=\"showWarning\">!!! Watch out !!!</i></uib-progressbar>\n    \n    <hr />\n    <h3>Stacked <button type=\"button\" class=\"btn btn-sm btn-primary\" ng-click=\"randomStacked()\">Randomize</button></h3>\n    <uib-progress><uib-bar ng-repeat=\"bar in stacked track by $index\" value=\"bar.value\" type=\"{{bar.type}}\"><span ng-hide=\"bar.value < 5\">{{bar.value}}%</span></uib-bar></uib-progress>\n\n</div>"
  },
  {
    "path": "src/progressbar/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('ProgressDemoCtrl', function ($scope) {\n  $scope.max = 200;\n\n  $scope.random = function() {\n    var value = Math.floor(Math.random() * 100 + 1);\n    var type;\n\n    if (value < 25) {\n      type = 'success';\n    } else if (value < 50) {\n      type = 'info';\n    } else if (value < 75) {\n      type = 'warning';\n    } else {\n      type = 'danger';\n    }\n\n    $scope.showWarning = type === 'danger' || type === 'warning';\n\n    $scope.dynamic = value;\n    $scope.type = type;\n  };\n\n  $scope.random();\n\n  $scope.randomStacked = function() {\n    $scope.stacked = [];\n    var types = ['success', 'info', 'warning', 'danger'];\n\n    for (var i = 0, n = Math.floor(Math.random() * 4 + 1); i < n; i++) {\n        var index = Math.floor(Math.random() * 4);\n        $scope.stacked.push({\n          value: Math.floor(Math.random() * 30 + 1),\n          type: types[index]\n        });\n    }\n  };\n\n  $scope.randomStacked();\n});\n"
  },
  {
    "path": "src/progressbar/docs/readme.md",
    "content": "A progress bar directive that is focused on providing feedback on the progress of a workflow or action.\n\nIt supports multiple (stacked) `<uib-bar>` into the same `<uib-progress>` element or a single `<uib-progressbar>` element with optional `max` attribute and transition animations.\n\n### uib-progressbar settings\n\n* `value`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  The current value of progress completed.\n\n* `type`\n  _(Default: `null`)_ -\n  Bootstrap style type. Possible values are 'success', 'info', 'warning', and, 'danger' to use Bootstrap's pre-existing styling, or any desired custom suffix.\n\n* `max`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `100`)_ -\n  A number that specifies the total value of bars that is required.\n\n* `animate`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether bars use transitions to achieve the width change.\n\n* `title`\n  _(Default: `progressbar`)_ -\n  Title to use as label (for accessibility).\n  \n### uib-progress settings\n\n* `max`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `100`)_ -\n  A number that specifies the total value of bars that is required.\n\n* `animate`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether bars use transitions to achieve the width change.\n\n* `title`\n  _(Default: `progressbar`)_ -\n  Title to use as label (for accessibility).\n  \n### uib-bar settings\n\n* `value`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  The current value of progress completed.\n\n* `type`\n  _(Default: `null`)_ -\n  Bootstrap style type. Possible values are 'success', 'info', 'warning', and, 'danger' to use Bootstrap's pre-existing styling, or any desired custom suffix.\n\n* `title`\n  _(Default: `progressbar`)_ -\n  Title to use as label (for accessibility).\n"
  },
  {
    "path": "src/progressbar/index.js",
    "content": "require('../../template/progressbar/progressbar.html.js');\nrequire('../../template/progressbar/progress.html.js');\nrequire('../../template/progressbar/bar.html.js');\nrequire('./progressbar');\n\nvar MODULE_NAME = 'ui.bootstrap.module.progressbar';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.progressbar', 'uib/template/progressbar/progressbar.html', 'uib/template/progressbar/progress.html', 'uib/template/progressbar/bar.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/progressbar/progressbar.js",
    "content": "angular.module('ui.bootstrap.progressbar', [])\n\n.constant('uibProgressConfig', {\n  animate: true,\n  max: 100\n})\n\n.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {\n  var self = this,\n      animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;\n\n  this.bars = [];\n  $scope.max = getMaxOrDefault();\n\n  this.addBar = function(bar, element, attrs) {\n    if (!animate) {\n      element.css({'transition': 'none'});\n    }\n\n    this.bars.push(bar);\n\n    bar.max = getMaxOrDefault();\n    bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';\n\n    bar.$watch('value', function(value) {\n      bar.recalculatePercentage();\n    });\n\n    bar.recalculatePercentage = function() {\n      var totalPercentage = self.bars.reduce(function(total, bar) {\n        bar.percent = +(100 * bar.value / bar.max).toFixed(2);\n        return total + bar.percent;\n      }, 0);\n\n      if (totalPercentage > 100) {\n        bar.percent -= totalPercentage - 100;\n      }\n    };\n\n    bar.$on('$destroy', function() {\n      element = null;\n      self.removeBar(bar);\n    });\n  };\n\n  this.removeBar = function(bar) {\n    this.bars.splice(this.bars.indexOf(bar), 1);\n    this.bars.forEach(function (bar) {\n      bar.recalculatePercentage();\n    });\n  };\n\n  //$attrs.$observe('maxParam', function(maxParam) {\n  $scope.$watch('maxParam', function(maxParam) {\n    self.bars.forEach(function(bar) {\n      bar.max = getMaxOrDefault();\n      bar.recalculatePercentage();\n    });\n  });\n\n  function getMaxOrDefault () {\n    return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;\n  }\n}])\n\n.directive('uibProgress', function() {\n  return {\n    replace: true,\n    transclude: true,\n    controller: 'UibProgressController',\n    require: 'uibProgress',\n    scope: {\n      maxParam: '=?max'\n    },\n    templateUrl: 'uib/template/progressbar/progress.html'\n  };\n})\n\n.directive('uibBar', function() {\n  return {\n    replace: true,\n    transclude: true,\n    require: '^uibProgress',\n    scope: {\n      value: '=',\n      type: '@'\n    },\n    templateUrl: 'uib/template/progressbar/bar.html',\n    link: function(scope, element, attrs, progressCtrl) {\n      progressCtrl.addBar(scope, element, attrs);\n    }\n  };\n})\n\n.directive('uibProgressbar', function() {\n  return {\n    replace: true,\n    transclude: true,\n    controller: 'UibProgressController',\n    scope: {\n      value: '=',\n      maxParam: '=?max',\n      type: '@'\n    },\n    templateUrl: 'uib/template/progressbar/progressbar.html',\n    link: function(scope, element, attrs, progressCtrl) {\n      progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});\n    }\n  };\n});\n"
  },
  {
    "path": "src/progressbar/test/progressbar.spec.js",
    "content": "describe('progressbar directive', function() {\n  var $rootScope, $compile, element;\n  beforeEach(module('ui.bootstrap.progressbar'));\n  beforeEach(module('uib/template/progressbar/progressbar.html', 'uib/template/progressbar/progress.html', 'uib/template/progressbar/bar.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $rootScope.value = 22;\n    element = $compile('<uib-progressbar animate=\"false\" value=\"value\" title=\"foo\">{{value}} %</uib-progressbar>')($rootScope);\n    $rootScope.$digest();\n  }));\n\n  var BAR_CLASS = 'progress-bar';\n\n  function getBar(i) {\n    return element.children().eq(i);\n  }\n\n  it('has a \"progress\" css class', function() {\n    expect(element).toHaveClass('progress');\n  });\n\n  it('contains one child element with \"bar\" css class', function() {\n    expect(element.children().length).toBe(1);\n    expect(getBar(0)).toHaveClass(BAR_CLASS);\n  });\n\n  it('has a \"bar\" element with expected width', function() {\n    expect(getBar(0).css('width')).toBe('22%');\n  });\n\n  it('has the appropriate aria markup', function() {\n    var bar = getBar(0);\n    expect(bar.attr('role')).toBe('progressbar');\n    expect(bar.attr('aria-valuemin')).toBe('0');\n    expect(bar.attr('aria-valuemax')).toBe('100');\n    expect(bar.attr('aria-valuenow')).toBe('22');\n    expect(bar.attr('aria-valuetext')).toBe('22%');\n    expect(bar.attr('aria-labelledby')).toBe('foo');\n  });\n\n  it('has the default aria-labelledby value of `progressbar`', function() {\n    element = $compile('<uib-progressbar animate=\"false\" value=\"value\">{{value}} %</uib-progressbar>')($rootScope);\n    $rootScope.$digest();\n    var bar = getBar(0);\n    expect(bar.attr('aria-labelledby')).toBe('progressbar');\n  });\n\n  it('transcludes \"bar\" text', function() {\n    expect(getBar(0).text()).toBe('22 %');\n  });\n\n  it('it should be possible to add additional classes', function() {\n    element = $compile('<progress class=\"progress-striped active\" max=\"200\"><bar class=\"pizza\"></bar></progress>')($rootScope);\n    $rootScope.$digest();\n\n    expect(element).toHaveClass('progress-striped');\n    expect(element).toHaveClass('active');\n\n    expect(getBar(0)).toHaveClass('pizza');\n  });\n\n  it('adjusts the \"bar\" width and aria when value changes', function() {\n    $rootScope.value = 60;\n    $rootScope.$digest();\n\n    var bar = getBar(0);\n    expect(bar.css('width')).toBe('60%');\n\n    expect(bar.attr('aria-valuemin')).toBe('0');\n    expect(bar.attr('aria-valuemax')).toBe('100');\n    expect(bar.attr('aria-valuenow')).toBe('60');\n    expect(bar.attr('aria-valuetext')).toBe('60%');\n  });\n\n  it('allows fractional \"bar\" width values, rounded to two places', function() {\n    $rootScope.value = 5.625;\n    $rootScope.$digest();\n    expect(getBar(0).css('width')).toBe('5.63%');\n\n    $rootScope.value = 1.3;\n    $rootScope.$digest();\n    expect(getBar(0).css('width')).toBe('1.3%');\n  });\n\n  it('does not include decimals in aria values', function() {\n    $rootScope.value = 50.34;\n    $rootScope.$digest();\n\n    var bar = getBar(0);\n    expect(bar.css('width')).toBe('50.34%');\n    expect(bar.attr('aria-valuetext')).toBe('50%');\n  });\n\n  describe('\"max\" attribute', function() {\n    beforeEach(inject(function() {\n      $rootScope.max = 200;\n      element = $compile('<uib-progressbar max=\"max\" animate=\"false\" value=\"value\">{{value}}/{{max}}</uib-progressbar>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('has the appropriate aria markup', function() {\n      expect(getBar(0).attr('aria-valuemax')).toBe('200');\n    });\n\n    it('adjusts the \"bar\" width', function() {\n      expect(element.children().eq(0).css('width')).toBe('11%');\n    });\n\n    it('adjusts the \"bar\" width when value changes', function() {\n      $rootScope.value = 60;\n      $rootScope.$digest();\n      expect(getBar(0).css('width')).toBe('30%');\n\n      $rootScope.value += 12;\n      $rootScope.$digest();\n      expect(getBar(0).css('width')).toBe('36%');\n\n      $rootScope.value = 0;\n      $rootScope.$digest();\n      expect(getBar(0).css('width')).toBe('0%');\n    });\n\n    it('transcludes \"bar\" text', function() {\n      expect(getBar(0).text()).toBe('22/200');\n    });\n\n    it('adjusts the valuemax when it changes', function() {\n      expect(getBar(0).attr('aria-valuemax')).toBe('200');\n      $rootScope.max = 300;\n      $rootScope.$digest();\n      expect(getBar(0).attr('aria-valuemax')).toBe('300');\n    });\n  });\n\n  describe('\"max\" attribute using object', function() {\n    beforeEach(inject(function() {\n      element = $compile('<uib-progressbar max=\"settings.max\" animate=\"false\" value=\"settings.value\">{{settings.value}}/{{settings.max}}</uib-progressbar>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('should not modify outside object', function() {\n      if (typeof $rootScope.settings === 'object') {\n        // angular set's up the nested object therefore we have to check like this to avoid test crash\n        expect($rootScope.settings.max).toBeUndefined();\n      }\n      expect($rootScope.settings).toBeUndefined();\n      expect(getBar(0).attr('aria-valuemax')).toBe('100');\n      $rootScope.settings = {\n        max: 300,\n        value: 40\n      };\n      $rootScope.$digest();\n      expect($rootScope.settings.max).toBe(300);\n      expect(getBar(0).attr('aria-valuemax')).toBe('300');\n    });\n  });\n\n\n  describe('\"type\" attribute', function() {\n    beforeEach(inject(function() {\n      $rootScope.type = 'success';\n      element = $compile('<uib-progressbar value=\"value\" type=\"{{type}}\"></uib-progressbar>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('should use correct classes', function() {\n      expect(getBar(0)).toHaveClass(BAR_CLASS);\n      expect(getBar(0)).toHaveClass(BAR_CLASS + '-success');\n    });\n\n    it('should change classes if type changed', function() {\n      $rootScope.type = 'warning';\n      $rootScope.value += 1;\n      $rootScope.$digest();\n\n      var barEl = getBar(0);\n      expect(barEl).toHaveClass(BAR_CLASS);\n      expect(barEl).not.toHaveClass(BAR_CLASS + '-success');\n      expect(barEl).toHaveClass(BAR_CLASS + '-warning');\n    });\n  });\n\n  describe('stacked', function() {\n    beforeEach(inject(function() {\n      $rootScope.objects = [\n        { value: 10, title: 'foo', type: 'success' },\n        { value: 50, title: 'bar', type: 'warning' },\n        { value: 20, title: 'baz' }\n      ];\n      element = $compile('<uib-progress animate=\"false\"><uib-bar ng-repeat=\"o in objects\" value=\"o.value\" type=\"{{o.type}}\" title=\"{{o.title}}\">{{o.value}}</uib-bar></uib-progress>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('contains the right number of bars', function() {\n      expect(element.children().length).toBe(3);\n      for (var i = 0; i < 3; i++) {\n        expect(getBar(i)).toHaveClass(BAR_CLASS);\n      }\n    });\n\n    it('renders each bar with the appropriate width', function() {\n      expect(getBar(0).css('width')).toBe('10%');\n      expect(getBar(1).css('width')).toBe('50%');\n      expect(getBar(2).css('width')).toBe('20%');\n    });\n\n    it('uses correct classes', function() {\n      expect(getBar(0)).toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-warning');\n\n      expect(getBar(1)).not.toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(1)).toHaveClass(BAR_CLASS + '-warning');\n\n      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-warning');\n    });\n\n    it('should change classes if type changed', function() {\n      $rootScope.objects = [\n        { value: 20, type: 'warning' },\n        { value: 50 },\n        { value: 30, type: 'info' }\n      ];\n      $rootScope.$digest();\n\n      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(0)).toHaveClass(BAR_CLASS + '-warning');\n\n      expect(getBar(1)).not.toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(1)).not.toHaveClass(BAR_CLASS + '-warning');\n\n      expect(getBar(2)).toHaveClass(BAR_CLASS + '-info');\n      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(2)).not.toHaveClass(BAR_CLASS + '-warning');\n    });\n\n    it('should change classes if type changed', function() {\n      $rootScope.objects = [\n        { value: 70, type: 'info' }\n      ];\n      $rootScope.$digest();\n\n      expect(element.children().length).toBe(1);\n\n      expect(getBar(0)).toHaveClass(BAR_CLASS + '-info');\n      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-success');\n      expect(getBar(0)).not.toHaveClass(BAR_CLASS + '-warning');\n    });\n\n    it('should have the correct aria markup', function() {\n      expect(getBar(0).attr('aria-valuenow')).toBe('10');\n      expect(getBar(0).attr('aria-valuemin')).toBe('0');\n      expect(getBar(0).attr('aria-valuemax')).toBe('100');\n      expect(getBar(0).attr('aria-valuetext')).toBe('10%');\n      expect(getBar(0).attr('aria-labelledby')).toBe('foo');\n\n      expect(getBar(1).attr('aria-valuenow')).toBe('50');\n      expect(getBar(1).attr('aria-valuemin')).toBe('0');\n      expect(getBar(1).attr('aria-valuemax')).toBe('100');\n      expect(getBar(1).attr('aria-valuetext')).toBe('50%');\n      expect(getBar(1).attr('aria-labelledby')).toBe('bar');\n\n      expect(getBar(2).attr('aria-valuenow')).toBe('20');\n      expect(getBar(2).attr('aria-valuemin')).toBe('0');\n      expect(getBar(2).attr('aria-valuemax')).toBe('100');\n      expect(getBar(2).attr('aria-valuetext')).toBe('20%');\n      expect(getBar(2).attr('aria-labelledby')).toBe('baz');\n    });\n\n    it('should default to `progressbar`', function() {\n      $rootScope.objects = [\n        { value: 10, title: 'foo', type: 'success' },\n        { value: 50, title: 'bar', type: 'warning' },\n        { value: 20, title: 'baz' }\n      ];\n      element = $compile('<uib-progress animate=\"false\"><uib-bar ng-repeat=\"o in objects\" value=\"o.value\" type=\"{{o.type}}\">{{o.value}}</uib-bar></uib-progress>')($rootScope);\n      $rootScope.$digest();\n\n      expect(getBar(0).attr('aria-labelledby')).toBe('progressbar');\n      expect(getBar(1).attr('aria-labelledby')).toBe('progressbar');\n      expect(getBar(2).attr('aria-labelledby')).toBe('progressbar');\n    });\n\n    describe('\"max\" attribute', function() {\n      beforeEach(inject(function() {\n        $rootScope.max = 200;\n        element = $compile('<uib-progress max=\"max\" animate=\"false\"><uib-bar ng-repeat=\"o in objects\" value=\"o.value\">{{o.value}}/{{max}}</uib-bar></uib-progress>')($rootScope);\n        $rootScope.$digest();\n      }));\n\n      it('has the appropriate aria markup', function() {\n        expect(getBar(0).attr('aria-valuemax')).toBe('200');\n      });\n\n      it('adjusts the \"bar\" width when it changes', function() {\n        expect(getBar(0).css('width')).toBe('5%');\n        $rootScope.max = 250;\n        $rootScope.$digest();\n        expect(getBar(0).css('width')).toBe('4%');\n      });\n\n      it('adjusts the \"bar\" width when value changes', function() {\n        $rootScope.objects[0].value = 60;\n        $rootScope.$digest();\n        expect(getBar(0).css('width')).toBe('30%');\n\n        $rootScope.objects[0].value += 12;\n        $rootScope.$digest();\n        expect(getBar(0).css('width')).toBe('36%');\n\n        $rootScope.objects[0].value = 0;\n        $rootScope.$digest();\n        expect(getBar(0).css('width')).toBe('0%');\n      });\n\n      it('transcludes \"bar\" text', function() {\n        expect(getBar(0).text()).toBe('10/200');\n      });\n\n      it('adjusts the valuemax when it changes', function() {\n        expect(getBar(0).attr('aria-valuemax')).toBe('200');\n        $rootScope.max = 300;\n        $rootScope.$digest();\n        expect(getBar(0).attr('aria-valuemax')).toBe('300');\n      });\n\n      it('should not have a total width over 100%', function() {\n        $rootScope.objects = [\n          { value: 60, type: 'warning' },\n          { value: 103 },\n          { value: 270, type: 'info' }\n        ];\n        $rootScope.max = 433;\n        $rootScope.$digest();\n        var totalWidth = 0;\n        for (var i = 0; i < 3; i++) {\n          totalWidth += parseFloat(getBar(i).css('width'));\n        }\n        expect(totalWidth.toFixed(2)).toBe('100.00');\n      });\n\n      it('should not have a total width over 37.65% when removing bar', function() {\n        $rootScope.objects = [\n          { value: 60, type: 'warning' },\n          { value: 103 },\n          { value: 270, type: 'info' }\n        ];\n        $rootScope.max = 433;\n        $rootScope.$digest();\n        var totalWidth = 0;\n        var i;\n        for (i = 0; i < 3; i++) {\n          totalWidth += parseFloat(getBar(i).css('width'));\n        }\n        expect(totalWidth.toFixed(2)).toBe('100.00');\n\n        $rootScope.objects.splice(2, 1);\n        $rootScope.$digest();\n        totalWidth = 0;\n        for (i = 0; i < 2; i++) {\n          totalWidth += parseFloat(getBar(i).css('width'));\n        }\n        expect(totalWidth.toFixed(2)).toBe('37.65');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/rating/docs/demo.html",
    "content": "<div ng-controller=\"RatingDemoCtrl\">\n    <h4>Default</h4>\n    <span uib-rating ng-model=\"rate\" max=\"max\" read-only=\"isReadonly\" on-hover=\"hoveringOver(value)\" on-leave=\"overStar = null\" titles=\"['one','two','three']\" aria-labelledby=\"default-rating\"></span>\n    <span class=\"label\" ng-class=\"{'label-warning': percent<30, 'label-info': percent>=30 && percent<70, 'label-success': percent>=70}\" ng-show=\"overStar && !isReadonly\">{{percent}}%</span>\n\n    <pre style=\"margin:15px 0;\">Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i> - Hovering over: <b>{{overStar || \"none\"}}</b></pre>\n\n    <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"rate = 0\" ng-disabled=\"isReadonly\">Clear</button>\n    <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"isReadonly = ! isReadonly\">Toggle Readonly</button>\n    <hr />\n\n    <h4>Custom icons</h4>\n    <div ng-init=\"x = 5\"><span uib-rating ng-model=\"x\" max=\"15\" state-on=\"'glyphicon-ok-sign'\" state-off=\"'glyphicon-ok-circle'\" aria-labelledby=\"custom-icons-1\"></span> <b>(<i>Rate:</i> {{x}})</b></div>\n    <div ng-init=\"y = 2\"><span uib-rating ng-model=\"y\" rating-states=\"ratingStates\" aria-labelledby=\"custom-icons-2\"></span> <b>(<i>Rate:</i> {{y}})</b></div>\n</div>\n"
  },
  {
    "path": "src/rating/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('RatingDemoCtrl', function ($scope) {\n  $scope.rate = 7;\n  $scope.max = 10;\n  $scope.isReadonly = false;\n\n  $scope.hoveringOver = function(value) {\n    $scope.overStar = value;\n    $scope.percent = 100 * (value / $scope.max);\n  };\n\n  $scope.ratingStates = [\n    {stateOn: 'glyphicon-ok-sign', stateOff: 'glyphicon-ok-circle'},\n    {stateOn: 'glyphicon-star', stateOff: 'glyphicon-star-empty'},\n    {stateOn: 'glyphicon-heart', stateOff: 'glyphicon-ban-circle'},\n    {stateOn: 'glyphicon-heart'},\n    {stateOff: 'glyphicon-off'}\n  ];\n});\n"
  },
  {
    "path": "src/rating/docs/readme.md",
    "content": "Rating directive that will take care of visualising a star rating bar.\n\n### uib-rating settings\n\n* `max`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `5`)_ -\n  Changes the number of icons.\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  The current rate.\n\n* `on-hover(value)`\n  <small class=\"badge\">$</small> -\n  An optional expression called when user's mouse is over a particular icon.\n\n* `on-leave()`\n  <small class=\"badge\">$</small> -\n  An optional expression called when user's mouse leaves the control altogether.\n\n* `rating-states`\n  <small class=\"badge\">$</small>\n  _(Default: `null`)_ -\n  An array of objects defining properties for all icons. In default template, `stateOn` & `stateOff` property is used to specify the icon's class.\n\n* `read-only`\n  <small class=\"badge\">$</small>\n  <i class=\"icon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Prevent user's interaction.\n\n* `titles`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: ['one', 'two', 'three', 'four', 'five']`)_ -\n  An array of strings defining titles for all icons.\n\n* `enable-reset`\n  <small class=\"badge\">$</small>\n  _(Default: `true`)_ -\n  Clicking the icon of the current rating will reset the rating to 0.\n\n* `state-off`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `null`)_ -\n  A variable used in the template to specify the state for unselected icons.\n\n* `state-on`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `null`)_ -\n \tA variable used in the template to specify the state (class, src, etc) for selected icons.\n"
  },
  {
    "path": "src/rating/index.js",
    "content": "require('../../template/rating/rating.html.js');\nrequire('./rating');\n\nvar MODULE_NAME = 'ui.bootstrap.module.rating';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.rating', 'uib/template/rating/rating.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/rating/rating.js",
    "content": "angular.module('ui.bootstrap.rating', [])\n\n.constant('uibRatingConfig', {\n  max: 5,\n  stateOn: null,\n  stateOff: null,\n  enableReset: true,\n  titles: ['one', 'two', 'three', 'four', 'five']\n})\n\n.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {\n  var ngModelCtrl = { $setViewValue: angular.noop },\n    self = this;\n\n  this.init = function(ngModelCtrl_) {\n    ngModelCtrl = ngModelCtrl_;\n    ngModelCtrl.$render = this.render;\n\n    ngModelCtrl.$formatters.push(function(value) {\n      if (angular.isNumber(value) && value << 0 !== value) {\n        value = Math.round(value);\n      }\n\n      return value;\n    });\n\n    this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;\n    this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;\n    this.enableReset = angular.isDefined($attrs.enableReset) ?\n      $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;\n    var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;\n    this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?\n      tmpTitles : ratingConfig.titles;\n\n    var ratingStates = angular.isDefined($attrs.ratingStates) ?\n      $scope.$parent.$eval($attrs.ratingStates) :\n      new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);\n    $scope.range = this.buildTemplateObjects(ratingStates);\n  };\n\n  this.buildTemplateObjects = function(states) {\n    for (var i = 0, n = states.length; i < n; i++) {\n      states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);\n    }\n    return states;\n  };\n\n  this.getTitle = function(index) {\n    if (index >= this.titles.length) {\n      return index + 1;\n    }\n\n    return this.titles[index];\n  };\n\n  $scope.rate = function(value) {\n    if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {\n      var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;\n      ngModelCtrl.$setViewValue(newViewValue);\n      ngModelCtrl.$render();\n    }\n  };\n\n  $scope.enter = function(value) {\n    if (!$scope.readonly) {\n      $scope.value = value;\n    }\n    $scope.onHover({value: value});\n  };\n\n  $scope.reset = function() {\n    $scope.value = ngModelCtrl.$viewValue;\n    $scope.onLeave();\n  };\n\n  $scope.onKeydown = function(evt) {\n    if (/(37|38|39|40)/.test(evt.which)) {\n      evt.preventDefault();\n      evt.stopPropagation();\n      $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));\n    }\n  };\n\n  this.render = function() {\n    $scope.value = ngModelCtrl.$viewValue;\n    $scope.title = self.getTitle($scope.value - 1);\n  };\n}])\n\n.directive('uibRating', function() {\n  return {\n    require: ['uibRating', 'ngModel'],\n    restrict: 'A',\n    scope: {\n      readonly: '=?readOnly',\n      onHover: '&',\n      onLeave: '&'\n    },\n    controller: 'UibRatingController',\n    templateUrl: 'uib/template/rating/rating.html',\n    link: function(scope, element, attrs, ctrls) {\n      var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n      ratingCtrl.init(ngModelCtrl);\n    }\n  };\n});\n"
  },
  {
    "path": "src/rating/test/rating.spec.js",
    "content": "describe('rating directive', function() {\n  var $rootScope, $compile, element, innerElem;\n  beforeEach(module('ui.bootstrap.rating'));\n  beforeEach(module('uib/template/rating/rating.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $rootScope.rate = 3;\n    element = $compile('<span uib-rating ng-model=\"rate\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n  }));\n\n  function getStars() {\n    return innerElem.find('i');\n  }\n\n  function getStar(number) {\n    return getStars().eq(number - 1);\n  }\n\n  function getState(classOn, classOff) {\n    var stars = getStars();\n    var state = [];\n    for (var i = 0, n = stars.length; i < n; i++) {\n      state.push(stars.eq(i).hasClass(classOn || 'glyphicon-star') &&\n        !stars.eq(i).hasClass(classOff || 'glyphicon-star-empty'));\n    }\n    return state;\n  }\n\n  function getTitles() {\n    var stars = getStars();\n    return stars.toArray().map(function(star) {\n      return angular.element(star).attr('title');\n    });\n  }\n\n  function triggerKeyDown(keyCode) {\n    var e = $.Event('keydown');\n    e.which = keyCode;\n    innerElem.trigger(e);\n  }\n\n  it('contains the default number of icons', function() {\n    expect(getStars().length).toBe(5);\n    expect(innerElem.attr('aria-valuemax')).toBe('5');\n  });\n\n  it('initializes the default star icons as selected', function() {\n    expect(getState()).toEqual([true, true, true, false, false]);\n    expect(innerElem.attr('aria-valuenow')).toBe('3');\n  });\n\n  it('handles correctly the click event', function() {\n    getStar(2).click();\n    $rootScope.$digest();\n    expect(getState()).toEqual([true, true, false, false, false]);\n    expect($rootScope.rate).toBe(2);\n    expect(innerElem.attr('aria-valuenow')).toBe('2');\n\n    getStar(5).click();\n    $rootScope.$digest();\n    expect(getState()).toEqual([true, true, true, true, true]);\n    expect($rootScope.rate).toBe(5);\n    expect(innerElem.attr('aria-valuenow')).toBe('5');\n\n    getStar(5).click();\n    $rootScope.$digest();\n    expect(getState()).toEqual([false, false, false, false, false]);\n    expect($rootScope.rate).toBe(0);\n    expect(innerElem.attr('aria-valuenow')).toBe('0');\n  });\n\n  it('handles correctly the hover event', function() {\n    getStar(2).trigger('mouseover');\n    $rootScope.$digest();\n    expect(getState()).toEqual([true, true, false, false, false]);\n    expect($rootScope.rate).toBe(3);\n\n    getStar(5).trigger('mouseover');\n    $rootScope.$digest();\n    expect(getState()).toEqual([true, true, true, true, true]);\n    expect($rootScope.rate).toBe(3);\n\n    innerElem.trigger('mouseout');\n    expect(getState()).toEqual([true, true, true, false, false]);\n    expect($rootScope.rate).toBe(3);\n  });\n\n  it('rounds off the number of stars shown with decimal values', function() {\n    $rootScope.rate = 2.1;\n    $rootScope.$digest();\n\n    expect(getState()).toEqual([true, true, false, false, false]);\n    expect(innerElem.attr('aria-valuenow')).toBe('2');\n\n    $rootScope.rate = 2.5;\n    $rootScope.$digest();\n\n    expect(getState()).toEqual([true, true, true, false, false]);\n    expect(innerElem.attr('aria-valuenow')).toBe('3');\n  });\n\n  it('changes the number of selected icons when value changes', function() {\n    $rootScope.rate = 2;\n    $rootScope.$digest();\n\n    expect(getState()).toEqual([true, true, false, false, false]);\n    expect(innerElem.attr('aria-valuenow')).toBe('2');\n    expect(innerElem.attr('aria-valuetext')).toBe('two');\n  });\n\n  it('shows different number of icons when `max` attribute is set', function() {\n    element = $compile('<span uib-rating ng-model=\"rate\" max=\"7\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n\n    expect(getStars().length).toBe(7);\n    expect(innerElem.attr('aria-valuemax')).toBe('7');\n  });\n\n  it('shows different number of icons when `max` attribute is from scope variable', function() {\n    $rootScope.max = 15;\n    element = $compile('<span uib-rating ng-model=\"rate\" max=\"max\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n    expect(getStars().length).toBe(15);\n    expect(innerElem.attr('aria-valuemax')).toBe('15');\n  });\n\n  it('handles read-only attribute', function() {\n    $rootScope.isReadonly = true;\n    element = $compile('<span uib-rating ng-model=\"rate\" read-only=\"isReadonly\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n\n    expect(getState()).toEqual([true, true, true, false, false]);\n\n    var star5 = getStar(5);\n    star5.trigger('mouseover');\n    $rootScope.$digest();\n    expect(getState()).toEqual([true, true, true, false, false]);\n\n    $rootScope.isReadonly = false;\n    $rootScope.$digest();\n\n    star5.trigger('mouseover');\n    $rootScope.$digest();\n    expect(getState()).toEqual([true, true, true, true, true]);\n  });\n\n  it('handles enable-reset attribute', function() {\n    $rootScope.canReset = false;\n    element = $compile('<span uib-rating ng-model=\"rate\" enable-reset=\"canReset\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n\n    var star = {\n      states: [true, true, true, true, true],\n      rating: 5\n    };\n\n    var selectStar = getStar(star.rating);\n\n    selectStar.click();\n    $rootScope.$digest();\n    expect(getState()).toEqual(star.states);\n    expect($rootScope.rate).toBe(5);\n    expect(innerElem.attr('aria-valuenow')).toBe('5');\n\n    selectStar.click();\n    $rootScope.$digest();\n    expect(getState()).toEqual(star.states);\n    expect($rootScope.rate).toBe(5);\n    expect(innerElem.attr('aria-valuenow')).toBe('5');\n  });\n\n  it('should fire onHover', function() {\n    $rootScope.hoveringOver = jasmine.createSpy('hoveringOver');\n    element = $compile('<span uib-rating ng-model=\"rate\" on-hover=\"hoveringOver(value)\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n\n    getStar(3).trigger('mouseover');\n    $rootScope.$digest();\n    expect($rootScope.hoveringOver).toHaveBeenCalledWith(3);\n  });\n\n  it('should fire onLeave', function() {\n    $rootScope.leaving = jasmine.createSpy('leaving');\n    element = $compile('<span uib-rating ng-model=\"rate\" on-leave=\"leaving()\"></span>')($rootScope);\n    $rootScope.$digest();\n    innerElem = element.children().eq(0);\n\n    innerElem.trigger('mouseleave');\n    $rootScope.$digest();\n    expect($rootScope.leaving).toHaveBeenCalled();\n  });\n\n  describe('keyboard navigation', function() {\n    it('supports arrow keys', function() {\n      triggerKeyDown(38);\n      expect($rootScope.rate).toBe(4);\n\n      triggerKeyDown(37);\n      expect($rootScope.rate).toBe(3);\n      triggerKeyDown(40);\n      expect($rootScope.rate).toBe(2);\n\n      triggerKeyDown(39);\n      expect($rootScope.rate).toBe(3);\n    });\n\n    it('supports only arrow keys', function() {\n      $rootScope.rate = undefined;\n      $rootScope.$digest();\n\n      triggerKeyDown(36);\n      expect($rootScope.rate).toBe(undefined);\n\n      triggerKeyDown(41);\n      expect($rootScope.rate).toBe(undefined);\n    });\n\n    it('can get zero value but not negative', function() {\n      $rootScope.rate = 1;\n      $rootScope.$digest();\n\n      triggerKeyDown(37);\n      expect($rootScope.rate).toBe(0);\n\n      triggerKeyDown(37);\n      expect($rootScope.rate).toBe(0);\n    });\n\n    it('cannot get value above max', function() {\n      $rootScope.rate = 4;\n      $rootScope.$digest();\n\n      triggerKeyDown(38);\n      expect($rootScope.rate).toBe(5);\n\n      triggerKeyDown(38);\n      expect($rootScope.rate).toBe(5);\n    });\n  });\n\n  describe('custom states', function() {\n    beforeEach(inject(function() {\n      $rootScope.classOn = 'icon-ok-sign';\n      $rootScope.classOff = 'icon-ok-circle';\n      element = $compile('<span uib-rating ng-model=\"rate\" state-on=\"classOn\" state-off=\"classOff\"></span>')($rootScope);\n      $rootScope.$digest();\n      innerElem = element.children().eq(0);\n    }));\n\n    it('changes the default icons', function() {\n      expect(getState($rootScope.classOn, $rootScope.classOff)).toEqual([true, true, true, false, false]);\n    });\n  });\n\n  describe('`rating-states`', function() {\n    beforeEach(inject(function() {\n      $rootScope.states = [\n        {stateOn: 'sign', stateOff: 'circle'},\n        {stateOn: 'heart', stateOff: 'ban'},\n        {stateOn: 'heart'},\n        {stateOff: 'off'}\n      ];\n      element = $compile('<span uib-rating ng-model=\"rate\" rating-states=\"states\"></span>')($rootScope);\n      $rootScope.$digest();\n      innerElem = element.children().eq(0);\n    }));\n\n    it('should define number of icon elements', function() {\n      expect(getStars().length).toBe(4);\n      expect(innerElem.attr('aria-valuemax')).toBe('4');\n    });\n\n    it('handles each icon', function() {\n      var stars = getStars();\n\n      for (var i = 0; i < stars.length; i++) {\n        var star = stars.eq(i);\n        var state = $rootScope.states[i];\n        var isOn = i < $rootScope.rate;\n\n        expect(star.hasClass(state.stateOn)).toBe(isOn);\n        expect(star.hasClass(state.stateOff)).toBe(!isOn);\n      }\n    });\n  });\n\n  describe('setting uibRatingConfig', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibRatingConfig) {\n      $rootScope.rate = 5;\n      angular.extend(originalConfig, uibRatingConfig);\n      uibRatingConfig.max = 10;\n      uibRatingConfig.stateOn = 'on';\n      uibRatingConfig.stateOff = 'off';\n      element = $compile('<span uib-rating ng-model=\"rate\"></span>')($rootScope);\n      $rootScope.$digest();\n      innerElem = element.children().eq(0);\n    }));\n    afterEach(inject(function(uibRatingConfig) {\n      // return it to the original state\n      angular.extend(uibRatingConfig, originalConfig);\n    }));\n\n    it('should change number of icon elements', function() {\n      expect(getStars().length).toBe(10);\n    });\n\n    it('should change icon states', function() {\n      expect(getState('on', 'off')).toEqual([true, true, true, true, true, false, false, false, false, false]);\n    });\n  });\n\n  describe('Default title', function() {\n    it('should return the default title for each star', function() {\n      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);\n    });\n  });\n\n  describe('shows different title when `max` attribute is greater than the titles array ', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(uibRatingConfig) {\n      $rootScope.rate = 5;\n      angular.extend(originalConfig, uibRatingConfig);\n      uibRatingConfig.max = 10;\n      element = $compile('<span uib-rating ng-model=\"rate\"></span>')($rootScope);\n      $rootScope.$digest();\n      innerElem = element.children().eq(0);\n    }));\n    afterEach(inject(function(uibRatingConfig) {\n      // return it to the original state\n      angular.extend(uibRatingConfig, originalConfig);\n    }));\n\n   it('should return the default title for each star', function() {\n      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five', '6', '7', '8', '9', '10']);\n    });\n  });\n\n  describe('shows custom titles ', function() {\n    it('should return the custom title for each star', function() {\n      $rootScope.titles = [44,45,46];\n      element = $compile('<span uib-rating ng-model=\"rate\" titles=\"titles\"></span>')($rootScope);\n      $rootScope.$digest();\n      innerElem = element.children().eq(0);\n      expect(getTitles()).toEqual(['44', '45', '46', '4', '5']);\n    });\n    it('should return the default title if the custom title is empty', function() {\n      $rootScope.titles = [];\n      element = $compile('<span uib-rating ng-model=\"rate\" titles=\"titles\"></span>')($rootScope);\n      $rootScope.$digest();\n      innerElem = element.children().eq(0);\n      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);\n    });\n   it('should return the default title if the custom title is not an array', function() {\n      element = $compile('<span uib-rating ng-model=\"rate\" titles=\"test\"></span>')($rootScope);\n      $rootScope.$digest();\n      expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);\n    });\n  });\n});\n"
  },
  {
    "path": "src/stackedMap/index.js",
    "content": "require('./stackedMap');\n\nvar MODULE_NAME = 'ui.bootstrap.module.stackedMap';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.stackedMap']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/stackedMap/stackedMap.js",
    "content": "angular.module('ui.bootstrap.stackedMap', [])\n/**\n * A helper, internal data structure that acts as a map but also allows getting / removing\n * elements in the LIFO order\n */\n  .factory('$$stackedMap', function() {\n    return {\n      createNew: function() {\n        var stack = [];\n\n        return {\n          add: function(key, value) {\n            stack.push({\n              key: key,\n              value: value\n            });\n          },\n          get: function(key) {\n            for (var i = 0; i < stack.length; i++) {\n              if (key === stack[i].key) {\n                return stack[i];\n              }\n            }\n          },\n          keys: function() {\n            var keys = [];\n            for (var i = 0; i < stack.length; i++) {\n              keys.push(stack[i].key);\n            }\n            return keys;\n          },\n          top: function() {\n            return stack[stack.length - 1];\n          },\n          remove: function(key) {\n            var idx = -1;\n            for (var i = 0; i < stack.length; i++) {\n              if (key === stack[i].key) {\n                idx = i;\n                break;\n              }\n            }\n            return stack.splice(idx, 1)[0];\n          },\n          removeTop: function() {\n            return stack.pop();\n          },\n          length: function() {\n            return stack.length;\n          }\n        };\n      }\n    };\n  });"
  },
  {
    "path": "src/stackedMap/test/stackedMap.spec.js",
    "content": "describe('stacked map', function() {\n  var stackedMap;\n\n  beforeEach(module('ui.bootstrap.modal'));\n  beforeEach(inject(function ($$stackedMap) {\n    stackedMap = $$stackedMap.createNew();\n  }));\n\n  it('should add and remove objects by key', function() {\n    stackedMap.add('foo', 'foo_value');\n    expect(stackedMap.length()).toEqual(1);\n    expect(stackedMap.get('foo').key).toEqual('foo');\n    expect(stackedMap.get('foo').value).toEqual('foo_value');\n\n    stackedMap.remove('foo');\n    expect(stackedMap.length()).toEqual(0);\n    expect(stackedMap.get('foo')).toBeUndefined();\n  });\n\n  it('should support listing keys', function() {\n    stackedMap.add('foo', 'foo_value');\n    stackedMap.add('bar', 'bar_value');\n\n    expect(stackedMap.keys()).toEqual(['foo', 'bar']);\n  });\n\n  it('should get topmost element', function() {\n    stackedMap.add('foo', 'foo_value');\n    stackedMap.add('bar', 'bar_value');\n    expect(stackedMap.length()).toEqual(2);\n\n    expect(stackedMap.top().key).toEqual('bar');\n    expect(stackedMap.length()).toEqual(2);\n  });\n\n  it('should remove topmost element', function() {\n    stackedMap.add('foo', 'foo_value');\n    stackedMap.add('bar', 'bar_value');\n\n    expect(stackedMap.removeTop().key).toEqual('bar');\n    expect(stackedMap.removeTop().key).toEqual('foo');\n  });\n\n  it('should preserve semantic of an empty stackedMap', function() {\n    expect(stackedMap.length()).toEqual(0);\n    expect(stackedMap.top()).toBeUndefined();\n  });\n\n  it('should ignore removal of non-existing elements', function() {\n    expect(stackedMap.remove('non-existing')).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "src/tabindex/index.js",
    "content": "require('./tabindex');\n\nvar MODULE_NAME = 'ui.bootstrap.module.tabindex';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.tabindex']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/tabindex/tabindex.js",
    "content": "angular.module('ui.bootstrap.tabindex', [])\n\n.directive('uibTabindexToggle', function() {\n  return {\n    restrict: 'A',\n    link: function(scope, elem, attrs) {\n      attrs.$observe('disabled', function(disabled) {\n        attrs.$set('tabindex', disabled ? -1 : null);\n      });\n    }\n  };\n});\n"
  },
  {
    "path": "src/tabindex/test/tabindex.spec.js",
    "content": "describe('tabindex toggle directive', function() {\n  var $rootScope, element;\n  beforeEach(module('ui.bootstrap.tabindex'));\n  beforeEach(inject(function($compile, _$rootScope_) {\n    $rootScope = _$rootScope_;\n    element = $compile('<a href uib-tabindex-toggle ng-disabled=\"disabled\">foo</a>')($rootScope);\n    $rootScope.$digest();\n  }));\n\n  it('should toggle the tabindex on disabled toggle', function() {\n    expect(element.prop('tabindex')).toBe(0);\n\n    $rootScope.disabled = true;\n    $rootScope.$digest();\n\n    expect(element.prop('tabindex')).toBe(-1);\n\n    $rootScope.disabled = false;\n    $rootScope.$digest();\n\n    expect(element.prop('tabindex')).toBe(0);\n  });\n});\n"
  },
  {
    "path": "src/tabs/docs/demo.html",
    "content": "<style type=\"text/css\">\n  form.tab-form-demo .tab-pane {\n    margin: 20px 20px;\n  }\n</style>\n\n<div ng-controller=\"TabsDemoCtrl\">\n  <p>Select a tab by setting active binding to true:</p>\n  <p>\n    <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"active = 1\">Select second tab</button>\n    <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"active = 2\">Select third tab</button>\n  </p>\n  <p>\n    <button type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"tabs[1].disabled = ! tabs[1].disabled\">Enable / Disable third tab</button>\n  </p>\n  <hr />\n\n  <uib-tabset active=\"active\">\n    <uib-tab index=\"0\" heading=\"Static title\">Static content</uib-tab>\n    <uib-tab index=\"$index + 1\" ng-repeat=\"tab in tabs\" heading=\"{{tab.title}}\" disable=\"tab.disabled\">\n      {{tab.content}}\n    </uib-tab>\n    <uib-tab index=\"3\" select=\"alertMe()\">\n      <uib-tab-heading>\n        <i class=\"glyphicon glyphicon-bell\"></i> Alert!\n      </uib-tab-heading>\n      I've got an HTML heading, and a select callback. Pretty cool!\n    </uib-tab>\n  </uib-tabset>\n\n  <hr />\n\n  <uib-tabset active=\"activePill\" vertical=\"true\" type=\"pills\">\n    <uib-tab index=\"0\" heading=\"Vertical 1\">Vertical content 1</uib-tab>\n    <uib-tab index=\"1\" heading=\"Vertical 2\">Vertical content 2</uib-tab>\n  </uib-tabset>\n\n  <hr />\n\n  <uib-tabset active=\"activeJustified\" justified=\"true\">\n    <uib-tab index=\"0\" heading=\"Justified\">Justified content</uib-tab>\n    <uib-tab index=\"1\" heading=\"SJ\">Short Labeled Justified content</uib-tab>\n    <uib-tab index=\"2\" heading=\"Long Justified\">Long Labeled Justified content</uib-tab>\n  </uib-tabset>\n\n  <hr />\n\n  Tabbed pills with CSS classes\n  <uib-tabset type=\"pills\">\n    <uib-tab heading=\"Default Size\">Tab 1 content</uib-tab>\n    <uib-tab heading=\"Small Button\" classes=\"btn-sm\">Tab 2 content</uib-tab>\n  </uib-tabset>\n\n  <hr />\n\n  Tabs using nested forms:\n  <form name=\"outerForm\" class=\"tab-form-demo\">\n    <uib-tabset active=\"activeForm\">\n      <uib-tab index=\"0\" heading=\"Form Tab\">\n        <ng-form name=\"nestedForm\">\n          <div class=\"form-group\">\n            <label>Name</label>\n            <input type=\"text\" class=\"form-control\" required ng-model=\"model.name\"/>\n          </div>\n        </ng-form>\n      </uib-tab>\n      <uib-tab index=\"1\" heading=\"Tab One\">\n        Some Tab Content\n      </uib-tab>\n      <uib-tab index=\"2\" heading=\"Tab Two\">\n        More Tab Content\n      </uib-tab>\n    </uib-tabset>\n  </form>\n  Model:\n  <pre>{{ model | json }}</pre>\n  Nested Form:\n  <pre>{{ outerForm.nestedForm | json }}</pre>\n</div>\n"
  },
  {
    "path": "src/tabs/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('TabsDemoCtrl', function ($scope, $window) {\n  $scope.tabs = [\n    { title:'Dynamic Title 1', content:'Dynamic content 1' },\n    { title:'Dynamic Title 2', content:'Dynamic content 2', disabled: true }\n  ];\n\n  $scope.alertMe = function() {\n    setTimeout(function() {\n      $window.alert('You\\'ve selected the alert tab!');\n    });\n  };\n\n  $scope.model = {\n    name: 'Tabs'\n  };\n});\n"
  },
  {
    "path": "src/tabs/docs/readme.md",
    "content": "AngularJS version of the tabs directive.\n\n### uib-tabset settings\n\n* `active`\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `Index of first tab`)_ -\n  Active index of tab. Setting this to an existing tab index will make that tab active.\n\n* `justified`\n  <small class=\"badge\">$</small>\n  _(Default: `false`)_ -\n  Whether tabs fill the container and have a consistent width.\n\n  * `template-url`\n  _(Default: `uib/template/tabs/tabset.html`)_ -\n  A URL representing the location of a template to use for the main component.\n\n* `type`\n  _(Defaults: `tabs`)_ -\n  Navigation type. Possible values are 'tabs' and 'pills'.\n\n* `vertical`\n  <small class=\"badge\">$</small>\n  _(Default: `false`)_ -\n  Whether tabs appear vertically stacked.\n\n### uib-tab settings\n\n* `classes`\n  <small class=\"badge\">$</small> -\n   An optional string of space-separated CSS classes.\n\n* `deselect()`\n  <small class=\"badge\">$</small> -\n  An optional expression called when tab is deactivated. Supports `$event` and `$selectedIndex` in template for expression. You may call `$event.preventDefault()` in this event handler to prevent a tab change from occurring. The `$selectedIndex` can be used to determine which tab was attempted to be opened.\n\n* `disable`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether tab is clickable and can be activated.\n\n* `heading` -\n  Heading text.\n\n* `index` -\n  Tab index. Must be unique number or string.\n\n* `select()`\n  <small class=\"badge\">$</small> -\n  An optional expression called when tab is activated. Supports $event in template for expression.\n\n* `template-url`\n  _(Default: `uib/template/tabs/tab.html`)_ -\n  A URL representing the location of a template to use for the tab heading.\n\n### Tabset heading\n\nInstead of the `heading` attribute on the `uib-tabset`, you can use an `uib-tab-heading` element inside a tabset that will be used as the tabset's header. There you can use HTML as well.\n\n### Known issues\n\nTo use clickable elements within the tab, you have override the tab template to use div elements instead of anchor elements, and replicate the desired styles from Bootstrap's CSS. This is due to browsers interpreting anchor elements as the target of any click event, which triggers routing when certain elements such as buttons are nested inside the anchor element.\n"
  },
  {
    "path": "src/tabs/index.js",
    "content": "require('../../template/tabs/tab.html.js');\nrequire('../../template/tabs/tabset.html.js');\nrequire('./tabs');\n\nvar MODULE_NAME = 'ui.bootstrap.module.tabs';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.tabs', 'uib/template/tabs/tab.html', 'uib/template/tabs/tabset.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/tabs/tabs.js",
    "content": "angular.module('ui.bootstrap.tabs', [])\n\n.controller('UibTabsetController', ['$scope', function ($scope) {\n  var ctrl = this,\n    oldIndex;\n  ctrl.tabs = [];\n\n  ctrl.select = function(index, evt) {\n    if (!destroyed) {\n      var previousIndex = findTabIndex(oldIndex);\n      var previousSelected = ctrl.tabs[previousIndex];\n      if (previousSelected) {\n        previousSelected.tab.onDeselect({\n          $event: evt,\n          $selectedIndex: index\n        });\n        if (evt && evt.isDefaultPrevented()) {\n          return;\n        }\n        previousSelected.tab.active = false;\n      }\n\n      var selected = ctrl.tabs[index];\n      if (selected) {\n        selected.tab.onSelect({\n          $event: evt\n        });\n        selected.tab.active = true;\n        ctrl.active = selected.index;\n        oldIndex = selected.index;\n      } else if (!selected && angular.isDefined(oldIndex)) {\n        ctrl.active = null;\n        oldIndex = null;\n      }\n    }\n  };\n\n  ctrl.addTab = function addTab(tab) {\n    ctrl.tabs.push({\n      tab: tab,\n      index: tab.index\n    });\n    ctrl.tabs.sort(function(t1, t2) {\n      if (t1.index > t2.index) {\n        return 1;\n      }\n\n      if (t1.index < t2.index) {\n        return -1;\n      }\n\n      return 0;\n    });\n\n    if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) {\n      var newActiveIndex = findTabIndex(tab.index);\n      ctrl.select(newActiveIndex);\n    }\n  };\n\n  ctrl.removeTab = function removeTab(tab) {\n    var index;\n    for (var i = 0; i < ctrl.tabs.length; i++) {\n      if (ctrl.tabs[i].tab === tab) {\n        index = i;\n        break;\n      }\n    }\n\n    if (ctrl.tabs[index].index === ctrl.active) {\n      var newActiveTabIndex = index === ctrl.tabs.length - 1 ?\n        index - 1 : index + 1 % ctrl.tabs.length;\n      ctrl.select(newActiveTabIndex);\n    }\n\n    ctrl.tabs.splice(index, 1);\n  };\n\n  $scope.$watch('tabset.active', function(val) {\n    if (angular.isDefined(val) && val !== oldIndex) {\n      ctrl.select(findTabIndex(val));\n    }\n  });\n\n  var destroyed;\n  $scope.$on('$destroy', function() {\n    destroyed = true;\n  });\n\n  function findTabIndex(index) {\n    for (var i = 0; i < ctrl.tabs.length; i++) {\n      if (ctrl.tabs[i].index === index) {\n        return i;\n      }\n    }\n  }\n}])\n\n.directive('uibTabset', function() {\n  return {\n    transclude: true,\n    replace: true,\n    scope: {},\n    bindToController: {\n      active: '=?',\n      type: '@'\n    },\n    controller: 'UibTabsetController',\n    controllerAs: 'tabset',\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/tabs/tabset.html';\n    },\n    link: function(scope, element, attrs) {\n      scope.vertical = angular.isDefined(attrs.vertical) ?\n        scope.$parent.$eval(attrs.vertical) : false;\n      scope.justified = angular.isDefined(attrs.justified) ?\n        scope.$parent.$eval(attrs.justified) : false;\n    }\n  };\n})\n\n.directive('uibTab', ['$parse', function($parse) {\n  return {\n    require: '^uibTabset',\n    replace: true,\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || 'uib/template/tabs/tab.html';\n    },\n    transclude: true,\n    scope: {\n      heading: '@',\n      index: '=?',\n      classes: '@?',\n      onSelect: '&select', //This callback is called in contentHeadingTransclude\n                          //once it inserts the tab's content into the dom\n      onDeselect: '&deselect'\n    },\n    controller: function() {\n      //Empty controller so other directives can require being 'under' a tab\n    },\n    controllerAs: 'tab',\n    link: function(scope, elm, attrs, tabsetCtrl, transclude) {\n      scope.disabled = false;\n      if (attrs.disable) {\n        scope.$parent.$watch($parse(attrs.disable), function(value) {\n          scope.disabled = !! value;\n        });\n      }\n\n      if (angular.isUndefined(attrs.index)) {\n        if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {\n          scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;\n        } else {\n          scope.index = 0;\n        }\n      }\n\n      if (angular.isUndefined(attrs.classes)) {\n        scope.classes = '';\n      }\n\n      scope.select = function(evt) {\n        if (!scope.disabled) {\n          var index;\n          for (var i = 0; i < tabsetCtrl.tabs.length; i++) {\n            if (tabsetCtrl.tabs[i].tab === scope) {\n              index = i;\n              break;\n            }\n          }\n\n          tabsetCtrl.select(index, evt);\n        }\n      };\n\n      tabsetCtrl.addTab(scope);\n      scope.$on('$destroy', function() {\n        tabsetCtrl.removeTab(scope);\n      });\n\n      //We need to transclude later, once the content container is ready.\n      //when this link happens, we're inside a tab heading.\n      scope.$transcludeFn = transclude;\n    }\n  };\n}])\n\n.directive('uibTabHeadingTransclude', function() {\n  return {\n    restrict: 'A',\n    require: '^uibTab',\n    link: function(scope, elm) {\n      scope.$watch('headingElement', function updateHeadingElement(heading) {\n        if (heading) {\n          elm.html('');\n          elm.append(heading);\n        }\n      });\n    }\n  };\n})\n\n.directive('uibTabContentTransclude', function() {\n  return {\n    restrict: 'A',\n    require: '^uibTabset',\n    link: function(scope, elm, attrs) {\n      var tab = scope.$eval(attrs.uibTabContentTransclude).tab;\n\n      //Now our tab is ready to be transcluded: both the tab heading area\n      //and the tab content area are loaded.  Transclude 'em both.\n      tab.$transcludeFn(tab.$parent, function(contents) {\n        angular.forEach(contents, function(node) {\n          if (isTabHeading(node)) {\n            //Let tabHeadingTransclude know.\n            tab.headingElement = node;\n          } else {\n            elm.append(node);\n          }\n        });\n      });\n    }\n  };\n\n  function isTabHeading(node) {\n    return node.tagName && (\n      node.hasAttribute('uib-tab-heading') ||\n      node.hasAttribute('data-uib-tab-heading') ||\n      node.hasAttribute('x-uib-tab-heading') ||\n      node.tagName.toLowerCase() === 'uib-tab-heading' ||\n      node.tagName.toLowerCase() === 'data-uib-tab-heading' ||\n      node.tagName.toLowerCase() === 'x-uib-tab-heading' ||\n      node.tagName.toLowerCase() === 'uib:tab-heading'\n    );\n  }\n});\n"
  },
  {
    "path": "src/tabs/test/tabs.spec.js",
    "content": "describe('tabs', function() {\n  var elm, scope;\n\n  beforeEach(module('ui.bootstrap.tabs'));\n  beforeEach(module('uib/template/tabs/tabset.html'));\n  beforeEach(module('uib/template/tabs/tab.html'));\n\n  function titles() {\n    return elm.find('ul.nav-tabs li');\n  }\n  function contents() {\n    return elm.find('div.tab-content div.tab-pane');\n  }\n\n  function expectTitles(titlesArray) {\n    var t = titles();\n    expect(t.length).toEqual(titlesArray.length);\n    for (var i = 0; i < t.length; i++) {\n      expect(t.eq(i).text().trim()).toEqual(titlesArray[i]);\n    }\n  }\n\n  function expectContents(contentsArray) {\n    var c = contents();\n    expect(c.length).toEqual(contentsArray.length);\n    for (var i = 0; i < c.length; i++) {\n      expect(c.eq(i).text().trim()).toEqual(contentsArray[i]);\n    }\n  }\n\n  describe('basics', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      scope.first = '1';\n      scope.second = '2';\n      scope.third = '3';\n      scope.active = 1;\n      scope.firstClass = 'first-class';\n      scope.secondClass = 'second-class-1 second-class-2';\n      scope.selectFirst = jasmine.createSpy();\n      scope.selectSecond = jasmine.createSpy();\n      scope.deselectFirst = jasmine.createSpy();\n      scope.deselectSecond = jasmine.createSpy();\n      scope.deselectThird = function($event) {\n        $event.preventDefault();\n      };\n      elm = $compile([\n        '<uib-tabset class=\"hello\" data-pizza=\"pepperoni\" active=\"active\">',\n        '  <uib-tab index=\"1\" heading=\"First Tab {{first}}\" classes=\"{{firstClass}}\" select=\"selectFirst($event)\" deselect=\"deselectFirst($event, $selectedIndex)\">',\n        '    first content is {{first}}',\n        '  </uib-tab>',\n        '  <uib-tab index=\"2\" classes=\"{{secondClass}}\" select=\"selectSecond($event)\" deselect=\"deselectSecond($event, $selectedIndex)\">',\n        '    <uib-tab-heading><b>Second</b> Tab {{second}}</uib-tab-heading>',\n        '    second content is {{second}}',\n        '  </uib-tab>',\n        '  <uib-tab index=\"3\" classes=\"{{thirdClass}}\" deselect=\"deselectThird($event, $selectedIndex)\">',\n        '    <uib-tab-heading><b>Second</b> Tab {{third}}</uib-tab-heading>',\n        '    third content is {{third}}',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n      return elm;\n    }));\n\n    it('should pass class and other attributes on to tab template', function() {\n      expect(elm).toHaveClass('hello');\n      expect(elm.attr('data-pizza')).toBe('pepperoni');\n      //Ensure that we have bootstrap 4 link class so things are future proofed.\n      var link = $(elm.find('a')[0]);\n      expect(link).toHaveClass('nav-link');\n    });\n\n    it('should create clickable titles', function() {\n      var t = titles();\n      expect(t.length).toBe(3);\n      expect(t.find('> a').eq(0).text()).toBe('First Tab 1');\n      //It should put the uib-tab-heading element into the 'a' title\n      expect(t.find('> a').eq(1).children().is('uib-tab-heading')).toBe(true);\n      expect(t.find('> a').eq(1).children().html()).toBe('<b>Second</b> Tab 2');\n    });\n\n    it('should bind tabs content and set first tab active', function() {\n      expectContents(['first content is 1', 'second content is 2', 'third content is 3']);\n      expect(titles().eq(0)).toHaveClass('active');\n      expect(titles().eq(1)).not.toHaveClass('active');\n      expect(scope.active).toBe(1);\n    });\n\n    it('should set optional classes on each tab', function() {\n      expect(titles().eq(0)).toHaveClass(scope.firstClass);\n\n      var secondClassArr = scope.secondClass.split(' ');\n      secondClassArr.forEach(function(clazz) {\n        expect(titles().eq(1)).toHaveClass(clazz);\n      });\n    });\n\n    it('should change active on click', function() {\n      titles().eq(1).find('> a').click();\n      expect(contents().eq(1)).toHaveClass('active');\n      expect(titles().eq(0)).not.toHaveClass('active');\n      expect(titles().eq(1)).toHaveClass('active');\n      expect(scope.active).toBe(2);\n    });\n\n    it('should call select callback on select', function() {\n      expect(scope.selectFirst.calls.count()).toBe(1);\n      titles().eq(1).find('> a').click();\n      expect(scope.selectSecond).toHaveBeenCalled();\n      expect(scope.selectSecond.calls.argsFor(0)[0].target).toBe(titles().eq(1).find('> a')[0]);\n      titles().eq(0).find('> a').click();\n      expect(scope.selectFirst).toHaveBeenCalled();\n      expect(scope.selectFirst.calls.argsFor(1)[0].target).toBe(titles().eq(0).find('> a')[0]);\n    });\n\n    it('should call deselect callback on deselect', function() {\n      titles().eq(1).find('> a').click();\n      expect(scope.deselectFirst).toHaveBeenCalled();\n      expect(scope.deselectFirst.calls.argsFor(0)[0].target).toBe(titles().eq(1).find('> a')[0]);\n      expect(scope.deselectFirst.calls.argsFor(0)[1]).toBe(1);\n      titles().eq(0).find('> a').click();\n      expect(scope.deselectSecond).toHaveBeenCalled();\n      expect(scope.deselectSecond.calls.argsFor(0)[0].target).toBe(titles().eq(0).find('> a')[0]);\n      expect(scope.deselectSecond.calls.argsFor(0)[1]).toBe(0);\n      titles().eq(1).find('> a').click();\n      expect(scope.deselectFirst.calls.count()).toBe(2);\n      expect(scope.deselectFirst.calls.argsFor(1)[0].target).toBe(titles().eq(1).find('> a')[0]);\n      expect(scope.deselectFirst.calls.argsFor(1)[1]).toBe(1);\n    });\n\n    it('should prevent tab deselection when $event.preventDefault() is called', function() {\n      spyOn(scope, 'deselectThird');\n      titles().eq(2).find('> a').click();\n      expect(scope.active).toBe(3);\n      titles().eq(1).find('> a').click();\n      expect(scope.deselectThird).toHaveBeenCalled();\n      expect(scope.active).not.toBe(1);\n      expect(scope.active).toBe(2);\n    });\n  });\n\n  describe('basics with initial active tab', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n\n      function makeTab(index) {\n        return {\n          index: index,\n          select: jasmine.createSpy()\n        };\n      }\n      scope.tabs = [\n        makeTab(1), makeTab(3), makeTab(5), makeTab(7)\n      ];\n      scope.active = 5;\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"1\" select=\"tabs[0].select()\">',\n        '  </uib-tab>',\n        '  <uib-tab index=\"3\" select=\"tabs[1].select()\">',\n        '  </uib-tab>',\n        '  <uib-tab index=\"5\" select=\"tabs[2].select()\">',\n        '  </uib-tab>',\n        '  <uib-tab index=\"7\" select=\"tabs[3].select()\">',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n    }));\n\n    function expectTabActive(activeTab) {\n      var _titles = titles();\n      angular.forEach(scope.tabs, function(tab, i) {\n        if (activeTab === tab) {\n          expect(scope.active).toBe(tab.index);\n          //It should only call select ONCE for each select\n          expect(tab.select).toHaveBeenCalled();\n          expect(_titles.eq(i)).toHaveClass('active');\n          expect(contents().eq(i)).toHaveClass('active');\n        } else {\n          expect(scope.active).not.toBe(tab.index);\n          expect(_titles.eq(i)).not.toHaveClass('active');\n        }\n      });\n    }\n\n    it('should make tab titles and set active tab active', function() {\n      expect(titles().length).toBe(scope.tabs.length);\n      expectTabActive(scope.tabs[2]);\n    });\n  });\n\n  describe('without active binding and index attributes', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      scope.first = '1';\n      scope.second = '2';\n      elm = $compile([\n        '<uib-tabset>',\n        '  <uib-tab heading=\"First Tab {{first}}\">',\n        '    first content is {{first}}',\n        '  </uib-tab>',\n        '  <uib-tab heading=\"Second Tab {{second}}\">',\n        '    second content is {{second}}',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n      return elm;\n    }));\n\n    it('should bind tabs content and set first tab active', function() {\n      expectContents(['first content is 1', 'second content is 2']);\n      expect(titles().eq(0)).toHaveClass('active');\n      expect(titles().eq(1)).not.toHaveClass('active');\n      expect(elm.controller('uibTabset').active).toBe(0);\n    });\n\n    it('should change active on click', function() {\n      titles().eq(1).find('> a').click();\n      expect(contents().eq(1)).toHaveClass('active');\n      expect(titles().eq(0)).not.toHaveClass('active');\n      expect(titles().eq(1)).toHaveClass('active');\n      expect(elm.controller('uibTabset').active).toBe(1);\n    });\n  });\n\n  describe('index as strings', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      scope.first = 'one';\n      scope.second = 'two';\n      scope.active = 'two';\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"first\" heading=\"First Tab\">',\n        '    first content',\n        '  </uib-tab>',\n        '  <uib-tab index=\"second\" heading=\"Second Tab\">',\n        '    second content',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n      return elm;\n    }));\n\n    it('should set second tab active', function() {\n      expect(titles().eq(0)).not.toHaveClass('active');\n      expect(titles().eq(1)).toHaveClass('active');\n      expect(elm.controller('uibTabset').active).toBe('two');\n    });\n\n    it('should change active on click', function() {\n      expect(titles().eq(0)).not.toHaveClass('active');\n      titles().eq(0).find('> a').click();\n      expect(titles().eq(0)).toHaveClass('active');\n      expect(titles().eq(1)).not.toHaveClass('active');\n      expect(elm.controller('uibTabset').active).toBe('one');\n    });\n  });\n\n  describe('tab callback order', function() {\n    var execOrder;\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      execOrder = [];\n\n      scope.execute = function(id) {\n        execOrder.push(id);\n      };\n\n      elm = $compile([\n        '<div>',\n        '  <uib-tabset class=\"hello\" data-pizza=\"pepperoni\" active=\"active\">',\n        '    <uib-tab index=\"1\" heading=\"First Tab\" select=\"execute(\\'select1\\')\" deselect=\"execute(\\'deselect1\\')\"></uib-tab>',\n        '    <uib-tab index=\"2\" select=\"execute(\\'select2\\')\" deselect=\"execute(\\'deselect2\\')\"></uib-tab>',\n        '  </uib-tabset>',\n        '</div>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n      return elm;\n    }));\n\n    it('should call select  for the first tab', function() {\n      expect(execOrder).toEqual([ 'select1' ]);\n    });\n\n    it('should call deselect, then select', function() {\n      execOrder = [];\n\n      // Select second tab\n      titles().eq(1).find('> a').click();\n      expect(execOrder).toEqual([ 'deselect1', 'select2' ]);\n\n      execOrder = [];\n\n      // Select again first tab\n      titles().eq(0).find('> a').click();\n      expect(execOrder).toEqual([ 'deselect2', 'select1' ]);\n    });\n  });\n\n  describe('custom template', function() {\n    var $compile, $templateCache;\n    beforeEach(inject(function($rootScope, _$compile_, _$templateCache_) {\n      scope = $rootScope;\n      $compile = _$compile_;\n      $templateCache = _$templateCache_;\n    }));\n\n    it('should support custom templates', function() {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      elm = $compile('<uib-tabset template-url=\"foo/bar.html\"></uib-tabset>')(scope);\n      scope.$digest();\n\n      expect(elm.html()).toBe('baz');\n    });\n  });\n\n  describe('uib-tab', function() {\n    var $compile, $templateCache;\n\n    beforeEach(inject(function($rootScope, _$compile_, _$templateCache_) {\n      scope = $rootScope;\n      $compile = _$compile_;\n      $templateCache = _$templateCache_;\n    }));\n\n    it('should expose the controller on the view', function() {\n      $templateCache.put('uib/template/tabs/tab.html', '<li class=\"uib-tab\">{{tab.text}}</li>');\n\n      elm = $compile('<uib-tabset><uib-tab heading=\"Tab\"></uib-tab></uib-tabset>')(scope);\n      scope.$digest();\n\n      var tab = titles().eq(0);\n      var ctrl = tab.controller('uibTab');\n\n      expect(ctrl).toBeDefined();\n\n      ctrl.text = 'foo';\n      scope.$digest();\n\n      expect(tab.text().trim()).toBe('foo');\n    });\n\n    it('should support custom templates', function() {\n      $templateCache.put('foo/bar.html', '<li>baz</li>');\n\n      elm = $compile('<uib-tabset><uib-tab template-url=\"foo/bar.html\"></uib-tab></uib-tabset>')(scope);\n      scope.$digest();\n\n      var tabTitle = titles().eq(0);\n\n      expect(tabTitle.html()).toBe('baz');\n    });\n  });\n\n  describe('ng-repeat', function() {\n    var $compile, $rootScope;\n    beforeEach(inject(function(_$compile_, _$rootScope_) {\n      $compile = _$compile_;\n      $rootScope = _$rootScope_;\n      scope = $rootScope.$new();\n\n      scope.tabs = [\n        makeTab(1), makeTab(3), makeTab(5), makeTab(7)\n      ];\n      scope.active = 5;\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"t.index\" ng-repeat=\"t in tabs\" active=\"t.active\" select=\"t.select()\">',\n        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',\n        '    content {{$index}}',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n    }));\n\n    function makeTab(index) {\n      return {\n        index: index,\n        select: jasmine.createSpy()\n      };\n    }\n\n    function titles() {\n      return elm.find('ul.nav-tabs li');\n    }\n    function contents() {\n      return elm.find('div.tab-content div.tab-pane');\n    }\n\n    function expectTabActive(activeTab) {\n      var _titles = titles();\n      angular.forEach(scope.tabs, function(tab, i) {\n        if (activeTab === tab) {\n          expect(scope.active).toBe(tab.index);\n          //It should only call select ONCE for each select\n          expect(tab.select).toHaveBeenCalled();\n          expect(_titles.eq(i)).toHaveClass('active');\n          expect(contents().eq(i).text().trim()).toBe('content ' + i);\n          expect(contents().eq(i)).toHaveClass('active');\n        } else {\n          expect(scope.active).not.toBe(tab.index);\n          expect(_titles.eq(i)).not.toHaveClass('active');\n        }\n      });\n    }\n\n    it('should make tab titles and set active tab active', function() {\n      expect(titles().length).toBe(scope.tabs.length);\n      expectTabActive(scope.tabs[2]);\n    });\n\n    it('should switch active when clicking', function() {\n      titles().eq(3).find('> a').click();\n      expectTabActive(scope.tabs[3]);\n    });\n\n    it('should switch active when changing active index', function() {\n      scope.$apply('active = 5');\n      expectTabActive(scope.tabs[2]);\n    });\n\n    it('should deselect all when no tabs are active', function() {\n      scope.active = 101;\n      scope.$apply();\n      expectTabActive(null);\n      expect(contents().filter('.active').length).toBe(0);\n\n      scope.active = 5;\n      scope.$apply();\n      expectTabActive(scope.tabs[2]);\n    });\n\n    it('should not select twice', function() {\n      elm.remove();\n      elm = null;\n      scope = $rootScope.$new();\n\n      scope.tabs = [\n        makeTab(2), makeTab(3), makeTab(5), makeTab(8)\n      ];\n      scope.active = 13;\n      scope.select = jasmine.createSpy();\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"t.index\" ng-repeat=\"t in tabs\" select=\"t.select()\">',\n        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',\n        '    content {{$index}}',\n        '  </uib-tab>',\n        '  <uib-tab index=\"13\" select=\"select()\">',\n        '    <uib-tab-heading><b>heading</b> foo</uib-tab-heading>',\n        '    content foo',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n\n      expect(scope.select.calls.count()).toBe(1);\n    });\n  });\n\n  describe('advanced uib-tab-heading element', function() {\n    beforeEach(inject(function($compile, $rootScope, $sce) {\n      scope = $rootScope.$new();\n      scope.myHtml = $sce.trustAsHtml('<b>hello</b>, there!');\n      scope.value = true;\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"0\">',\n        '    <uib-tab-heading ng-bind-html=\"myHtml\" ng-show=\"value\"></uib-tab-heading>',\n        '  </uib-tab>',\n        '  <uib-tab index=\"1\"><data-uib-tab-heading>1</data-uib-tab-heading></uib-tab>',\n        '  <uib-tab index=\"2\"><div data-uib-tab-heading>2</div></uib-tab>',\n        '  <uib-tab index=\"3\"><div uib-tab-heading>3</div></uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n    }));\n\n    function heading() {\n      return elm.find('ul li > a').children();\n    }\n\n    it('should create a heading bound to myHtml', function() {\n      expect(heading().eq(0).html()).toBe('<b>hello</b>, there!');\n    });\n\n    it('should hide and show the heading depending on value', function() {\n      expect(heading().eq(0)).not.toBeHidden();\n      scope.$apply('value = false');\n      expect(heading().eq(0)).toBeHidden();\n      scope.$apply('value = true');\n      expect(heading().eq(0)).not.toBeHidden();\n    });\n\n    it('should have a uib-tab-heading no matter what syntax was used', function() {\n      expect(heading().eq(1).text()).toBe('1');\n      expect(heading().eq(2).text()).toBe('2');\n      expect(heading().eq(3).text()).toBe('3');\n    });\n\n  });\n\n  //Tests that http://git.io/lG6I9Q is fixed\n  describe('tab ordering', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      scope.tabs = [\n        { title:'Title 1', available:true },\n        { title:'Title 2', available:true },\n        { title:'Title 3', available:true }\n      ];\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <!-- a comment -->',\n        '  <div>div that makes troubles</div>',\n        '  <uib-tab index=\"0\" heading=\"first\">First Static</uib-tab>',\n        '  <div>another div that may do evil</div>',\n        '  <uib-tab index=\"$index + 1\" ng-repeat=\"tab in tabs | filter:tabIsAvailable\" active=\"tab.active\" heading=\"{{tab.title}}\">some content</uib-tab>',\n        '  <!-- another comment -->',\n        '  <uib-tab heading=\"mid\">Mid Static</uib-tab>',\n        '  a text node',\n        '  <!-- another comment -->',\n        '  <span>yet another span that may do evil</span>',\n        '  <uib-tab index=\"$index + 4\" ng-repeat=\"tab in tabs | filter:tabIsAvailable\" heading=\"Second {{tab.title}}\">some content</uib-tab>',\n        '  a text node',\n        '  <span>yet another span that may do evil</span>',\n        '  <!-- another comment -->',\n        '  <uib-tab index=\"7\" heading=\"last\">Last Static</uib-tab>',\n        '  a text node',\n        '  <span>yet another span that may do evil</span>',\n        '  <!-- another comment -->',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n\n      scope.tabIsAvailable = function(tab) {\n        return tab.available;\n      };\n    }));\n\n    it('should preserve correct ordering', function() {\n      function titles() {\n        return elm.find('ul.nav-tabs li > a');\n      }\n      scope.$apply();\n      expect(titles().length).toBe(9);\n      scope.$apply('tabs[1].available=false');\n      scope.$digest();\n      expect(titles().length).toBe(7);\n      scope.$apply('tabs[0].available=false');\n      scope.$digest();\n      expect(titles().length).toBe(5);\n      scope.$apply('tabs[2].available=false');\n      scope.$digest();\n      expect(titles().length).toBe(3);\n      scope.$apply('tabs[0].available=true');\n      scope.$digest();\n      expect(titles().length).toBe(5);\n      scope.$apply('tabs[1].available=true');\n      scope.$apply('tabs[2].available=true');\n      scope.$digest();\n      expect(titles().length).toBe(9);\n      expect(titles().eq(0).text().trim()).toBe('first');\n      expect(titles().eq(1).text().trim()).toBe('Title 1');\n      expect(titles().eq(2).text().trim()).toBe('Title 2');\n      expect(titles().eq(3).text().trim()).toBe('Title 3');\n      expect(titles().eq(4).text().trim()).toBe('mid');\n      expect(titles().eq(5).text().trim()).toBe('Second Title 1');\n      expect(titles().eq(6).text().trim()).toBe('Second Title 2');\n      expect(titles().eq(7).text().trim()).toBe('Second Title 3');\n      expect(titles().eq(8).text().trim()).toBe('last');\n    });\n  });\n\n  describe('uib-tabset controller', function() {\n    function mockTab(index) {\n      return {\n        index: index,\n        onSelect : angular.noop,\n        onDeselect : angular.noop\n      };\n    }\n\n    var ctrl;\n    beforeEach(inject(function($controller, $rootScope) {\n      scope = $rootScope;\n      //instantiate the controller stand-alone, without the directive\n      ctrl = $controller('UibTabsetController', {$scope: scope});\n    }));\n\n    describe('select', function() {\n      it('should mark given tab selected', function() {\n        ctrl.tabs = [\n          {\n            tab: mockTab(0),\n            index: 0\n          }\n        ];\n\n        ctrl.select(0);\n        expect(ctrl.active).toBe(0);\n      });\n\n\n      it('should deselect other tabs', function() {\n        var tab1 = mockTab(1), tab2 = mockTab(2), tab3 = mockTab(3);\n\n        ctrl.addTab(tab1);\n        ctrl.addTab(tab2);\n        ctrl.addTab(tab3);\n\n        ctrl.select(0);\n        expect(ctrl.active).toBe(1);\n\n        ctrl.select(1);\n        expect(ctrl.active).toBe(2);\n\n        ctrl.select(2);\n        expect(ctrl.active).toBe(3);\n      });\n    });\n\n\n    describe('addTab', function() {\n      it('should append tab', function() {\n        var tab1 = mockTab(1), tab2 = mockTab(2);\n\n        expect(ctrl.tabs).toEqual([]);\n\n        ctrl.addTab(tab1);\n        expect(ctrl.tabs).toEqual([\n          {\n            tab: tab1,\n            index: 1\n          }\n        ]);\n\n        ctrl.addTab(tab2);\n        expect(ctrl.tabs).toEqual([\n          {\n            tab: tab1,\n            index: 1\n          },\n          {\n            tab: tab2,\n            index: 2\n          }\n        ]);\n      });\n\n      it('should select the first one', function() {\n        var tab1 = mockTab(1), tab2 = mockTab(2);\n\n        ctrl.addTab(tab1);\n        expect(ctrl.active).toBe(1);\n\n        ctrl.addTab(tab2);\n        expect(ctrl.active).toBe(1);\n      });\n\n      it('should not select first active === false tab as selected', function() {\n        var tab = mockTab(0);\n        ctrl.active = 1;\n\n        ctrl.addTab(tab);\n        expect(ctrl.active).toBe(1);\n      });\n\n      it('should retain active state when adding tab of different index', function() {\n        var tab1 = mockTab(1), tab2 = mockTab(2);\n        ctrl.active = 2;\n        ctrl.addTab(tab1);\n        expect(ctrl.active).toBe(2);\n\n        ctrl.addTab(tab2);\n        expect(ctrl.active).toBe(2);\n      });\n    });\n  });\n\n  describe('remove', function() {\n    it('should remove title tabs when elements are destroyed and change selection', inject(function($controller, $compile, $rootScope) {\n      scope = $rootScope.$new();\n      elm = $compile('<uib-tabset active=\"active\"><uib-tab index=\"0\" heading=\"1\">Hello</uib-tab><uib-tab index=\"$index + 1\" ng-repeat=\"i in list\" heading=\"tab {{i}}\">content {{i}}</uib-tab></uib-tabset>')(scope);\n      scope.$apply();\n\n      expectTitles(['1']);\n      expectContents(['Hello']);\n\n      scope.$apply('list = [1,2,3]');\n      expectTitles(['1', 'tab 1', 'tab 2', 'tab 3']);\n      expectContents(['Hello', 'content 1', 'content 2', 'content 3']);\n\n      // Select last tab\n      titles().find('> a').eq(3).click();\n      expect(contents().eq(3)).toHaveClass('active');\n      expect(titles().eq(3)).toHaveClass('active');\n\n      // Remove last tab\n      scope.$apply('list = [1,2]');\n      expectTitles(['1', 'tab 1', 'tab 2']);\n      expectContents(['Hello', 'content 1', 'content 2']);\n\n      // \"tab 2\" is now selected\n      expect(titles().eq(2)).toHaveClass('active');\n      expect(contents().eq(2)).toHaveClass('active');\n\n      // Select 2nd tab (\"tab 1\")\n      titles().find('> a').eq(1).click();\n      expect(titles().eq(1)).toHaveClass('active');\n      expect(contents().eq(1)).toHaveClass('active');\n\n      // Remove 2nd tab\n      scope.$apply('list = [2]');\n      expectTitles(['1', 'tab 2']);\n      expectContents(['Hello', 'content 2']);\n\n      // New 2nd tab is now selected\n      expect(titles().eq(1)).toHaveClass('active');\n      expect(contents().eq(1)).toHaveClass('active');\n    }));\n\n    it('should use updated index in tab', inject(function($controller, $compile, $rootScope) {\n      scope = $rootScope.$new();\n      elm = $compile('<uib-tabset active=\"active\"><uib-tab index=\"0\" heading=\"1\">Hello</uib-tab><uib-tab index=\"$index + 1\" ng-repeat=\"i in list\" heading=\"tab {{i}}\">content {{i}}</uib-tab></uib-tabset>')(scope);\n      scope.$apply();\n\n      scope.$apply('list = [1,2,3]');\n      expectTitles(['1', 'tab 1', 'tab 2', 'tab 3']);\n      expectContents(['Hello', 'content 1', 'content 2', 'content 3']);\n\n      // Remove middle \"tab 2\" tab\n      scope.$apply('list = [1,3]');\n      expectTitles(['1', 'tab 1', 'tab 3']);\n      expectContents(['Hello', 'content 1', 'content 3']);\n\n      // Remove last \"tab 3\" tab\n      scope.$apply('list = [1]');\n      expectTitles(['1', 'tab 1']);\n      expectContents(['Hello', 'content 1']);\n\n      // Select first tab\n      titles().find('> a').eq(0).click();\n      expect(titles().eq(0)).toHaveClass('active');\n      expect(contents().eq(0)).toHaveClass('active');\n    }));\n\n    it('should not select tabs when being destroyed', inject(function($controller, $compile, $rootScope) {\n      var selectList = [],\n        deselectList = [],\n        getTab = function(index) {\n          return {\n            index: index,\n            select: function() {\n              selectList.push('select');\n            },\n            deselect: function() {\n              deselectList.push('deselect');\n            }\n          };\n        };\n\n      scope = $rootScope.$new();\n      scope.tabs = [\n        getTab(0),\n        getTab(1)\n      ];\n      scope.active = 1;\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"$index\" ng-repeat=\"t in tabs\" active=\"t.active\" select=\"t.select()\" deselect=\"t.deselect()\">',\n        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',\n        '    content {{$index}}',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n\n      // The first tab is selected the during the initial $digest.\n      expect(selectList.length).toEqual(1);\n\n      // Destroy the tabs - we should not trigger selection/deselection any more.\n      scope.$destroy();\n      expect(selectList.length).toEqual(1);\n      expect(deselectList.length).toEqual(0);\n    }));\n  });\n\n  describe('disable', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n\n      function makeTab(disable, index) {\n        return {\n          index: index,\n          select: jasmine.createSpy(),\n          disable: disable\n        };\n      }\n      scope.tabs = [\n        makeTab(false, 0), makeTab(true, 1), makeTab(false, 2), makeTab(true, 3)\n      ];\n      scope.active = 1;\n      elm = $compile([\n        '<uib-tabset active=\"active\">',\n        '  <uib-tab index=\"$index\" ng-repeat=\"t in tabs\" select=\"t.select()\" disable=\"t.disable\">',\n        '    <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>',\n        '    content {{$index}}',\n        '  </uib-tab>',\n        '</uib-tabset>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n    }));\n\n    function expectTabActive(activeTab) {\n      var _titles = titles();\n      angular.forEach(scope.tabs, function(tab, i) {\n        if (activeTab === tab) {\n          expect(scope.active).toBe(tab.index);\n          expect(tab.select.calls.count()).toBe(tab.disable ? 0 : 1);\n          expect(_titles.eq(i)).toHaveClass('active');\n          expect(contents().eq(i).text().trim()).toBe('content ' + i);\n          expect(contents().eq(i)).toHaveClass('active');\n        } else {\n          expect(scope.active).not.toBe(tab.index);\n          expect(_titles.eq(i)).not.toHaveClass('active');\n        }\n      });\n    }\n\n    it('should not switch active when clicking on title', function() {\n      titles().eq(2).find('> a').click();\n      expectTabActive(scope.tabs[2]);\n\n      titles().eq(3).find('> a').click();\n      expectTabActive(scope.tabs[2]);\n    });\n\n    it('should toggle between states', function() {\n      expect(titles().eq(3)).toHaveClass('disabled');\n      scope.$apply('tabs[3].disable = false');\n      expect(titles().eq(3)).not.toHaveClass('disabled');\n\n      expect(titles().eq(2)).not.toHaveClass('disabled');\n      scope.$apply('tabs[2].disable = true');\n      expect(titles().eq(2)).toHaveClass('disabled');\n    });\n  });\n\n  describe('vertical', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      scope.vertical = true;\n      elm = $compile('<uib-tabset vertical=\"vertical\"></uib-tabset>')(scope);\n      scope.$apply();\n    }));\n\n    it('to stack tabs', function() {\n      expect(elm.find('ul.nav-tabs')).toHaveClass('nav-stacked');\n    });\n  });\n\n  describe('justified', function() {\n      beforeEach(inject(function($compile, $rootScope) {\n          scope = $rootScope.$new();\n          scope.justified = true;\n          elm = $compile('<uib-tabset justified=\"justified\"></uib-tabset>')(scope);\n          scope.$apply();\n      }));\n\n      it('to justify tabs', function() {\n          expect(elm.find('ul.nav-tabs')).toHaveClass('nav-justified');\n      });\n  });\n\n  describe('type', function() {\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope.$new();\n      scope.navType = 'pills';\n\n      elm = $compile('<uib-tabset type=\"{{navType}}\"></uib-tabset>')(scope);\n      scope.$apply();\n    }));\n\n    it('to show pills', function() {\n      expect(elm.find('ul')).toHaveClass('nav-pills');\n      expect(elm.find('ul')).not.toHaveClass('nav-tabs');\n    });\n  });\n\n  //https://github.com/angular-ui/bootstrap/issues/524\n  describe('child compilation', function() {\n    var elm;\n    beforeEach(inject(function($compile, $rootScope) {\n      elm = $compile('<uib-tabset><uib-tab><div></div></uib-tab></uib-tabset></div>')($rootScope.$new());\n      $rootScope.$apply();\n    }));\n\n    it('should hookup the tab\\'s children to the tab with $compile', function() {\n      var tabChild = $('.tab-pane', elm).children().first();\n      expect(tabChild.inheritedData('$uibTabsetController')).toBeTruthy();\n    });\n  });\n\n  //https://github.com/angular-ui/bootstrap/issues/631\n  describe('ng-options in content', function() {\n    var elm;\n    it('should render correct amount of options', inject(function($compile, $rootScope) {\n      var scope = $rootScope.$new();\n      elm = $compile('<uib-tabset><uib-tab><select ng-model=\"foo\" ng-options=\"i for i in [1,2,3]\"></uib-tab></uib-tabset>')(scope);\n      scope.$apply();\n\n      var select = elm.find('select');\n      scope.$apply();\n      expect(select.children().length).toBe(4);\n    }));\n  });\n\n  //https://github.com/angular-ui/bootstrap/issues/599\n  describe('ng-repeat in content', function() {\n    var elm;\n    it('should render ng-repeat', inject(function($compile, $rootScope) {\n      var scope = $rootScope.$new();\n      scope.tabs = [\n        {title:'a', array:[1,2,3]},\n        {title:'b', array:[2,3,4]},\n        {title:'c', array:[3,4,5]}\n      ];\n      elm = $compile('<div><uib-tabset>' +\n        '<uib-tab ng-repeat=\"tab in tabs\" heading=\"{{tab.title}}\">' +\n          '<uib-tab-heading>{{$index}}</uib-tab-heading>' +\n          '<span ng-repeat=\"a in tab.array\">{{a}},</span>' +\n        '</uib-tab>' +\n      '</uib-tabset></div>')(scope);\n      scope.$apply();\n\n      var contents = elm.find('.tab-pane');\n      expect(contents.eq(0).text().trim()).toEqual('1,2,3,');\n      expect(contents.eq(1).text().trim()).toEqual('2,3,4,');\n      expect(contents.eq(2).text().trim()).toEqual('3,4,5,');\n    }));\n  });\n\n  //https://github.com/angular-ui/bootstrap/issues/783\n  describe('nested tabs', function() {\n    var elm;\n    it('should render without errors', inject(function($compile, $rootScope) {\n      var scope = $rootScope.$new();\n      elm = $compile([\n        '<div>',\n        '  <uib-tabset class=\"tabbable\">',\n        '    <uib-tab heading=\"Tab 1\">',\n        '      <uib-tabset class=\"tabbable\">',\n        '        <uib-tab heading=\"Tab 1A\">',\n        '        </uib-tab>',\n        '      </uib-tabset>',\n        '    </uib-tab>',\n        '    <uib-tab heading=\"Tab 2\">',\n        '      <uib-tabset class=\"tabbable\">',\n        '        <uib-tab heading=\"Tab 2A\">',\n        '        </uib-tab>',\n        '      </uib-tabset>',\n        '    </uib-tab>',\n        '  </uib-tabset>',\n        '</div>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n\n      // 1 outside tabset, 2 nested tabsets\n      expect(elm.find('.tabbable').length).toEqual(3);\n    }));\n\n    it('should render with the correct scopes', inject(function($compile, $rootScope) {\n      var scope = $rootScope.$new();\n      scope.tab1Text = 'abc';\n      scope.tab1aText = '123';\n      scope.tab1aHead = '123';\n      scope.tab2aaText = '456';\n      elm = $compile([\n        '<div>',\n        '  <uib-tabset class=\"tabbable\">',\n        '    <uib-tab heading=\"Tab 1\">',\n        '      <uib-tabset class=\"tabbable\">',\n        '        <uib-tab heading=\"{{ tab1aHead }}\">',\n        '          {{ tab1aText }}',\n        '        </uib-tab>',\n        '      </uib-tabset>',\n        '      <span class=\"tab-1\">{{ tab1Text }}</span>',\n        '    </uib-tab>',\n        '    <uib-tab heading=\"Tab 2\">',\n        '      <uib-tabset class=\"tabbable\">',\n        '        <uib-tab heading=\"Tab 2A\">',\n        '          <uib-tabset class=\"tabbable\">',\n        '            <uib-tab heading=\"Tab 2AA\">',\n        '              <span class=\"tab-2aa\">{{ tab2aaText }}</span>',\n        '            </uib-tab>',\n        '          </uib-tabset>',\n        '        </uib-tab>',\n        '      </uib-tabset>',\n        '    </uib-tab>',\n        '  </uib-tabset>',\n        '</div>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n\n      var outsideTabset = elm.find('.tabbable').eq(0);\n      var nestedTabset = outsideTabset.find('.tabbable');\n\n      expect(elm.find('.tabbable').length).toEqual(4);\n      expect(outsideTabset.find('.tab-pane').eq(0).find('.tab-1').text().trim()).toEqual(scope.tab1Text);\n      expect(nestedTabset.find('.tab-pane').eq(0).text().trim()).toEqual(scope.tab1aText);\n      expect(nestedTabset.find('ul.nav-tabs li').eq(0).text().trim()).toEqual(scope.tab1aHead);\n      expect(nestedTabset.eq(2).find('.tab-pane').eq(0).find('.tab-2aa').text().trim()).toEqual(scope.tab2aaText);\n    }));\n\n    it('ng-repeat works with nested tabs', inject(function($compile, $rootScope) {\n      var scope = $rootScope.$new();\n      scope.tabs = [\n        {\n          tabs: [\n          {\n            content: 'tab1a'\n          },\n          {\n            content: 'tab2a'\n          }\n          ],\n          content: 'tab1'\n        }\n      ];\n      elm = $compile([\n        '<div>',\n        '  <uib-tabset>',\n        '    <uib-tab ng-repeat=\"tab in tabs\">',\n        '      <uib-tabset>',\n        '        <uib-tab ng-repeat=\"innerTab in tab.tabs\">',\n        '          <span class=\"inner-tab-content\">{{ innerTab.content }}</span>',\n        '        </uib-tab>',\n        '      </uib-tabset>',\n        '      <span class=\"outer-tab-content\">{{ tab.content }}</span>',\n        '    </uib-tab>',\n        '  </uib-tabset>',\n        '</div>'\n      ].join('\\n'))(scope);\n      scope.$apply();\n\n      expect(elm.find('.inner-tab-content').eq(0).text().trim()).toEqual(scope.tabs[0].tabs[0].content);\n      expect(elm.find('.inner-tab-content').eq(1).text().trim()).toEqual(scope.tabs[0].tabs[1].content);\n      expect(elm.find('.outer-tab-content').eq(0).text().trim()).toEqual(scope.tabs[0].content);\n    }));\n  });\n});\n"
  },
  {
    "path": "src/timepicker/docs/demo.html",
    "content": "<div ng-controller=\"TimepickerDemoCtrl\">\n\n  <div uib-timepicker ng-model=\"mytime\" ng-change=\"changed()\" hour-step=\"hstep\" minute-step=\"mstep\" show-meridian=\"ismeridian\"></div>\n\n  <pre class=\"alert alert-info\">Time is: {{mytime | date:'shortTime' }}</pre>\n\n  <div class=\"row\"> \n    <div class=\"col-xs-6\">\n        Hours step is:\n      <select class=\"form-control\" ng-model=\"hstep\" ng-options=\"opt for opt in options.hstep\"></select>\n    </div>\n    <div class=\"col-xs-6\">\n        Minutes step is:\n      <select class=\"form-control\" ng-model=\"mstep\" ng-options=\"opt for opt in options.mstep\"></select>\n    </div>\n  </div>\n\n  <hr>\n\n  <button type=\"button\" class=\"btn btn-info\" ng-click=\"toggleMode()\">12H / 24H</button>\n  <button type=\"button\" class=\"btn btn-default\" ng-click=\"update()\">Set to 14:00</button>\n  <button type=\"button\" class=\"btn btn-danger\" ng-click=\"clear()\">Clear</button>\n\n</div>\n"
  },
  {
    "path": "src/timepicker/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('TimepickerDemoCtrl', function ($scope, $log) {\n  $scope.mytime = new Date();\n\n  $scope.hstep = 1;\n  $scope.mstep = 15;\n\n  $scope.options = {\n    hstep: [1, 2, 3],\n    mstep: [1, 5, 10, 15, 25, 30]\n  };\n\n  $scope.ismeridian = true;\n  $scope.toggleMode = function() {\n    $scope.ismeridian = ! $scope.ismeridian;\n  };\n\n  $scope.update = function() {\n    var d = new Date();\n    d.setHours( 14 );\n    d.setMinutes( 0 );\n    $scope.mytime = d;\n  };\n\n  $scope.changed = function () {\n    $log.log('Time changed to: ' + $scope.mytime);\n  };\n\n  $scope.clear = function() {\n    $scope.mytime = null;\n  };\n});\n"
  },
  {
    "path": "src/timepicker/docs/readme.md",
    "content": "A lightweight & configurable timepicker directive.\n\n### uib-timepicker settings\n\n* `arrowkeys`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether user can use up/down arrow keys inside the hours & minutes input to increase or decrease its values.\n\n* `hour-step`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `1`)_ -\n  Number of hours to increase or decrease when using a button.\n\n* `max`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `undefined`)_ -\n  Maximum time a user can select.\n\n* `meridians`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `null`)_ -\n  Meridian labels based on locale. To override you must supply an array like `['AM', 'PM']`.\n\n* `min`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `undefined`)_ -\n  Minimum time a user can select\n\n* `minute-step`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `1`)_ -\n  Number of minutes to increase or decrease when using a button.\n\n* `mousewheel`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Whether user can scroll inside the hours & minutes input to increase or decrease its values.\n\n* `ng-disabled`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether or not to disable the component.\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Date object that provides the time state.\n\n* `pad-hours`\n  <small class=\"badge\">$</small>\n  _(Default: true)_ -\n  Whether the hours column is padded with a 0.\n\n* `readonly-input`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `false`)_ -\n  Whether user can type inside the hours & minutes input.\n\n* `second-step`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `1`)_ -\n  Number of seconds to increase or decrease when using a button.\n\n* `show-meridian`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `true`)_ -\n  Whether to display 12H or 24H mode.\n\n* `show-seconds`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Show seconds input.\n\n* `show-spinners`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`)_ -\n  Show spinner arrows above and below the inputs.\n\n* `tabindex`\n  _(Defaults: `0`)_ -\n  Sets tabindex for each control in the timepicker.\n\n* `template-url`\n  <small class=\"badge\">C</small>\n  _(Defaults: `uib/template/timepicker/timepicker.html`)_ -\n  Add the ability to override the template used on the component.\n\n**Notes**\n\nThis component makes no claims of absolutely supporting the preservation of dates in all cases, and it is highly recommended that model tracking of dates is encapsulated in a different object. This component should not be used with the same model as the datepicker. This is due to edge cases with situations such as Daylight Savings timezone changes which require a modification of the date in order to prevent an impossible to increment or decrement situation. See [#5485](https://github.com/angular-ui/bootstrap/issues/5485) for details.\n\nIf the model value is updated (i.e. via `Date.prototype.setDate`), you must update the model value by breaking the reference by `modelValue = new Date(modelValue)` in order to have the timepicker update.\n"
  },
  {
    "path": "src/timepicker/index-nocss.js",
    "content": "require('../../template/timepicker/timepicker.html.js');\nrequire('./timepicker');\n\nvar MODULE_NAME = 'ui.bootstrap.module.timepicker';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.timepicker', 'uib/template/timepicker/timepicker.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/timepicker/index.js",
    "content": "require('./timepicker.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/timepicker/test/timepicker.spec.js",
    "content": "describe('timepicker directive', function() {\n  var $rootScope, $compile, $templateCache, element, modelCtrl;\n\n  beforeEach(module('ui.bootstrap.timepicker'));\n  beforeEach(module('uib/template/timepicker/timepicker.html'));\n  beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_) {\n    $compile = _$compile_;\n    $rootScope = _$rootScope_;\n    $rootScope.time = newTime(14, 40, 25);\n    $templateCache = _$templateCache_;\n\n    element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n    $rootScope.$digest();\n\n    modelCtrl = element.controller('ngModel');\n  }));\n\n  function newTime(hours, minutes, seconds) {\n    seconds = seconds ? seconds : 0;\n    var time = new Date();\n    time.setHours(hours);\n    time.setMinutes(minutes);\n    time.setSeconds(seconds);\n    //this is required, otherwise rollover edges cases tests will have\n    //time reset to dates that are off by milliseconds.\n    time.setMilliseconds(0);\n    return time;\n  }\n\n  function getTimeState(withoutMeridian, withoutSeconds) {\n    var inputs = element.find('input');\n    var limit = withoutSeconds ? 2 : 3;\n\n    var state = [];\n    for (var i = 0; i < limit; i ++) {\n      state.push(inputs.eq(i).val());\n    }\n\n    if (withoutMeridian !== true) {\n      state.push(getMeridianButton().text());\n    }\n\n    return state;\n  }\n\n  function getModelState(withoutSeconds) {\n    if (withoutSeconds) {\n      return [$rootScope.time.getHours(), $rootScope.time.getMinutes()];\n    }\n\n    return [$rootScope.time.getHours(), $rootScope.time.getMinutes(), $rootScope.time.getSeconds()];\n  }\n\n  function getArrow(isUp, tdIndex) {\n    return element.find('tr').eq(isUp ? 0 : 2).find('td').eq(tdIndex).find('a').eq(0);\n  }\n\n  function getHoursButton(isUp) {\n    return getArrow(isUp, 0);\n  }\n\n  function getMinutesButton(isUp) {\n    return getArrow(isUp, 2);\n  }\n\n  function getSecondsButton(isUp) {\n    return getArrow(isUp, 4);\n  }\n\n  function getMeridianButton() {\n    return element.find('button').eq(0);\n  }\n\n  function doClick(button, n) {\n    for (var i = 0, max = n || 1; i < max; i++) {\n      button.click();\n      $rootScope.$digest();\n    }\n  }\n\n  function wheelThatMouse(delta) {\n    var e = $.Event('mousewheel');\n    e.wheelDelta = delta;\n    return e;\n  }\n\n  function wheelThatOtherMouse(delta) {\n    var e = $.Event('wheel');\n    e.deltaY = delta;\n    return e;\n  }\n\n  function keydown(key) {\n    var e = $.Event('keydown');\n    switch(key) {\n      case 'left':\n        e.which = 37;\n        break;\n      case 'up':\n        e.which = 38;\n        break;\n      case 'right':\n        e.which = 39;\n        break;\n      case 'down':\n        e.which = 40;\n        break;\n    }\n    return e;\n  }\n\n  it('contains three row & four input elements', function() {\n    expect(element.find('tr').length).toBe(3);\n    expect(element.find('input').length).toBe(3);\n    expect(element.find('button').length).toBe(1);\n  });\n\n  it('has initially the correct time & meridian', function() {\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n  });\n\n  it('should be pristine', function() {\n    expect(modelCtrl.$pristine).toBe(true);\n  });\n\n  it('should be untouched', function() {\n    expect(modelCtrl.$untouched).toBe(true);\n  });\n\n  it('has `selected` current time when model is initially cleared', function() {\n    $rootScope.time = null;\n    element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n    $rootScope.$digest();\n\n    expect($rootScope.time).toBe(null);\n    expect(getTimeState()).not.toEqual(['', '', '' , '']);\n  });\n\n  it('changes inputs when model changes value', function() {\n    $rootScope.time = newTime(11, 50, 20);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['11', '50', '20', 'AM']);\n    expect(getModelState()).toEqual([11, 50, 20]);\n\n    $rootScope.time = newTime(16, 40, 45);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '45', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 45]);\n  });\n\n  it('increases / decreases hours when arrows are clicked', function() {\n    var up = getHoursButton(true);\n    var down = getHoursButton(false);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['01', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([13, 40, 25]);\n  });\n\n  it('increase / decreases minutes by default step when arrows are clicked', function() {\n    var up = getMinutesButton(true);\n    var down = getMinutesButton(false);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['02', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 41, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['02', '39', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 39, 25]);\n  });\n\n  it('increase / decreases seconds by default step when arrows are clicked', function() {\n    var up = getSecondsButton(true);\n    var down = getSecondsButton(false);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['02', '40', '26', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 26]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['02', '40', '24', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 24]);\n  });\n\n  it('should be dirty when input changes', function() {\n    var upHours = getHoursButton(true);\n    var upMinutes = getMinutesButton(true);\n    var upSeconds = getSecondsButton(true);\n\n    doClick(upHours);\n    expect(modelCtrl.$dirty).toBe(true);\n\n    modelCtrl.$setPristine();\n\n    doClick(upMinutes);\n    expect(modelCtrl.$dirty).toBe(true);\n\n    modelCtrl.$setPristine();\n\n    doClick(upSeconds);\n    expect(modelCtrl.$dirty).toBe(true);\n  });\n\n  it('should be touched when input blurs', function() {\n    var inputs = element.find('input');\n    var hoursInput = inputs.eq(0),\n      minutesInput = inputs.eq(1),\n      secondsInput = inputs.eq(2);\n\n    hoursInput.val(12);\n    $rootScope.$digest();\n    hoursInput.blur();\n    expect(modelCtrl.$touched).toBe(true);\n\n    modelCtrl.$setUntouched();\n\n    minutesInput.val(20);\n    $rootScope.$digest();\n    hoursInput.blur();\n    expect(modelCtrl.$touched).toBe(true);\n\n    modelCtrl.$setUntouched();\n\n    secondsInput.val(9);\n    $rootScope.$digest();\n    hoursInput.blur();\n    expect(modelCtrl.$touched).toBe(true);\n  });\n\n  it('meridian button has correct type', function() {\n    var button = getMeridianButton();\n    expect(button.attr('type')).toBe('button');\n  });\n\n  it('toggles meridian when button is clicked', function() {\n    var button = getMeridianButton();\n\n    doClick(button);\n    expect(getTimeState()).toEqual(['02', '40', '25', 'AM']);\n    expect(getModelState()).toEqual([2, 40, 25]);\n\n    doClick(button);\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    doClick(button);\n    expect(getTimeState()).toEqual(['02', '40', '25', 'AM']);\n    expect(getModelState()).toEqual([2, 40, 25]);\n  });\n\n  it('has minutes \"connected\" to hours', function() {\n    var up = getMinutesButton(true);\n    var down = getMinutesButton(false);\n\n    doClick(up, 10);\n    expect(getTimeState()).toEqual(['02', '50', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 50, 25]);\n\n    doClick(up, 10);\n    expect(getTimeState()).toEqual(['03', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 0, 25]);\n\n    doClick(up, 10);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '10', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 10, 25]);\n\n    doClick(down, 10);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 0, 25]);\n\n    doClick(down, 10);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '50', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 50, 25]);\n  });\n\n  it('has seconds \"connected\" to minutes', function() {\n    var up = getSecondsButton(true);\n    var down = getSecondsButton(false);\n\n    doClick(up, 15);\n    expect(getTimeState()).toEqual(['02', '40', '40', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 40]);\n\n    doClick(up, 15);\n    expect(getTimeState()).toEqual(['02', '40', '55', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 55]);\n\n    doClick(up, 15);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '41', '10', 'PM']);\n    expect(getModelState()).toEqual([14, 41, 10]);\n\n    doClick(down, 15);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '55', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 55]);\n\n    doClick(down, 15);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '40', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 40]);\n  });\n\n  it('has hours \"connected\" to meridian', function() {\n    var up = getHoursButton(true);\n    var down = getHoursButton(false);\n\n    // AM -> PM\n    $rootScope.time = newTime(11, 0, 25);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['11', '00', '25', 'AM']);\n    expect(getModelState()).toEqual([11, 0, 25]);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['12', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([12, 0, 25]);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['01', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([13, 0, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['12', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([12, 0, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['11', '00', '25', 'AM']);\n    expect(getModelState()).toEqual([11, 0, 25]);\n\n    // PM -> AM\n    $rootScope.time = newTime(23, 0, 25);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['11', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([23, 0, 25]);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['12', '00', '25', 'AM']);\n    expect(getModelState()).toEqual([0, 0, 25]);\n\n    doClick(up);\n    expect(getTimeState()).toEqual(['01', '00', '25', 'AM']);\n    expect(getModelState()).toEqual([1, 0, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['12', '00', '25', 'AM']);\n    expect(getModelState()).toEqual([0, 0, 25]);\n\n    doClick(down);\n    expect(getTimeState()).toEqual(['11', '00', '25', 'PM']);\n    expect(getModelState()).toEqual([23, 0, 25]);\n  });\n\n  it('changes only the time part when hours change', function() {\n    $rootScope.time = newTime(23, 50, 20);\n    $rootScope.$digest();\n\n    var date = $rootScope.time.getDate();\n    var up = getHoursButton(true);\n    doClick(up);\n\n    expect(getTimeState()).toEqual(['12', '50', '20', 'AM']);\n    expect(getModelState()).toEqual([0, 50, 20]);\n    expect(date).toEqual($rootScope.time.getDate());\n  });\n\n  it('changes only the time part when minutes change', function() {\n    element = $compile('<div uib-timepicker ng-model=\"time\" minute-step=\"15\"></div>')($rootScope);\n    $rootScope.time = newTime(0, 0, 0);\n    $rootScope.$digest();\n\n    var date = $rootScope.time.getDate();\n    var up = getMinutesButton(true);\n    doClick(up, 2);\n    expect(getTimeState()).toEqual(['12', '30', '00', 'AM']);\n    expect(getModelState()).toEqual([0, 30, 0]);\n    expect(date).toEqual($rootScope.time.getDate());\n\n    var down = getMinutesButton(false);\n    doClick(down, 2);\n    expect(getTimeState()).toEqual(['12', '00', '00', 'AM']);\n    expect(getModelState()).toEqual([0, 0, 0]);\n    expect(date).toEqual($rootScope.time.getDate());\n\n    doClick(down, 2);\n    expect(getTimeState()).toEqual(['11', '30', '00', 'PM']);\n    expect(getModelState()).toEqual([23, 30, 0]);\n    expect(date).toEqual($rootScope.time.getDate());\n  });\n\n  it('responds properly on \"mousewheel\" events', function() {\n    var inputs = element.find('input');\n    var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1), secondsEl = inputs.eq(2);\n    var upMouseWheelEvent = wheelThatMouse(1);\n    var downMouseWheelEvent = wheelThatMouse(-1);\n\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    // UP\n    hoursEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    hoursEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 25]);\n\n    minutesEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 41, 25]);\n\n    minutesEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 25]);\n\n    secondsEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '26', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 26]);\n\n    secondsEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '27', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 27]);\n\n    // DOWN\n    secondsEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '26', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 26]);\n\n    secondsEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 25]);\n\n    minutesEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 41, 25]);\n\n    minutesEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 25]);\n\n    hoursEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    hoursEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n  });\n\n  it('responds properly on \"wheel\" events', function() {\n    var inputs = element.find('input');\n    var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1), secondsEl = inputs.eq(2);\n    var upMouseWheelEvent = wheelThatOtherMouse(-1);\n    var downMouseWheelEvent = wheelThatOtherMouse(1);\n\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    // UP\n    hoursEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    hoursEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 25]);\n\n    minutesEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 41, 25]);\n\n    minutesEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 25]);\n\n    secondsEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '26', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 26]);\n\n    secondsEl.trigger(upMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '27', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 27]);\n\n    // DOWN\n    secondsEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '26', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 26]);\n\n    secondsEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 25]);\n\n    minutesEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 41, 25]);\n\n    minutesEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 25]);\n\n    hoursEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    hoursEl.trigger(downMouseWheelEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n  });\n\n  it('responds properly on \"keydown\" events', function() {\n    var inputs = element.find('input');\n    var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1),\n        secondsEl = inputs.eq(2);\n    var upKeydownEvent = keydown('up');\n    var downKeydownEvent = keydown('down');\n    var leftKeydownEvent = keydown('left');\n\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    // UP\n    hoursEl.trigger(upKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    hoursEl.trigger(upKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 25]);\n\n    minutesEl.trigger(upKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 41, 25]);\n\n    minutesEl.trigger(upKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 25]);\n\n    secondsEl.trigger(upKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '26', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 26]);\n\n    secondsEl.trigger(upKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '27', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 27]);\n\n    // DOWN\n    secondsEl.trigger(downKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '26', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 26]);\n\n    secondsEl.trigger(downKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '42', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 42, 25]);\n\n    minutesEl.trigger(downKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '41', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 41, 25]);\n\n    minutesEl.trigger(downKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['04', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([16, 40, 25]);\n\n    hoursEl.trigger(downKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['03', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([15, 40, 25]);\n\n    hoursEl.trigger(downKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    // Other keydown\n    hoursEl.trigger(leftKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    minutesEl.trigger(leftKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n\n    secondsEl.trigger(leftKeydownEvent);\n    $rootScope.$digest();\n    expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n    expect(getModelState()).toEqual([14, 40, 25]);\n  });\n\n  describe('attributes', function() {\n    beforeEach(function() {\n      $rootScope.hstep = 2;\n      $rootScope.mstep = 30;\n      $rootScope.sstep = 30;\n      $rootScope.time = newTime(14, 0 , 0);\n      element = $compile('<div uib-timepicker ng-model=\"time\" hour-step=\"hstep\" minute-step=\"mstep\" second-step=\"sstep\"></div>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('increases / decreases hours by configurable step', function() {\n      var up = getHoursButton(true);\n      var down = getHoursButton(false);\n\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      doClick(up);\n      expect(getTimeState()).toEqual(['04', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 0, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['12', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([12, 0, 0]);\n\n      // Change step\n      $rootScope.hstep = 3;\n      $rootScope.$digest();\n\n      doClick(up);\n      expect(getTimeState()).toEqual(['03', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([15, 0, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['12', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([12, 0, 0]);\n    });\n\n    it('increases / decreases minutes by configurable step', function() {\n      var up = getMinutesButton(true);\n      var down = getMinutesButton(false);\n\n      doClick(up);\n      expect(getTimeState()).toEqual(['02', '30', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 30, 0]);\n\n      doClick(up);\n      expect(getTimeState()).toEqual(['03', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([15, 0, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['02', '30', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 30, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      // Change step\n      $rootScope.mstep = 15;\n      $rootScope.$digest();\n\n      doClick(up);\n      expect(getTimeState()).toEqual(['02', '15', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 15, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      doClick(down);\n      expect(getTimeState()).toEqual(['01', '45', '00', 'PM']);\n      expect(getModelState()).toEqual([13, 45, 0]);\n    });\n\n    it('responds properly on \"mousewheel\" events with configurable steps', function() {\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1), secondsEl = inputs.eq(2);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      // UP\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 0, 0]);\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '30', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 30, 0]);\n\n      secondsEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '30', '30', 'PM']);\n      expect(getModelState()).toEqual([16, 30, 30]);\n\n      // DOWN\n\n      secondsEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '30', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 30, 0]);\n\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 0, 0]);\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n    });\n\n    it('responds properly on \"wheel\" events with configurable steps', function() {\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1), secondsEl = inputs.eq(2);\n      var upMouseWheelEvent = wheelThatOtherMouse(-1);\n      var downMouseWheelEvent = wheelThatOtherMouse(1);\n\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      // UP\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 0, 0]);\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '30', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 30, 0]);\n\n      secondsEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '30', '30', 'PM']);\n      expect(getModelState()).toEqual([16, 30, 30]);\n\n      // DOWN\n\n      secondsEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '30', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 30, 0]);\n\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['04', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([16, 0, 0]);\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n    });\n\n    it('can handle strings as steps', function() {\n      var upHours = getHoursButton(true);\n      var upMinutes = getMinutesButton(true);\n      var upSeconds = getSecondsButton(true);\n\n      expect(getTimeState()).toEqual(['02', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([14, 0, 0]);\n\n      $rootScope.hstep = '4';\n      $rootScope.mstep = '20';\n      $rootScope.sstep = '20';\n      $rootScope.$digest();\n\n      doClick(upHours);\n      expect(getTimeState()).toEqual(['06', '00', '00', 'PM']);\n      expect(getModelState()).toEqual([18, 0, 0]);\n\n      doClick(upMinutes);\n      expect(getTimeState()).toEqual(['06', '20', '00', 'PM']);\n      expect(getModelState()).toEqual([18, 20, 0]);\n\n      doClick(upSeconds);\n      expect(getTimeState()).toEqual(['06', '20', '20', 'PM']);\n      expect(getModelState()).toEqual([18, 20, 20]);\n\n    });\n\n  });\n\n  describe('without seconds mode',function(){\n    beforeEach(function(){\n      $rootScope.displaysSeconds = false;\n      $rootScope.time = newTime(14,40,35);\n      element = $compile('<div uib-timepicker ng-model=\"time\" show-seconds=\"displaysSeconds\"></div>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    it('increases / decreases hours when arrows are clicked', function() {\n      var up = getHoursButton(true);\n      var down = getHoursButton(false);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['03', '40', 'PM']);\n      expect(getModelState(true)).toEqual([15, 40]);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['01', '40', 'PM']);\n      expect(getModelState(true)).toEqual([13, 40]);\n\n    });\n\n    it('increase / decreases minutes by default step when arrows are clicked', function() {\n      var up = getMinutesButton(true);\n      var down = getMinutesButton(false);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['02', '41', 'PM']);\n      expect(getModelState(true)).toEqual([14, 41]);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['02', '39', 'PM']);\n      expect(getModelState(true)).toEqual([14, 39]);\n    });\n\n    it('has minutes \"connected\" to hours', function() {\n\n      var up = getMinutesButton(true);\n      var down = getMinutesButton(false);\n\n      doClick(up, 10);\n      expect(getTimeState(false, true)).toEqual(['02', '50', 'PM']);\n      expect(getModelState(true)).toEqual([14, 50]);\n\n      doClick(up, 10);\n      expect(getTimeState(false, true)).toEqual(['03', '00', 'PM']);\n      expect(getModelState(true)).toEqual([15, 0]);\n\n      doClick(up, 10);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['03', '10', 'PM']);\n      expect(getModelState(true)).toEqual([15, 10]);\n\n      doClick(down, 10);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['03', '00', 'PM']);\n      expect(getModelState(true)).toEqual([15, 0]);\n\n      doClick(down, 10);\n      $rootScope.$digest();\n      expect(getTimeState(false,true)).toEqual(['02', '50', 'PM']);\n      expect(getModelState(true)).toEqual([14, 50]);\n    });\n\n  });\n\n  describe('12 / 24 hour mode', function() {\n    beforeEach(function() {\n      $rootScope.meridian = false;\n      $rootScope.time = newTime(14, 10, 20);\n      element = $compile('<div uib-timepicker ng-model=\"time\" show-meridian=\"meridian\"></div>')($rootScope);\n      $rootScope.$digest();\n    });\n\n    function getMeridianTd() {\n      return element.find('tr').eq(1).find('td').eq(5);\n    }\n\n    it('initially displays correct time when `show-meridian` is false', function() {\n      expect(getTimeState(true)).toEqual(['14', '10', '20']);\n      expect(getModelState()).toEqual([14, 10, 20]);\n      expect(getMeridianTd()).toBeHidden();\n    });\n\n    it('toggles correctly between different modes', function() {\n      expect(getTimeState(true)).toEqual(['14', '10', '20']);\n\n      $rootScope.meridian = true;\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '10', '20', 'PM']);\n      expect(getModelState()).toEqual([14, 10, 20]);\n      expect(getMeridianTd()).not.toBeHidden();\n\n      $rootScope.meridian = false;\n      $rootScope.$digest();\n      expect(getTimeState(true)).toEqual(['14', '10', '20']);\n      expect(getModelState()).toEqual([14, 10, 20]);\n      expect(getMeridianTd()).toBeHidden();\n    });\n\n    it('handles correctly initially empty model on parent element', function() {\n      $rootScope.time = null;\n      element = $compile('<span ng-model=\"time\"><div uib-timepicker show-meridian=\"meridian\"></div></span>')($rootScope);\n      $rootScope.$digest();\n\n      expect($rootScope.time).toBe(null);\n    });\n  });\n\n  describe('`meridians` attribute', function() {\n    beforeEach(inject(function() {\n      $rootScope.meridiansArray = ['am', 'pm'];\n      element = $compile('<div uib-timepicker ng-model=\"time\" meridians=\"meridiansArray\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('displays correctly', function() {\n      expect(getTimeState()[3]).toBe('pm');\n    });\n\n    it('toggles correctly', function() {\n      $rootScope.time = newTime(2, 40, 20);\n      $rootScope.$digest();\n      expect(getTimeState()[3]).toBe('am');\n    });\n  });\n\n  describe('`readonly-input` attribute', function() {\n    beforeEach(inject(function() {\n      $rootScope.meridiansArray = ['am', 'pm'];\n      element = $compile('<div uib-timepicker ng-model=\"time\" readonly-input=\"true\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('should make inputs readonly', function() {\n      var inputs = element.find('input');\n      expect(inputs.eq(0).attr('readonly')).toBe('readonly');\n      expect(inputs.eq(1).attr('readonly')).toBe('readonly');\n      expect(inputs.eq(2).attr('readonly')).toBe('readonly');\n    });\n  });\n\n  describe('`pad-hours` attribute', function() {\n    function triggerInput(elem, val) {\n      elem.val(val);\n      elem.trigger('input');\n    }\n\n    it('should pad the hours by default', function() {\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var inputs = element.find('input');\n      var hoursInput = inputs.eq(0);\n      triggerInput(hoursInput, 4);\n      hoursInput.blur();\n\n      expect(hoursInput.val()).toBe('04');\n    });\n\n    it('should not pad the hours', function() {\n      element = $compile('<div uib-timepicker ng-model=\"time\" pad-hours=\"false\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var inputs = element.find('input');\n      var hoursInput = inputs.eq(0);\n      triggerInput(hoursInput, 4);\n      hoursInput.blur();\n\n      expect(hoursInput.val()).toBe('4');\n    });\n  });\n\n  describe('setting uibTimepickerConfig steps', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(_$compile_, _$rootScope_, uibTimepickerConfig) {\n      angular.extend(originalConfig, uibTimepickerConfig);\n      uibTimepickerConfig.hourStep = 2;\n      uibTimepickerConfig.minuteStep = 10;\n      uibTimepickerConfig.secondStep = 10;\n      uibTimepickerConfig.showMeridian = false;\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    afterEach(inject(function(uibTimepickerConfig) {\n      // return it to the original state\n      angular.extend(uibTimepickerConfig, originalConfig);\n    }));\n\n    it('does not affect the initial value', function() {\n      expect(getTimeState(true)).toEqual(['14', '40', '25']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n    });\n\n    it('increases / decreases hours with configured step', function() {\n      var up = getHoursButton(true);\n      var down = getHoursButton(false);\n\n      doClick(up, 2);\n      expect(getTimeState(true)).toEqual(['18', '40', '25']);\n      expect(getModelState()).toEqual([18, 40, 25]);\n\n      doClick(down, 3);\n      expect(getTimeState(true)).toEqual(['12', '40', '25']);\n      expect(getModelState()).toEqual([12, 40, 25]);\n    });\n\n    it('increases / decreases minutes with configured step', function() {\n      var up = getMinutesButton(true);\n      var down = getMinutesButton(false);\n\n      doClick(up);\n      expect(getTimeState(true)).toEqual(['14', '50', '25']);\n      expect(getModelState()).toEqual([14, 50, 25]);\n\n      doClick(down, 3);\n      expect(getTimeState(true)).toEqual(['14', '20' , '25']);\n      expect(getModelState()).toEqual([14, 20, 25]);\n    });\n\n    it('increases / decreases seconds with configured step', function() {\n      var up = getSecondsButton(true);\n      var down = getSecondsButton(false);\n\n      doClick(up);\n      expect(getTimeState(true)).toEqual(['14', '40', '35']);\n      expect(getModelState()).toEqual([14, 40, 35]);\n\n      doClick(down, 3);\n      expect(getTimeState(true)).toEqual(['14', '40', '05']);\n      expect(getModelState()).toEqual([14, 40, 5]);\n    });\n\n  });\n\n  describe('setting uibTimepickerConfig meridian labels', function() {\n    var originalConfig = {};\n    beforeEach(inject(function(_$compile_, _$rootScope_, uibTimepickerConfig) {\n      angular.extend(originalConfig, uibTimepickerConfig);\n      uibTimepickerConfig.meridians = ['π.μ.', 'μ.μ.'];\n      uibTimepickerConfig.showMeridian = true;\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n    afterEach(inject(function(uibTimepickerConfig) {\n      // return it to the original state\n      angular.extend(uibTimepickerConfig, originalConfig);\n    }));\n\n    it('displays correctly', function() {\n      expect(getTimeState()).toEqual(['02', '40', '25', 'μ.μ.']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n    });\n\n    it('toggles correctly', function() {\n      $rootScope.time = newTime(2, 40, 20);\n      $rootScope.$digest();\n\n      expect(getTimeState()).toEqual(['02', '40', '20', 'π.μ.']);\n      expect(getModelState()).toEqual([2, 40, 20]);\n    });\n  });\n\n  describe('setting uibTimepickerConfig template url', function() {\n    var originalConfig = {};\n    var newTemplateUrl = 'foo/bar.html';\n    beforeEach(inject(function(_$compile_, _$rootScope_, uibTimepickerConfig) {\n      angular.extend(originalConfig, uibTimepickerConfig);\n      $templateCache.put(newTemplateUrl, '<div>baz</div>');\n      uibTimepickerConfig.templateUrl = newTemplateUrl;\n\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n    afterEach(inject(function(uibTimepickerConfig) {\n      // return it to the original state\n      angular.extend(uibTimepickerConfig, originalConfig);\n    }));\n\n    it('should use a custom template', function() {\n      expect(element[0].tagName.toLowerCase()).toBe('div');\n      expect(element.html()).toBe('<div>baz</div>');\n    });\n  });\n\n  describe('$formatter', function() {\n    var ngModel,\n      date;\n\n    beforeEach(function() {\n      ngModel = element.controller('ngModel');\n      date = new Date('Mon Mar 23 2015 14:40:11 GMT-0700 (PDT)');\n    });\n\n    it('should have one formatter', function() {\n      expect(ngModel.$formatters.length).toBe(1);\n    });\n\n    it('should convert a date to a new reference representing the same date', function() {\n      expect(ngModel.$formatters[0](date)).toEqual(date);\n    });\n\n    it('should convert a valid date string to a date object', function() {\n      expect(ngModel.$formatters[0]('Mon Mar 23 2015 14:40:11 GMT-0700 (PDT)')).toEqual(date);\n    });\n\n    it('should set falsy values as null', function() {\n      expect(ngModel.$formatters[0](undefined)).toBe(null);\n      expect(ngModel.$formatters[0](null)).toBe(null);\n      expect(ngModel.$formatters[0]('')).toBe(null);\n      expect(ngModel.$formatters[0](0)).toBe(null);\n      expect(ngModel.$formatters[0](NaN)).toBe(null);\n    });\n  });\n\n  describe('user input validation', function() {\n    var changeInputValueTo;\n\n    beforeEach(inject(function($sniffer) {\n      changeInputValueTo = function(inputEl, value) {\n        inputEl.val(value);\n        inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n        $rootScope.$digest();\n      };\n    }));\n\n    function getHoursInputEl() {\n      return element.find('input').eq(0);\n    }\n\n    function getMinutesInputEl() {\n      return element.find('input').eq(1);\n    }\n\n    function getSecondsInputEl() {\n      return element.find('input').eq(2);\n    }\n\n    it('has initially the correct time & meridian', function() {\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n    });\n\n    it('updates hours & pads on input change & pads on blur', function() {\n      var el = getHoursInputEl();\n\n      changeInputValueTo(el, 5);\n      expect(getTimeState()).toEqual(['5', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([17, 40, 25]);\n\n      el.blur();\n      expect(getTimeState()).toEqual(['05', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([17, 40, 25]);\n    });\n\n    it('updates minutes & pads on input change & pads on blur', function() {\n      var el = getMinutesInputEl();\n\n      changeInputValueTo(el, 9);\n      expect(getTimeState()).toEqual(['02', '9', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 9, 25]);\n\n      el.blur();\n      expect(getTimeState()).toEqual(['02', '09', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 9, 25]);\n    });\n\n    it('updates seconds & pads on input change & pads on blur', function() {\n      var el = getSecondsInputEl();\n\n      changeInputValueTo(el, 4);\n      expect(getTimeState()).toEqual(['02', '40', '4', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 4]);\n\n      el.blur();\n      expect(getTimeState()).toEqual(['02', '40', '04', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 4]);\n    });\n\n    it('clears model when input hours is invalid & alerts the UI', function() {\n      var el = getHoursInputEl();\n\n      changeInputValueTo(el, 'pizza');\n      expect($rootScope.time).toBe(null);\n      expect(el.parent().hasClass('has-error')).toBe(true);\n      expect(el.hasClass('ng-invalid-hours'));\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n\n      changeInputValueTo(el, 8);\n      el.blur();\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['08', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([20, 40, 25]);\n      expect(el.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('clears model when input minutes is invalid & alerts the UI', function() {\n      var el = getMinutesInputEl();\n\n      changeInputValueTo(el, '8a');\n      expect($rootScope.time).toBe(null);\n      expect(el.parent().hasClass('has-error')).toBe(true);\n      expect(el.hasClass('ng-invalid-minutes'));\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n\n      changeInputValueTo(el, 22);\n      expect(getTimeState()).toEqual(['02', '22', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 22, 25]);\n      expect(el.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('clears model when input seconds is invalid & alerts the UI', function() {\n      var el = getSecondsInputEl();\n\n      changeInputValueTo(el, 'pizza');\n      expect($rootScope.time).toBe(null);\n      expect(el.parent().hasClass('has-error')).toBe(true);\n      expect(el.hasClass('ng-invalid-seconds'));\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n\n      changeInputValueTo(el, 13);\n      expect(getTimeState()).toEqual(['02', '40', '13', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 13]);\n      expect(el.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should not be invalid when the model is cleared', function() {\n      var elH = getHoursInputEl();\n      var elM = getMinutesInputEl();\n      var elS = getSecondsInputEl();\n\n      $rootScope.time = newTime(10, 20, 30);\n      $rootScope.$digest();\n\n      expect(getModelState()).toEqual([10, 20, 30]);\n\n      changeInputValueTo(elH, '');\n      elH.blur();\n      $rootScope.$digest();\n      changeInputValueTo(elM, '');\n      elM.blur();\n      $rootScope.$digest();\n      changeInputValueTo(elS, '');\n      elS.blur();\n      $rootScope.$digest();\n\n      expect(elH.hasClass('ng-valid'));\n      expect(elM.hasClass('ng-valid'));\n      expect(elS.hasClass('ng-valid'));\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('timepicker1 leaves view alone when hours are invalid and minutes are updated', function() {\n      var hoursEl = getHoursInputEl(),\n        minutesEl = getMinutesInputEl();\n\n      changeInputValueTo(hoursEl, '25');\n      hoursEl.blur();\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['25', '40', '25', 'PM']);\n\n      changeInputValueTo(minutesEl, '2');\n      minutesEl.blur();\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['25', '2', '25', 'PM']);\n    });\n\n    it('leaves view alone when minutes are invalid and hours are updated', function() {\n      var hoursEl = getHoursInputEl(),\n        minutesEl = getMinutesInputEl();\n\n      changeInputValueTo(minutesEl, '61');\n      minutesEl.blur();\n      $rootScope.$digest();\n      expect($rootScope.time).toBe(null);\n      expect(getTimeState()).toEqual(['02', '61', '25', 'PM']);\n\n      changeInputValueTo(hoursEl, '2');\n      hoursEl.blur();\n      $rootScope.$digest();\n      expect($rootScope.time).toBe(null);\n      expect(getTimeState()).toEqual(['2', '61', '25', 'PM']);\n    });\n\n    it('handles 12/24H mode change', function() {\n      $rootScope.meridian = true;\n      element = $compile('<div uib-timepicker ng-model=\"time\" show-meridian=\"meridian\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var el = getHoursInputEl();\n\n      changeInputValueTo(el, '16');\n      expect($rootScope.time).toBe(null);\n      expect(el.parent().hasClass('has-error')).toBe(true);\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n\n      $rootScope.meridian = false;\n      $rootScope.$digest();\n      expect(getTimeState(true)).toEqual(['16', '40', '25']);\n      expect(getModelState()).toEqual([16, 40, 25]);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should have a default tabindex of 0', function() {\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      expect(element.isolateScope().tabindex).toBe(0);\n    });\n\n    it('should have the correct tabindex', function() {\n      element = $compile('<div uib-timepicker ng-model=\"time\" tabindex=\"5\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      expect(element.attr('tabindex')).toBe(undefined);\n      expect(element.isolateScope().tabindex).toBe('5');\n    });\n  });\n\n  describe('when model is not a Date', function() {\n    beforeEach(inject(function() {\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n    }));\n\n    it('should not be invalid when the model is null', function() {\n      $rootScope.time = null;\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should not be invalid when the model is undefined', function() {\n      $rootScope.time = undefined;\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should not be invalid when the model is a valid string date representation', function() {\n      $rootScope.time = 'September 30, 2010 15:30:10';\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n      expect(getTimeState()).toEqual(['03', '30', '10','PM']);\n    });\n\n    it('should be invalid when the model is not a valid string date representation', function() {\n      $rootScope.time = 'pizza';\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n    });\n\n    it('should return valid when the model becomes valid', function() {\n      $rootScope.time = 'pizza';\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n\n      $rootScope.time = new Date();\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should return valid when the model is cleared', function() {\n      $rootScope.time = 'pizza';\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n\n      $rootScope.time = null;\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n  });\n\n  describe('use with `ng-required` directive', function() {\n    beforeEach(inject(function() {\n      $rootScope.time = null;\n      element = $compile('<div uib-timepicker ng-model=\"time\" ng-required=\"true\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('should be invalid initially', function() {\n      expect(element.hasClass('ng-invalid')).toBe(true);\n    });\n\n    it('should be valid if model has been specified', function() {\n      $rootScope.time = new Date();\n      $rootScope.$digest();\n      expect(element.hasClass('ng-invalid')).toBe(false);\n    });\n  });\n\n  describe('use with `ng-change` directive', function() {\n    beforeEach(inject(function() {\n      $rootScope.changeHandler = jasmine.createSpy('changeHandler');\n      $rootScope.time = new Date();\n      element = $compile('<div uib-timepicker ng-model=\"time\" ng-change=\"changeHandler()\"></div>')($rootScope);\n      $rootScope.$digest();\n    }));\n\n    it('should not be called initially', function() {\n      expect($rootScope.changeHandler).not.toHaveBeenCalled();\n    });\n\n    it('should be called when hours / minutes buttons clicked', function() {\n      var btn1 = getHoursButton(true);\n      var btn2 = getMinutesButton(false);\n      var btn3 = getSecondsButton(false);\n\n      doClick(btn1, 2);\n      doClick(btn2, 3);\n      doClick(btn3, 1);\n      $rootScope.$digest();\n\n      expect($rootScope.changeHandler.calls.count()).toBe(6);\n    });\n\n    it('should not be called when model changes programatically', function() {\n      $rootScope.time = new Date();\n      $rootScope.$digest();\n      expect($rootScope.changeHandler).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('when used with min', function() {\n    var changeInputValueTo;\n    beforeEach(inject(function($sniffer) {\n      element = $compile('<div uib-timepicker ng-model=\"time\" min=\"min\"></div>')($rootScope);\n      $rootScope.$digest();\n      changeInputValueTo = function(inputEl, value) {\n        inputEl.val(value);\n        inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n        $rootScope.$digest();\n      };\n    }));\n\n    it('should not decrease hours when it would result in a time earlier than min', function() {\n      var down = getHoursButton(false);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.min = newTime(13, 41);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(true);\n      doClick(down);\n      expect(getTimeState(false,true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      hoursEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n    });\n\n    it('should decrease hours when it would not result in a time earlier than min', function() {\n      var down = getHoursButton(false);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.min = newTime(0, 0);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(false);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['01', '40', 'PM']);\n      expect(getModelState(true)).toEqual([13, 40]);\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '40', 'PM']);\n      expect(getModelState(true)).toEqual([12, 40]);\n\n      hoursEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '40', 'AM']);\n      expect(getModelState(true)).toEqual([11, 40]);\n    });\n\n    it('should not decrease minutes when it would result in a time ealier than min', function() {\n      var down = getMinutesButton(false);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.min = newTime(14, 40);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(true);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      minutesEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n    });\n\n    it('should decrease minutes when it would not result in a time ealier than min', function() {\n      var down = getMinutesButton(false);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.min = newTime(0, 0);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(false);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['02', '39', 'PM']);\n      expect(getModelState(true)).toEqual([14, 39]);\n\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '38', 'PM']);\n      expect(getModelState(true)).toEqual([14, 38]);\n\n      minutesEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '37', 'PM']);\n      expect(getModelState(true)).toEqual([14, 37]);\n    });\n\n    it('should not increase hours when time would rollover to a time earlier than min', function() {\n      var up = getHoursButton(true);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.min = newTime(13, 40);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(true);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n\n      hoursEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n    });\n\n    it('should increase hours when time would rollover to a time not earlier than min', function() {\n      var up = getHoursButton(true);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.min = newTime(0, 0);\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(false);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['12', '59', 'AM']);\n      expect(getModelState(true)).toEqual([0, 59]);\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.$digest();\n\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '59', 'AM']);\n      expect(getModelState(true)).toEqual([0, 59]);\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.$digest();\n\n      hoursEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '59', 'AM']);\n      expect(getModelState(true)).toEqual([0, 59]);\n    });\n\n\n    it('should not increase minutes when time would rollover to a time earlier than min', function() {\n      var up = getMinutesButton(true);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.min = newTime(13, 40);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(true);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n\n      minutesEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n    });\n\n    it('should increase minutes when time would rollover to a time not earlier than min', function() {\n      var up = getMinutesButton(true);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.min = newTime(0, 0);\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(false);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.$digest();\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n\n      $rootScope.time = newTime(23, 59);\n      $rootScope.$digest();\n\n      minutesEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n    });\n\n    it('should not change meridian when it would result a in time earlier than min', function() {\n      var button = getMeridianButton();\n\n      $rootScope.min = newTime(2, 41);\n      $rootScope.$digest();\n\n      expect(button.hasClass('disabled')).toBe(true);\n\n      doClick(button);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n    });\n\n    it('should change meridian when it would not result in a time earlier than min', function() {\n      var button = getMeridianButton();\n\n      $rootScope.min = newTime(2, 39);\n      $rootScope.$digest();\n\n      expect(button.hasClass('disabled')).toBe(false);\n\n      doClick(button);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'AM']);\n      expect(getModelState(true)).toEqual([2, 40]);\n    });\n\n    it('should return invalid when the hours are changes such that the time is earlier than min', function() {\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n\n      $rootScope.min = newTime(14, 0);\n      $rootScope.$digest();\n\n      changeInputValueTo(hoursEl, 1);\n      expect($rootScope.time).toBe(null);\n      expect(hoursEl.parent().hasClass('has-error')).toBe(true);\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n    });\n\n    it('should return valid when the hours are changes such that the time is not earlier than min', function() {\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n\n      $rootScope.min = newTime(14, 41);\n      $rootScope.$digest();\n\n      changeInputValueTo(hoursEl, 3);\n      expect(getTimeState(false, true)).toEqual(['3', '40', 'PM']);\n      expect(getModelState(true)).toEqual([15, 40]);\n      expect(hoursEl.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should return invalid when the minutes are changes such that the time is earlier than min', function() {\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n\n      $rootScope.min = newTime(14, 30);\n      $rootScope.$digest();\n\n      changeInputValueTo(minutesEl, 1);\n      expect($rootScope.time).toBe(null);\n      expect(minutesEl.parent().hasClass('has-error')).toBe(true);\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n    });\n\n    it('should return valid when the minutes are changes such that the time is not earlier than min', function() {\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n\n      $rootScope.min = newTime(14, 41);\n      $rootScope.$digest();\n\n      changeInputValueTo(minutesEl, 42);\n      expect(getTimeState(false, true)).toEqual(['02', '42', 'PM']);\n      expect(getModelState(true)).toEqual([14, 42]);\n      expect(minutesEl.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n  });\n\n  describe('when used with max', function() {\n    var changeInputValueTo;\n    beforeEach(inject(function($sniffer) {\n      element = $compile('<div uib-timepicker ng-model=\"time\" max=\"max\"></div>')($rootScope);\n      $rootScope.$digest();\n      changeInputValueTo = function(inputEl, value) {\n        inputEl.val(value);\n        inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n        $rootScope.$digest();\n      };\n    }));\n\n    it('should not increase hours when it would result in a time later than max', function() {\n      var up = getHoursButton(true);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.max = newTime(15, 39);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(true);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      hoursEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n    });\n\n    it('should increase hours when it would not result in a time later than max', function() {\n      var up = getHoursButton(true);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.max = newTime(23, 59);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(false);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['03', '40', 'PM']);\n      expect(getModelState(true)).toEqual([15, 40]);\n\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['04', '40', 'PM']);\n      expect(getModelState(true)).toEqual([16, 40]);\n\n      hoursEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['05', '40', 'PM']);\n      expect(getModelState(true)).toEqual([17, 40]);\n    });\n\n    it('should not increase minutes when it would result in a time later than max', function() {\n      var up = getMinutesButton(true);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.max = newTime(14, 40);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(true);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n\n      minutesEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n    });\n\n    it('should increase minutes when it would not result in a time later than max', function() {\n      var up = getMinutesButton(true);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var upKeydownEvent = keydown('up');\n\n      $rootScope.max = newTime(23, 59);\n      $rootScope.$digest();\n\n      expect(up.hasClass('disabled')).toBe(false);\n\n      doClick(up);\n      expect(getTimeState(false, true)).toEqual(['02', '41', 'PM']);\n      expect(getModelState(true)).toEqual([14, 41]);\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '42', 'PM']);\n      expect(getModelState(true)).toEqual([14, 42]);\n\n      minutesEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['02', '43', 'PM']);\n      expect(getModelState(true)).toEqual([14, 43]);\n    });\n\n    it('should not decrease hours when time would rollover to a time later than max', function() {\n      var down = getHoursButton(false);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.max = newTime(13, 40);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(true);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n\n      hoursEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n    });\n\n    it('should decrease hours when time would rollover to a time not later than max', function() {\n      var down = getHoursButton(false);\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.max = newTime(23, 59);\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(false);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['11', '00', 'PM']);\n      expect(getModelState(true)).toEqual([23, 0]);\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.$digest();\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '00', 'PM']);\n      expect(getModelState(true)).toEqual([23, 0]);\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.$digest();\n\n      hoursEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '00', 'PM']);\n      expect(getModelState(true)).toEqual([23, 0]);\n    });\n\n    it('should not decrease minutes when time would rollover to a time later than max', function() {\n      var down = getMinutesButton(false);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.max = newTime(13, 40);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(true);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n\n      minutesEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['12', '00', 'AM']);\n      expect(getModelState(true)).toEqual([0, 0]);\n    });\n\n    it('should decrease minutes when time would rollover to a time not later than max', function() {\n      var down = getMinutesButton(false);\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n      var downKeydownEvent = keydown('down');\n\n      $rootScope.max = newTime(23, 59);\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.$digest();\n\n      expect(down.hasClass('disabled')).toBe(false);\n\n      doClick(down);\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.$digest();\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n\n      $rootScope.time = newTime(0, 0);\n      $rootScope.$digest();\n\n      minutesEl.trigger( downKeydownEvent );\n      $rootScope.$digest();\n      expect(getTimeState(false, true)).toEqual(['11', '59', 'PM']);\n      expect(getModelState(true)).toEqual([23, 59]);\n    });\n\n    it('should not change meridian when it would result a in time later than max', function() {\n      var button = getMeridianButton();\n\n      $rootScope.time = newTime(2, 40);\n      $rootScope.max = newTime(14, 39);\n      $rootScope.$digest();\n\n      expect(button.hasClass('disabled')).toBe(true);\n\n      doClick(button);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'AM']);\n      expect(getModelState(true)).toEqual([2, 40]);\n    });\n\n    it('should change meridian when it would not result in a time later than max', function() {\n      var button = getMeridianButton();\n\n      $rootScope.time = newTime(2, 40);\n      $rootScope.max = newTime(14, 41);\n      $rootScope.$digest();\n\n      expect(button.hasClass('disabled')).toBe(false);\n\n      doClick(button);\n      expect(getTimeState(false, true)).toEqual(['02', '40', 'PM']);\n      expect(getModelState(true)).toEqual([14, 40]);\n    });\n\n    it('should return invalid when the hours are changes such that the time is later than max', function() {\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n\n      $rootScope.max = newTime(14, 0);\n      $rootScope.$digest();\n\n      changeInputValueTo(hoursEl, 3);\n      expect($rootScope.time).toBe(null);\n      expect(hoursEl.parent().hasClass('has-error')).toBe(true);\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n    });\n\n    it('should return valid when the hours are changes such that the time is not later than max', function() {\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0);\n\n      $rootScope.max = newTime(15, 41);\n      $rootScope.$digest();\n\n      changeInputValueTo(hoursEl, 3);\n      expect(getTimeState(false, true)).toEqual(['3', '40', 'PM']);\n      expect(getModelState(true)).toEqual([15, 40]);\n      expect(hoursEl.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n\n    it('should return invalid when the minutes are changes such that the time is later than max', function() {\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n\n      $rootScope.max = newTime(14, 50);\n      $rootScope.$digest();\n\n      changeInputValueTo(minutesEl, 51);\n      expect($rootScope.time).toBe(null);\n      expect(minutesEl.parent().hasClass('has-error')).toBe(true);\n      expect(element.hasClass('ng-invalid-time')).toBe(true);\n    });\n\n    it('should return valid when the minutes are changes such that the time is not later than max', function() {\n      var inputs = element.find('input');\n      var minutesEl = inputs.eq(1);\n\n      $rootScope.max = newTime(14, 42);\n      $rootScope.$digest();\n\n      changeInputValueTo(minutesEl, 41);\n      expect(getTimeState(false, true)).toEqual(['02', '41', 'PM']);\n      expect(getModelState(true)).toEqual([14, 41]);\n      expect(minutesEl.parent().hasClass('has-error')).toBe(false);\n      expect(element.hasClass('ng-invalid-time')).toBe(false);\n    });\n  });\n\n  describe('custom template and controllerAs', function() {\n    it('should allow custom templates', function() {\n      $templateCache.put('foo/bar.html', '<div>baz</div>');\n\n      element = $compile('<div uib-timepicker ng-model=\"time\" template-url=\"foo/bar.html\"></div>')($rootScope);\n      $rootScope.$digest();\n      expect(element[0].tagName.toLowerCase()).toBe('div');\n      expect(element.html()).toBe('<div>baz</div>');\n    });\n\n    it('should expose the controller on the view', function() {\n      $templateCache.put('uib/template/timepicker/timepicker.html', '<div><div>{{timepicker.text}}</div></div>');\n\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var ctrl = element.controller('uibTimepicker');\n      expect(ctrl).toBeDefined();\n\n      ctrl.text = 'foo';\n      $rootScope.$digest();\n\n      expect(element.html()).toBe('<div><div class=\"ng-binding\">foo</div></div>');\n    });\n  });\n\n  describe('ngDisabled', function() {\n    it('prevents modifying date via controls when true', function() {\n      $rootScope.disabled = false;\n      element = $compile('<div uib-timepicker ng-model=\"time\" ng-disabled=\"disabled\"></div>')($rootScope);\n      $rootScope.$digest();\n\n      var inputs = element.find('input');\n      var hoursEl = inputs.eq(0), minutesEl = inputs.eq(1),\n          secondsEl = inputs.eq(2);\n      var upKeydownEvent = keydown('up');\n      var downKeydownEvent = keydown('down');\n      var leftKeydownEvent = keydown('left');\n      var upMouseWheelEvent = wheelThatMouse(1);\n      var downMouseWheelEvent = wheelThatMouse(-1);\n\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      $rootScope.disabled = true;\n      $rootScope.$digest();\n\n      // UP\n      hoursEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      minutesEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      secondsEl.trigger(upKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      // DOWN\n      secondsEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      minutesEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      hoursEl.trigger(downKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      // Other keydown\n      hoursEl.trigger(leftKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      minutesEl.trigger(leftKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      secondsEl.trigger(leftKeydownEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      // WHEEL UP\n      secondsEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      minutesEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      hoursEl.trigger(upMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      // WHEEL DOWN\n      secondsEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      minutesEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n\n      hoursEl.trigger(downMouseWheelEvent);\n      $rootScope.$digest();\n      expect(getTimeState()).toEqual(['02', '40', '25', 'PM']);\n      expect(getModelState()).toEqual([14, 40, 25]);\n    });\n  });\n\n  describe('gc', function() {\n    var $scope;\n    beforeEach(inject(function() {\n      $scope = $rootScope.$new();\n      element = $compile('<div uib-timepicker ng-model=\"time\"></div>')($scope);\n      $rootScope.$digest();\n    }));\n\n    it('should clean up watchers', function() {\n      expect($scope.$$watchers.length > 1).toBe(true);\n\n      element.isolateScope().$destroy();\n\n      expect($scope.$$watchers.length).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "src/timepicker/timepicker.css",
    "content": ".uib-time input {\n  width: 50px;\n}\n"
  },
  {
    "path": "src/timepicker/timepicker.js",
    "content": "angular.module('ui.bootstrap.timepicker', [])\n\n.constant('uibTimepickerConfig', {\n  hourStep: 1,\n  minuteStep: 1,\n  secondStep: 1,\n  showMeridian: true,\n  showSeconds: false,\n  meridians: null,\n  readonlyInput: false,\n  mousewheel: true,\n  arrowkeys: true,\n  showSpinners: true,\n  templateUrl: 'uib/template/timepicker/timepicker.html'\n})\n\n.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {\n  var hoursModelCtrl, minutesModelCtrl, secondsModelCtrl;\n  var selected = new Date(),\n    watchers = [],\n    ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl\n    meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,\n    padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;\n\n  $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;\n  $element.removeAttr('tabindex');\n\n  this.init = function(ngModelCtrl_, inputs) {\n    ngModelCtrl = ngModelCtrl_;\n    ngModelCtrl.$render = this.render;\n\n    ngModelCtrl.$formatters.unshift(function(modelValue) {\n      return modelValue ? new Date(modelValue) : null;\n    });\n\n    var hoursInputEl = inputs.eq(0),\n        minutesInputEl = inputs.eq(1),\n        secondsInputEl = inputs.eq(2);\n\n    hoursModelCtrl = hoursInputEl.controller('ngModel');\n    minutesModelCtrl = minutesInputEl.controller('ngModel');\n    secondsModelCtrl = secondsInputEl.controller('ngModel');\n\n    var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;\n\n    if (mousewheel) {\n      this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);\n    }\n\n    var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;\n    if (arrowkeys) {\n      this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);\n    }\n\n    $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;\n    this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);\n  };\n\n  var hourStep = timepickerConfig.hourStep;\n  if ($attrs.hourStep) {\n    watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {\n      hourStep = +value;\n    }));\n  }\n\n  var minuteStep = timepickerConfig.minuteStep;\n  if ($attrs.minuteStep) {\n    watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {\n      minuteStep = +value;\n    }));\n  }\n\n  var min;\n  watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {\n    var dt = new Date(value);\n    min = isNaN(dt) ? undefined : dt;\n  }));\n\n  var max;\n  watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {\n    var dt = new Date(value);\n    max = isNaN(dt) ? undefined : dt;\n  }));\n\n  var disabled = false;\n  if ($attrs.ngDisabled) {\n    watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {\n      disabled = value;\n    }));\n  }\n\n  $scope.noIncrementHours = function() {\n    var incrementedSelected = addMinutes(selected, hourStep * 60);\n    return disabled || incrementedSelected > max ||\n      incrementedSelected < selected && incrementedSelected < min;\n  };\n\n  $scope.noDecrementHours = function() {\n    var decrementedSelected = addMinutes(selected, -hourStep * 60);\n    return disabled || decrementedSelected < min ||\n      decrementedSelected > selected && decrementedSelected > max;\n  };\n\n  $scope.noIncrementMinutes = function() {\n    var incrementedSelected = addMinutes(selected, minuteStep);\n    return disabled || incrementedSelected > max ||\n      incrementedSelected < selected && incrementedSelected < min;\n  };\n\n  $scope.noDecrementMinutes = function() {\n    var decrementedSelected = addMinutes(selected, -minuteStep);\n    return disabled || decrementedSelected < min ||\n      decrementedSelected > selected && decrementedSelected > max;\n  };\n\n  $scope.noIncrementSeconds = function() {\n    var incrementedSelected = addSeconds(selected, secondStep);\n    return disabled || incrementedSelected > max ||\n      incrementedSelected < selected && incrementedSelected < min;\n  };\n\n  $scope.noDecrementSeconds = function() {\n    var decrementedSelected = addSeconds(selected, -secondStep);\n    return disabled || decrementedSelected < min ||\n      decrementedSelected > selected && decrementedSelected > max;\n  };\n\n  $scope.noToggleMeridian = function() {\n    if (selected.getHours() < 12) {\n      return disabled || addMinutes(selected, 12 * 60) > max;\n    }\n\n    return disabled || addMinutes(selected, -12 * 60) < min;\n  };\n\n  var secondStep = timepickerConfig.secondStep;\n  if ($attrs.secondStep) {\n    watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {\n      secondStep = +value;\n    }));\n  }\n\n  $scope.showSeconds = timepickerConfig.showSeconds;\n  if ($attrs.showSeconds) {\n    watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {\n      $scope.showSeconds = !!value;\n    }));\n  }\n\n  // 12H / 24H mode\n  $scope.showMeridian = timepickerConfig.showMeridian;\n  if ($attrs.showMeridian) {\n    watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {\n      $scope.showMeridian = !!value;\n\n      if (ngModelCtrl.$error.time) {\n        // Evaluate from template\n        var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();\n        if (angular.isDefined(hours) && angular.isDefined(minutes)) {\n          selected.setHours(hours);\n          refresh();\n        }\n      } else {\n        updateTemplate();\n      }\n    }));\n  }\n\n  // Get $scope.hours in 24H mode if valid\n  function getHoursFromTemplate() {\n    var hours = +$scope.hours;\n    var valid = $scope.showMeridian ? hours > 0 && hours < 13 :\n      hours >= 0 && hours < 24;\n    if (!valid || $scope.hours === '') {\n      return undefined;\n    }\n\n    if ($scope.showMeridian) {\n      if (hours === 12) {\n        hours = 0;\n      }\n      if ($scope.meridian === meridians[1]) {\n        hours = hours + 12;\n      }\n    }\n    return hours;\n  }\n\n  function getMinutesFromTemplate() {\n    var minutes = +$scope.minutes;\n    var valid = minutes >= 0 && minutes < 60;\n    if (!valid || $scope.minutes === '') {\n      return undefined;\n    }\n    return minutes;\n  }\n\n  function getSecondsFromTemplate() {\n    var seconds = +$scope.seconds;\n    return seconds >= 0 && seconds < 60 ? seconds : undefined;\n  }\n\n  function pad(value, noPad) {\n    if (value === null) {\n      return '';\n    }\n\n    return angular.isDefined(value) && value.toString().length < 2 && !noPad ?\n      '0' + value : value.toString();\n  }\n\n  // Respond on mousewheel spin\n  this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {\n    var isScrollingUp = function(e) {\n      if (e.originalEvent) {\n        e = e.originalEvent;\n      }\n      //pick correct delta variable depending on event\n      var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;\n      return e.detail || delta > 0;\n    };\n\n    hoursInputEl.on('mousewheel wheel', function(e) {\n      if (!disabled) {\n        $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());\n      }\n      e.preventDefault();\n    });\n\n    minutesInputEl.on('mousewheel wheel', function(e) {\n      if (!disabled) {\n        $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());\n      }\n      e.preventDefault();\n    });\n\n     secondsInputEl.on('mousewheel wheel', function(e) {\n      if (!disabled) {\n        $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());\n      }\n      e.preventDefault();\n    });\n  };\n\n  // Respond on up/down arrowkeys\n  this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {\n    hoursInputEl.on('keydown', function(e) {\n      if (!disabled) {\n        if (e.which === 38) { // up\n          e.preventDefault();\n          $scope.incrementHours();\n          $scope.$apply();\n        } else if (e.which === 40) { // down\n          e.preventDefault();\n          $scope.decrementHours();\n          $scope.$apply();\n        }\n      }\n    });\n\n    minutesInputEl.on('keydown', function(e) {\n      if (!disabled) {\n        if (e.which === 38) { // up\n          e.preventDefault();\n          $scope.incrementMinutes();\n          $scope.$apply();\n        } else if (e.which === 40) { // down\n          e.preventDefault();\n          $scope.decrementMinutes();\n          $scope.$apply();\n        }\n      }\n    });\n\n    secondsInputEl.on('keydown', function(e) {\n      if (!disabled) {\n        if (e.which === 38) { // up\n          e.preventDefault();\n          $scope.incrementSeconds();\n          $scope.$apply();\n        } else if (e.which === 40) { // down\n          e.preventDefault();\n          $scope.decrementSeconds();\n          $scope.$apply();\n        }\n      }\n    });\n  };\n\n  this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {\n    if ($scope.readonlyInput) {\n      $scope.updateHours = angular.noop;\n      $scope.updateMinutes = angular.noop;\n      $scope.updateSeconds = angular.noop;\n      return;\n    }\n\n    var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {\n      ngModelCtrl.$setViewValue(null);\n      ngModelCtrl.$setValidity('time', false);\n      if (angular.isDefined(invalidHours)) {\n        $scope.invalidHours = invalidHours;\n        if (hoursModelCtrl) {\n          hoursModelCtrl.$setValidity('hours', false);\n        }\n      }\n\n      if (angular.isDefined(invalidMinutes)) {\n        $scope.invalidMinutes = invalidMinutes;\n        if (minutesModelCtrl) {\n          minutesModelCtrl.$setValidity('minutes', false);\n        }\n      }\n\n      if (angular.isDefined(invalidSeconds)) {\n        $scope.invalidSeconds = invalidSeconds;\n        if (secondsModelCtrl) {\n          secondsModelCtrl.$setValidity('seconds', false);\n        }\n      }\n    };\n\n    $scope.updateHours = function() {\n      var hours = getHoursFromTemplate(),\n        minutes = getMinutesFromTemplate();\n\n      ngModelCtrl.$setDirty();\n\n      if (angular.isDefined(hours) && angular.isDefined(minutes)) {\n        selected.setHours(hours);\n        selected.setMinutes(minutes);\n        if (selected < min || selected > max) {\n          invalidate(true);\n        } else {\n          refresh('h');\n        }\n      } else {\n        invalidate(true);\n      }\n    };\n\n    hoursInputEl.on('blur', function(e) {\n      ngModelCtrl.$setTouched();\n      if (modelIsEmpty()) {\n        makeValid();\n      } else if ($scope.hours === null || $scope.hours === '') {\n        invalidate(true);\n      } else if (!$scope.invalidHours && $scope.hours < 10) {\n        $scope.$apply(function() {\n          $scope.hours = pad($scope.hours, !padHours);\n        });\n      }\n    });\n\n    $scope.updateMinutes = function() {\n      var minutes = getMinutesFromTemplate(),\n        hours = getHoursFromTemplate();\n\n      ngModelCtrl.$setDirty();\n\n      if (angular.isDefined(minutes) && angular.isDefined(hours)) {\n        selected.setHours(hours);\n        selected.setMinutes(minutes);\n        if (selected < min || selected > max) {\n          invalidate(undefined, true);\n        } else {\n          refresh('m');\n        }\n      } else {\n        invalidate(undefined, true);\n      }\n    };\n\n    minutesInputEl.on('blur', function(e) {\n      ngModelCtrl.$setTouched();\n      if (modelIsEmpty()) {\n        makeValid();\n      } else if ($scope.minutes === null) {\n        invalidate(undefined, true);\n      } else if (!$scope.invalidMinutes && $scope.minutes < 10) {\n        $scope.$apply(function() {\n          $scope.minutes = pad($scope.minutes);\n        });\n      }\n    });\n\n    $scope.updateSeconds = function() {\n      var seconds = getSecondsFromTemplate();\n\n      ngModelCtrl.$setDirty();\n\n      if (angular.isDefined(seconds)) {\n        selected.setSeconds(seconds);\n        refresh('s');\n      } else {\n        invalidate(undefined, undefined, true);\n      }\n    };\n\n    secondsInputEl.on('blur', function(e) {\n      if (modelIsEmpty()) {\n        makeValid();\n      } else if (!$scope.invalidSeconds && $scope.seconds < 10) {\n        $scope.$apply( function() {\n          $scope.seconds = pad($scope.seconds);\n        });\n      }\n    });\n\n  };\n\n  this.render = function() {\n    var date = ngModelCtrl.$viewValue;\n\n    if (isNaN(date)) {\n      ngModelCtrl.$setValidity('time', false);\n      $log.error('Timepicker directive: \"ng-model\" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');\n    } else {\n      if (date) {\n        selected = date;\n      }\n\n      if (selected < min || selected > max) {\n        ngModelCtrl.$setValidity('time', false);\n        $scope.invalidHours = true;\n        $scope.invalidMinutes = true;\n      } else {\n        makeValid();\n      }\n      updateTemplate();\n    }\n  };\n\n  // Call internally when we know that model is valid.\n  function refresh(keyboardChange) {\n    makeValid();\n    ngModelCtrl.$setViewValue(new Date(selected));\n    updateTemplate(keyboardChange);\n  }\n\n  function makeValid() {\n    if (hoursModelCtrl) {\n      hoursModelCtrl.$setValidity('hours', true);\n    }\n\n    if (minutesModelCtrl) {\n      minutesModelCtrl.$setValidity('minutes', true);\n    }\n\n    if (secondsModelCtrl) {\n      secondsModelCtrl.$setValidity('seconds', true);\n    }\n\n    ngModelCtrl.$setValidity('time', true);\n    $scope.invalidHours = false;\n    $scope.invalidMinutes = false;\n    $scope.invalidSeconds = false;\n  }\n\n  function updateTemplate(keyboardChange) {\n    if (!ngModelCtrl.$modelValue) {\n      $scope.hours = null;\n      $scope.minutes = null;\n      $scope.seconds = null;\n      $scope.meridian = meridians[0];\n    } else {\n      var hours = selected.getHours(),\n        minutes = selected.getMinutes(),\n        seconds = selected.getSeconds();\n\n      if ($scope.showMeridian) {\n        hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system\n      }\n\n      $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);\n      if (keyboardChange !== 'm') {\n        $scope.minutes = pad(minutes);\n      }\n      $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];\n\n      if (keyboardChange !== 's') {\n        $scope.seconds = pad(seconds);\n      }\n      $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];\n    }\n  }\n\n  function addSecondsToSelected(seconds) {\n    selected = addSeconds(selected, seconds);\n    refresh();\n  }\n\n  function addMinutes(selected, minutes) {\n    return addSeconds(selected, minutes*60);\n  }\n\n  function addSeconds(date, seconds) {\n    var dt = new Date(date.getTime() + seconds * 1000);\n    var newDate = new Date(date);\n    newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());\n    return newDate;\n  }\n\n  function modelIsEmpty() {\n    return ($scope.hours === null || $scope.hours === '') &&\n      ($scope.minutes === null || $scope.minutes === '') &&\n      (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));\n  }\n\n  $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?\n    $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;\n\n  $scope.incrementHours = function() {\n    if (!$scope.noIncrementHours()) {\n      addSecondsToSelected(hourStep * 60 * 60);\n    }\n  };\n\n  $scope.decrementHours = function() {\n    if (!$scope.noDecrementHours()) {\n      addSecondsToSelected(-hourStep * 60 * 60);\n    }\n  };\n\n  $scope.incrementMinutes = function() {\n    if (!$scope.noIncrementMinutes()) {\n      addSecondsToSelected(minuteStep * 60);\n    }\n  };\n\n  $scope.decrementMinutes = function() {\n    if (!$scope.noDecrementMinutes()) {\n      addSecondsToSelected(-minuteStep * 60);\n    }\n  };\n\n  $scope.incrementSeconds = function() {\n    if (!$scope.noIncrementSeconds()) {\n      addSecondsToSelected(secondStep);\n    }\n  };\n\n  $scope.decrementSeconds = function() {\n    if (!$scope.noDecrementSeconds()) {\n      addSecondsToSelected(-secondStep);\n    }\n  };\n\n  $scope.toggleMeridian = function() {\n    var minutes = getMinutesFromTemplate(),\n        hours = getHoursFromTemplate();\n\n    if (!$scope.noToggleMeridian()) {\n      if (angular.isDefined(minutes) && angular.isDefined(hours)) {\n        addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));\n      } else {\n        $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];\n      }\n    }\n  };\n\n  $scope.blur = function() {\n    ngModelCtrl.$setTouched();\n  };\n\n  $scope.$on('$destroy', function() {\n    while (watchers.length) {\n      watchers.shift()();\n    }\n  });\n}])\n\n.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {\n  return {\n    require: ['uibTimepicker', '?^ngModel'],\n    restrict: 'A',\n    controller: 'UibTimepickerController',\n    controllerAs: 'timepicker',\n    scope: {},\n    templateUrl: function(element, attrs) {\n      return attrs.templateUrl || uibTimepickerConfig.templateUrl;\n    },\n    link: function(scope, element, attrs, ctrls) {\n      var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];\n\n      if (ngModelCtrl) {\n        timepickerCtrl.init(ngModelCtrl, element.find('input'));\n      }\n    }\n  };\n}]);\n"
  },
  {
    "path": "src/tooltip/docs/demo.html",
    "content": "<div ng-controller=\"TooltipDemoCtrl\">\n    <div class=\"form-group\">\n      <label>Tooltip placement</label>\n      <select class=\"form-control\" ng-model=\"placement.selected\" ng-options=\"o as o for o in placement.options\"></select>\n    </div>\n    <button tooltip-placement=\"{{placement.selected}}\" uib-tooltip=\"On the {{placement.selected}}\" type=\"button\" class=\"btn btn-default\">Tooltip {{placement.selected}}</button>\n\n    <hr />\n    <div class=\"form-group\">\n      <label>Dynamic Tooltip Text</label>\n      <input type=\"text\" ng-model=\"dynamicTooltipText\" class=\"form-control\">\n    </div>\n    <div class=\"form-group\">\n      <label>Dynamic Tooltip Popup Text</label>\n      <input type=\"text\" ng-model=\"dynamicTooltip\" class=\"form-control\">\n    </div>\n    <p>\n      Pellentesque <a href=\"#\" uib-tooltip=\"{{dynamicTooltip}}\">{{dynamicTooltipText}}</a>,\n      sit amet venenatis urna cursus eget nunc scelerisque viverra mauris, in\n      aliquam. Tincidunt lobortis feugiat vivamus at\n      <a href=\"#\" tooltip-animation=\"false\" uib-tooltip=\"I don't fade. :-(\">fading</a>\n      eget arcu dictum varius duis at consectetur lorem. Vitae elementum curabitur\n      <a href=\"#\" tooltip-popup-delay='1000' uib-tooltip='appears with delay'>show delay</a>\n      nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas\n      <a href=\"#\" tooltip-popup-close-delay='1000' uib-tooltip='hides with delay'>hide delay</a>\n      pharetra convallis posuere morbi leo urna,\n      <a href=\"#\" uib-tooltip-template=\"'myTooltipTemplate.html'\">Custom template</a>\n      at elementum eu, facilisis sed odio morbi quis commodo odio.\n    </p>\n\n    <p>\n        I can even contain HTML as a\n        <a href=\"#\" uib-tooltip-html=\"htmlTooltip\">scope variable</a> or\n        <a href=\"#\" uib-tooltip-html=\"'static. {{dynamicTooltipText}}. <b>bold.</b>'\">inline string</a>\n    </p>\n\n    <p>\n      <style>\n        /* Specify styling for tooltip contents */\n        .tooltip.customClass .tooltip-inner {\n          color: #880000;\n          background-color: #ffff66;\n          box-shadow: 0 6px 12px rgba(0,0,0,.175);\n        }\n        /* Hide arrow */\n        .tooltip.customClass .tooltip-arrow {\n          display: none;\n        }\n      </style>\n      I can have a custom class. <a href=\"#\" uib-tooltip=\"I can have a custom class applied to me!\" tooltip-class=\"customClass\">Check me out!</a>\n    </p>\n\n\n    <div class=\"form-group\">\n      <label>Or use custom triggers, like focus: </label>\n      <input type=\"text\" value=\"Click me!\" uib-tooltip=\"See? Now click away...\" tooltip-trigger=\"'focus'\" tooltip-placement=\"right\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\" ng-class=\"{'has-error' : !inputModel}\">\n      <label>Disable tooltips conditionally:</label>\n      <input type=\"text\" ng-model=\"inputModel\" class=\"form-control\"\n             placeholder=\"Hover over this for a tooltip until this is filled\"\n             uib-tooltip=\"Enter something in this input field to disable this tooltip\"\n             tooltip-placement=\"top\"\n             tooltip-trigger=\"'mouseenter'\"\n             tooltip-enable=\"!inputModel\" />\n    </div>\n    <div class=\"form-group\">\n      <label>\n        Open tooltips <span uib-tooltip=\"Hello!\" tooltip-is-open=\"tooltipIsOpen\" tooltip-placement=\"bottom\">conditionally.</span>\n      </label>\n      <button ng-click=\"tooltipIsOpen = !tooltipIsOpen\">Toggle tooltip</button>\n    </div>\n    <script type=\"text/ng-template\" id=\"myTooltipTemplate.html\">\n      <span>Special Tooltip with <strong>markup</strong> and {{ dynamicTooltipText }}</span>\n    </script>\n</div>\n"
  },
  {
    "path": "src/tooltip/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('TooltipDemoCtrl', function ($scope, $sce) {\n  $scope.dynamicTooltip = 'Hello, World!';\n  $scope.dynamicTooltipText = 'dynamic';\n  $scope.htmlTooltip = $sce.trustAsHtml('I\\'ve been made <b>bold</b>!');\n  $scope.placement = {\n    options: [\n      'top',\n      'top-left',\n      'top-right',\n      'bottom',\n      'bottom-left',\n      'bottom-right',\n      'left',\n      'left-top',\n      'left-bottom',\n      'right',\n      'right-top',\n      'right-bottom'\n    ],\n    selected: 'top'\n  };\n});\n"
  },
  {
    "path": "src/tooltip/docs/readme.md",
    "content": "A lightweight, extensible directive for fancy tooltip creation. The tooltip\ndirective supports multiple placements, optional transition animation, and more.\n\n__Note to mobile developers__:  Please note that while tooltips may work correctly on mobile devices (including tablets),\n  we have made the decision to not officially support such a use-case because it does not make sense from a UX perspective.\n\nThere are three versions of the tooltip: `uib-tooltip`, `uib-tooltip-template`, and\n`uib-tooltip-html`:\n\n* `uib-tooltip` -\n  Takes text only and will escape any HTML provided.\n* `uib-tooltip-html`\n  <small class=\"badge\">$</small> -\n  Takes an expression that evaluates to an HTML string. Note that this HTML is not compiled. If compilation is required, please use the `uib-tooltip-template` attribute option instead. *The user is responsible for ensuring the content is safe to put into the DOM!*\n* `uib-tooltip-template`\n  <small class=\"badge\">$</small> -\n  Takes text that specifies the location of a template to use for the tooltip. Note that this needs to be wrapped in a tag.\n\n### uib-tooltip-* settings\n\nAll these settings are available for the three types of tooltips.\n\n* `tooltip-animation`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `true`, Config: `animation`)_ -\n  Should it fade in and out?\n\n* `tooltip-append-to-body`\n  <small class=\"badge\">$</small>\n  <small class=\"badge\">C</small>\n  _(Default: `false`, Config: `appendToBody`)_ -\n  Should the tooltip be appended to '$body' instead of the parent element?\n\n* `tooltip-class` -\n  Custom class to be applied to the tooltip.\n\n* `tooltip-enable`\n  <small class=\"badge\">$</small>\n  _(Default: `true`)_ -\n  Is it enabled? It will enable or disable the configured tooltip-trigger.\n\n* `tooltip-is-open`\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Whether to show the tooltip.\n\n* `tooltip-placement`\n  <small class=\"badge\">C</small>\n  _(Default: `top`, Config: `placement`)_ -\n  Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: \"auto bottom-left\". The tooltip will attempt to position where it fits in the closest scrollable ancestor. Accepts:\n\n   * `top` - tooltip on top, horizontally centered on host element.\n   * `top-left` - tooltip on top, left edge aligned with host element left edge.\n   * `top-right` - tooltip on top, right edge aligned with host element right edge.\n   * `bottom` - tooltip on bottom, horizontally centered on host element.\n   * `bottom-left` - tooltip on bottom, left edge aligned with host element left edge.\n   * `bottom-right` - tooltip on bottom, right edge aligned with host element right edge.\n   * `left` - tooltip on left, vertically centered on host element.\n   * `left-top` - tooltip on left, top edge aligned with host element top edge.\n   * `left-bottom` - tooltip on left, bottom edge aligned with host element bottom edge.\n   * `right` - tooltip on right, vertically centered on host element.\n   * `right-top` - tooltip on right, top edge aligned with host element top edge.\n   * `right-bottom` - tooltip on right, bottom edge aligned with host element bottom edge.\n\n* `tooltip-popup-close-delay`\n  <small class=\"badge\">C</small>\n  _(Default: `0`, Config: `popupCloseDelay`)_ -\n  For how long should the tooltip remain open after the close trigger event?\n\n* `tooltip-popup-delay`\n  <small class=\"badge\">C</small>\n  _(Default: `0`, Config: `popupDelay`)_ -\n  Popup delay in milliseconds until it opens.\n\n* `tooltip-trigger`\n  <small class=\"badge\">$</small>\n  _(Default: `'mouseenter'`)_ -\n  What should trigger a show of the tooltip? Supports a space separated list of event names, or objects (see below).\n\n**Note:** To configure the tooltips, you need to do it on `$uibTooltipProvider` (also see below).\n\n### Triggers\n\nThe following show triggers are supported out of the box, along with their provided hide triggers:\n\n- `mouseenter`: `mouseleave`\n- `click`: `click`\n- `outsideClick`: `outsideClick`\n- `focus`: `blur`\n- `none`\n\nThe `outsideClick` trigger will cause the tooltip to toggle on click, and hide when anything else is clicked.\n\nFor any non-supported value, the trigger will be used to both show and hide the\ntooltip. Using the 'none' trigger will disable the internal trigger(s), one can\nthen use the `tooltip-is-open` attribute exclusively to show and hide the tooltip.\n\n### $uibTooltipProvider\n\nThrough the `$uibTooltipProvider`, you can change the way tooltips and popovers\nbehave by default; the attributes above always take precedence. The following\nmethods are available:\n\n* `setTriggers(obj)`\n  _(Example: `{ 'openTrigger': 'closeTrigger' }`)_ -\n  Extends the default trigger mappings mentioned above with mappings of your own.\n\n* `options(obj)` -\n  Provide a set of defaults for certain tooltip and popover attributes. Currently supports the ones with the <small class=\"badge\">C</small> badge.\n\n### Known issues\n\nFor Safari 7+ support, if you want to use the **focus** `tooltip-trigger`, you need to use an anchor tag with a tab index. For example:\n\n```\n<a tabindex=\"0\" uib-tooltip=\"Test\" tooltip-trigger=\"focus\" class=\"btn btn-default\">\n  Click Me\n</a>\n```\n\nFor Safari (potentially all versions up to 9), there is an issue with the hover CSS selector when using multiple elements grouped close to each other that are using the tooltip - it is possible for multiple elements to gain the hover state when mousing between the elements quickly and exiting the container at the right time. See [issue #5445](https://github.com/angular-ui/bootstrap/issues/5445) for more details.\n"
  },
  {
    "path": "src/tooltip/index-nocss.js",
    "content": "require('../position/index-nocss.js');\nrequire('../stackedMap');\nrequire('../../template/tooltip/tooltip-popup.html.js');\nrequire('../../template/tooltip/tooltip-html-popup.html.js');\nrequire('../../template/tooltip/tooltip-template-popup.html.js');\nrequire('./tooltip');\n\nvar MODULE_NAME = 'ui.bootstrap.module.tooltip';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.tooltip', 'uib/template/tooltip/tooltip-popup.html', 'uib/template/tooltip/tooltip-html-popup.html', 'uib/template/tooltip/tooltip-template-popup.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/tooltip/index.js",
    "content": "require('../position/position.css');\nrequire('./tooltip.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/tooltip/test/tooltip-template.spec.js",
    "content": "describe('tooltip template', function() {\n  var elm,\n      elmBody,\n      scope,\n      elmScope,\n      tooltipScope,\n      $document;\n\n  // load the popover code\n  beforeEach(module('ui.bootstrap.tooltip'));\n\n  // load the template\n  beforeEach(module('uib/template/tooltip/tooltip-template-popup.html'));\n\n  beforeEach(inject(function($templateCache) {\n    $templateCache.put('myUrl', [200, '<span>{{ myTemplateText }}</span>', {}]);\n  }));\n\n  beforeEach(inject(function($rootScope, $compile, _$document_) {\n    $document = _$document_;\n    elmBody = angular.element(\n      '<div><span uib-tooltip-template=\"templateUrl\">Selector Text</span></div>'\n    );\n\n    scope = $rootScope;\n    $compile(elmBody)(scope);\n    scope.templateUrl = 'myUrl';\n\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  afterEach(function() {\n    $document.off('keypress');\n  });\n\n  function trigger(element, evt) {\n    element.trigger(evt);\n    element.scope().$$childTail.$digest();\n  }\n\n  it('should open on mouseenter', inject(function() {\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(true);\n\n    expect(elmBody.children().length).toBe(2);\n  }));\n\n  it('should not open on mouseenter if templateUrl is empty', inject(function() {\n    scope.templateUrl = null;\n    scope.$digest();\n\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(false);\n\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should show updated text', inject(function() {\n    scope.myTemplateText = 'some text';\n\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(true);\n    scope.$digest();\n\n    expect(elmBody.children().eq(1).text().trim()).toBe('some text');\n\n    scope.myTemplateText = 'new text';\n    scope.$digest();\n\n    expect(elmBody.children().eq(1).text().trim()).toBe('new text');\n  }));\n\n  it('should hide tooltip when template becomes empty', inject(function($timeout) {\n    trigger(elm, 'mouseenter');\n    $timeout.flush(0);\n    expect(tooltipScope.isOpen).toBe(true);\n\n    scope.templateUrl = '';\n    scope.$digest();\n\n    expect(tooltipScope.isOpen).toBe(false);\n\n    $timeout.flush();\n    expect(elmBody.children().length).toBe(1);\n  }));\n});\n"
  },
  {
    "path": "src/tooltip/test/tooltip.spec.js",
    "content": "describe('tooltip', function() {\n  var elm,\n      elmBody,\n      scope,\n      elmScope,\n      tooltipScope,\n      $document;\n\n  // load the tooltip code\n  beforeEach(module('ui.bootstrap.tooltip'));\n\n  // load the template\n  beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n  beforeEach(inject(function($rootScope, $compile, _$document_) {\n    elmBody = angular.element(\n      '<div><span uib-tooltip=\"tooltip text\" tooltip-animation=\"false\">Selector Text</span></div>'\n    );\n\n    $document = _$document_;\n    scope = $rootScope;\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  afterEach(function() {\n    $document.off('keyup');\n  });\n\n  function trigger(element, evt) {\n    element.trigger(evt);\n    element.scope().$$childTail.$digest();\n  }\n\n  it('should not be open initially', inject(function() {\n    expect(tooltipScope.isOpen).toBe(false);\n\n    // We can only test *that* the tooltip-popup element wasn't created as the\n    // implementation is templated and replaced.\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should open on mouseenter', inject(function() {\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(true);\n\n    // We can only test *that* the tooltip-popup element was created as the\n    // implementation is templated and replaced.\n    expect(elmBody.children().length).toBe(2);\n  }));\n\n  it('should close on mouseleave', inject(function() {\n    trigger(elm, 'mouseenter');\n    trigger(elm, 'mouseleave');\n    expect(tooltipScope.isOpen).toBe(false);\n  }));\n\n  it('should not animate on animation set to false', inject(function() {\n    expect(tooltipScope.animation).toBe(false);\n  }));\n\n  it('should have default placement of \"top\"', inject(function() {\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.placement).toBe('top');\n  }));\n\n  it('should allow specification of placement', inject(function($compile) {\n    elm = $compile(angular.element(\n      '<span uib-tooltip=\"tooltip text\" tooltip-placement=\"bottom\">Selector Text</span>'\n    ))(scope);\n    scope.$apply();\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.placement).toBe('bottom');\n  }));\n\n  it('should update placement dynamically', inject(function($compile, $timeout) {\n    scope.place = 'bottom';\n    elm = $compile(angular.element(\n      '<span uib-tooltip=\"tooltip text\" tooltip-placement=\"{{place}}\">Selector Text</span>'\n    ))(scope);\n    scope.$apply();\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.placement).toBe('bottom');\n\n    scope.place = 'right';\n    scope.$digest();\n    $timeout.flush();\n    expect(tooltipScope.placement).toBe('right');\n  }));\n\n  it('should work inside an ngRepeat', inject(function($compile) {\n    elm = $compile(angular.element(\n      '<ul>'+\n        '<li ng-repeat=\"item in items\">'+\n          '<span uib-tooltip=\"{{item.tooltip}}\">{{item.name}}</span>'+\n        '</li>'+\n      '</ul>'\n    ))(scope);\n\n    scope.items = [\n      { name: 'One', tooltip: 'First Tooltip' }\n    ];\n\n    scope.$digest();\n\n    var tt = angular.element(elm.find('li > span')[0]);\n    trigger(tt, 'mouseenter');\n\n    expect(tt.text()).toBe(scope.items[0].name);\n\n    tooltipScope = tt.scope().$$childTail;\n    expect(tooltipScope.content).toBe(scope.items[0].tooltip);\n\n    trigger(tt, 'mouseleave');\n    expect(tooltipScope.isOpen).toBeFalsy();\n  }));\n\n  it('should show correct text when in an ngRepeat', inject(function($compile, $timeout) {\n    elm = $compile(angular.element(\n      '<ul>'+\n        '<li ng-repeat=\"item in items\">'+\n          '<span uib-tooltip=\"{{item.tooltip}}\">{{item.name}}</span>'+\n        '</li>'+\n      '</ul>'\n    ))(scope);\n\n    scope.items = [\n      { name: 'One', tooltip: 'First Tooltip' },\n      { name: 'Second', tooltip: 'Second Tooltip' }\n    ];\n\n    scope.$digest();\n\n    var tt_1 = angular.element(elm.find('li > span')[0]);\n    var tt_2 = angular.element(elm.find('li > span')[1]);\n\n    trigger(tt_1, 'mouseenter');\n    trigger(tt_1, 'mouseleave');\n\n    $timeout.flush();\n\n    trigger(tt_2, 'mouseenter');\n\n    expect(tt_1.text()).toBe(scope.items[0].name);\n    expect(tt_2.text()).toBe(scope.items[1].name);\n\n    tooltipScope = tt_2.scope().$$childTail;\n    expect(tooltipScope.content).toBe(scope.items[1].tooltip);\n    expect(elm.find('.tooltip-inner').text()).toBe(scope.items[1].tooltip);\n\n    trigger(tt_2, 'mouseleave');\n  }));\n\n  it('should only have an isolate scope on the popup', inject(function($compile) {\n    var ttScope;\n\n    scope.tooltipMsg = 'Tooltip Text';\n    scope.alt = 'Alt Message';\n\n    elmBody = $compile(angular.element(\n      '<div><span alt={{alt}} uib-tooltip=\"{{tooltipMsg}}\" tooltip-animation=\"false\">Selector Text</span></div>'\n    ))(scope);\n\n    $compile(elmBody)(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n\n    trigger(elm, 'mouseenter');\n    expect(elm.attr('alt')).toBe(scope.alt);\n\n    ttScope = angular.element(elmBody.children()[1]).isolateScope();\n    expect(ttScope.content).toBe(scope.tooltipMsg);\n\n    trigger(elm, 'mouseleave');\n\n    //Isolate scope contents should be the same after hiding and showing again (issue 1191)\n    trigger(elm, 'mouseenter');\n\n    ttScope = angular.element(elmBody.children()[1]).isolateScope();\n    expect(ttScope.content).toBe(scope.tooltipMsg);\n  }));\n\n  it('should not show tooltips if there is nothing to show - issue #129', inject(function($compile) {\n    elmBody = $compile(angular.element(\n      '<div><span uib-tooltip=\"\">Selector Text</span></div>'\n    ))(scope);\n    scope.$digest();\n    elmBody.find('span').trigger('mouseenter');\n\n    expect(elmBody.children().length).toBe(1);\n  }));\n\n  it('should close the tooltip when its trigger element is destroyed', inject(function() {\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(true);\n\n    elm.remove();\n    elmScope.$destroy();\n    expect(elmBody.children().length).toBe(0);\n  }));\n\n  it('issue 1191 - scope on the popup should always be child of correct element scope', function() {\n    var ttScope;\n    trigger(elm, 'mouseenter');\n\n    ttScope = angular.element(elmBody.children()[1]).scope();\n    expect(ttScope.$parent).toBe(tooltipScope);\n\n    trigger(elm, 'mouseleave');\n\n    // After leaving and coming back, the scope's parent should be the same\n    trigger(elm, 'mouseenter');\n\n    ttScope = angular.element(elmBody.children()[1]).scope();\n    expect(ttScope.$parent).toBe(tooltipScope);\n\n    trigger(elm, 'mouseleave');\n  });\n\n  describe('with specified enable expression', function() {\n    beforeEach(inject(function($compile) {\n      scope.enable = false;\n      elmBody = $compile(angular.element(\n        '<div><span uib-tooltip=\"tooltip text\" tooltip-enable=\"enable\">Selector Text</span></div>'\n      ))(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n    }));\n\n    it('should not open ', inject(function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBeFalsy();\n      expect(elmBody.children().length).toBe(1);\n    }));\n\n    it('should open', inject(function() {\n      scope.enable = true;\n      scope.$digest();\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      expect(elmBody.children().length).toBe(2);\n    }));\n  });\n\n  describe('with specified popup delay', function() {\n    var $timeout;\n    beforeEach(inject(function($compile, _$timeout_) {\n      $timeout = _$timeout_;\n      scope.delay = '1000';\n      elm = $compile(angular.element(\n        '<span uib-tooltip=\"tooltip text\" tooltip-popup-delay=\"{{delay}}\" ng-disabled=\"disabled\">Selector Text</span>'\n      ))(scope);\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n      scope.$digest();\n    }));\n\n    it('should open after timeout', function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(false);\n\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(true);\n    });\n\n    it('should not open if mouseleave before timeout', function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(false);\n\n      trigger(elm, 'mouseleave');\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n\n    it('should use default popup delay if specified delay is not a number', function() {\n      scope.delay = 'text1000';\n      scope.$digest();\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(true);\n    });\n\n    it('should not open if disabled is present', function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(false);\n\n      elmScope.disabled = true;\n      elmScope.$digest();\n      $timeout.flush(500);\n\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n\n    it('should open when not disabled after being disabled - issue #4204', function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(false);\n\n      $timeout.flush(500);\n      elmScope.disabled = true;\n      elmScope.$digest();\n\n      $timeout.flush(500);\n      expect(tooltipScope.isOpen).toBe(false);\n\n      elmScope.disabled = false;\n      elmScope.$digest();\n\n      trigger(elm, 'mouseenter');\n      $timeout.flush();\n\n      expect(tooltipScope.isOpen).toBe(true);\n    });\n\n    it('should close the tooltips in order', inject(function($compile) {\n      var elm2 = $compile('<div><span uib-tooltip=\"tooltip #2\" tooltip-is-open=\"isOpen2\">Selector Text</span></div>')(scope);\n      scope.$digest();\n      elm2 = elm2.find('span');\n      var tooltipScope2 = elm2.scope().$$childTail;\n      tooltipScope2.isOpen = false;\n      scope.$digest();\n\n      trigger(elm, 'mouseenter');\n      tooltipScope2.$digest();\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(tooltipScope2.isOpen).toBe(false);\n\n      trigger(elm2, 'mouseenter');\n      tooltipScope2.$digest();\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(tooltipScope2.isOpen).toBe(true);\n\n      var evt = $.Event('keyup');\n      evt.which = 27;\n\n      $document.trigger(evt);\n      tooltipScope.$digest();\n      tooltipScope2.$digest();\n      $timeout.flush();\n\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(tooltipScope2.isOpen).toBe(false);\n\n      var evt2 = $.Event('keyup');\n      evt2.which = 27;\n\n      $document.trigger(evt2);\n      tooltipScope.$digest();\n      tooltipScope2.$digest();\n      $timeout.flush(500);\n\n      expect(tooltipScope.isOpen).toBe(false);\n      expect(tooltipScope2.isOpen).toBe(false);\n    }));\n  });\n\n  describe('with specified popup close delay', function() {\n    var $timeout;\n    beforeEach(inject(function($compile, _$timeout_) {\n      $timeout = _$timeout_;\n      scope.delay = '1000';\n      elm = $compile(angular.element(\n        '<span uib-tooltip=\"tooltip text\" tooltip-popup-close-delay=\"{{delay}}\" ng-disabled=\"disabled\">Selector Text</span>'\n      ))(scope);\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n      scope.$digest();\n    }));\n\n    it('should close after timeout', function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(true);\n      trigger(elm, 'mouseleave');\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n\n    it('should use default popup close delay if specified delay is not a number and close immediately', function() {\n      scope.delay = 'text1000';\n      scope.$digest();\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.popupCloseDelay).toBe(0);\n      expect(tooltipScope.isOpen).toBe(true);\n      trigger(elm, 'mouseleave');\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n\n    it('should open when not disabled after being disabled and close after delay - issue #4204', function() {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(true);\n\n      elmScope.disabled = true;\n      elmScope.$digest();\n\n      $timeout.flush(500);\n      expect(tooltipScope.isOpen).toBe(false);\n\n      elmScope.disabled = false;\n      elmScope.$digest();\n\n      trigger(elm, 'mouseenter');\n\n      expect(tooltipScope.isOpen).toBe(true);\n      trigger(elm, 'mouseleave');\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n  });\n\n  describe('with specified popup and popup close delay', function() {\n    var $timeout;\n    beforeEach(inject(function($compile, _$timeout_) {\n      $timeout = _$timeout_;\n      scope.delay = '1000';\n      elm = $compile(angular.element(\n        '<span uib-tooltip=\"tooltip text\" tooltip-popup-close-delay=\"{{delay}}\" tooltip-popup-close-delay=\"{{delay}}\" ng-disabled=\"disabled\">Selector Text</span>'\n      ))(scope);\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n      scope.$digest();\n    }));\n\n    it('should not open if mouseleave before timeout', function() {\n      trigger(elm, 'mouseenter');\n      $timeout.flush(500);\n      trigger(elm, 'mouseleave');\n      $timeout.flush();\n\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n  });\n\n  describe('with an is-open attribute', function() {\n    beforeEach(inject(function ($compile) {\n      scope.isOpen = false;\n      elm = $compile(angular.element(\n        '<span uib-tooltip=\"tooltip text\" tooltip-is-open=\"isOpen\" >Selector Text</span>'\n      ))(scope);\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n      scope.$digest();\n    }));\n\n    it('should show and hide with the controller value', function() {\n      expect(tooltipScope.isOpen).toBe(false);\n      elmScope.isOpen = true;\n      elmScope.$digest();\n      expect(tooltipScope.isOpen).toBe(true);\n      elmScope.isOpen = false;\n      elmScope.$digest();\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n\n    it('should update the controller value', function() {\n      trigger(elm, 'mouseenter');\n      expect(elmScope.isOpen).toBe(true);\n      trigger(elm, 'mouseleave');\n      expect(elmScope.isOpen).toBe(false);\n    });\n  });\n\n  describe('with an is-open attribute expression', function() {\n    beforeEach(inject(function($compile) {\n      scope.isOpen = false;\n      elm = $compile(angular.element(\n        '<span uib-tooltip=\"tooltip text\" tooltip-is-open=\"isOpen === true\" >Selector Text</span>'\n      ))(scope);\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n      scope.$digest();\n    }));\n\n    it('should show and hide with the expression', function() {\n      expect(tooltipScope.isOpen).toBe(false);\n      elmScope.isOpen = true;\n      elmScope.$digest();\n      expect(tooltipScope.isOpen).toBe(true);\n      elmScope.isOpen = false;\n      elmScope.$digest();\n      expect(tooltipScope.isOpen).toBe(false);\n    });\n  });\n\n  describe('with a trigger attribute', function() {\n    var scope, elmBody, elm, elmScope;\n\n    beforeEach(inject(function($rootScope) {\n      scope = $rootScope;\n    }));\n\n    it('should use it to show but set the hide trigger based on the map for mapped triggers', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><input uib-tooltip=\"Hello!\" tooltip-trigger=\"\\'focus\\'\" /></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$apply();\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      expect(tooltipScope.isOpen).toBeFalsy();\n      trigger(elm, 'focus');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      trigger(elm, 'blur');\n      expect(tooltipScope.isOpen).toBeFalsy();\n    }));\n\n    it('should use it as both the show and hide triggers for unmapped triggers', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><input uib-tooltip=\"Hello!\" tooltip-trigger=\"\\'fakeTriggerAttr\\'\" /></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$apply();\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      expect(tooltipScope.isOpen).toBeFalsy();\n      trigger(elm, 'fakeTriggerAttr');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      trigger(elm, 'fakeTriggerAttr');\n      expect(tooltipScope.isOpen).toBeFalsy();\n    }));\n\n    it('should only set up triggers once', inject(function($compile) {\n      scope.test = true;\n      elmBody = angular.element(\n        '<div>' +\n          '<input uib-tooltip=\"Hello!\" tooltip-trigger=\"test && \\'mouseenter\\' || \\'click\\'\" />' +\n          '<input uib-tooltip=\"Hello!\" tooltip-trigger=\"test && \\'mouseenter\\' || \\'click\\'\" />' +\n        '</div>'\n      );\n\n      $compile(elmBody)(scope);\n      scope.$apply();\n      var elm1 = elmBody.find('input').eq(0);\n      var elm2 = elmBody.find('input').eq(1);\n      var elmScope1 = elm1.scope();\n      var elmScope2 = elm2.scope();\n      var tooltipScope2 = elmScope2.$$childTail;\n\n      scope.$apply('test = false');\n\n      // click trigger isn't set\n      elm2.click();\n      expect(tooltipScope2.isOpen).toBeFalsy();\n\n      // mouseenter trigger is still set\n      trigger(elm2, 'mouseenter');\n      expect(tooltipScope2.isOpen).toBeTruthy();\n    }));\n\n    it('should accept multiple triggers based on the map for mapped triggers', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><input uib-tooltip=\"Hello!\" tooltip-trigger=\"\\'focus fakeTriggerAttr\\'\" /></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$apply();\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      expect(tooltipScope.isOpen).toBeFalsy();\n      trigger(elm, 'focus');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      trigger(elm, 'blur');\n      expect(tooltipScope.isOpen).toBeFalsy();\n      trigger(elm, 'fakeTriggerAttr');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      trigger(elm, 'fakeTriggerAttr');\n      expect(tooltipScope.isOpen).toBeFalsy();\n    }));\n\n    it('should not show when trigger is set to \"none\"', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><input uib-tooltip=\"Hello!\" tooltip-trigger=\"\\'none\\'\" /></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$apply();\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n      expect(tooltipScope.isOpen).toBeFalsy();\n      elm.trigger('mouseenter');\n      expect(tooltipScope.isOpen).toBeFalsy();\n    }));\n\n    it('should toggle on click and hide when anything else is clicked when trigger is set to \"outsideClick\"', inject(function($compile, $document) {\n      elm = $compile(angular.element(\n        '<span uib-tooltip=\"tooltip text\" tooltip-trigger=\"\\'outsideClick\\'\">Selector Text</span>'\n      ))(scope);\n      scope.$apply();\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      // start off\n      expect(tooltipScope.isOpen).toBeFalsy();\n\n      // toggle\n      trigger(elm, 'click');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      trigger(elm, 'click');\n      expect(tooltipScope.isOpen).toBeFalsy();\n\n      // click on, outsideClick off\n      trigger(elm, 'click');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      angular.element($document[0].body).trigger('click');\n      tooltipScope.$digest();\n      expect(tooltipScope.isOpen).toBeFalsy();\n    }));\n\n    it('should support objects', inject(function($compile) {\n      elmBody = angular.element(\n        '<div><input uib-tooltip=\"Hello!\" tooltip-trigger=\"{show: \\'hide\\'}\" /></div>'\n      );\n      $compile(elmBody)(scope);\n      scope.$apply();\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      expect(tooltipScope.isOpen).toBeFalsy();\n      trigger(elm, 'show');\n      expect(tooltipScope.isOpen).toBeTruthy();\n      trigger(elm, 'hide');\n      expect(tooltipScope.isOpen).toBeFalsy();\n    }));\n  });\n\n  describe('with an append-to-body attribute', function() {\n    var scope, elmBody, elm, elmScope, $body;\n\n    beforeEach(inject(function($rootScope) {\n      scope = $rootScope;\n    }));\n\n    afterEach(function() {\n      $body.find('.tooltip').remove();\n    });\n\n    it('should append to the body', inject(function($compile, $document) {\n      $body = $document.find('body');\n      elmBody = angular.element(\n        '<div><span uib-tooltip=\"tooltip text\" tooltip-append-to-body=\"true\">Selector Text</span></div>'\n      );\n\n      $compile(elmBody)(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      var bodyLength = $body.children().length;\n      trigger(elm, 'mouseenter');\n\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(elmBody.children().length).toBe(1);\n      expect($body.children().length).toEqual(bodyLength + 1);\n    }));\n  });\n\n  describe('cleanup', function() {\n    var elmBody, elm, elmScope, tooltipScope;\n\n    function inCache() {\n      var match = false;\n\n      angular.forEach(angular.element.cache, function(item) {\n        if (item.data && item.data.$scope === tooltipScope) {\n          match = true;\n        }\n      });\n\n      return match;\n    }\n\n    beforeEach(inject(function($compile, $rootScope) {\n      elmBody = angular.element('<div><input uib-tooltip=\"Hello!\" tooltip-trigger=\"\\'fooTrigger\\'\" /></div>');\n\n      $compile(elmBody)($rootScope);\n      $rootScope.$apply();\n\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      trigger(elm, 'fooTrigger');\n      tooltipScope = elmScope.$$childTail.$$childTail;\n    }));\n\n    it('should not contain a cached reference when not visible', inject(function($timeout) {\n      expect(inCache()).toBeTruthy();\n      elmScope.$destroy();\n      expect(inCache()).toBeFalsy();\n    }));\n  });\n\n  describe('observers', function() {\n    var elmBody, elm, elmScope, scope, tooltipScope;\n\n    beforeEach(inject(function($compile, $rootScope) {\n      scope = $rootScope;\n      scope.content = 'tooltip content';\n      scope.placement = 'top';\n      elmBody = angular.element('<div><input uib-tooltip=\"{{content}}\" tooltip-placement={{placement}} /></div>');\n      $compile(elmBody)(scope);\n      scope.$apply();\n\n      elm = elmBody.find('input');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n    }));\n\n    it('should be removed when tooltip hides', inject(function($timeout) {\n      expect(tooltipScope.content).toBe(undefined);\n      expect(tooltipScope.placement).toBe(undefined);\n\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.content).toBe('tooltip content');\n      expect(tooltipScope.placement).toBe('top');\n      scope.content = 'tooltip content updated';\n\n      scope.placement = 'bottom';\n      scope.$apply();\n      expect(tooltipScope.content).toBe('tooltip content updated');\n      expect(tooltipScope.placement).toBe('bottom');\n\n      trigger(elm, 'mouseleave');\n      $timeout.flush();\n      scope.content = 'tooltip content updated after close';\n      scope.placement = 'left';\n      scope.$apply();\n      expect(tooltipScope.content).toBe('tooltip content updated');\n      expect(tooltipScope.placement).toBe('bottom');\n    }));\n  });\n});\n\ndescribe('tooltipWithDifferentSymbols', function() {\n    var elmBody;\n\n    // load the tooltip code\n    beforeEach(module('ui.bootstrap.tooltip'));\n\n    // load the template\n    beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n    // configure interpolate provider to use [[ ]] instead of {{ }}\n    beforeEach(module(function($interpolateProvider) {\n      $interpolateProvider.startSymbol('[[');\n      $interpolateProvider.startSymbol(']]');\n    }));\n\n    function trigger(element, evt) {\n      element.trigger(evt);\n      element.scope().$$childTail.$digest();\n    }\n\n    it('should show the correct tooltip text', inject(function($compile, $rootScope) {\n      elmBody = angular.element(\n        '<div><input type=\"text\" uib-tooltip=\"My tooltip\" tooltip-trigger=\"\\'focus\\'\" tooltip-placement=\"right\" /></div>'\n      );\n      $compile(elmBody)($rootScope);\n      $rootScope.$apply();\n      var elmInput = elmBody.find('input');\n      trigger(elmInput, 'focus');\n\n      expect(elmInput.next().find('div').next().html()).toBe('My tooltip');\n    }));\n});\n\ndescribe('tooltip positioning', function() {\n  var elm, elmBody, elmScope, tooltipScope, scope;\n  var $position;\n\n  // load the tooltip code\n  beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n    $uibTooltipProvider.options({ animation: false });\n  }));\n\n  // load the template\n  beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n  beforeEach(inject(function($rootScope, $compile, $uibPosition) {\n    $position = $uibPosition;\n    spyOn($position, 'positionElements').and.callThrough();\n\n    scope = $rootScope;\n    scope.text = 'Some Text';\n\n    elmBody = $compile(angular.element(\n      '<div><span uib-tooltip=\"{{ text }}\">Selector Text</span></div>'\n    ))(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  function trigger(element, evt) {\n    element.trigger(evt);\n    element.scope().$$childTail.$digest();\n  }\n\n  it('should re-position when value changes', inject(function($timeout) {\n    trigger(elm, 'mouseenter');\n\n    scope.$digest();\n    $timeout.flush();\n    var startingPositionCalls = $position.positionElements.calls.count();\n\n    scope.text = 'New Text';\n    scope.$digest();\n    $timeout.flush();\n    expect(elm.attr('uib-tooltip')).toBe('New Text');\n    expect($position.positionElements.calls.count()).toEqual(startingPositionCalls + 1);\n    // Check that positionElements was called with elm\n    expect($position.positionElements.calls.argsFor(startingPositionCalls)[0][0])\n      .toBe(elm[0]);\n\n    scope.$digest();\n    $timeout.verifyNoPendingTasks();\n    expect($position.positionElements.calls.count()).toEqual(startingPositionCalls + 1);\n    expect($position.positionElements.calls.argsFor(startingPositionCalls)[0][0])\n      .toBe(elm[0]);\n    scope.$digest();\n  }));\n\n});\n\ndescribe('tooltipHtml', function() {\n  var elm, elmBody, elmScope, tooltipScope, scope;\n\n  // load the tooltip code\n  beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n    $uibTooltipProvider.options({ animation: false });\n  }));\n\n  // load the template\n  beforeEach(module('uib/template/tooltip/tooltip-html-popup.html'));\n\n  beforeEach(inject(function($rootScope, $compile, $sce) {\n    scope = $rootScope;\n    scope.html = 'I say: <strong class=\"hello\">Hello!</strong>';\n    scope.safeHtml = $sce.trustAsHtml(scope.html);\n\n    elmBody = $compile(angular.element(\n      '<div><span uib-tooltip-html=\"safeHtml\">Selector Text</span></div>'\n    ))(scope);\n    scope.$digest();\n    elm = elmBody.find('span');\n    elmScope = elm.scope();\n    tooltipScope = elmScope.$$childTail;\n  }));\n\n  function trigger(element, evt) {\n    element.trigger(evt);\n    element.scope().$$childTail.$digest();\n  }\n\n  it('should render html properly', inject(function() {\n    trigger(elm, 'mouseenter');\n    expect(elmBody.find('.tooltip-inner').html()).toBe(scope.html);\n  }));\n\n  it('should not open if html is empty', function() {\n    scope.safeHtml = null;\n    scope.$digest();\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(false);\n  });\n\n  it('should show on mouseenter and hide on mouseleave', inject(function($sce) {\n    expect(tooltipScope.isOpen).toBe(false);\n\n    trigger(elm, 'mouseenter');\n    expect(tooltipScope.isOpen).toBe(true);\n    expect(elmBody.children().length).toBe(2);\n\n    expect($sce.getTrustedHtml(tooltipScope.contentExp())).toEqual(scope.html);\n\n    trigger(elm, 'mouseleave');\n    expect(tooltipScope.isOpen).toBe(false);\n    expect(elmBody.children().length).toBe(1);\n  }));\n});\n\ndescribe('$uibTooltipProvider', function() {\n  var elm,\n      elmBody,\n      scope,\n      elmScope,\n      tooltipScope;\n\n  function trigger(element, evt) {\n    element.trigger(evt);\n    element.scope().$$childTail.$digest();\n  }\n\n  describe('popupDelay', function() {\n    beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n      $uibTooltipProvider.options({popupDelay: 1000});\n    }));\n\n    // load the template\n    beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n    beforeEach(inject(function($rootScope, $compile) {\n      elmBody = angular.element(\n        '<div><span uib-tooltip=\"tooltip text\">Selector Text</span></div>'\n      );\n\n      scope = $rootScope;\n      $compile(elmBody)(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n    }));\n\n    it('should open after timeout', inject(function($timeout) {\n      trigger(elm, 'mouseenter');\n      expect(tooltipScope.isOpen).toBe(false);\n\n      $timeout.flush();\n      expect(tooltipScope.isOpen).toBe(true);\n    }));\n  });\n\n  describe('appendToBody', function() {\n    var $body;\n\n    beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n    beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n      $uibTooltipProvider.options({ appendToBody: true });\n    }));\n\n    afterEach(function() {\n      $body.find('.tooltip').remove();\n    });\n\n    it('should append to the body', inject(function($rootScope, $compile, $document) {\n      $body = $document.find('body');\n      elmBody = angular.element(\n        '<div><span uib-tooltip=\"tooltip text\">Selector Text</span></div>'\n      );\n\n      scope = $rootScope;\n      $compile(elmBody)(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      var bodyLength = $body.children().length;\n      trigger(elm, 'mouseenter');\n\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(elmBody.children().length).toBe(1);\n      expect($body.children().length).toEqual(bodyLength + 1);\n    }));\n\n    it('should append to the body when only attribute present', inject(function($rootScope, $compile, $document) {\n      $body = $document.find('body');\n      elmBody = angular.element(\n        '<div><span uib-tooltip=\"tooltip text\" tooltip-append-to-body>Selector Text</span></div>'\n      );\n\n      scope = $rootScope;\n      $compile(elmBody)(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      var bodyLength = $body.children().length;\n      trigger(elm, 'mouseenter');\n\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(elmBody.children().length).toBe(1);\n      expect($body.children().length).toEqual(bodyLength + 1);\n    }));\n\n    it('should not append to the body when attribute value is false', inject(function($rootScope, $compile, $document) {\n      $body = $document.find('body');\n      elmBody = angular.element(\n        '<div><span uib-tooltip=\"tooltip text\" tooltip-append-to-body=\"false\">Selector Text</span></div>'\n      );\n\n      scope = $rootScope;\n      $compile(elmBody)(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      var bodyLength = $body.children().length;\n      trigger(elm, 'mouseenter');\n\n      expect(tooltipScope.isOpen).toBe(true);\n      expect(elmBody.children().length).toBe(2);\n      expect($body.children().length).toEqual(bodyLength);\n    }));\n\n  });\n\n  describe('triggers', function() {\n    describe('with a mapped value', function() {\n      beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n        $uibTooltipProvider.options({trigger: 'focus'});\n      }));\n\n      // load the template\n      beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n      it('should use the show trigger and the mapped value for the hide trigger', inject(function($rootScope, $compile) {\n        elmBody = angular.element(\n          '<div><input uib-tooltip=\"tooltip text\" /></div>'\n        );\n\n        scope = $rootScope;\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('input');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        expect(tooltipScope.isOpen).toBeFalsy();\n        trigger(elm, 'focus');\n        expect(tooltipScope.isOpen).toBeTruthy();\n        trigger(elm, 'blur');\n        expect(tooltipScope.isOpen).toBeFalsy();\n      }));\n\n      it('should override the show and hide triggers if there is an attribute', inject(function($rootScope, $compile) {\n        elmBody = angular.element(\n          '<div><input uib-tooltip=\"tooltip text\" tooltip-trigger=\"\\'mouseenter\\'\"/></div>'\n        );\n\n        scope = $rootScope;\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('input');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        expect(tooltipScope.isOpen).toBeFalsy();\n        trigger(elm, 'mouseenter');\n        expect(tooltipScope.isOpen).toBeTruthy();\n        trigger(elm, 'mouseleave');\n        expect(tooltipScope.isOpen).toBeFalsy();\n      }));\n    });\n\n    describe('with a custom mapped value', function() {\n      beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n        $uibTooltipProvider.setTriggers({ customOpenTrigger: 'foo bar' });\n        $uibTooltipProvider.options({trigger: 'customOpenTrigger'});\n      }));\n\n      // load the template\n      beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n      it('should use the show trigger and the mapped value for the hide trigger', inject(function($rootScope, $compile) {\n        elmBody = angular.element(\n          '<div><input uib-tooltip=\"tooltip text\" /></div>'\n        );\n\n        scope = $rootScope;\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('input');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        expect(tooltipScope.isOpen).toBeFalsy();\n        trigger(elm, 'customOpenTrigger');\n        expect(tooltipScope.isOpen).toBeTruthy();\n        trigger(elm, 'foo');\n        expect(tooltipScope.isOpen).toBeFalsy();\n        trigger(elm, 'customOpenTrigger');\n        expect(tooltipScope.isOpen).toBeTruthy();\n        trigger(elm, 'bar');\n        expect(tooltipScope.isOpen).toBeFalsy();\n      }));\n    });\n\n    describe('triggers without a mapped value', function() {\n      beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n        $uibTooltipProvider.options({trigger: 'fakeTrigger'});\n      }));\n\n      // load the template\n      beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n      it('should use the show trigger to hide', inject(function($rootScope, $compile) {\n        elmBody = angular.element(\n          '<div><span uib-tooltip=\"tooltip text\">Selector Text</span></div>'\n        );\n\n        scope = $rootScope;\n        $compile(elmBody)(scope);\n        scope.$digest();\n        elm = elmBody.find('span');\n        elmScope = elm.scope();\n        tooltipScope = elmScope.$$childTail;\n\n        expect(tooltipScope.isOpen).toBeFalsy();\n        trigger(elm, 'fakeTrigger');\n        expect(tooltipScope.isOpen).toBeTruthy();\n        trigger(elm, 'fakeTrigger');\n        expect(tooltipScope.isOpen).toBeFalsy();\n      }));\n    });\n  });\n\n  describe('placementClassPrefix', function() {\n    beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) {\n      $uibTooltipProvider.options({placementClassPrefix: 'uib-'});\n    }));\n\n    // load the template\n    beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n\n    it('should add the classes', inject(function($rootScope, $compile, $timeout) {\n      elmBody = angular.element(\n        '<div><span uib-tooltip=\"tooltip text\" tooltip-placement=\"top-right\"></span></div>'\n      );\n\n      scope = $rootScope;\n      $compile(elmBody)(scope);\n      scope.$digest();\n      elm = elmBody.find('span');\n      elmScope = elm.scope();\n      tooltipScope = elmScope.$$childTail;\n\n      expect(elmBody.children().length).toBe(1);\n\n      trigger(elm, 'mouseenter');\n      $timeout.flush();\n\n      var tooltipElm = elmBody.find('.tooltip');\n      expect(tooltipElm.hasClass('top')).toBe(true);\n      expect(tooltipElm.hasClass('uib-top-right')).toBe(true);\n    }));\n  });\n});\n"
  },
  {
    "path": "src/tooltip/test/tooltip2.spec.js",
    "content": "describe('tooltip directive', function() {\n  var $rootScope, $compile, $document, $timeout, body, fragment;\n\n  beforeEach(module('ui.bootstrap.tooltip'));\n  beforeEach(module('uib/template/tooltip/tooltip-popup.html'));\n  beforeEach(module('uib/template/tooltip/tooltip-template-popup.html'));\n  beforeEach(module('uib/template/tooltip/tooltip-html-popup.html'));\n  beforeEach(inject(function(_$rootScope_, _$compile_, _$document_, _$timeout_) {\n    $rootScope = _$rootScope_;\n    $compile = _$compile_;\n    $document = _$document_;\n    $timeout = _$timeout_;\n\n    body = $document.find('body');\n  }));\n\n  beforeEach(function() {\n    jasmine.addMatchers({\n      toHaveOpenTooltips: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, noOfOpened) {\n            var ttipElements = actual.find('div.tooltip');\n            noOfOpened = noOfOpened || 1;\n\n            var result = {\n              pass: util.equals(ttipElements.length, noOfOpened, customEqualityTesters)\n            };\n\n            if (result.pass) {\n              result.message = 'Expected \"' + angular.mock.dump(ttipElements) + '\" not to have \"' + ttipElements.length + '\" opened tooltips.';\n            } else {\n              result.message = 'Expected \"' + angular.mock.dump(ttipElements) + '\" to have \"' + ttipElements.length + '\" opened tooltips.';\n            }\n\n            return result;\n          }\n        };\n      }\n    });\n  });\n\n  afterEach(function() {\n    $document.off('keypress');\n    fragment.remove();\n  });\n\n  function compileTooltip(ttipMarkup) {\n    fragment = $compile('<div>' + ttipMarkup + '</div>')($rootScope);\n    $rootScope.$digest();\n    body.append(fragment);\n  }\n\n  function closeTooltip(hostEl, triggerEvt, shouldNotFlush) {\n    trigger(hostEl, triggerEvt || 'mouseleave');\n    hostEl.scope().$$childTail.$digest();\n    if (!shouldNotFlush) {\n      $timeout.flush();\n    }\n  }\n\n  function trigger(element, evt) {\n    element.trigger(evt);\n    element.scope().$$childTail.$digest();\n  }\n\n  describe('basic scenarios with default options', function() {\n    it('shows default tooltip on mouse enter and closes on mouse leave', function() {\n      compileTooltip('<span uib-tooltip=\"tooltip text\">Trigger here</span>');\n\n      trigger(fragment.find('span'), 'mouseenter');\n      expect(fragment).toHaveOpenTooltips();\n\n      closeTooltip(fragment.find('span'));\n      expect(fragment).not.toHaveOpenTooltips();\n    });\n\n    it('should not show a tooltip when its content is empty', function() {\n      compileTooltip('<span uib-tooltip=\"\"></span>');\n      trigger(fragment.find('span'), 'mouseenter');\n      expect(fragment).not.toHaveOpenTooltips();\n    });\n\n    it('should not show a tooltip when its content becomes empty', function() {\n      $rootScope.content = 'some text';\n      compileTooltip('<span uib-tooltip=\"{{ content }}\"></span>');\n\n      trigger(fragment.find('span'), 'mouseenter');\n      $timeout.flush(0);\n      expect(fragment).toHaveOpenTooltips();\n\n      $rootScope.content = '';\n      $rootScope.$digest();\n      $timeout.flush();\n      expect(fragment).not.toHaveOpenTooltips();\n    });\n\n    it('should update tooltip when its content becomes empty', function() {\n      $rootScope.content = 'some text';\n      compileTooltip('<span uib-tooltip=\"{{ content }}\"></span>');\n\n      $rootScope.content = '';\n      $rootScope.$digest();\n\n      trigger(fragment.find('span'), 'mouseenter');\n      expect(fragment).not.toHaveOpenTooltips();\n    });\n  });\n\n  describe('option by option', function() {\n    var tooltipTypes = {\n      'tooltip': 'uib-tooltip=\"tooltip text\"',\n      'tooltip-html': 'uib-tooltip-html=\"tooltipSafeHtml\"',\n      'tooltip-template': 'uib-tooltip-template=\"\\'tooltipTextUrl\\'\"'\n    };\n\n    beforeEach(inject(function($sce, $templateCache) {\n      $rootScope.tooltipText = 'tooltip text';\n      $rootScope.tooltipSafeHtml = $sce.trustAsHtml('tooltip text');\n      $templateCache.put('tooltipTextUrl', [200, '<span>tooltip text</span>', {}]);\n    }));\n\n    angular.forEach(tooltipTypes, function(html, key) {\n      describe(key, function() {\n        describe('placement', function() {\n          it('can specify an alternative, valid placement', function() {\n            compileTooltip('<span ' + html + ' tooltip-placement=\"left\">Trigger here</span>');\n            trigger(fragment.find('span'), 'mouseenter');\n\n            var ttipElement = fragment.find('div.tooltip');\n            expect(fragment).toHaveOpenTooltips();\n            expect(ttipElement).toHaveClass('left');\n\n            closeTooltip(fragment.find('span'));\n            expect(fragment).not.toHaveOpenTooltips();\n          });\n        });\n\n        describe('class', function() {\n          it('can specify a custom class', function() {\n            compileTooltip('<span ' + html + ' tooltip-class=\"custom\">Trigger here</span>');\n            trigger(fragment.find('span'), 'mouseenter');\n\n            var ttipElement = fragment.find('div.tooltip');\n            expect(fragment).toHaveOpenTooltips();\n            expect(ttipElement).toHaveClass('custom');\n\n            closeTooltip(fragment.find('span'));\n            expect(fragment).not.toHaveOpenTooltips();\n          });\n        });\n      });\n    });\n  });\n\n  it('should show even after close trigger is called multiple times - issue #1847', function() {\n    compileTooltip('<span uib-tooltip=\"tooltip text\">Trigger here</span>');\n\n    trigger(fragment.find('span'), 'mouseenter');\n    expect(fragment).toHaveOpenTooltips();\n\n    closeTooltip(fragment.find('span'), null, true);\n    // Close trigger is called again before timer completes\n    // The close trigger can be called any number of times (even after close has already been called)\n    // since users can trigger the hide triggers manually.\n    closeTooltip(fragment.find('span'), null, true);\n    expect(fragment).toHaveOpenTooltips();\n\n    trigger(fragment.find('span'), 'mouseenter');\n    expect(fragment).toHaveOpenTooltips();\n\n    $timeout.flush();\n    expect(fragment).toHaveOpenTooltips();\n  });\n\n  it('should hide even after show trigger is called multiple times', function() {\n    compileTooltip('<span uib-tooltip=\"tooltip text\" tooltip-popup-delay=\"1000\">Trigger here</span>');\n\n    trigger(fragment.find('span'), 'mouseenter');\n    trigger(fragment.find('span'), 'mouseenter');\n\n    closeTooltip(fragment.find('span'));\n    expect(fragment).not.toHaveOpenTooltips();\n  });\n\n  it('should not show tooltips element is disabled (button) - issue #3167', function() {\n    compileTooltip('<button uib-tooltip=\"cancel!\" ng-disabled=\"disabled\" ng-click=\"disabled = true\">Cancel</button>');\n\n    trigger(fragment.find('button'), 'mouseenter');\n    expect(fragment).toHaveOpenTooltips();\n\n    trigger(fragment.find('button'), 'click');\n    $timeout.flush();\n    // One needs to flush deferred functions before checking there is no tooltip.\n    expect(fragment).not.toHaveOpenTooltips();\n  });\n});\n"
  },
  {
    "path": "src/tooltip/tooltip.css",
    "content": "[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,\n[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,\n[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,\n[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,\n[uib-popover-popup].popover.top-left > .arrow,\n[uib-popover-popup].popover.top-right > .arrow,\n[uib-popover-popup].popover.bottom-left > .arrow,\n[uib-popover-popup].popover.bottom-right > .arrow,\n[uib-popover-popup].popover.left-top > .arrow,\n[uib-popover-popup].popover.left-bottom > .arrow,\n[uib-popover-popup].popover.right-top > .arrow,\n[uib-popover-popup].popover.right-bottom > .arrow,\n[uib-popover-html-popup].popover.top-left > .arrow,\n[uib-popover-html-popup].popover.top-right > .arrow,\n[uib-popover-html-popup].popover.bottom-left > .arrow,\n[uib-popover-html-popup].popover.bottom-right > .arrow,\n[uib-popover-html-popup].popover.left-top > .arrow,\n[uib-popover-html-popup].popover.left-bottom > .arrow,\n[uib-popover-html-popup].popover.right-top > .arrow,\n[uib-popover-html-popup].popover.right-bottom > .arrow,\n[uib-popover-template-popup].popover.top-left > .arrow,\n[uib-popover-template-popup].popover.top-right > .arrow,\n[uib-popover-template-popup].popover.bottom-left > .arrow,\n[uib-popover-template-popup].popover.bottom-right > .arrow,\n[uib-popover-template-popup].popover.left-top > .arrow,\n[uib-popover-template-popup].popover.left-bottom > .arrow,\n[uib-popover-template-popup].popover.right-top > .arrow,\n[uib-popover-template-popup].popover.right-bottom > .arrow {\n  top: auto;\n  bottom: auto;\n  left: auto;\n  right: auto;\n  margin: 0;\n}\n\n[uib-popover-popup].popover,\n[uib-popover-html-popup].popover,\n[uib-popover-template-popup].popover {\n  display: block !important;\n}\n"
  },
  {
    "path": "src/tooltip/tooltip.js",
    "content": "/**\n * The following features are still outstanding: animation as a\n * function, placement as a function, inside, support for more triggers than\n * just mouse enter/leave, html tooltips, and selector delegation.\n */\nangular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])\n\n/**\n * The $tooltip service creates tooltip- and popover-like directives as well as\n * houses global options for them.\n */\n.provider('$uibTooltip', function() {\n  // The default options tooltip and popover.\n  var defaultOptions = {\n    placement: 'top',\n    placementClassPrefix: '',\n    animation: true,\n    popupDelay: 0,\n    popupCloseDelay: 0,\n    useContentExp: false\n  };\n\n  // Default hide triggers for each show trigger\n  var triggerMap = {\n    'mouseenter': 'mouseleave',\n    'click': 'click',\n    'outsideClick': 'outsideClick',\n    'focus': 'blur',\n    'none': ''\n  };\n\n  // The options specified to the provider globally.\n  var globalOptions = {};\n\n  /**\n   * `options({})` allows global configuration of all tooltips in the\n   * application.\n   *\n   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {\n   *     // place tooltips left instead of top by default\n   *     $tooltipProvider.options( { placement: 'left' } );\n   *   });\n   */\n\tthis.options = function(value) {\n\t\tangular.extend(globalOptions, value);\n\t};\n\n  /**\n   * This allows you to extend the set of trigger mappings available. E.g.:\n   *\n   *   $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );\n   */\n  this.setTriggers = function setTriggers(triggers) {\n    angular.extend(triggerMap, triggers);\n  };\n\n  /**\n   * This is a helper function for translating camel-case to snake_case.\n   */\n  function snake_case(name) {\n    var regexp = /[A-Z]/g;\n    var separator = '-';\n    return name.replace(regexp, function(letter, pos) {\n      return (pos ? separator : '') + letter.toLowerCase();\n    });\n  }\n\n  /**\n   * Returns the actual instance of the $tooltip service.\n   * TODO support multiple triggers\n   */\n  this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {\n    var openedTooltips = $$stackedMap.createNew();\n    $document.on('keyup', keypressListener);\n\n    $rootScope.$on('$destroy', function() {\n      $document.off('keyup', keypressListener);\n    });\n\n    function keypressListener(e) {\n      if (e.which === 27) {\n        var last = openedTooltips.top();\n        if (last) {\n          last.value.close();\n          last = null;\n        }\n      }\n    }\n\n    return function $tooltip(ttType, prefix, defaultTriggerShow, options) {\n      options = angular.extend({}, defaultOptions, globalOptions, options);\n\n      /**\n       * Returns an object of show and hide triggers.\n       *\n       * If a trigger is supplied,\n       * it is used to show the tooltip; otherwise, it will use the `trigger`\n       * option passed to the `$tooltipProvider.options` method; else it will\n       * default to the trigger supplied to this directive factory.\n       *\n       * The hide trigger is based on the show trigger. If the `trigger` option\n       * was passed to the `$tooltipProvider.options` method, it will use the\n       * mapped trigger from `triggerMap` or the passed trigger if the map is\n       * undefined; otherwise, it uses the `triggerMap` value of the show\n       * trigger; else it will just use the show trigger.\n       */\n      function getTriggers(trigger) {\n        var show = (trigger || options.trigger || defaultTriggerShow).split(' ');\n        var hide = show.map(function(trigger) {\n          return triggerMap[trigger] || trigger;\n        });\n        return {\n          show: show,\n          hide: hide\n        };\n      }\n\n      var directiveName = snake_case(ttType);\n\n      var startSym = $interpolate.startSymbol();\n      var endSym = $interpolate.endSymbol();\n      var template =\n        '<div '+ directiveName + '-popup ' +\n          'uib-title=\"' + startSym + 'title' + endSym + '\" ' +\n          (options.useContentExp ?\n            'content-exp=\"contentExp()\" ' :\n            'content=\"' + startSym + 'content' + endSym + '\" ') +\n          'origin-scope=\"origScope\" ' +\n          'class=\"uib-position-measure ' + prefix + '\" ' +\n          'tooltip-animation-class=\"fade\"' +\n          'uib-tooltip-classes ' +\n          'ng-class=\"{ in: isOpen }\" ' +\n          '>' +\n        '</div>';\n\n      return {\n        compile: function(tElem, tAttrs) {\n          var tooltipLinker = $compile(template);\n\n          return function link(scope, element, attrs, tooltipCtrl) {\n            var tooltip;\n            var tooltipLinkedScope;\n            var transitionTimeout;\n            var showTimeout;\n            var hideTimeout;\n            var positionTimeout;\n            var adjustmentTimeout;\n            var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;\n            var triggers = getTriggers(undefined);\n            var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);\n            var ttScope = scope.$new(true);\n            var repositionScheduled = false;\n            var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;\n            var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;\n            var observers = [];\n            var lastPlacement;\n\n            var positionTooltip = function() {\n              // check if tooltip exists and is not empty\n              if (!tooltip || !tooltip.html()) { return; }\n\n              if (!positionTimeout) {\n                positionTimeout = $timeout(function() {\n                  var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);\n                  var initialHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');\n                  var elementPos = appendToBody ? $position.offset(element) : $position.position(element);\n                  tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });\n                  var placementClasses = ttPosition.placement.split('-');\n\n                  if (!tooltip.hasClass(placementClasses[0])) {\n                    tooltip.removeClass(lastPlacement.split('-')[0]);\n                    tooltip.addClass(placementClasses[0]);\n                  }\n\n                  if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {\n                    tooltip.removeClass(options.placementClassPrefix + lastPlacement);\n                    tooltip.addClass(options.placementClassPrefix + ttPosition.placement);\n                  }\n\n                  adjustmentTimeout = $timeout(function() {\n                    var currentHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');\n                    var adjustment = $position.adjustTop(placementClasses, elementPos, initialHeight, currentHeight);\n                    if (adjustment) {\n                      tooltip.css(adjustment);\n                    }\n                    adjustmentTimeout = null;\n                  }, 0, false);\n\n                  // first time through tt element will have the\n                  // uib-position-measure class or if the placement\n                  // has changed we need to position the arrow.\n                  if (tooltip.hasClass('uib-position-measure')) {\n                    $position.positionArrow(tooltip, ttPosition.placement);\n                    tooltip.removeClass('uib-position-measure');\n                  } else if (lastPlacement !== ttPosition.placement) {\n                    $position.positionArrow(tooltip, ttPosition.placement);\n                  }\n                  lastPlacement = ttPosition.placement;\n\n                  positionTimeout = null;\n                }, 0, false);\n              }\n            };\n\n            // Set up the correct scope to allow transclusion later\n            ttScope.origScope = scope;\n\n            // By default, the tooltip is not open.\n            // TODO add ability to start tooltip opened\n            ttScope.isOpen = false;\n\n            function toggleTooltipBind() {\n              if (!ttScope.isOpen) {\n                showTooltipBind();\n              } else {\n                hideTooltipBind();\n              }\n            }\n\n            // Show the tooltip with delay if specified, otherwise show it immediately\n            function showTooltipBind() {\n              if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {\n                return;\n              }\n\n              cancelHide();\n              prepareTooltip();\n\n              if (ttScope.popupDelay) {\n                // Do nothing if the tooltip was already scheduled to pop-up.\n                // This happens if show is triggered multiple times before any hide is triggered.\n                if (!showTimeout) {\n                  showTimeout = $timeout(show, ttScope.popupDelay, false);\n                }\n              } else {\n                show();\n              }\n            }\n\n            function hideTooltipBind() {\n              cancelShow();\n\n              if (ttScope.popupCloseDelay) {\n                if (!hideTimeout) {\n                  hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);\n                }\n              } else {\n                hide();\n              }\n            }\n\n            // Show the tooltip popup element.\n            function show() {\n              cancelShow();\n              cancelHide();\n\n              // Don't show empty tooltips.\n              if (!ttScope.content) {\n                return angular.noop;\n              }\n\n              createTooltip();\n\n              // And show the tooltip.\n              ttScope.$evalAsync(function() {\n                ttScope.isOpen = true;\n                assignIsOpen(true);\n                positionTooltip();\n              });\n            }\n\n            function cancelShow() {\n              if (showTimeout) {\n                $timeout.cancel(showTimeout);\n                showTimeout = null;\n              }\n\n              if (positionTimeout) {\n                $timeout.cancel(positionTimeout);\n                positionTimeout = null;\n              }\n            }\n\n            // Hide the tooltip popup element.\n            function hide() {\n              if (!ttScope) {\n                return;\n              }\n\n              // First things first: we don't show it anymore.\n              ttScope.$evalAsync(function() {\n                if (ttScope) {\n                  ttScope.isOpen = false;\n                  assignIsOpen(false);\n                  // And now we remove it from the DOM. However, if we have animation, we\n                  // need to wait for it to expire beforehand.\n                  // FIXME: this is a placeholder for a port of the transitions library.\n                  // The fade transition in TWBS is 150ms.\n                  if (ttScope.animation) {\n                    if (!transitionTimeout) {\n                      transitionTimeout = $timeout(removeTooltip, 150, false);\n                    }\n                  } else {\n                    removeTooltip();\n                  }\n                }\n              });\n            }\n\n            function cancelHide() {\n              if (hideTimeout) {\n                $timeout.cancel(hideTimeout);\n                hideTimeout = null;\n              }\n\n              if (transitionTimeout) {\n                $timeout.cancel(transitionTimeout);\n                transitionTimeout = null;\n              }\n            }\n\n            function createTooltip() {\n              // There can only be one tooltip element per directive shown at once.\n              if (tooltip) {\n                return;\n              }\n\n              tooltipLinkedScope = ttScope.$new();\n              tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {\n                if (appendToBody) {\n                  $document.find('body').append(tooltip);\n                } else {\n                  element.after(tooltip);\n                }\n              });\n\n              openedTooltips.add(ttScope, {\n                close: hide\n              });\n\n              prepObservers();\n            }\n\n            function removeTooltip() {\n              cancelShow();\n              cancelHide();\n              unregisterObservers();\n\n              if (tooltip) {\n                tooltip.remove();\n                \n                tooltip = null;\n                if (adjustmentTimeout) {\n                  $timeout.cancel(adjustmentTimeout);\n                }\n              }\n\n              openedTooltips.remove(ttScope);\n              \n              if (tooltipLinkedScope) {\n                tooltipLinkedScope.$destroy();\n                tooltipLinkedScope = null;\n              }\n            }\n\n            /**\n             * Set the initial scope values. Once\n             * the tooltip is created, the observers\n             * will be added to keep things in sync.\n             */\n            function prepareTooltip() {\n              ttScope.title = attrs[prefix + 'Title'];\n              if (contentParse) {\n                ttScope.content = contentParse(scope);\n              } else {\n                ttScope.content = attrs[ttType];\n              }\n\n              ttScope.popupClass = attrs[prefix + 'Class'];\n              ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;\n              var placement = $position.parsePlacement(ttScope.placement);\n              lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];\n\n              var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);\n              var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);\n              ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;\n              ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;\n            }\n\n            function assignIsOpen(isOpen) {\n              if (isOpenParse && angular.isFunction(isOpenParse.assign)) {\n                isOpenParse.assign(scope, isOpen);\n              }\n            }\n\n            ttScope.contentExp = function() {\n              return ttScope.content;\n            };\n\n            /**\n             * Observe the relevant attributes.\n             */\n            attrs.$observe('disabled', function(val) {\n              if (val) {\n                cancelShow();\n              }\n\n              if (val && ttScope.isOpen) {\n                hide();\n              }\n            });\n\n            if (isOpenParse) {\n              scope.$watch(isOpenParse, function(val) {\n                if (ttScope && !val === ttScope.isOpen) {\n                  toggleTooltipBind();\n                }\n              });\n            }\n\n            function prepObservers() {\n              observers.length = 0;\n\n              if (contentParse) {\n                observers.push(\n                  scope.$watch(contentParse, function(val) {\n                    ttScope.content = val;\n                    if (!val && ttScope.isOpen) {\n                      hide();\n                    }\n                  })\n                );\n\n                observers.push(\n                  tooltipLinkedScope.$watch(function() {\n                    if (!repositionScheduled) {\n                      repositionScheduled = true;\n                      tooltipLinkedScope.$$postDigest(function() {\n                        repositionScheduled = false;\n                        if (ttScope && ttScope.isOpen) {\n                          positionTooltip();\n                        }\n                      });\n                    }\n                  })\n                );\n              } else {\n                observers.push(\n                  attrs.$observe(ttType, function(val) {\n                    ttScope.content = val;\n                    if (!val && ttScope.isOpen) {\n                      hide();\n                    } else {\n                      positionTooltip();\n                    }\n                  })\n                );\n              }\n\n              observers.push(\n                attrs.$observe(prefix + 'Title', function(val) {\n                  ttScope.title = val;\n                  if (ttScope.isOpen) {\n                    positionTooltip();\n                  }\n                })\n              );\n\n              observers.push(\n                attrs.$observe(prefix + 'Placement', function(val) {\n                  ttScope.placement = val ? val : options.placement;\n                  if (ttScope.isOpen) {\n                    positionTooltip();\n                  }\n                })\n              );\n            }\n\n            function unregisterObservers() {\n              if (observers.length) {\n                angular.forEach(observers, function(observer) {\n                  observer();\n                });\n                observers.length = 0;\n              }\n            }\n\n            // hide tooltips/popovers for outsideClick trigger\n            function bodyHideTooltipBind(e) {\n              if (!ttScope || !ttScope.isOpen || !tooltip) {\n                return;\n              }\n              // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked\n              if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {\n                hideTooltipBind();\n              }\n            }\n\n            // KeyboardEvent handler to hide the tooltip on Escape key press\n            function hideOnEscapeKey(e) {\n              if (e.which === 27) {\n                hideTooltipBind();\n              }\n            }\n\n            var unregisterTriggers = function() {\n              triggers.show.forEach(function(trigger) {\n                if (trigger === 'outsideClick') {\n                  element.off('click', toggleTooltipBind);\n                } else {\n                  element.off(trigger, showTooltipBind);\n                  element.off(trigger, toggleTooltipBind);\n                }\n                element.off('keypress', hideOnEscapeKey);\n              });\n              triggers.hide.forEach(function(trigger) {\n                if (trigger === 'outsideClick') {\n                  $document.off('click', bodyHideTooltipBind);\n                } else {\n                  element.off(trigger, hideTooltipBind);\n                }\n              });\n            };\n\n            function prepTriggers() {\n              var showTriggers = [], hideTriggers = [];\n              var val = scope.$eval(attrs[prefix + 'Trigger']);\n              unregisterTriggers();\n\n              if (angular.isObject(val)) {\n                Object.keys(val).forEach(function(key) {\n                  showTriggers.push(key);\n                  hideTriggers.push(val[key]);\n                });\n                triggers = {\n                  show: showTriggers,\n                  hide: hideTriggers\n                };\n              } else {\n                triggers = getTriggers(val);\n              }\n\n              if (triggers.show !== 'none') {\n                triggers.show.forEach(function(trigger, idx) {\n                  if (trigger === 'outsideClick') {\n                    element.on('click', toggleTooltipBind);\n                    $document.on('click', bodyHideTooltipBind);\n                  } else if (trigger === triggers.hide[idx]) {\n                    element.on(trigger, toggleTooltipBind);\n                  } else if (trigger) {\n                    element.on(trigger, showTooltipBind);\n                    element.on(triggers.hide[idx], hideTooltipBind);\n                  }\n                  element.on('keypress', hideOnEscapeKey);\n                });\n              }\n            }\n\n            prepTriggers();\n\n            var animation = scope.$eval(attrs[prefix + 'Animation']);\n            ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;\n\n            var appendToBodyVal;\n            var appendKey = prefix + 'AppendToBody';\n            if (appendKey in attrs && attrs[appendKey] === undefined) {\n              appendToBodyVal = true;\n            } else {\n              appendToBodyVal = scope.$eval(attrs[appendKey]);\n            }\n\n            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;\n\n            // Make sure tooltip is destroyed and removed.\n            scope.$on('$destroy', function onDestroyTooltip() {\n              unregisterTriggers();\n              removeTooltip();\n              ttScope = null;\n            });\n          };\n        }\n      };\n    };\n  }];\n})\n\n// This is mostly ngInclude code but with a custom scope\n.directive('uibTooltipTemplateTransclude', [\n         '$animate', '$sce', '$compile', '$templateRequest',\nfunction ($animate, $sce, $compile, $templateRequest) {\n  return {\n    link: function(scope, elem, attrs) {\n      var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);\n\n      var changeCounter = 0,\n        currentScope,\n        previousElement,\n        currentElement;\n\n      var cleanupLastIncludeContent = function() {\n        if (previousElement) {\n          previousElement.remove();\n          previousElement = null;\n        }\n\n        if (currentScope) {\n          currentScope.$destroy();\n          currentScope = null;\n        }\n\n        if (currentElement) {\n          $animate.leave(currentElement).then(function() {\n            previousElement = null;\n          });\n          previousElement = currentElement;\n          currentElement = null;\n        }\n      };\n\n      scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {\n        var thisChangeId = ++changeCounter;\n\n        if (src) {\n          //set the 2nd param to true to ignore the template request error so that the inner\n          //contents and scope can be cleaned up.\n          $templateRequest(src, true).then(function(response) {\n            if (thisChangeId !== changeCounter) { return; }\n            var newScope = origScope.$new();\n            var template = response;\n\n            var clone = $compile(template)(newScope, function(clone) {\n              cleanupLastIncludeContent();\n              $animate.enter(clone, elem);\n            });\n\n            currentScope = newScope;\n            currentElement = clone;\n\n            currentScope.$emit('$includeContentLoaded', src);\n          }, function() {\n            if (thisChangeId === changeCounter) {\n              cleanupLastIncludeContent();\n              scope.$emit('$includeContentError', src);\n            }\n          });\n          scope.$emit('$includeContentRequested', src);\n        } else {\n          cleanupLastIncludeContent();\n        }\n      });\n\n      scope.$on('$destroy', cleanupLastIncludeContent);\n    }\n  };\n}])\n\n/**\n * Note that it's intentional that these classes are *not* applied through $animate.\n * They must not be animated as they're expected to be present on the tooltip on\n * initialization.\n */\n.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {\n  return {\n    restrict: 'A',\n    link: function(scope, element, attrs) {\n      // need to set the primary position so the\n      // arrow has space during position measure.\n      // tooltip.positionTooltip()\n      if (scope.placement) {\n        // // There are no top-left etc... classes\n        // // in TWBS, so we need the primary position.\n        var position = $uibPosition.parsePlacement(scope.placement);\n        element.addClass(position[0]);\n      }\n\n      if (scope.popupClass) {\n        element.addClass(scope.popupClass);\n      }\n\n      if (scope.animation) {\n        element.addClass(attrs.tooltipAnimationClass);\n      }\n    }\n  };\n}])\n\n.directive('uibTooltipPopup', function() {\n  return {\n    restrict: 'A',\n    scope: { content: '@' },\n    templateUrl: 'uib/template/tooltip/tooltip-popup.html'\n  };\n})\n\n.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {\n  return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');\n}])\n\n.directive('uibTooltipTemplatePopup', function() {\n  return {\n    restrict: 'A',\n    scope: { contentExp: '&', originScope: '&' },\n    templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'\n  };\n})\n\n.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {\n  return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {\n    useContentExp: true\n  });\n}])\n\n.directive('uibTooltipHtmlPopup', function() {\n  return {\n    restrict: 'A',\n    scope: { contentExp: '&' },\n    templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'\n  };\n})\n\n.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {\n  return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {\n    useContentExp: true\n  });\n}]);\n"
  },
  {
    "path": "src/typeahead/docs/demo.html",
    "content": "<style>\n  .typeahead-demo .custom-popup-wrapper {\n    position: absolute;\n    top: 100%;\n    left: 0;\n    z-index: 1000;\n    display: none;\n    background-color: #f9f9f9;\n  }\n\n  .typeahead-demo .custom-popup-wrapper > .message {\n    padding: 10px 20px;\n    border-bottom: 1px solid #ddd;\n    color: #868686;\n  }\n\n  .typeahead-demo .custom-popup-wrapper > .dropdown-menu {\n    position: static;\n    float: none;\n    display: block;\n    min-width: 160px;\n    background-color: transparent;\n    border: none;\n    border-radius: 0;\n    box-shadow: none;\n  }\n</style>\n\n<script type=\"text/ng-template\" id=\"customTemplate.html\">\n  <a>\n      <img ng-src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/{{match.model.flag}}\" width=\"16\">\n      <span ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></span>\n  </a>\n</script>\n\n<script type=\"text/ng-template\" id=\"customPopupTemplate.html\">\n  <div class=\"custom-popup-wrapper\"\n     ng-style=\"{top: position().top+'px', left: position().left+'px'}\"\n     style=\"display: block;\"\n     ng-show=\"isOpen() && !moveInProgress\"\n     aria-hidden=\"{{!isOpen()}}\">\n    <p class=\"message\">select location from drop down.</p>\n\n    <ul class=\"dropdown-menu\" role=\"listbox\">\n      <li class=\"uib-typeahead-match\" ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\"\n        ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{::match.id}}\">\n        <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n      </li>\n    </ul>\n  </div>\n</script>\n\n<div class='container-fluid typeahead-demo' ng-controller=\"TypeaheadCtrl\">\n\n    <h4>Static arrays</h4>\n    <pre>Model: {{selected | json}}</pre>\n    <input type=\"text\" ng-model=\"selected\" uib-typeahead=\"state for state in states | filter:$viewValue | limitTo:8\" class=\"form-control\">\n\n    <h4>Asynchronous results</h4>\n    <pre>Model: {{asyncSelected | json}}</pre>\n    <input type=\"text\" ng-model=\"asyncSelected\" placeholder=\"Locations loaded via $http\" uib-typeahead=\"address for address in getLocation($viewValue)\" typeahead-loading=\"loadingLocations\" typeahead-no-results=\"noResults\" class=\"form-control\">\n    <i ng-show=\"loadingLocations\" class=\"glyphicon glyphicon-refresh\"></i>\n    <div ng-show=\"noResults\">\n      <i class=\"glyphicon glyphicon-remove\"></i> No Results Found\n    </div>\n\n    <h4>ngModelOptions support</h4>\n    <pre>Model: {{ngModelOptionsSelected | json}}</pre>\n    <input type=\"text\" ng-model=\"ngModelOptionsSelected\" ng-model-options=\"modelOptions\" uib-typeahead=\"state for state in states | filter:$viewValue | limitTo:8\" class=\"form-control\">\n\n    <h4>Custom templates for results</h4>\n    <pre>Model: {{customSelected | json}}</pre>\n    <input type=\"text\" ng-model=\"customSelected\" placeholder=\"Custom template\" uib-typeahead=\"state as state.name for state in statesWithFlags | filter:{name:$viewValue}\" typeahead-template-url=\"customTemplate.html\" class=\"form-control\" typeahead-show-hint=\"true\" typeahead-min-length=\"0\">\n\n    <h4>Custom popup templates for typeahead's dropdown</h4>\n    <pre>Model: {{customPopupSelected | json}}</pre>\n    <input type=\"text\" ng-model=\"customPopupSelected\" placeholder=\"Custom popup template\" uib-typeahead=\"state as state.name for state in statesWithFlags | filter:{name:$viewValue}\" typeahead-popup-template-url=\"customPopupTemplate.html\" class=\"form-control\">\n</div>\n"
  },
  {
    "path": "src/typeahead/docs/demo.js",
    "content": "angular.module('ui.bootstrap.demo').controller('TypeaheadCtrl', function($scope, $http) {\n\n  var _selected;\n\n  $scope.selected = undefined;\n  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];\n  // Any function returning a promise object can be used to load values asynchronously\n  $scope.getLocation = function(val) {\n    return $http.get('//maps.googleapis.com/maps/api/geocode/json', {\n      params: {\n        address: val,\n        sensor: false\n      }\n    }).then(function(response){\n      return response.data.results.map(function(item){\n        return item.formatted_address;\n      });\n    });\n  };\n\n  $scope.ngModelOptionsSelected = function(value) {\n    if (arguments.length) {\n      _selected = value;\n    } else {\n      return _selected;\n    }\n  };\n\n  $scope.modelOptions = {\n    debounce: {\n      default: 500,\n      blur: 250\n    },\n    getterSetter: true\n  };\n\n  $scope.statesWithFlags = [{'name':'Alabama','flag':'5/5c/Flag_of_Alabama.svg/45px-Flag_of_Alabama.svg.png'},{'name':'Alaska','flag':'e/e6/Flag_of_Alaska.svg/43px-Flag_of_Alaska.svg.png'},{'name':'Arizona','flag':'9/9d/Flag_of_Arizona.svg/45px-Flag_of_Arizona.svg.png'},{'name':'Arkansas','flag':'9/9d/Flag_of_Arkansas.svg/45px-Flag_of_Arkansas.svg.png'},{'name':'California','flag':'0/01/Flag_of_California.svg/45px-Flag_of_California.svg.png'},{'name':'Colorado','flag':'4/46/Flag_of_Colorado.svg/45px-Flag_of_Colorado.svg.png'},{'name':'Connecticut','flag':'9/96/Flag_of_Connecticut.svg/39px-Flag_of_Connecticut.svg.png'},{'name':'Delaware','flag':'c/c6/Flag_of_Delaware.svg/45px-Flag_of_Delaware.svg.png'},{'name':'Florida','flag':'f/f7/Flag_of_Florida.svg/45px-Flag_of_Florida.svg.png'},{'name':'Georgia','flag':'5/54/Flag_of_Georgia_%28U.S._state%29.svg/46px-Flag_of_Georgia_%28U.S._state%29.svg.png'},{'name':'Hawaii','flag':'e/ef/Flag_of_Hawaii.svg/46px-Flag_of_Hawaii.svg.png'},{'name':'Idaho','flag':'a/a4/Flag_of_Idaho.svg/38px-Flag_of_Idaho.svg.png'},{'name':'Illinois','flag':'0/01/Flag_of_Illinois.svg/46px-Flag_of_Illinois.svg.png'},{'name':'Indiana','flag':'a/ac/Flag_of_Indiana.svg/45px-Flag_of_Indiana.svg.png'},{'name':'Iowa','flag':'a/aa/Flag_of_Iowa.svg/44px-Flag_of_Iowa.svg.png'},{'name':'Kansas','flag':'d/da/Flag_of_Kansas.svg/46px-Flag_of_Kansas.svg.png'},{'name':'Kentucky','flag':'8/8d/Flag_of_Kentucky.svg/46px-Flag_of_Kentucky.svg.png'},{'name':'Louisiana','flag':'e/e0/Flag_of_Louisiana.svg/46px-Flag_of_Louisiana.svg.png'},{'name':'Maine','flag':'3/35/Flag_of_Maine.svg/45px-Flag_of_Maine.svg.png'},{'name':'Maryland','flag':'a/a0/Flag_of_Maryland.svg/45px-Flag_of_Maryland.svg.png'},{'name':'Massachusetts','flag':'f/f2/Flag_of_Massachusetts.svg/46px-Flag_of_Massachusetts.svg.png'},{'name':'Michigan','flag':'b/b5/Flag_of_Michigan.svg/45px-Flag_of_Michigan.svg.png'},{'name':'Minnesota','flag':'b/b9/Flag_of_Minnesota.svg/46px-Flag_of_Minnesota.svg.png'},{'name':'Mississippi','flag':'4/42/Flag_of_Mississippi.svg/45px-Flag_of_Mississippi.svg.png'},{'name':'Missouri','flag':'5/5a/Flag_of_Missouri.svg/46px-Flag_of_Missouri.svg.png'},{'name':'Montana','flag':'c/cb/Flag_of_Montana.svg/45px-Flag_of_Montana.svg.png'},{'name':'Nebraska','flag':'4/4d/Flag_of_Nebraska.svg/46px-Flag_of_Nebraska.svg.png'},{'name':'Nevada','flag':'f/f1/Flag_of_Nevada.svg/45px-Flag_of_Nevada.svg.png'},{'name':'New Hampshire','flag':'2/28/Flag_of_New_Hampshire.svg/45px-Flag_of_New_Hampshire.svg.png'},{'name':'New Jersey','flag':'9/92/Flag_of_New_Jersey.svg/45px-Flag_of_New_Jersey.svg.png'},{'name':'New Mexico','flag':'c/c3/Flag_of_New_Mexico.svg/45px-Flag_of_New_Mexico.svg.png'},{'name':'New York','flag':'1/1a/Flag_of_New_York.svg/46px-Flag_of_New_York.svg.png'},{'name':'North Carolina','flag':'b/bb/Flag_of_North_Carolina.svg/45px-Flag_of_North_Carolina.svg.png'},{'name':'North Dakota','flag':'e/ee/Flag_of_North_Dakota.svg/38px-Flag_of_North_Dakota.svg.png'},{'name':'Ohio','flag':'4/4c/Flag_of_Ohio.svg/46px-Flag_of_Ohio.svg.png'},{'name':'Oklahoma','flag':'6/6e/Flag_of_Oklahoma.svg/45px-Flag_of_Oklahoma.svg.png'},{'name':'Oregon','flag':'b/b9/Flag_of_Oregon.svg/46px-Flag_of_Oregon.svg.png'},{'name':'Pennsylvania','flag':'f/f7/Flag_of_Pennsylvania.svg/45px-Flag_of_Pennsylvania.svg.png'},{'name':'Rhode Island','flag':'f/f3/Flag_of_Rhode_Island.svg/32px-Flag_of_Rhode_Island.svg.png'},{'name':'South Carolina','flag':'6/69/Flag_of_South_Carolina.svg/45px-Flag_of_South_Carolina.svg.png'},{'name':'South Dakota','flag':'1/1a/Flag_of_South_Dakota.svg/46px-Flag_of_South_Dakota.svg.png'},{'name':'Tennessee','flag':'9/9e/Flag_of_Tennessee.svg/46px-Flag_of_Tennessee.svg.png'},{'name':'Texas','flag':'f/f7/Flag_of_Texas.svg/45px-Flag_of_Texas.svg.png'},{'name':'Utah','flag':'f/f6/Flag_of_Utah.svg/45px-Flag_of_Utah.svg.png'},{'name':'Vermont','flag':'4/49/Flag_of_Vermont.svg/46px-Flag_of_Vermont.svg.png'},{'name':'Virginia','flag':'4/47/Flag_of_Virginia.svg/44px-Flag_of_Virginia.svg.png'},{'name':'Washington','flag':'5/54/Flag_of_Washington.svg/46px-Flag_of_Washington.svg.png'},{'name':'West Virginia','flag':'2/22/Flag_of_West_Virginia.svg/46px-Flag_of_West_Virginia.svg.png'},{'name':'Wisconsin','flag':'2/22/Flag_of_Wisconsin.svg/45px-Flag_of_Wisconsin.svg.png'},{'name':'Wyoming','flag':'b/bc/Flag_of_Wyoming.svg/43px-Flag_of_Wyoming.svg.png'}];\n});\n"
  },
  {
    "path": "src/typeahead/docs/readme.md",
    "content": "Typeahead is a AngularJS version of [Bootstrap v2's typeahead plugin](http://getbootstrap.com/2.3.2/javascript.html#typeahead).\nThis directive can be used to quickly create elegant typeaheads with any form text input.\n\nIt is very well integrated into AngularJS as it uses a subset of the\n[select directive](http://docs.angularjs.org/api/ng.directive:select) syntax, which is very flexible. Supported expressions are:\n\n* _label_ for _value_ in _sourceArray_\n* _select_ as _label_ for _value_ in _sourceArray_\n\nThe `sourceArray` expression can use a special `$viewValue` variable that corresponds to the value entered inside the input.\n\nThis directive works with promises, meaning you can retrieve matches using the `$http` service with minimal effort.\n\n### uib-typeahead settings\n\n* `ng-model`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Assignable angular expression to data-bind to.\n\n* `ng-model-options`\n  <small class=\"badge\">$</small> -\n  Options for ng-model (see [ng-model-options directive](https://docs.angularjs.org/api/ng/directive/ngModelOptions)). Currently supports the `debounce` and `getterSetter` options.\n\n* `typeahead-append-to`\n  <small class=\"badge\">$</small>\n  _(Default: `null`)_ -\n  Should the typeahead popup be appended to an element instead of the parent element?\n\n* `typeahead-append-to-body`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `false`)_ -\n  Should the typeahead popup be appended to $body instead of the parent element?\n\n* `typeahead-editable`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `true`)_ -\n  Should it restrict model values to the ones selected from the popup only?\n\n* `typeahead-focus-first`\n  <small class=\"badge\">$</small>\n  _(Default: `true`)_ -\n  Should the first match automatically be focused as you type?\n\n* `typeahead-focus-on-select`\n  _(Default: `true`)_ -\n  On selection, focus the input element the typeahead directive is associated with.\n\n* `typeahead-input-formatter`\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `undefined`)_ -\n  Format the ng-model result after selection.\n\n* `typeahead-is-open`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `angular.noop`)_ -\n  Binding to a variable that indicates if the dropdown is open.\n\n* `typeahead-loading`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `angular.noop`)_ -\n  Binding to a variable that indicates if matches are being retrieved asynchronously.\n\n* `typeahead-min-length`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `1`)_ -\n  Minimal no of characters that needs to be entered before typeahead kicks-in. Must be greater than or equal to 0.\n\n* `typeahead-no-results`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `angular.noop`)_ -\n  Binding to a variable that indicates if no matching results were found.\n\n* `typeahead-should-select($event)`\n  <small class=\"badge\">$</small>\n  _(Default: `null`)_ -\n  A callback executed when a `keyup` event that might trigger a selection occurs. Selection will only occur if this function returns true.\n\n* `typeahead-on-select($item, $model, $label, $event)`\n  <small class=\"badge\">$</small>\n  _(Default: `null`)_ -\n  A callback executed when a match is selected. $event can be undefined if selection not triggered from a user event.\n\n* `typeahead-popup-template-url`\n  _(Default: `uib/template/typeahead/typeahead-popup.html`)_ -\n  Set custom popup template.\n\n* `typeahead-select-on-blur`\n  <small class=\"badge\">$</small>\n  _(Default: `false`)_ -\n  On blur, select the currently highlighted match.\n\n* `typeahead-select-on-exact`\n  <small class=\"badge\">$</small>\n  _(Default: `false`)_ -\n  Automatically select the item when it is the only one that exactly matches the user input.\n\n* `typeahead-show-hint`\n  <small class=\"badge\">$</small>\n  _(Default: `false`)_ -\n  Show hint when the first option matches.\n\n* `typeahead-template-url`\n  _(Default: `uib/template/typeahead/typeahead-match.html`)_ -\n  Set custom item template.\n\n* `typeahead-wait-ms`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i>\n  _(Default: `0`)_ -\n  Minimal wait time after last character typed before typeahead kicks-in.\n\n* `uib-typeahead`\n  <small class=\"badge\">$</small>\n  <i class=\"glyphicon glyphicon-eye-open\"></i> -\n  Comprehension Angular expression (see [select directive](http://docs.angularjs.org/api/ng.directive:select)).\n\n**Notes**\n\nIf a custom template for the popup is used, the wrapper selector used for the match items is the `uib-typeahead-match` class.\n"
  },
  {
    "path": "src/typeahead/index-nocss.js",
    "content": "require('../debounce');\nrequire('../position/index-nocss.js');\nrequire('../../template/typeahead/typeahead-match.html.js');\nrequire('../../template/typeahead/typeahead-popup.html.js');\nrequire('./typeahead');\n\nvar MODULE_NAME = 'ui.bootstrap.module.typeahead';\n\nangular.module(MODULE_NAME, ['ui.bootstrap.typeahead', 'uib/template/typeahead/typeahead-match.html', 'uib/template/typeahead/typeahead-popup.html']);\n\nmodule.exports = MODULE_NAME;\n"
  },
  {
    "path": "src/typeahead/index.js",
    "content": "require('../position/position.css');\nrequire('./typeahead.css');\nmodule.exports = require('./index-nocss.js');\n"
  },
  {
    "path": "src/typeahead/test/typeahead-highlight-ngsanitize.spec.js",
    "content": "describe('Security concerns', function() {\n  var highlightFilter, $sanitize, logSpy;\n\n  beforeEach(module('ui.bootstrap.typeahead', 'ngSanitize'));\n\n  beforeEach(inject(function (uibTypeaheadHighlightFilter, _$sanitize_, $log) {\n    highlightFilter = uibTypeaheadHighlightFilter;\n    $sanitize = _$sanitize_;\n    logSpy = spyOn($log, 'warn');\n  }));\n\n  it('should not call the $log service when ngSanitize is present', function() {\n    highlightFilter('before <script src=\"\">match</script> after', 'match');\n    expect(logSpy).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/typeahead/test/typeahead-highlight.spec.js",
    "content": "describe('typeaheadHighlight', function () {\n\n  var highlightFilter, $log, $sce, logSpy;\n\n  beforeEach(module('ui.bootstrap.typeahead'));\n\n  beforeEach(inject(function(_$log_, _$sce_) {\n    $log = _$log_;\n    $sce = _$sce_;\n    logSpy = spyOn($log, 'warn');\n  }));\n\n  beforeEach(inject(function(uibTypeaheadHighlightFilter) {\n    highlightFilter = uibTypeaheadHighlightFilter;\n  }));\n\n  it('should higlight a match', function() {\n    expect($sce.getTrustedHtml(highlightFilter('before match after', 'match'))).toEqual('before <strong>match</strong> after');\n  });\n\n  it('should higlight a match with mixed case', function() {\n    expect($sce.getTrustedHtml(highlightFilter('before MaTch after', 'match'))).toEqual('before <strong>MaTch</strong> after');\n  });\n\n  it('should higlight all matches', function() {\n    expect($sce.getTrustedHtml(highlightFilter('before MaTch after match', 'match'))).toEqual('before <strong>MaTch</strong> after <strong>match</strong>');\n  });\n\n  it('should do nothing if no match', function() {\n    expect($sce.getTrustedHtml(highlightFilter('before match after', 'nomatch'))).toEqual('before match after');\n  });\n\n  it('should do nothing if no or empty query', function() {\n    expect($sce.getTrustedHtml(highlightFilter('before match after', ''))).toEqual('before match after');\n    expect($sce.getTrustedHtml(highlightFilter('before match after', null))).toEqual('before match after');\n    expect($sce.getTrustedHtml(highlightFilter('before match after', undefined))).toEqual('before match after');\n  });\n\n  it('issue 316 - should work correctly for regexp reserved words', function() {\n    expect($sce.getTrustedHtml(highlightFilter('before (match after', '(match'))).toEqual('before <strong>(match</strong> after');\n  });\n\n  it('issue 1777 - should work correctly with numeric values', function() {\n    expect($sce.getTrustedHtml(highlightFilter(123, '2'))).toEqual('1<strong>2</strong>3');\n  });\n\n  it('should show a warning when this component is being used unsafely', function() {\n    highlightFilter('<i>before</i> match after', 'match');\n    expect(logSpy).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/typeahead/test/typeahead-parser.spec.js",
    "content": "describe('syntax parser', function() {\n  var typeaheadParser, scope, filterFilter;\n\n  beforeEach(module('ui.bootstrap.typeahead'));\n  beforeEach(inject(function(_$rootScope_, _filterFilter_, uibTypeaheadParser) {\n    typeaheadParser = uibTypeaheadParser;\n    scope = _$rootScope_;\n    filterFilter = _filterFilter_;\n  }));\n\n  it('should parse the simplest array-based syntax', function() {\n    scope.states = ['Alabama', 'California', 'Delaware'];\n    var result = typeaheadParser.parse('state for state in states | filter:$viewValue');\n\n    var itemName = result.itemName;\n    var locals = {$viewValue:'al'};\n    expect(result.source(scope, locals)).toEqual(['Alabama', 'California']);\n\n    locals[itemName] = 'Alabama';\n    expect(result.viewMapper(scope, locals)).toEqual('Alabama');\n    expect(result.modelMapper(scope, locals)).toEqual('Alabama');\n  });\n\n  it('should parse the simplest function-based syntax', function() {\n    scope.getStates = function($viewValue) {\n      return filterFilter(['Alabama', 'California', 'Delaware'], $viewValue);\n    };\n    var result = typeaheadParser.parse('state for state in getStates($viewValue)');\n\n    var itemName = result.itemName;\n    var locals = {$viewValue:'al'};\n    expect(result.source(scope, locals)).toEqual(['Alabama', 'California']);\n\n    locals[itemName] = 'Alabama';\n    expect(result.viewMapper(scope, locals)).toEqual('Alabama');\n    expect(result.modelMapper(scope, locals)).toEqual('Alabama');\n  });\n\n  it('should allow to specify custom model mapping that is used as a label as well', function () {\n    scope.states = [\n      {code:'AL', name:'Alabama'},\n      {code:'CA', name:'California'},\n      {code:'DE', name:'Delaware'}\n    ];\n    var result = typeaheadParser.parse('state.name for state in states | filter:$viewValue | orderBy:\"name\":true');\n\n    var itemName = result.itemName;\n    expect(itemName).toEqual('state');\n    expect(result.source(scope, {$viewValue:'al'})).toEqual([\n      {code:'CA', name:'California'},\n      {code:'AL', name:'Alabama'}\n    ]);\n\n    var locals = {$viewValue:'al'};\n    locals[itemName] = {code:'AL', name:'Alabama'};\n    expect(result.viewMapper(scope, locals)).toEqual('Alabama');\n    expect(result.modelMapper(scope, locals)).toEqual('Alabama');\n  });\n\n  it('should allow to specify custom view and model mappers', function() {\n    scope.states = [\n      {code:'AL', name:'Alabama'},\n      {code:'CA', name:'California'},\n      {code:'DE', name:'Delaware'}\n    ];\n    var result = typeaheadParser.parse('state.code as state.name + \" (\"+state.code+\")\" for state in states | filter:$viewValue | orderBy:\"name\":true');\n\n    var itemName = result.itemName;\n    expect(result.source(scope, {$viewValue:'al'})).toEqual([\n      {code:'CA', name:'California'},\n      {code:'AL', name:'Alabama'}\n    ]);\n\n    var locals = {$viewValue:'al'};\n    locals[itemName] = {code:'AL', name:'Alabama'};\n    expect(result.viewMapper(scope, locals)).toEqual('Alabama (AL)');\n    expect(result.modelMapper(scope, locals)).toEqual('AL');\n  });\n});"
  },
  {
    "path": "src/typeahead/test/typeahead-popup.spec.js",
    "content": "describe('typeaheadPopup - result rendering', function() {\n  var scope, $rootScope, $compile;\n\n  beforeEach(module('ui.bootstrap.typeahead'));\n  beforeEach(module('uib/template/typeahead/typeahead-popup.html'));\n  beforeEach(module('uib/template/typeahead/typeahead-match.html'));\n  beforeEach(inject(function(_$rootScope_, _$compile_) {\n    $rootScope = _$rootScope_;\n    scope = $rootScope.$new();\n    $compile = _$compile_;\n  }));\n\n  it('should render initial results', function() {\n    scope.matches = ['foo', 'bar', 'baz'];\n    scope.active = 1;\n\n    var el = $compile('<div><uib-typeahead-popup matches=\"matches\" active=\"active\" select=\"select(activeIdx)\"></uib-typeahead-popup></div>')(scope);\n    $rootScope.$digest();\n\n    var liElems = el.find('li');\n    expect(liElems.length).toEqual(3);\n    expect(liElems.eq(0)).not.toHaveClass('active');\n    expect(liElems.eq(1)).toHaveClass('active');\n    expect(liElems.eq(2)).not.toHaveClass('active');\n  });\n\n  it('should change active item on mouseenter', function() {\n    scope.matches = ['foo', 'bar', 'baz'];\n    scope.active = 1;\n\n    var el = $compile('<div><uib-typeahead-popup matches=\"matches\" active=\"active\" select=\"select(activeIdx)\"></uib-typeahead-popup></div>')(scope);\n    $rootScope.$digest();\n\n    var liElems = el.find('li');\n    expect(liElems.eq(1)).toHaveClass('active');\n    expect(liElems.eq(2)).not.toHaveClass('active');\n\n    liElems.eq(2).trigger('mouseenter');\n\n    expect(liElems.eq(1)).not.toHaveClass('active');\n    expect(liElems.eq(2)).toHaveClass('active');\n  });\n\n  it('should select an item on mouse click', function() {\n    scope.matches = ['foo', 'bar', 'baz'];\n    scope.active = 1;\n    $rootScope.select = angular.noop;\n    spyOn($rootScope, 'select');\n\n    var el = $compile('<div><uib-typeahead-popup matches=\"matches\" active=\"active\" select=\"select(activeIdx)\"></uib-typeahead-popup></div>')(scope);\n    $rootScope.$digest();\n\n    var liElems = el.find('li');\n    liElems.eq(2).find('a').trigger('click');\n    expect($rootScope.select).toHaveBeenCalledWith(2);\n  });\n});\n"
  },
  {
    "path": "src/typeahead/test/typeahead.spec.js",
    "content": "describe('typeahead tests', function() {\n  var $scope, $compile, $document, $templateCache, $timeout, $window;\n  var changeInputValueTo;\n\n  beforeEach(module('ui.bootstrap.typeahead'));\n  beforeEach(module('ngSanitize'));\n  beforeEach(module('uib/template/typeahead/typeahead-popup.html'));\n  beforeEach(module('uib/template/typeahead/typeahead-match.html'));\n  beforeEach(module(function($compileProvider) {\n    $compileProvider.directive('formatter', function() {\n      return {\n        require: 'ngModel',\n        link: function (scope, elm, attrs, ngModelCtrl) {\n          ngModelCtrl.$formatters.unshift(function(viewVal) {\n            return 'formatted' + viewVal;\n          });\n        }\n      };\n    });\n    $compileProvider.directive('childDirective', function() {\n      return {\n          restrict: 'A',\n          require: '^parentDirective',\n          link: function(scope, element, attrs, ctrl) {}\n      };\n    });\n  }));\n  beforeEach(inject(function(_$rootScope_, _$compile_, _$document_, _$templateCache_, _$timeout_, _$window_, $sniffer) {\n    $scope = _$rootScope_;\n    $scope.source = ['foo', 'bar', 'baz'];\n    $scope.states = [\n      {code: 'AL', name: 'Alaska'},\n      {code: 'CL', name: 'California'}\n    ];\n    $compile = _$compile_;\n    $document = _$document_;\n    $templateCache = _$templateCache_;\n    $timeout = _$timeout_;\n    $window = _$window_;\n    changeInputValueTo = function(element, value) {\n      var inputEl = findInput(element);\n      inputEl.val(value);\n      inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n      $scope.$digest();\n    };\n  }));\n\n  //utility functions\n  var prepareInputEl = function(inputTpl) {\n    var el = $compile(angular.element(inputTpl))($scope);\n    $scope.$digest();\n    return el;\n  };\n\n  var findInput = function(element) {\n    return element.find('input');\n  };\n\n  var findDropDown = function(element) {\n    return element.find('ul.dropdown-menu');\n  };\n\n  var findMatches = function(element) {\n    return findDropDown(element).find('li');\n  };\n\n  var triggerKeyDown = function(element, keyCode, options) {\n    options = options || {};\n    var inputEl = findInput(element);\n    var e = $.Event('keydown');\n    e.which = keyCode;\n    if (options.shiftKey) {\n      e.shiftKey = true;\n    }\n    inputEl.trigger(e);\n  };\n\n  //custom matchers\n  beforeEach(function () {\n    jasmine.addMatchers({\n      toBeClosed: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, expected) {\n            var typeaheadEl = findDropDown(actual);\n\n            var result = {\n              pass: util.equals(typeaheadEl.hasClass('ng-hide'), true, customEqualityTesters)\n            };\n\n            if (result.pass) {\n              result.message = 'Expected \"' + angular.mock.dump(typeaheadEl) + '\" not to be closed.';\n            } else {\n              result.message = 'Expected \"' + angular.mock.dump(typeaheadEl) + '\" to be closed.';\n            }\n\n            return result;\n          }\n        };\n      },\n      toBeOpenWithActive: function(util, customEqualityTesters) {\n        return {\n          compare: function(actual, noOfMatches, activeIdx) {\n            var typeaheadEl = findDropDown(actual);\n            var liEls = findMatches(actual);\n\n            var result = {\n              pass: util.equals(typeaheadEl.length, 1, customEqualityTesters) &&\n                    util.equals(typeaheadEl.hasClass('ng-hide'), false, customEqualityTesters) &&\n                    util.equals(liEls.length, noOfMatches, customEqualityTesters) &&\n                    activeIdx === -1 ? !$(liEls).hasClass('active') : $(liEls[activeIdx]).hasClass('active')\n            };\n\n            if (result.pass) {\n              result.message = 'Expected \"' + actual + '\" not to be opened.';\n            } else {\n              result.message = 'Expected \"' + actual + '\" to be opened.';\n            }\n\n            return result;\n          }\n        };\n      }\n    });\n  });\n\n  afterEach(function() {\n    findDropDown($document.find('body')).remove();\n  });\n\n  //coarse grained, \"integration\" tests\n  describe('initial state and model changes', function() {\n    it('should be closed by default', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source\"></div>');\n      expect(element).toBeClosed();\n    });\n\n    it('should correctly render initial state if the \"as\" keyword is used', function() {\n      $scope.result = $scope.states[0];\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"state as state.name for state in states\"></div>');\n      var inputEl = findInput(element);\n\n      expect(inputEl.val()).toEqual('Alaska');\n    });\n\n    it('should default to bound model for initial rendering if there is not enough info to render label', function() {\n      $scope.result = $scope.states[0].code;\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"state.code as state.name + state.code for state in states\"></div>');\n      var inputEl = findInput(element);\n\n      expect(inputEl.val()).toEqual('AL');\n    });\n\n    it('should not get open on model change', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source\"></div>');\n      $scope.$apply(function () {\n        $scope.result = 'foo';\n      });\n      expect(element).toBeClosed();\n    });\n  });\n\n  describe('basic functionality', function() {\n    it('should open and close typeahead based on matches', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n      var ownsId = inputEl.attr('aria-owns');\n\n      expect(inputEl.attr('aria-expanded')).toBe('false');\n      expect(inputEl.attr('aria-activedescendant')).toBeUndefined();\n\n      changeInputValueTo(element, 'ba');\n      expect(element).toBeOpenWithActive(2, 0);\n      expect(findDropDown(element).attr('id')).toBe(ownsId);\n      expect(inputEl.attr('aria-expanded')).toBe('true');\n      var activeOptionId = ownsId + '-option-0';\n      expect(inputEl.attr('aria-activedescendant')).toBe(activeOptionId);\n      expect(findDropDown(element).find('li.active').attr('id')).toBe(activeOptionId);\n\n      changeInputValueTo(element, '');\n      expect(element).toBeClosed();\n      expect(inputEl.attr('aria-expanded')).toBe('false');\n      expect(inputEl.attr('aria-activedescendant')).toBeUndefined();\n    });\n\n    it('should allow expressions over multiple lines', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source \\n' +\n        '| filter:$viewValue\"></div>');\n      changeInputValueTo(element, 'ba');\n      expect(element).toBeOpenWithActive(2, 0);\n\n      changeInputValueTo(element, '');\n      expect(element).toBeClosed();\n    });\n\n    it('should not open typeahead if input value smaller than a defined threshold', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-min-length=\"2\"></div>');\n      changeInputValueTo(element, 'b');\n      expect(element).toBeClosed();\n    });\n\n\n    it('should support changing min-length', function() {\n        $scope.typeAheadMinLength = 2;\n        var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-min-length=\"typeAheadMinLength\"></div>');\n\n        changeInputValueTo(element, 'b');\n\n        expect(element).toBeClosed();\n\n        $scope.typeAheadMinLength = 0;\n        $scope.$digest();\n        changeInputValueTo(element, '');\n\n        expect(element).toBeOpenWithActive(3, 0);\n\n        $scope.typeAheadMinLength = 2;\n        $scope.$digest();\n        changeInputValueTo(element, 'b');\n\n        expect(element).toBeClosed();\n    });\n\n    it('should support custom model selecting function', function() {\n      $scope.updaterFn = function(selectedItem) {\n        return 'prefix' + selectedItem;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"updaterFn(item) as item for item in source | filter:$viewValue\"></div>');\n      changeInputValueTo(element, 'f');\n      triggerKeyDown(element, 13);\n      expect($scope.result).toEqual('prefixfoo');\n    });\n\n    it('should support custom label rendering function', function() {\n      $scope.formatterFn = function(sourceItem) {\n        return 'prefix' + sourceItem;\n      };\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item as formatterFn(item) for item in source | filter:$viewValue\"></div>');\n      changeInputValueTo(element, 'fo');\n      var matchHighlight = findMatches(element).find('a').html();\n      expect(matchHighlight).toEqual('prefix<strong>fo</strong>o');\n    });\n\n    it('should by default bind view value to model even if not part of matches', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual('not in matches');\n    });\n\n    it('should support the editable property to limit model bindings to matches only', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\"></div>');\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n    });\n\n    it('should set validation errors for non-editable inputs', function() {\n      var element = prepareInputEl(\n        '<div><form name=\"form\">' +\n          '<input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\">' +\n        '</form></div>');\n\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n      expect($scope.form.input.$error.editable).toBeTruthy();\n\n      changeInputValueTo(element, 'foo');\n      triggerKeyDown(element, 13);\n      expect($scope.result).toEqual('foo');\n      expect($scope.form.input.$error.editable).toBeFalsy();\n    });\n\n    it('should not set editable validation error for empty input', function() {\n      var element = prepareInputEl(\n        '<div><form name=\"form\">' +\n          '<input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\">' +\n        '</form></div>');\n\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n      expect($scope.form.input.$error.editable).toBeTruthy();\n      changeInputValueTo(element, '');\n      expect($scope.result).toEqual(null);\n      expect($scope.form.input.$error.editable).toBeFalsy();\n    });\n\n    it('should clear view value after blur for typeahead-editable=\"false\"', function () {\n        var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'not in matches');\n        expect($scope.result).toEqual(undefined);\n        expect(inputEl.val()).toEqual('not in matches');\n        inputEl.blur(); // input loses focus\n        expect($scope.result).toEqual(undefined);\n        expect(inputEl.val()).toEqual('');\n    });\n\n    it('should clear errors after blur for typeahead-editable=\"false\"', function () {\n      var element = prepareInputEl(\n        '<div><form name=\"form\">' +\n          '<input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\">' +\n        '</form></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n      expect(inputEl.val()).toEqual('not in matches');\n      inputEl.blur();\n\n      expect($scope.form.input.$error.editable).toBeFalsy();\n      expect($scope.form.input.$error.parse).toBeFalsy();\n    });\n\n    // fix for #6032\n    it('should clear errors and refresh scope after blur for typeahead-editable=\"false\"', function () {\n      var element = prepareInputEl(\n        '<div><form name=\"form\" ng-class=\"{invalid : form.input.$invalid}\">' +\n          '<input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\">' +\n        '</form></div>');\n      var inputEl = findInput(element);\n\n      // first try\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n      expect(inputEl.val()).toEqual('not in matches');\n      expect(element.find('form')).toHaveClass('invalid');\n      inputEl.blur();\n\n      expect(inputEl.val()).toEqual('');  // <-- input is reset\n      expect($scope.form.input.$error.editable).toBeFalsy();\n      expect($scope.form.input.$error.parse).toBeFalsy();\n      expect(element.find('form')).not.toHaveClass('invalid');  // <-- form has no error (it always works for some reason)\n\n      // second try\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n      expect(inputEl.val()).toEqual('not in matches');\n      expect(element.find('form')).toHaveClass('invalid');\n      inputEl.blur();\n\n      expect(inputEl.val()).toEqual('');  // <-- input is reset\n      expect($scope.form.input.$error.editable).toBeFalsy();\n      expect($scope.form.input.$error.parse).toBeFalsy();\n      expect(element.find('form')).not.toHaveClass('invalid');  // <-- form has no error (it didn't work prior to #6032 fix)\n    });\n\n    it('should go through other validators after blur for typeahead-editable=\"false\"', function () {\n        var element = prepareInputEl(\n        '<div><form name=\"form\">' +\n          '<input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\" required>' +\n        '</form></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'not in matches');\n        expect($scope.result).toEqual(undefined);\n        expect(inputEl.val()).toEqual('not in matches');\n        inputEl.blur(); // input loses focus\n        expect($scope.result).toEqual(undefined);\n        expect($scope.form.input.$error.required).toBeTruthy();\n    });\n\n    it('should clear view value when no value selected for typeahead-editable=\"false\" typeahead-select-on-blur=\"false\"', function () {\n        var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\" typeahead-select-on-blur=\"false\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'b');\n        expect($scope.result).toEqual(undefined);\n        expect(inputEl.val()).toEqual('b');\n        inputEl.blur(); // input loses focus\n        expect($scope.result).toEqual(undefined);\n        expect(inputEl.val()).toEqual('');\n    });\n\n    it('should not clear view value when there is match but no value selected for typeahead-editable=\"false\" typeahead-select-on-blur=\"true\"', function () {\n        var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\" typeahead-select-on-blur=\"true\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'b');\n        expect($scope.result).toEqual(undefined);\n        expect(inputEl.val()).toEqual('b');\n        inputEl.blur(); // input loses focus\n        expect($scope.result).toEqual('bar');\n        expect(inputEl.val()).toEqual('bar');\n    });\n\n    it('should support changing the editable property to limit model bindings to matches only', function() {\n      $scope.isEditable = true;\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"isEditable\"></div>');\n      $scope.isEditable = false;\n      $scope.$digest();\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual(undefined);\n    });\n\n    it('should support changing the editable property to bind view value to model even if not part of matches', function() {\n      $scope.isEditable = false;\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"isEditable\"></div>');\n      $scope.isEditable = true;\n      $scope.$digest();\n      changeInputValueTo(element, 'not in matches');\n      expect($scope.result).toEqual('not in matches');\n    });\n\n    it('should bind loading indicator expression', inject(function($timeout) {\n      $scope.isLoading = false;\n      $scope.loadMatches = function(viewValue) {\n        return $timeout(function() {\n          return [];\n        }, 1000);\n      };\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in loadMatches()\" typeahead-loading=\"isLoading\"></div>');\n      changeInputValueTo(element, 'foo');\n\n      expect($scope.isLoading).toBeTruthy();\n      $timeout.flush();\n      expect($scope.isLoading).toBeFalsy();\n    }));\n\n    it('should support timeout before trying to match $viewValue', inject(function($timeout) {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-wait-ms=\"200\"></div>');\n      changeInputValueTo(element, 'foo');\n      expect(element).toBeClosed();\n\n      $timeout.flush();\n      expect(element).toBeOpenWithActive(1, 0);\n    }));\n\n    it('should cancel old timeouts when something is typed within waitTime', inject(function($timeout) {\n      var values = [];\n      $scope.loadMatches = function(viewValue) {\n        values.push(viewValue);\n        return $scope.source;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in loadMatches($viewValue) | filter:$viewValue\" typeahead-wait-ms=\"200\"></div>');\n      changeInputValueTo(element, 'first');\n      changeInputValueTo(element, 'second');\n\n      $timeout.flush();\n\n      expect(values).not.toContain('first');\n    }));\n\n    it('should allow timeouts when something is typed after waitTime has passed', inject(function($timeout) {\n      var values = [];\n\n      $scope.loadMatches = function(viewValue) {\n        values.push(viewValue);\n        return $scope.source;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in loadMatches($viewValue) | filter:$viewValue\" typeahead-wait-ms=\"200\"></div>');\n\n      changeInputValueTo(element, 'first');\n      $timeout.flush();\n\n      expect(values).toContain('first');\n\n      changeInputValueTo(element, 'second');\n      $timeout.flush();\n\n      expect(values).toContain('second');\n    }));\n\n    it('should support custom popup templates', function() {\n      $templateCache.put('custom.html', '<div class=\"custom\">foo</div>');\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-popup-template-url=\"custom.html\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\"></div>');\n\n      changeInputValueTo(element, 'Al');\n\n      expect(element.find('.custom').text()).toBe('foo');\n    });\n\n    it('should support custom templates for matched items', function() {\n      $templateCache.put('custom.html', '<p>{{ index }} {{ match.label }}</p>');\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-template-url=\"custom.html\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\"></div>');\n\n      changeInputValueTo(element, 'Al');\n\n      expect(findMatches(element).eq(0).find('p').text()).toEqual('0 Alaska');\n    });\n\n    it('should support directives which require controllers in custom templates for matched items', function() {\n      $templateCache.put('custom.html', '<p child-directive>{{ index }} {{ match.label }}</p>');\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-template-url=\"custom.html\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\"></div>');\n\n      element.data('$parentDirectiveController', {});\n\n      changeInputValueTo(element, 'Al');\n\n      expect(findMatches(element).eq(0).find('p').text()).toEqual('0 Alaska');\n    });\n\n    it('should throw error on invalid expression', function() {\n      var prepareInvalidDir = function() {\n        prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"an invalid expression\"></div>');\n      };\n      expect(prepareInvalidDir).toThrow();\n    });\n\n    it('should remove the id attribute from the original DOM element', function() {\n      var element = prepareInputEl('<div><input id=\"typeahead-element\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-show-hint=\"true\"></div>');\n      var inputEl = findInput(element);\n\n      expect(inputEl.size()).toBe(2);\n      expect(inputEl.eq(0).attr('id')).toBe(undefined);\n      expect(inputEl.eq(1).attr('id')).toBe('typeahead-element');\n    });\n  });\n\n  describe('shouldSelect', function() {\n    it('should select a match when function returns true', function() {\n      $scope.shouldSelectFn = function() {\n        return true;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-should-select=\"shouldSelectFn($event)\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      triggerKeyDown(element, 13);\n\n      expect($scope.result).toEqual('bar');\n      expect(inputEl.val()).toEqual('bar');\n      expect(element).toBeClosed();\n    });\n    it('should not select a match when function returns false', function() {\n      $scope.shouldSelectFn = function() {\n        return false;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-should-select=\"shouldSelectFn($event)\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      triggerKeyDown(element, 13);\n\n      // no change\n      expect($scope.result).toEqual('b');\n      expect(inputEl.val()).toEqual('b');\n    });\n    it('should pass key event into select trigger function', function() {\n      $scope.shouldSelectFn = jasmine.createSpy('shouldSelectFn');//.and.returnValue(true);\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-should-select=\"shouldSelectFn($event)\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      triggerKeyDown(element, 13);\n\n      expect($scope.shouldSelectFn.calls.count()).toEqual(1);\n      expect($scope.shouldSelectFn.calls.argsFor(0)[0].which).toEqual(13);\n    });\n  });\n\n  describe('selecting a match', function() {\n    it('should select a match on enter', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      triggerKeyDown(element, 13);\n\n      expect($scope.result).toEqual('bar');\n      expect(inputEl.val()).toEqual('bar');\n      expect(element).toBeClosed();\n    });\n\n    it('should select a match on tab', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      triggerKeyDown(element, 9);\n\n      expect($scope.result).toEqual('bar');\n      expect(inputEl.val()).toEqual('bar');\n      expect(element).toBeClosed();\n    });\n\n    it('should not select any match on blur without \\'select-on-blur=true\\' option', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      inputEl.blur(); // input loses focus\n\n      // no change\n      expect($scope.result).toEqual('b');\n      expect(inputEl.val()).toEqual('b');\n    });\n\n    it('should select a match on blur with \\'select-on-blur=true\\' option', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-select-on-blur=\"true\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      inputEl.blur(); // input loses focus\n\n      // first element should be selected\n      expect($scope.result).toEqual('bar');\n      expect(inputEl.val()).toEqual('bar');\n    });\n\n    it('should select match on click', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      var match = $(findMatches(element)[1]).find('a')[0];\n\n      $(match).click();\n      $scope.$digest();\n\n      expect($scope.result).toEqual('baz');\n      expect(inputEl.val()).toEqual('baz');\n      expect(element).toBeClosed();\n    });\n\n    it('should invoke select callback on select', function() {\n      $scope.onSelect = function($item, $model, $label, $event) {\n        $scope.$item = $item;\n        $scope.$model = $model;\n        $scope.$label = $label;\n        $scope.$event = $event;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-on-select=\"onSelect($item, $model, $label, $event)\" uib-typeahead=\"state.code as state.name for state in states | filter:$viewValue\"></div>');\n\n      changeInputValueTo(element, 'Alas');\n      triggerKeyDown(element, 13);\n\n      expect($scope.result).toEqual('AL');\n      expect($scope.$item).toEqual($scope.states[0]);\n      expect($scope.$model).toEqual('AL');\n      expect($scope.$label).toEqual('Alaska');\n      expect($scope.$event.type).toEqual(\"keydown\");\n    });\n\n    it('should correctly update inputs value on mapping where label is not derived from the model', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"state.code as state.name for state in states | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'Alas');\n      triggerKeyDown(element, 13);\n\n      expect($scope.result).toEqual('AL');\n      expect(inputEl.val()).toEqual('AL');\n    });\n\n    it('should bind no results indicator as true when no matches returned', inject(function($timeout) {\n      $scope.isNoResults = false;\n      $scope.loadMatches = function(viewValue) {\n        return $timeout(function() {\n          return [];\n        }, 1000);\n      };\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in loadMatches()\" typeahead-no-results=\"isNoResults\"></div>');\n      changeInputValueTo(element, 'foo');\n\n      expect($scope.isNoResults).toBeFalsy();\n      $timeout.flush();\n      expect($scope.isNoResults).toBeTruthy();\n    }));\n\n    it('should bind no results indicator as false when matches are returned', inject(function($timeout) {\n      $scope.isNoResults = false;\n      $scope.loadMatches = function(viewValue) {\n        return $timeout(function() {\n          return [viewValue];\n        }, 1000);\n      };\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in loadMatches()\" typeahead-no-results=\"isNoResults\"></div>');\n      changeInputValueTo(element, 'foo');\n\n      expect($scope.isNoResults).toBeFalsy();\n      $timeout.flush();\n      expect($scope.isNoResults).toBeFalsy();\n    }));\n\n    it('should not focus the input if `typeahead-focus-on-select` is false', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-focus-on-select=\"false\"></div>');\n      $document.find('body').append(element);\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'b');\n      var match = $(findMatches(element)[1]).find('a')[0];\n\n      $(match).click();\n      $scope.$digest();\n      $timeout.flush();\n\n      expect(document.activeElement).not.toBe(inputEl[0]);\n      expect($scope.result).toEqual('baz');\n    });\n  });\n\n  describe('select on exact match', function() {\n    it('should select on an exact match when set', function() {\n      $scope.onSelect = jasmine.createSpy('onSelect');\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-editable=\"false\" typeahead-on-select=\"onSelect()\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-select-on-exact=\"true\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'bar');\n\n      expect($scope.result).toEqual('bar');\n      expect(inputEl.val()).toEqual('bar');\n      expect(element).toBeClosed();\n      expect($scope.onSelect).toHaveBeenCalled();\n    });\n\n    it('should not select on an exact match by default', function() {\n      $scope.onSelect = jasmine.createSpy('onSelect');\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-editable=\"false\" typeahead-on-select=\"onSelect()\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'bar');\n\n      expect($scope.result).toBeUndefined();\n      expect(inputEl.val()).toEqual('bar');\n      expect($scope.onSelect.calls.any()).toBe(false);\n    });\n\n    it('should not be case sensitive when select on an exact match', function() {\n      $scope.onSelect = jasmine.createSpy('onSelect');\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-editable=\"false\" typeahead-on-select=\"onSelect()\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-select-on-exact=\"true\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'BaR');\n\n      expect($scope.result).toEqual('bar');\n      expect(inputEl.val()).toEqual('bar');\n      expect(element).toBeClosed();\n      expect($scope.onSelect).toHaveBeenCalled();\n    });\n\n    it('should not auto select when not a match with one potential result left', function() {\n      $scope.onSelect = jasmine.createSpy('onSelect');\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-editable=\"false\" typeahead-on-select=\"onSelect()\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-select-on-exact=\"true\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'fo');\n\n      expect($scope.result).toBeUndefined();\n      expect(inputEl.val()).toEqual('fo');\n      expect($scope.onSelect.calls.any()).toBe(false);\n    });\n  });\n\n  describe('is-open indicator', function () {\n      var element;\n\n      beforeEach(function () {\n          element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-is-open=\"isOpen\"></div>');\n      });\n\n      it('should bind is-open indicator as true when matches are returned', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n      });\n\n      it('should bind is-open indicator as false when no matches returned', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n          changeInputValueTo(element, 'not match');\n          expect($scope.isOpen).toBeFalsy();\n      });\n\n      it('should bind is-open indicator as false when a match is clicked', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n          var match = findMatches(element).find('a').eq(0);\n\n          match.click();\n          $scope.$digest();\n          expect($scope.isOpen).toBeFalsy();\n      });\n      it('should bind is-open indicator as false when click outside', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n          $document.find('body').click();\n          $scope.$digest();\n          expect($scope.isOpen).toBeFalsy();\n      });\n\n      it('should bind is-open indicator as false on enter', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n          triggerKeyDown(element, 13);\n          expect($scope.isOpen).toBeFalsy();\n      });\n\n      it('should bind is-open indicator as false on tab', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n          triggerKeyDown(element, 9);\n          expect($scope.isOpen).toBeFalsy();\n      });\n\n      it('should bind is-open indicator as false on escape key', function () {\n          expect($scope.isOpen).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isOpen).toBeTruthy();\n          triggerKeyDown(element, 27);\n          expect($scope.isOpen).toBeFalsy();\n      });\n\n      it('should bind is-open indicator as false input value smaller than a defined threshold', function () {\n          var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-is-open=\"isToggled\" typeahead-min-length=\"2\"></div>');\n          expect($scope.isToggled).toBeFalsy();\n          changeInputValueTo(element, 'b');\n          expect($scope.isToggled).toBeFalsy();\n      });\n  });\n\n  describe('pop-up interaction', function() {\n    var element;\n\n    beforeEach(function() {\n      element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n    });\n\n    it('should activate prev/next matches on up/down keys', function() {\n      changeInputValueTo(element, 'b');\n      var parentNode = element.find('ul').eq(0)[0];\n      var liIndex;\n\n      liIndex = 0;\n      expect(element).toBeOpenWithActive(2, liIndex);\n      expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop);\n\n      // Down arrow key\n      triggerKeyDown(element, 40);\n      liIndex = 1;\n      expect(element).toBeOpenWithActive(2, liIndex);\n      expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop);\n\n      // Down arrow key goes back to first element\n      triggerKeyDown(element, 40);\n      liIndex = 0;\n      expect(element).toBeOpenWithActive(2, liIndex);\n      expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop);\n\n      // Up arrow key goes back to last element\n      triggerKeyDown(element, 38);\n      liIndex = 1;\n      expect(element).toBeOpenWithActive(2, liIndex);\n      expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop);\n\n      // Up arrow key goes back to first element\n      triggerKeyDown(element, 38);\n      liIndex = 0;\n      expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop);\n      expect(element).toBeOpenWithActive(2, liIndex);\n    });\n\n    it('should close popup on escape key', function() {\n      changeInputValueTo(element, 'b');\n      expect(element).toBeOpenWithActive(2, 0);\n\n      // Escape key\n      triggerKeyDown(element, 27);\n      expect(element).toBeClosed();\n    });\n\n    it('should highlight match on mouseenter', function() {\n      changeInputValueTo(element, 'b');\n      expect(element).toBeOpenWithActive(2, 0);\n\n      findMatches(element).eq(1).trigger('mouseenter');\n      expect(element).toBeOpenWithActive(2, 1);\n    });\n  });\n\n  describe('promises', function() {\n    var element, deferred;\n\n    beforeEach(inject(function($q) {\n      deferred = $q.defer();\n      $scope.source = function() {\n        return deferred.promise;\n      };\n      element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source()\"></div>');\n    }));\n\n    it('should display matches from promise', function() {\n      changeInputValueTo(element, 'c');\n      expect(element).toBeClosed();\n\n      deferred.resolve(['good', 'stuff']);\n      $scope.$digest();\n      expect(element).toBeOpenWithActive(2, 0);\n    });\n\n    it('should not display anything when promise is rejected', function() {\n      changeInputValueTo(element, 'c');\n      expect(element).toBeClosed();\n\n      deferred.reject('fail');\n      $scope.$digest();\n      expect(element).toBeClosed();\n    });\n\n    it('PR #3178, resolves #2999 - should not return property \"length\" of undefined for undefined matches', function() {\n      changeInputValueTo(element, 'c');\n      expect(element).toBeClosed();\n\n      deferred.resolve();\n      $scope.$digest();\n      expect(element).toBeClosed();\n    });\n  });\n\n  describe('non-regressions tests', function() {\n\n    it('issue 231 - closes matches popup on click outside typeahead', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n\n      changeInputValueTo(element, 'b');\n\n      $document.find('body').click();\n      $scope.$digest();\n\n      expect(element).toBeClosed();\n    });\n\n    it('issue 591 - initial formatting for un-selected match and complex label expression', function() {\n      var inputEl = findInput(prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"state as state.name + \\' \\' + state.code for state in states | filter:$viewValue\"></div>'));\n      expect(inputEl.val()).toEqual('');\n    });\n\n    it('issue 786 - name of internal model should not conflict with scope model name', function() {\n      $scope.state = $scope.states[0];\n      var element = prepareInputEl('<div><input ng-model=\"state\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      expect(inputEl.val()).toEqual('Alaska');\n    });\n\n    it('issue 863 - it should work correctly with input type=\"email\"', function() {\n      $scope.emails = ['foo@host.com', 'bar@host.com'];\n      var element = prepareInputEl('<div><input type=\"email\" ng-model=\"email\" uib-typeahead=\"email for email in emails | filter:$viewValue\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'bar');\n      expect(element).toBeOpenWithActive(1, 0);\n\n      triggerKeyDown(element, 13);\n\n      expect($scope.email).toEqual('bar@host.com');\n      expect(inputEl.val()).toEqual('bar@host.com');\n    });\n\n    it('issue 964 - should not show popup with matches if an element is not focused', function() {\n      $scope.items = function(viewValue) {\n        return $timeout(function() {\n          return [viewValue];\n        });\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in items($viewValue)\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'match');\n      $scope.$digest();\n\n      inputEl.blur();\n      $timeout.flush();\n\n      expect(element).toBeClosed();\n    });\n\n    it('should properly update loading callback if an element is not focused', function() {\n      $scope.items = function(viewValue) {\n        return $timeout(function(){\n          return [viewValue];\n        });\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-loading=\"isLoading\" uib-typeahead=\"item for item in items($viewValue)\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'match');\n      $scope.$digest();\n\n      inputEl.blur();\n      $timeout.flush();\n\n      expect($scope.isLoading).toBeFalsy();\n    });\n\n    it('issue 1140 - should properly update loading callback when deleting characters', function() {\n      $scope.items = function(viewValue) {\n        return $timeout(function() {\n          return [viewValue];\n        });\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-min-length=\"2\" typeahead-loading=\"isLoading\" uib-typeahead=\"item for item in items($viewValue)\"></div>');\n\n      changeInputValueTo(element, 'match');\n      $scope.$digest();\n\n      expect($scope.isLoading).toBeTruthy();\n\n      changeInputValueTo(element, 'm');\n      $timeout.flush();\n      $scope.$digest();\n\n      expect($scope.isLoading).toBeFalsy();\n    });\n\n    it('should cancel old timeout when deleting characters', inject(function($timeout) {\n      var values = [];\n      $scope.loadMatches = function(viewValue) {\n        values.push(viewValue);\n        return $scope.source;\n      };\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in loadMatches($viewValue) | filter:$viewValue\" typeahead-min-length=\"2\" typeahead-wait-ms=\"200\"></div>');\n      changeInputValueTo(element, 'match');\n      changeInputValueTo(element, 'm');\n\n      $timeout.flush();\n\n      expect(values).not.toContain('match');\n    }));\n\n    describe('', function() {\n      // Dummy describe to be able to create an after hook for this tests\n      var element;\n\n      it('does not close matches popup on click in input', function() {\n        element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n        var inputEl = findInput(element);\n\n        // Note that this bug can only be found when element is in the document\n        $document.find('body').append(element);\n\n        changeInputValueTo(element, 'b');\n\n        inputEl.click();\n        $scope.$digest();\n\n        expect(element).toBeOpenWithActive(2, 0);\n      });\n\n      it('issue #1773 - should not trigger an error when used with ng-focus', function() {\n        element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" ng-focus=\"foo()\"></div>');\n        var inputEl = findInput(element);\n\n        // Note that this bug can only be found when element is in the document\n        $document.find('body').append(element);\n\n        changeInputValueTo(element, 'b');\n        var match = $(findMatches(element)[1]).find('a')[0];\n\n        $(match).click();\n        $scope.$digest();\n      });\n\n      afterEach(function() {\n        element.remove();\n      });\n    });\n\n    it('issue #1238 - allow names like \"query\" to be used inside \"in\" expressions ', function() {\n      $scope.query = function() {\n        return ['foo', 'bar'];\n      };\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in query($viewValue)\"></div>');\n      changeInputValueTo(element, 'bar');\n\n      expect(element).toBeOpenWithActive(2, 0);\n    });\n\n    it('issue #3318 - should set model validity to true when set manually', function() {\n      var element = prepareInputEl(\n        '<div><form name=\"form\">' +\n          '<input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-editable=\"false\">' +\n        '</form></div>');\n\n      changeInputValueTo(element, 'not in matches');\n      $scope.$apply(function() {\n        $scope.result = 'manually set';\n      });\n\n      expect($scope.result).toEqual('manually set');\n      expect($scope.form.input.$valid).toBeTruthy();\n    });\n\n    it('issue #3166 - should set \\'parse\\' key as valid when selecting a perfect match and not editable', function() {\n      var element = prepareInputEl('<div ng-form=\"test\"><input name=\"typeahead\" ng-model=\"result\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\" typeahead-editable=\"false\"></div>');\n      var inputEl = findInput(element);\n\n      changeInputValueTo(element, 'Alaska');\n      triggerKeyDown(element, 13);\n\n      expect($scope.test.typeahead.$error.parse).toBeUndefined();\n    });\n  });\n\n  describe('ng-model-options', function() {\n    it('should support getterSetter', function() {\n      function resultSetter(state) {\n        return state;\n      }\n      $scope.result = resultSetter;\n      var element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{getterSetter: true}\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\" typeahead-editable=\"false\"></div>');\n\n      changeInputValueTo(element, 'Alaska');\n      triggerKeyDown(element, 13);\n\n      expect($scope.result).toBe(resultSetter);\n    });\n\n    describe('debounce as a number', function() {\n      it('should work with selecting via keyboard', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: 400}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n        triggerKeyDown(element, 13);\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(400);\n\n        expect($scope.result).toBe('Alaska');\n      });\n\n      it('should work with select on exact', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: 400}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\" typeahead-select-on-exact=\"true\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(400);\n\n        expect($scope.result).toBe('Alaska');\n      });\n\n      it('should work with selecting a match via click', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: 400}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n        var match = $(findMatches(element)[0]).find('a')[0];\n\n        $(match).click();\n        $scope.$digest();\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(400);\n\n        expect($scope.result).toBe('Alaska');\n      });\n    });\n\n    describe('debounce as an object', function() {\n      it('should work with selecting via keyboard', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: {default: 400, blur: 500}}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n        triggerKeyDown(element, 13);\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(400);\n\n        expect($scope.result).toBe('Alaska');\n      });\n\n      it('should work with select on exact', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: {default: 400, blur: 500}}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\" typeahead-select-on-exact=\"true\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(400);\n\n        expect($scope.result).toBe('Alaska');\n      });\n\n      it('should work with selecting a match via click', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: {default: 400, blur: 500}}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n        var match = $(findMatches(element)[0]).find('a')[0];\n\n        $(match).click();\n        $scope.$digest();\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(400);\n\n        expect($scope.result).toBe('Alaska');\n      });\n\n      it('should work when blurring and select on blur', function() {\n        element = prepareInputEl('<div><input name=\"typeahead\" ng-model=\"result\" ng-model-options=\"{debounce: {default: 400, blur: 500}}\" uib-typeahead=\"state as state.name for state in states | filter:$viewvalue\" typeahead-select-on-blur=\"true\"></div>');\n        var inputEl = findInput(element);\n\n        changeInputValueTo(element, 'Alaska');\n        element.blur();\n        $scope.$digest();\n\n        expect($scope.result).not.toBe('Alaska');\n\n        $timeout.flush(500);\n\n        expect($scope.result).toBe('Alaska');\n      });\n    });\n  });\n\n  describe('input formatting', function() {\n    it('should co-operate with existing formatters', function() {\n      $scope.result = $scope.states[0];\n\n      var element = prepareInputEl('<div><input ng-model=\"result.name\" formatter uib-typeahead=\"state.name for state in states | filter:$viewValue\"></div>'),\n      inputEl = findInput(element);\n\n      expect(inputEl.val()).toEqual('formatted' + $scope.result.name);\n    });\n\n    it('should support a custom input formatting function', function() {\n      $scope.result = $scope.states[0];\n      $scope.formatInput = function($model) {\n        return $model.code;\n      };\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" typeahead-input-formatter=\"formatInput($model)\" uib-typeahead=\"state as state.name for state in states | filter:$viewValue\"></div>'),\n      inputEl = findInput(element);\n\n      expect(inputEl.val()).toEqual('AL');\n      expect($scope.result).toEqual($scope.states[0]);\n    });\n  });\n\n  describe('input hint', function() {\n    var element;\n\n    beforeEach(function() {\n      element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"state.name for state in states| filter:$viewValue\" typeahead-show-hint=\"true\"></div>');\n    });\n\n    it('should show hint when input matches first match', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'Alas');\n      expect(hintEl.val()).toEqual('Alaska');\n    });\n\n    it('should not show hint when input does not match first match', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'las');\n      expect(hintEl.val()).toEqual('');\n    });\n\n    it('should reset hint when a match is clicked', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'Alas');\n      expect(hintEl.val()).toEqual('Alaska');\n\n      var match = findMatches(element).find('a').eq(0);\n      match.click();\n      $scope.$digest();\n      expect(hintEl.val()).toEqual('');\n    });\n\n    it('should reset hint when click outside', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'Alas');\n      expect(hintEl.val()).toEqual('Alaska');\n\n      $document.find('body').click();\n      $scope.$digest();\n      expect(hintEl.val()).toEqual('');\n    });\n\n    it('should reset hint on enter', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'Alas');\n      expect(hintEl.val()).toEqual('Alaska');\n      triggerKeyDown(element, 13);\n      expect(hintEl.val()).toEqual('');\n    });\n\n    it('should reset hint on tab', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'Alas');\n      expect(hintEl.val()).toEqual('Alaska');\n      triggerKeyDown(element, 9);\n      expect(hintEl.val()).toEqual('');\n    });\n\n    it('should reset hint on escape key', function() {\n      var hintEl = findInput(element);\n\n      expect(hintEl.val()).toEqual('');\n      changeInputValueTo(element, 'Alas');\n      expect(hintEl.val()).toEqual('Alaska');\n      triggerKeyDown(element, 27);\n      expect(hintEl.val()).toEqual('');\n    });\n\n    it(\"should set tab index on hint input element\", function(){\n      var hintEl = findInput(element);\n      expect(hintEl.attr('tabindex')).toEqual('-1');\n    });\n  });\n\n  describe('append to', function() {\n    it('append typeahead results to element', function() {\n      $document.find('body').append('<div id=\"myElement\"></div>');\n      $scope.myElement = $document.find('#myElement');\n      var element = prepareInputEl('<div><input name=\"input\" ng-model=\"result\" uib-typeahead=\"item for item in states | filter:$viewValue\" typeahead-append-to=\"myElement\"></div>');\n      changeInputValueTo(element, 'al');\n      expect($document.find('#myElement')).toBeOpenWithActive(2, 0);\n      $document.find('#myElement').remove();\n    });\n  });\n\n  describe('append to body', function() {\n    afterEach(function() {\n      angular.element($window).off('resize');\n      $document.find('body').off('scroll');\n    });\n\n    it('append typeahead results to body', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-append-to-body=\"true\"></div>');\n      changeInputValueTo(element, 'ba');\n      expect($document.find('body')).toBeOpenWithActive(2, 0);\n    });\n\n    it('should not append to body when value of the attribute is false', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-append-to-body=\"false\"></div>');\n      changeInputValueTo(element, 'ba');\n      expect(findDropDown($document.find('body')).length).toEqual(0);\n    });\n\n    it('should have right position after scroll', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-append-to-body=\"true\"></div>');\n      var dropdown = findDropDown($document.find('body'));\n      var body = angular.element(document.body);\n\n      // Set body height to allow scrolling\n      body.css({height:'10000px'});\n\n      // Scroll top\n      window.scroll(0, 1000);\n\n      // Set input value to show dropdown\n      changeInputValueTo(element, 'ba');\n\n      // Init position of dropdown must be 1000px\n      expect(dropdown.css('top') ).toEqual('1000px');\n\n      // After scroll, must have new position\n      window.scroll(0, 500);\n      body.triggerHandler('scroll');\n      $timeout.flush();\n      expect(dropdown.css('top')).toEqual('500px');\n    });\n  });\n\n  describe('focus first', function() {\n    it('should focus the first element by default', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\"></div>');\n      changeInputValueTo(element, 'b');\n      expect(element).toBeOpenWithActive(2, 0);\n\n      // Down arrow key\n      triggerKeyDown(element, 40);\n      expect(element).toBeOpenWithActive(2, 1);\n\n      // Down arrow key goes back to first element\n      triggerKeyDown(element, 40);\n      expect(element).toBeOpenWithActive(2, 0);\n\n      // Up arrow key goes back to last element\n      triggerKeyDown(element, 38);\n      expect(element).toBeOpenWithActive(2, 1);\n\n      // Up arrow key goes back to first element\n      triggerKeyDown(element, 38);\n      expect(element).toBeOpenWithActive(2, 0);\n    });\n\n    it('should not focus the first element until keys are pressed', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-focus-first=\"false\"></div>');\n      changeInputValueTo(element, 'b');\n      expect(element).toBeOpenWithActive(2, -1);\n\n      // Down arrow key goes to first element\n      triggerKeyDown(element, 40);\n      expect(element).toBeOpenWithActive(2, 0);\n\n      // Down arrow key goes to second element\n      triggerKeyDown(element, 40);\n      expect(element).toBeOpenWithActive(2, 1);\n\n      // Down arrow key goes back to first element\n      triggerKeyDown(element, 40);\n      expect(element).toBeOpenWithActive(2, 0);\n\n      // Up arrow key goes back to last element\n      triggerKeyDown(element, 38);\n      expect(element).toBeOpenWithActive(2, 1);\n\n      // Up arrow key goes back to first element\n      triggerKeyDown(element, 38);\n      expect(element).toBeOpenWithActive(2, 0);\n\n      // New input goes back to no focus\n      changeInputValueTo(element, 'a');\n      changeInputValueTo(element, 'b');\n      expect(element).toBeOpenWithActive(2, -1);\n\n      // Up arrow key goes to last element\n      triggerKeyDown(element, 38);\n      expect(element).toBeOpenWithActive(2, 1);\n    });\n  });\n\n  it('should not capture enter or tab when an item is not focused', function() {\n    $scope.select_count = 0;\n    $scope.onSelect = function($item, $model, $label) {\n      $scope.select_count = $scope.select_count + 1;\n    };\n    var element = prepareInputEl('<div><input ng-model=\"result\" ng-keydown=\"keyDownEvent = $event\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-on-select=\"onSelect($item, $model, $label)\" typeahead-focus-first=\"false\"></div>');\n    changeInputValueTo(element, 'b');\n\n    // enter key should not be captured when nothing is focused\n    triggerKeyDown(element, 13);\n    expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();\n    expect($scope.select_count).toEqual(0);\n\n    // tab key should close the dropdown when nothing is focused\n    triggerKeyDown(element, 9);\n    expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();\n    expect($scope.select_count).toEqual(0);\n    expect(element).toBeClosed();\n  });\n\n  it(\"should not capture tab when shift key is pressed\", function(){\n    $scope.select_count = 0;\n    $scope.onSelect = function($item, $model, $label) {\n      $scope.select_count = $scope.select_count + 1;\n    };\n    var element = prepareInputEl('<div><input ng-model=\"result\" ng-keydown=\"keyDownEvent = $event\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-on-select=\"onSelect($item, $model, $label)\" typeahead-focus-first=\"false\"></div>');\n    changeInputValueTo(element, 'b');\n\n    // down key should be captured and focus first element\n    triggerKeyDown(element, 40);\n\n    triggerKeyDown(element, 9, {shiftKey: true});\n    expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();\n    expect($scope.select_count).toEqual(0);\n    expect(element).toBeClosed();\n  });\n\n  it('should capture enter or tab when an item is focused', function() {\n    $scope.select_count = 0;\n    $scope.onSelect = function($item, $model, $label) {\n      $scope.select_count = $scope.select_count + 1;\n    };\n    var element = prepareInputEl('<div><input ng-model=\"result\" ng-keydown=\"keyDownEvent = $event\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-on-select=\"onSelect($item, $model, $label)\" typeahead-focus-first=\"false\"></div>');\n    changeInputValueTo(element, 'b');\n\n    // down key should be captured and focus first element\n    triggerKeyDown(element, 40);\n    expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();\n    expect(element).toBeOpenWithActive(2, 0);\n\n    // enter key should be captured now that something is focused\n    triggerKeyDown(element, 13);\n    expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();\n    expect($scope.select_count).toEqual(1);\n  });\n\n  describe('minLength set to 0', function() {\n    it('should open typeahead if input is changed to empty string if defined threshold is 0', function() {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-min-length=\"0\"></div>');\n      changeInputValueTo(element, '');\n      expect(element).toBeOpenWithActive(3, 0);\n    });\n\n    it('should open typeahead when input is focused and value is empty if defined threshold is 0', function () {\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-min-length=\"0\"></div>');\n      var inputEl = findInput(element);\n      inputEl.focus();\n      $timeout.flush();\n      $scope.$digest();\n      expect(element).toBeOpenWithActive(3, 0);\n    });\n  });\n\n  describe('event listeners', function() {\n    afterEach(function() {\n      angular.element($window).off('resize');\n      $document.find('body').off('scroll');\n    });\n\n    it('should register event listeners when attached to body', function() {\n      spyOn(window, 'addEventListener');\n      spyOn(document.body, 'addEventListener');\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-append-to-body=\"true\"></div>');\n\n      expect(window.addEventListener).toHaveBeenCalledWith('resize', jasmine.any(Function), false);\n      expect(document.body.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), false);\n    });\n\n    it('should remove event listeners when attached to body', function() {\n      spyOn(window, 'removeEventListener');\n      spyOn(document.body, 'removeEventListener');\n\n      var element = prepareInputEl('<div><input ng-model=\"result\" uib-typeahead=\"item for item in source | filter:$viewValue\" typeahead-append-to-body=\"true\"></div>');\n      $scope.$destroy();\n\n      expect(window.removeEventListener).toHaveBeenCalledWith('resize', jasmine.any(Function), false);\n      expect(document.body.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), false);\n    });\n  });\n});\n\ndescribe('typeahead tests', function() {\n  it('should allow directives in template to require parent controller', function() {\n    module('ui.bootstrap.typeahead');\n    module('ngSanitize');\n    module('uib/template/typeahead/typeahead-popup.html');\n    module(function($compileProvider) {\n      $compileProvider\n        .directive('uibCustomParent', function() {\n          return {\n            controller: function() {\n              this.text = 'foo';\n            }\n          };\n        })\n        .directive('uibCustomDirective', function() {\n          return {\n            require: '^uibCustomParent',\n            link: function(scope, element, attrs, ctrl) {\n              scope.text = ctrl.text;\n            }\n          };\n        });\n    });\n\n    inject(function($compile, $rootScope, $sniffer, $templateCache) {\n      var element;\n      var $scope = $rootScope.$new();\n      $templateCache.put('uib/template/typeahead/typeahead-match.html', '<div uib-custom-directive>{{text}}</div>');\n      $scope.states = [\n        {code: 'AL', name: 'Alaska'},\n        {code: 'CL', name: 'California'}\n      ];\n\n      element = $compile('<div uib-custom-parent><input ng-model=\"result\" uib-typeahead=\"state.code as state.name + state.code for state in states\"></div>')($scope);\n      $rootScope.$digest();\n\n      var inputEl = element.find('input');\n      inputEl.val('Al');\n      inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change');\n      $scope.$digest();\n\n      expect(element.find('ul.dropdown-menu li').eq(0).find('[uib-custom-directive]').text()).toEqual('foo');\n    });\n  });\n});\n"
  },
  {
    "path": "src/typeahead/typeahead.css",
    "content": "[uib-typeahead-popup].dropdown-menu {\n  display: block;\n}\n"
  },
  {
    "path": "src/typeahead/typeahead.js",
    "content": "angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])\n\n/**\n * A helper service that can parse typeahead's syntax (string provided by users)\n * Extracted to a separate service for ease of unit testing\n */\n  .factory('uibTypeaheadParser', ['$parse', function($parse) {\n    //                      000001111111100000000000002222222200000000000000003333333333333330000000000044444444000\n    var TYPEAHEAD_REGEXP = /^\\s*([\\s\\S]+?)(?:\\s+as\\s+([\\s\\S]+?))?\\s+for\\s+(?:([\\$\\w][\\$\\w\\d]*))\\s+in\\s+([\\s\\S]+?)$/;\n    return {\n      parse: function(input) {\n        var match = input.match(TYPEAHEAD_REGEXP);\n        if (!match) {\n          throw new Error(\n            'Expected typeahead specification in form of \"_modelValue_ (as _label_)? for _item_ in _collection_\"' +\n              ' but got \"' + input + '\".');\n        }\n\n        return {\n          itemName: match[3],\n          source: $parse(match[4]),\n          viewMapper: $parse(match[2] || match[1]),\n          modelMapper: $parse(match[1])\n        };\n      }\n    };\n  }])\n\n  .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',\n    function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {\n    var HOT_KEYS = [9, 13, 27, 38, 40];\n    var eventDebounceTime = 200;\n    var modelCtrl, ngModelOptions;\n    //SUPPORTED ATTRIBUTES (OPTIONS)\n\n    //minimal no of characters that needs to be entered before typeahead kicks-in\n    var minLength = originalScope.$eval(attrs.typeaheadMinLength);\n    if (!minLength && minLength !== 0) {\n      minLength = 1;\n    }\n\n    originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {\n        minLength = !newVal && newVal !== 0 ? 1 : newVal;\n    });\n\n    //minimal wait time after last character typed before typeahead kicks-in\n    var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;\n\n    //should it restrict model values to the ones selected from the popup only?\n    var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;\n    originalScope.$watch(attrs.typeaheadEditable, function (newVal) {\n      isEditable = newVal !== false;\n    });\n\n    //binding to a variable that indicates if matches are being retrieved asynchronously\n    var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;\n\n    //a function to determine if an event should cause selection\n    var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) {\n      var evt = vals.$event;\n      return evt.which === 13 || evt.which === 9;\n    };\n\n    //a callback executed when a match is selected\n    var onSelectCallback = $parse(attrs.typeaheadOnSelect);\n\n    //should it select highlighted popup value when losing focus?\n    var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;\n\n    //binding to a variable that indicates if there were no results after the query is completed\n    var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;\n\n    var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;\n\n    var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;\n\n    var appendTo = attrs.typeaheadAppendTo ?\n      originalScope.$eval(attrs.typeaheadAppendTo) : null;\n\n    var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;\n\n    //If input matches an item of the list exactly, select it automatically\n    var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;\n\n    //binding to a variable that indicates if dropdown is open\n    var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;\n\n    var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;\n\n    //INTERNAL VARIABLES\n\n    //model setter executed upon match selection\n    var parsedModel = $parse(attrs.ngModel);\n    var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');\n    var $setModelValue = function(scope, newValue) {\n      if (angular.isFunction(parsedModel(originalScope)) &&\n        ngModelOptions.getOption('getterSetter')) {\n        return invokeModelSetter(scope, {$$$p: newValue});\n      }\n\n      return parsedModel.assign(scope, newValue);\n    };\n\n    //expressions used by typeahead\n    var parserResult = typeaheadParser.parse(attrs.uibTypeahead);\n\n    var hasFocus;\n\n    //Used to avoid bug in iOS webview where iOS keyboard does not fire\n    //mousedown & mouseup events\n    //Issue #3699\n    var selected;\n\n    //create a child scope for the typeahead directive so we are not polluting original scope\n    //with typeahead-specific data (matches, query etc.)\n    var scope = originalScope.$new();\n    var offDestroy = originalScope.$on('$destroy', function() {\n      scope.$destroy();\n    });\n    scope.$on('$destroy', offDestroy);\n\n    // WAI-ARIA\n    var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);\n    element.attr({\n      'aria-autocomplete': 'list',\n      'aria-expanded': false,\n      'aria-owns': popupId\n    });\n\n    var inputsContainer, hintInputElem;\n    //add read-only input to show hint\n    if (showHint) {\n      inputsContainer = angular.element('<div></div>');\n      inputsContainer.css('position', 'relative');\n      element.after(inputsContainer);\n      hintInputElem = element.clone();\n      hintInputElem.attr('placeholder', '');\n      hintInputElem.attr('tabindex', '-1');\n      hintInputElem.val('');\n      hintInputElem.css({\n        'position': 'absolute',\n        'top': '0px',\n        'left': '0px',\n        'border-color': 'transparent',\n        'box-shadow': 'none',\n        'opacity': 1,\n        'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',\n        'color': '#999'\n      });\n      element.css({\n        'position': 'relative',\n        'vertical-align': 'top',\n        'background-color': 'transparent'\n      });\n\n      if (hintInputElem.attr('id')) {\n        hintInputElem.removeAttr('id'); // remove duplicate id if present.\n      }\n      inputsContainer.append(hintInputElem);\n      hintInputElem.after(element);\n    }\n\n    //pop-up element used to display matches\n    var popUpEl = angular.element('<div uib-typeahead-popup></div>');\n    popUpEl.attr({\n      id: popupId,\n      matches: 'matches',\n      active: 'activeIdx',\n      select: 'select(activeIdx, evt)',\n      'move-in-progress': 'moveInProgress',\n      query: 'query',\n      position: 'position',\n      'assign-is-open': 'assignIsOpen(isOpen)',\n      debounce: 'debounceUpdate'\n    });\n    //custom item template\n    if (angular.isDefined(attrs.typeaheadTemplateUrl)) {\n      popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);\n    }\n\n    if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {\n      popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);\n    }\n\n    var resetHint = function() {\n      if (showHint) {\n        hintInputElem.val('');\n      }\n    };\n\n    var resetMatches = function() {\n      scope.matches = [];\n      scope.activeIdx = -1;\n      element.attr('aria-expanded', false);\n      resetHint();\n    };\n\n    var getMatchId = function(index) {\n      return popupId + '-option-' + index;\n    };\n\n    // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.\n    // This attribute is added or removed automatically when the `activeIdx` changes.\n    scope.$watch('activeIdx', function(index) {\n      if (index < 0) {\n        element.removeAttr('aria-activedescendant');\n      } else {\n        element.attr('aria-activedescendant', getMatchId(index));\n      }\n    });\n\n    var inputIsExactMatch = function(inputValue, index) {\n      if (scope.matches.length > index && inputValue) {\n        return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();\n      }\n\n      return false;\n    };\n\n    var getMatchesAsync = function(inputValue, evt) {\n      var locals = {$viewValue: inputValue};\n      isLoadingSetter(originalScope, true);\n      isNoResultsSetter(originalScope, false);\n      $q.when(parserResult.source(originalScope, locals)).then(function(matches) {\n        //it might happen that several async queries were in progress if a user were typing fast\n        //but we are interested only in responses that correspond to the current view value\n        var onCurrentRequest = inputValue === modelCtrl.$viewValue;\n        if (onCurrentRequest && hasFocus) {\n          if (matches && matches.length > 0) {\n            scope.activeIdx = focusFirst ? 0 : -1;\n            isNoResultsSetter(originalScope, false);\n            scope.matches.length = 0;\n\n            //transform labels\n            for (var i = 0; i < matches.length; i++) {\n              locals[parserResult.itemName] = matches[i];\n              scope.matches.push({\n                id: getMatchId(i),\n                label: parserResult.viewMapper(scope, locals),\n                model: matches[i]\n              });\n            }\n\n            scope.query = inputValue;\n            //position pop-up with matches - we need to re-calculate its position each time we are opening a window\n            //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page\n            //due to other elements being rendered\n            recalculatePosition();\n\n            element.attr('aria-expanded', true);\n\n            //Select the single remaining option if user input matches\n            if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {\n              if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {\n                $$debounce(function() {\n                  scope.select(0, evt);\n                }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);\n              } else {\n                scope.select(0, evt);\n              }\n            }\n\n            if (showHint) {\n              var firstLabel = scope.matches[0].label;\n              if (angular.isString(inputValue) &&\n                inputValue.length > 0 &&\n                firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {\n                hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));\n              } else {\n                hintInputElem.val('');\n              }\n            }\n          } else {\n            resetMatches();\n            isNoResultsSetter(originalScope, true);\n          }\n        }\n        if (onCurrentRequest) {\n          isLoadingSetter(originalScope, false);\n        }\n      }, function() {\n        resetMatches();\n        isLoadingSetter(originalScope, false);\n        isNoResultsSetter(originalScope, true);\n      });\n    };\n\n    // bind events only if appendToBody params exist - performance feature\n    if (appendToBody) {\n      angular.element($window).on('resize', fireRecalculating);\n      $document.find('body').on('scroll', fireRecalculating);\n    }\n\n    // Declare the debounced function outside recalculating for\n    // proper debouncing\n    var debouncedRecalculate = $$debounce(function() {\n      // if popup is visible\n      if (scope.matches.length) {\n        recalculatePosition();\n      }\n\n      scope.moveInProgress = false;\n    }, eventDebounceTime);\n\n    // Default progress type\n    scope.moveInProgress = false;\n\n    function fireRecalculating() {\n      if (!scope.moveInProgress) {\n        scope.moveInProgress = true;\n        scope.$digest();\n      }\n\n      debouncedRecalculate();\n    }\n\n    // recalculate actual position and set new values to scope\n    // after digest loop is popup in right position\n    function recalculatePosition() {\n      scope.position = appendToBody ? $position.offset(element) : $position.position(element);\n      scope.position.top += element.prop('offsetHeight');\n    }\n\n    //we need to propagate user's query so we can higlight matches\n    scope.query = undefined;\n\n    //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later\n    var timeoutPromise;\n\n    var scheduleSearchWithTimeout = function(inputValue) {\n      timeoutPromise = $timeout(function() {\n        getMatchesAsync(inputValue);\n      }, waitTime);\n    };\n\n    var cancelPreviousTimeout = function() {\n      if (timeoutPromise) {\n        $timeout.cancel(timeoutPromise);\n      }\n    };\n\n    resetMatches();\n\n    scope.assignIsOpen = function (isOpen) {\n      isOpenSetter(originalScope, isOpen);\n    };\n\n    scope.select = function(activeIdx, evt) {\n      //called from within the $digest() cycle\n      var locals = {};\n      var model, item;\n\n      selected = true;\n      locals[parserResult.itemName] = item = scope.matches[activeIdx].model;\n      model = parserResult.modelMapper(originalScope, locals);\n      $setModelValue(originalScope, model);\n      modelCtrl.$setValidity('editable', true);\n      modelCtrl.$setValidity('parse', true);\n\n      onSelectCallback(originalScope, {\n        $item: item,\n        $model: model,\n        $label: parserResult.viewMapper(originalScope, locals),\n        $event: evt\n      });\n\n      resetMatches();\n\n      //return focus to the input element if a match was selected via a mouse click event\n      // use timeout to avoid $rootScope:inprog error\n      if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {\n        $timeout(function() { element[0].focus(); }, 0, false);\n      }\n    };\n\n    //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)\n    element.on('keydown', function(evt) {\n      //typeahead is open and an \"interesting\" key was pressed\n      if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {\n        return;\n      }\n\n      var shouldSelect = isSelectEvent(originalScope, {$event: evt});\n\n      /**\n       * if there's nothing selected (i.e. focusFirst) and enter or tab is hit\n       * or\n       * shift + tab is pressed to bring focus to the previous element\n       * then clear the results\n       */\n      if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) {\n        resetMatches();\n        scope.$digest();\n        return;\n      }\n\n      evt.preventDefault();\n      var target;\n      switch (evt.which) {\n        case 27: // escape\n          evt.stopPropagation();\n\n          resetMatches();\n          originalScope.$digest();\n          break;\n        case 38: // up arrow\n          scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;\n          scope.$digest();\n          target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];\n          target.parentNode.scrollTop = target.offsetTop;\n          break;\n        case 40: // down arrow\n          scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;\n          scope.$digest();\n          target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];\n          target.parentNode.scrollTop = target.offsetTop;\n          break;\n        default:\n          if (shouldSelect) {\n            scope.$apply(function() {\n              if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {\n                $$debounce(function() {\n                  scope.select(scope.activeIdx, evt);\n                }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);\n              } else {\n                scope.select(scope.activeIdx, evt);\n              }\n            });\n          }\n      }\n    });\n\n    element.on('focus', function (evt) {\n      hasFocus = true;\n      if (minLength === 0 && !modelCtrl.$viewValue) {\n        $timeout(function() {\n          getMatchesAsync(modelCtrl.$viewValue, evt);\n        }, 0);\n      }\n    });\n\n    element.on('blur', function(evt) {\n      if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {\n        selected = true;\n        scope.$apply(function() {\n          if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {\n            $$debounce(function() {\n              scope.select(scope.activeIdx, evt);\n            }, scope.debounceUpdate.blur);\n          } else {\n            scope.select(scope.activeIdx, evt);\n          }\n        });\n      }\n      if (!isEditable && modelCtrl.$error.editable) {\n        modelCtrl.$setViewValue();\n        scope.$apply(function() {\n          // Reset validity as we are clearing\n          modelCtrl.$setValidity('editable', true);\n          modelCtrl.$setValidity('parse', true);\n        });\n        element.val('');\n      }\n      hasFocus = false;\n      selected = false;\n    });\n\n    // Keep reference to click handler to unbind it.\n    var dismissClickHandler = function(evt) {\n      // Issue #3973\n      // Firefox treats right click as a click on document\n      if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {\n        resetMatches();\n        if (!$rootScope.$$phase) {\n          originalScope.$digest();\n        }\n      }\n    };\n\n    $document.on('click', dismissClickHandler);\n\n    originalScope.$on('$destroy', function() {\n      $document.off('click', dismissClickHandler);\n      if (appendToBody || appendTo) {\n        $popup.remove();\n      }\n\n      if (appendToBody) {\n        angular.element($window).off('resize', fireRecalculating);\n        $document.find('body').off('scroll', fireRecalculating);\n      }\n      // Prevent jQuery cache memory leak\n      popUpEl.remove();\n\n      if (showHint) {\n          inputsContainer.remove();\n      }\n    });\n\n    var $popup = $compile(popUpEl)(scope);\n\n    if (appendToBody) {\n      $document.find('body').append($popup);\n    } else if (appendTo) {\n      angular.element(appendTo).eq(0).append($popup);\n    } else {\n      element.after($popup);\n    }\n\n    this.init = function(_modelCtrl) {\n      modelCtrl = _modelCtrl;\n      ngModelOptions = extractOptions(modelCtrl);\n\n      scope.debounceUpdate = $parse(ngModelOptions.getOption('debounce'))(originalScope);\n\n      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM\n      //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue\n      modelCtrl.$parsers.unshift(function(inputValue) {\n        hasFocus = true;\n\n        if (minLength === 0 || inputValue && inputValue.length >= minLength) {\n          if (waitTime > 0) {\n            cancelPreviousTimeout();\n            scheduleSearchWithTimeout(inputValue);\n          } else {\n            getMatchesAsync(inputValue);\n          }\n        } else {\n          isLoadingSetter(originalScope, false);\n          cancelPreviousTimeout();\n          resetMatches();\n        }\n\n        if (isEditable) {\n          return inputValue;\n        }\n\n        if (!inputValue) {\n          // Reset in case user had typed something previously.\n          modelCtrl.$setValidity('editable', true);\n          return null;\n        }\n\n        modelCtrl.$setValidity('editable', false);\n        return undefined;\n      });\n\n      modelCtrl.$formatters.push(function(modelValue) {\n        var candidateViewValue, emptyViewValue;\n        var locals = {};\n\n        // The validity may be set to false via $parsers (see above) if\n        // the model is restricted to selected values. If the model\n        // is set manually it is considered to be valid.\n        if (!isEditable) {\n          modelCtrl.$setValidity('editable', true);\n        }\n\n        if (inputFormatter) {\n          locals.$model = modelValue;\n          return inputFormatter(originalScope, locals);\n        }\n\n        //it might happen that we don't have enough info to properly render input value\n        //we need to check for this situation and simply return model value if we can't apply custom formatting\n        locals[parserResult.itemName] = modelValue;\n        candidateViewValue = parserResult.viewMapper(originalScope, locals);\n        locals[parserResult.itemName] = undefined;\n        emptyViewValue = parserResult.viewMapper(originalScope, locals);\n\n        return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;\n      });\n    };\n\n    function extractOptions(ngModelCtrl) {\n      var ngModelOptions;\n\n      if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing\n        // guarantee a value\n        ngModelOptions = ngModelCtrl.$options || {};\n\n        // mimic 1.6+ api\n        ngModelOptions.getOption = function (key) {\n          return ngModelOptions[key];\n        };\n      } else { // in angular >=1.6 $options is always present\n        ngModelOptions = ngModelCtrl.$options;\n      }\n\n      return ngModelOptions;\n    }\n  }])\n\n  .directive('uibTypeahead', function() {\n    return {\n      controller: 'UibTypeaheadController',\n      require: ['ngModel', 'uibTypeahead'],\n      link: function(originalScope, element, attrs, ctrls) {\n        ctrls[1].init(ctrls[0]);\n      }\n    };\n  })\n\n  .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {\n    return {\n      scope: {\n        matches: '=',\n        query: '=',\n        active: '=',\n        position: '&',\n        moveInProgress: '=',\n        select: '&',\n        assignIsOpen: '&',\n        debounce: '&'\n      },\n      replace: true,\n      templateUrl: function(element, attrs) {\n        return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';\n      },\n      link: function(scope, element, attrs) {\n        scope.templateUrl = attrs.templateUrl;\n\n        scope.isOpen = function() {\n          var isDropdownOpen = scope.matches.length > 0;\n          scope.assignIsOpen({ isOpen: isDropdownOpen });\n          return isDropdownOpen;\n        };\n\n        scope.isActive = function(matchIdx) {\n          return scope.active === matchIdx;\n        };\n\n        scope.selectActive = function(matchIdx) {\n          scope.active = matchIdx;\n        };\n\n        scope.selectMatch = function(activeIdx, evt) {\n          var debounce = scope.debounce();\n          if (angular.isNumber(debounce) || angular.isObject(debounce)) {\n            $$debounce(function() {\n              scope.select({activeIdx: activeIdx, evt: evt});\n            }, angular.isNumber(debounce) ? debounce : debounce['default']);\n          } else {\n            scope.select({activeIdx: activeIdx, evt: evt});\n          }\n        };\n      }\n    };\n  }])\n\n  .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {\n    return {\n      scope: {\n        index: '=',\n        match: '=',\n        query: '='\n      },\n      link: function(scope, element, attrs) {\n        var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';\n        $templateRequest(tplUrl).then(function(tplContent) {\n          var tplEl = angular.element(tplContent.trim());\n          element.replaceWith(tplEl);\n          $compile(tplEl)(scope);\n        });\n      }\n    };\n  }])\n\n  .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {\n    var isSanitizePresent;\n    isSanitizePresent = $injector.has('$sanitize');\n\n    function escapeRegexp(queryToEscape) {\n      // Regex: capture the whole query string and replace it with the string that will be used to match\n      // the results, for example if the capture is \"a\" the result will be \\a\n      return queryToEscape.replace(/([.?*+^$[\\]\\\\(){}|-])/g, '\\\\$1');\n    }\n\n    function containsHtml(matchItem) {\n      return /<.*>/g.test(matchItem);\n    }\n\n    return function(matchItem, query) {\n      if (!isSanitizePresent && containsHtml(matchItem)) {\n        $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger\n      }\n      matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a \"strong\" tag\n      if (!isSanitizePresent) {\n        matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive\n      }\n      return matchItem;\n    };\n  }]);\n"
  },
  {
    "path": "template/accordion/accordion-group.html",
    "content": "<div role=\"tab\" id=\"{{::headingId}}\" aria-selected=\"{{isOpen}}\" class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n  <h4 class=\"panel-title\">\n    <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\" ng-disabled=\"isDisabled\" uib-tabindex-toggle><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n  </h4>\n</div>\n<div id=\"{{::panelId}}\" aria-labelledby=\"{{::headingId}}\" aria-hidden=\"{{!isOpen}}\" role=\"tabpanel\" class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n  <div class=\"panel-body\" ng-transclude></div>\n</div>\n"
  },
  {
    "path": "template/accordion/accordion.html",
    "content": "<div role=\"tablist\" class=\"panel-group\" ng-transclude></div>"
  },
  {
    "path": "template/alert/alert.html",
    "content": "<button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n  <span aria-hidden=\"true\">&times;</span>\n  <span class=\"sr-only\">Close</span>\n</button>\n<div ng-transclude></div>\n"
  },
  {
    "path": "template/carousel/carousel.html",
    "content": "<div class=\"carousel-inner\" ng-transclude></div>\n<a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-class=\"{ disabled: isPrevDisabled() }\" ng-show=\"slides.length > 1\">\n  <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n  <span class=\"sr-only\">previous</span>\n</a>\n<a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-class=\"{ disabled: isNextDisabled() }\" ng-show=\"slides.length > 1\">\n  <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n  <span class=\"sr-only\">next</span>\n</a>\n<ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n  <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n    <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n  </li>\n</ol>\n"
  },
  {
    "path": "template/carousel/slide.html",
    "content": "<div class=\"text-center\" ng-transclude></div>\n"
  },
  {
    "path": "template/datepicker/datepicker.html",
    "content": "<div ng-switch=\"datepickerMode\">\n  <div uib-daypicker ng-switch-when=\"day\" tabindex=\"0\" class=\"uib-daypicker\"></div>\n  <div uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\" class=\"uib-monthpicker\"></div>\n  <div uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\" class=\"uib-yearpicker\"></div>\n</div>\n"
  },
  {
    "path": "template/datepicker/day.html",
    "content": "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n  <thead>\n    <tr>\n      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></i><span class=\"sr-only\">previous</span></button></th>\n      <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></i><span class=\"sr-only\">next</span></button></th>\n    </tr>\n    <tr>\n      <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n      <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n      <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n      <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n        id=\"{{::dt.uid}}\"\n        ng-class=\"::dt.customClass\">\n        <button type=\"button\" class=\"btn btn-default btn-sm\"\n          uib-is-class=\"\n            'btn-info' for selectedDt,\n            'active' for activeDt\n            on dt\"\n          ng-click=\"select(dt.date)\"\n          ng-disabled=\"::dt.disabled\"\n          tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "template/datepicker/month.html",
    "content": "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n  <thead>\n    <tr>\n      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></i><span class=\"sr-only\">previous</span></button></th>\n      <th colspan=\"{{::yearHeaderColspan}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></i><span class=\"sr-only\">next</span></i></button></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n      <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n        id=\"{{::dt.uid}}\"\n        ng-class=\"::dt.customClass\">\n        <button type=\"button\" class=\"btn btn-default\"\n          uib-is-class=\"\n            'btn-info' for selectedDt,\n            'active' for activeDt\n            on dt\"\n          ng-click=\"select(dt.date)\"\n          ng-disabled=\"::dt.disabled\"\n          tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "template/datepicker/year.html",
    "content": "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n  <thead>\n    <tr>\n      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></i><span class=\"sr-only\">previous</span></button></th>\n      <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></i><span class=\"sr-only\">next</span></button></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n      <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n        id=\"{{::dt.uid}}\"\n        ng-class=\"::dt.customClass\">\n        <button type=\"button\" class=\"btn btn-default\"\n          uib-is-class=\"\n            'btn-info' for selectedDt,\n            'active' for activeDt\n            on dt\"\n          ng-click=\"select(dt.date)\"\n          ng-disabled=\"::dt.disabled\"\n          tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "template/datepickerPopup/popup.html",
    "content": "<ul role=\"presentation\" class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n  <li ng-transclude></li>\n  <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n    <span class=\"btn-group pull-left\">\n      <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n      <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n    </span>\n    <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n  </li>\n</ul>\n"
  },
  {
    "path": "template/modal/window.html",
    "content": "<div class=\"modal-dialog {{size ? 'modal-' + size : ''}}\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n"
  },
  {
    "path": "template/pager/pager.html",
    "content": "<li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n<li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n"
  },
  {
    "path": "template/pagination/pagination.html",
    "content": "<li role=\"menuitem\" ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('first')}}</a></li>\n<li role=\"menuitem\" ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n<li role=\"menuitem\" ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\" ng-disabled=\"ngDisabled&&!page.active\" uib-tabindex-toggle>{{page.text}}</a></li>\n<li role=\"menuitem\" ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n<li role=\"menuitem\" ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('last')}}</a></li>\n"
  },
  {
    "path": "template/popover/popover-html.html",
    "content": "<div class=\"arrow\"></div>\n\n<div class=\"popover-inner\">\n    <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n    <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n</div>\n"
  },
  {
    "path": "template/popover/popover-template.html",
    "content": "<div class=\"arrow\"></div>\n\n<div class=\"popover-inner\">\n    <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n    <div class=\"popover-content\"\n      uib-tooltip-template-transclude=\"contentExp()\"\n      tooltip-template-transclude-scope=\"originScope()\"></div>\n</div>\n"
  },
  {
    "path": "template/popover/popover.html",
    "content": "<div class=\"arrow\"></div>\n\n<div class=\"popover-inner\">\n    <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n    <div class=\"popover-content\" ng-bind=\"content\"></div>\n</div>\n"
  },
  {
    "path": "template/progressbar/bar.html",
    "content": "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n"
  },
  {
    "path": "template/progressbar/progress.html",
    "content": "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>"
  },
  {
    "path": "template/progressbar/progressbar.html",
    "content": "<div class=\"progress\">\n  <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n</div>\n"
  },
  {
    "path": "template/rating/rating.html",
    "content": "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\" aria-valuetext=\"{{title}}\">\n    <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n    <i ng-repeat-end ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\" ng-attr-title=\"{{r.title}}\"></i>\n</span>\n"
  },
  {
    "path": "template/tabs/tab.html",
    "content": "<li ng-class=\"[{active: active, disabled: disabled}, classes]\" class=\"uib-tab nav-item\">\n  <a href ng-click=\"select($event)\" class=\"nav-link\" uib-tab-heading-transclude>{{heading}}</a>\n</li>\n"
  },
  {
    "path": "template/tabs/tabset.html",
    "content": "<div>\n  <ul class=\"nav nav-{{tabset.type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n  <div class=\"tab-content\">\n    <div class=\"tab-pane\"\n         ng-repeat=\"tab in tabset.tabs\"\n         ng-class=\"{active: tabset.active === tab.index}\"\n         uib-tab-content-transclude=\"tab\">\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "template/timepicker/timepicker.html",
    "content": "<table class=\"uib-timepicker\">\n  <tbody>\n    <tr class=\"text-center\" ng-show=\"::showSpinners\">\n      <td class=\"uib-increment hours\"><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n      <td>&nbsp;</td>\n      <td class=\"uib-increment minutes\"><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n      <td ng-show=\"showSeconds\">&nbsp;</td>\n      <td ng-show=\"showSeconds\" class=\"uib-increment seconds\"><a ng-click=\"incrementSeconds()\" ng-class=\"{disabled: noIncrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementSeconds()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n      <td ng-show=\"showMeridian\"></td>\n    </tr>\n    <tr>\n      <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n        <input type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementHours()\" ng-blur=\"blur()\">\n      </td>\n      <td class=\"uib-separator\">:</td>\n      <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n        <input type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementMinutes()\" ng-blur=\"blur()\">\n      </td>\n      <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n      <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n        <input type=\"text\" placeholder=\"SS\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementSeconds()\" ng-blur=\"blur()\">\n      </td>\n      <td ng-show=\"showMeridian\" class=\"uib-time am-pm\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n    </tr>\n    <tr class=\"text-center\" ng-show=\"::showSpinners\">\n      <td class=\"uib-decrement hours\"><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n      <td>&nbsp;</td>\n      <td class=\"uib-decrement minutes\"><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n      <td ng-show=\"showSeconds\">&nbsp;</td>\n      <td ng-show=\"showSeconds\" class=\"uib-decrement seconds\"><a ng-click=\"decrementSeconds()\" ng-class=\"{disabled: noDecrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementSeconds()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n      <td ng-show=\"showMeridian\"></td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "template/tooltip/tooltip-html-popup.html",
    "content": "<div class=\"tooltip-arrow\"></div>\n<div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n"
  },
  {
    "path": "template/tooltip/tooltip-popup.html",
    "content": "<div class=\"tooltip-arrow\"></div>\n<div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n"
  },
  {
    "path": "template/tooltip/tooltip-template-popup.html",
    "content": "<div class=\"tooltip-arrow\"></div>\n<div class=\"tooltip-inner\"\n  uib-tooltip-template-transclude=\"contentExp()\"\n  tooltip-template-transclude-scope=\"originScope()\"></div>\n"
  },
  {
    "path": "template/typeahead/typeahead-match.html",
    "content": "<a href\n   tabindex=\"-1\"\n   ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"\n   ng-attr-title=\"{{match.label}}\"></a>\n"
  },
  {
    "path": "template/typeahead/typeahead-popup.html",
    "content": "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n    <li class=\"uib-typeahead-match\" ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index, $event)\" role=\"option\" id=\"{{::match.id}}\">\n        <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n    </li>\n</ul>\n"
  }
]