diff --git a/.eslintrc b/.eslintrc index e754434..8540796 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,8 @@ { + "env": { + "browser": true, + "es6": true + }, "rules": { "comma-dangle": 2, "no-cond-assign": 2, diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..9e92540 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,15 @@ +The issues forum is __NOT__ for support requests. It is for bugs and feature requests only. +Please read https://github.com/angular-ui/bootstrap/blob/master/CONTRIBUTING.md and search +existing issues (both open and closed) prior to opening any new issue and ensure you follow the instructions therein. + +### Bug description: + +### Link to minimally-working plunker that reproduces the issue: + +### Version of Angular, UIBS, and Bootstrap + +Angular: + +UIBS: + +Bootstrap: diff --git a/.npmignore b/.npmignore index 5895bce..925a2cd 100644 --- a/.npmignore +++ b/.npmignore @@ -14,13 +14,11 @@ pids logs results # test coverage files -coverage/ +.coverage/ node_modules npm-debug.log -template/**/*.js - .git docs misc diff --git a/.travis.yml b/.travis.yml index 1c8ee40..fba6738 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,14 @@ language: node_js node_js: - - "5.1" + - "5.9" +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 before_install: - export DISPLAY=:99.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 944768f..4376216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,504 @@ - -## [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...v0.14.3) (2015-10-23) + +## [1.3.3](https://github.com/angular-ui/bootstrap/compare/1.3.2...v1.3.3) (2016-05-23) + + +### Bug Fixes + +* **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) +* **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) +* **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) + + +### Features + +* **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) +* **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) +* **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) +* **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) +* **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) + + +### BREAKING CHANGES + +* 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 + + + + +## [1.3.2](https://github.com/angular-ui/bootstrap/compare/1.3.1...v1.3.2) (2016-04-14) + + +### Bug Fixes + +* **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) +* **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) +* **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) +* **modal:** missing require in modal ([a887d2b](https://github.com/angular-ui/bootstrap/commit/a887d2b)), closes [#5786](https://github.com/angular-ui/bootstrap/issues/5786) +* **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) +* **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) +* **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) +* **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) + +### Features + +* **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) + + +### BREAKING CHANGES + +* dropdown: Stops propagation of keydown event when escape key is pressed. Removes keydown event from the document and moves it to the dropdown element. + + + + +## [1.3.1](https://github.com/angular-ui/bootstrap/compare/1.3.0...v1.3.1) (2016-04-05) + + +### Bug Fixes + +* **datepickerPopup:** register popup module ([86ee9c3](https://github.com/angular-ui/bootstrap/commit/86ee9c3)), closes [#5745](https://github.com/angular-ui/bootstrap/issues/5745) +* **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) + + + + +# [1.3.0](https://github.com/angular-ui/bootstrap/compare/1.2.5...v1.3.0) (2016-04-05) + + +### Bug Fixes + +* **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) +* 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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) + +### Features + +* 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) +* **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) +* **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) +* **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) +* **datepicker:** remove deprecated code ([ad91c82](https://github.com/angular-ui/bootstrap/commit/ad91c82)), closes [#5660](https://github.com/angular-ui/bootstrap/issues/5660) +* **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) +* **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) +* **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) +* **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) + + +### BREAKING CHANGES + +* 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 +* timepicker: This removes automatic padding done by the timepicker +when the input is empty - if that behavior is desired, write a custom +directive implementing it +* 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. +* datepickerPopup: The datepicker popup is no longer a part of the +datepicker module, but now a part of the datepickerPopup module. Please +change code accordingly if including specific modules +* datepicker: This removes inline datepicker attribute support and +attribute pass-throughs in the popup + + + + +## [1.2.5](https://github.com/angular-ui/bootstrap/compare/1.2.4...v1.2.5) (2016-03-20) + + +### Bug Fixes + +* **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) +* **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) +* **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) +* **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) +* **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) +* **typeahead:** reset errors when clearing ([bc7c55a](https://github.com/angular-ui/bootstrap/commit/bc7c55a)), closes [#5641](https://github.com/angular-ui/bootstrap/issues/5641) + +### Features + +* **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) +* **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) +* **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) +* **tab:** add select expressions ([bb36e40](https://github.com/angular-ui/bootstrap/commit/bb36e40)), closes [#5438](https://github.com/angular-ui/bootstrap/issues/5438) +* **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) + + + + +## [1.2.4](https://github.com/angular-ui/bootstrap/compare/1.2.3...v1.2.4) (2016-03-06) + + + + + +## [1.2.3](https://github.com/angular-ui/bootstrap/compare/1.2.2...v1.2.3) (2016-03-06) + + +### Bug Fixes + +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) + +### Features + +* **datepicker:** add helpers for styling ([c15dcbd](https://github.com/angular-ui/bootstrap/commit/c15dcbd)), closes [#5580](https://github.com/angular-ui/bootstrap/issues/5580) + + + + +## [1.2.2](https://github.com/angular-ui/bootstrap/compare/1.2.1...v1.2.2) (2016-03-03) + + +### Bug Fixes + +* **Gruntfile:** add per module flag ([a816251](https://github.com/angular-ui/bootstrap/commit/a816251)), closes [#5567](https://github.com/angular-ui/bootstrap/issues/5567) +* **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) +* **modal:** fix bindToController props ([5e43870](https://github.com/angular-ui/bootstrap/commit/5e43870)), closes [#5569](https://github.com/angular-ui/bootstrap/issues/5569) +* **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) +* **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) +* **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) +* **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) +* **tooltip:** import CSS ([c8922a2](https://github.com/angular-ui/bootstrap/commit/c8922a2)), closes [#5552](https://github.com/angular-ui/bootstrap/issues/5552) + + + + +## [1.2.1](https://github.com/angular-ui/bootstrap/compare/1.2.0...v1.2.1) (2016-02-27) ### Bug Fixes +* **popover:** add missing selector ([70c1c90](https://github.com/angular-ui/bootstrap/commit/70c1c90)), closes [#5549](https://github.com/angular-ui/bootstrap/issues/5549) + + + + +# [1.2.0](https://github.com/angular-ui/bootstrap/compare/1.1.2...v1.2.0) (2016-02-26) + + +### Bug Fixes + +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **tab:** make active optional ([d1553a4](https://github.com/angular-ui/bootstrap/commit/d1553a4)), closes [#5489](https://github.com/angular-ui/bootstrap/issues/5489) +* **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) +* **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) +* **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) + +### Features + +* **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) +* **datepicker:** add deprecation notices ([10eac7c](https://github.com/angular-ui/bootstrap/commit/10eac7c)), closes [#5415](https://github.com/angular-ui/bootstrap/issues/5415) +* **datepicker:** implement auto position ([f9903ba](https://github.com/angular-ui/bootstrap/commit/f9903ba)), closes [#5444](https://github.com/angular-ui/bootstrap/issues/5444) +* **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) +* **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) +* **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) +* **datepicker:** preserve popup attributes ([bbc4912](https://github.com/angular-ui/bootstrap/commit/bbc4912)), closes [#5537](https://github.com/angular-ui/bootstrap/issues/5537) +* **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) +* **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) +* **position:** add isScrollable ([3c0a7cd](https://github.com/angular-ui/bootstrap/commit/3c0a7cd)), closes [#5508](https://github.com/angular-ui/bootstrap/issues/5508) +* **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) +* **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) +* **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) +* **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) + + +### BREAKING CHANGES + +* rating: Attribute supported has been changed to `read-only` +from `readonly` +* accordion: This changes to use the `uibAccordionHeader` attribute instead of a `` 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. +* datepicker: This requires $event to be passed in the second +argument of the select function and in the close argument for the popup +template +* 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 +* datepicker: This breaks any snake-cased key usage, i.e. +`show-weeks`. Convert all keys used to camelCase. +* tab: Make the `active` attribute optional and move to `tabset` element. +* carousel: the `active` attribute is now required on the `uib-carousel` element and a unique `index` attribute is required on each `uib-slide` element. + + + + +## [1.1.2](https://github.com/angular-ui/bootstrap/compare/1.1.1...v1.1.2) (2016-02-01) + + +### Bug Fixes + +* **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) +* **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) +* **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) +* **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) + +### Features + +* **accordion:** add aria tags ([61cdef1](https://github.com/angular-ui/bootstrap/commit/61cdef1)), closes [#5338](https://github.com/angular-ui/bootstrap/issues/5338) +* **datepicker:** add datepickerOptions support ([e58c42c](https://github.com/angular-ui/bootstrap/commit/e58c42c)), closes [#5340](https://github.com/angular-ui/bootstrap/issues/5340) + + + + +## [1.1.1](https://github.com/angular-ui/bootstrap/compare/v1.1.0...v1.1.1) (2016-01-25) + + +### Bug Fixes + +* **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) +* **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) + + + + +# [1.1.0](https://github.com/angular-ui/bootstrap/compare/v1.0.3...v1.1.0) (2016-01-18) + + +## Bug Fixes + +* **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) +* **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) +* **datepicker:** fix popup CSS ([b3e6d17](https://github.com/angular-ui/bootstrap/commit/b3e6d17)), closes [#5258](https://github.com/angular-ui/bootstrap/issues/5258) +* **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) +* **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) +* **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) +* **paging:** garbage collect parent $watchers ([32b88f1](https://github.com/angular-ui/bootstrap/commit/32b88f1)), closes [#5276](https://github.com/angular-ui/bootstrap/issues/5276) +* **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) +* **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) +* **timepicker:** garbage collect parent watchers ([3f809af](https://github.com/angular-ui/bootstrap/commit/3f809af)), closes [#5277](https://github.com/angular-ui/bootstrap/issues/5277) +* **tooltip:** fix addClass signature ([6a2d91b](https://github.com/angular-ui/bootstrap/commit/6a2d91b)), closes [#5235](https://github.com/angular-ui/bootstrap/issues/5235) +* **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) +* **typeahead:** fix css selector ([00ac93e](https://github.com/angular-ui/bootstrap/commit/00ac93e)), closes [#5251](https://github.com/angular-ui/bootstrap/issues/5251) + +## Features + +* **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) +* **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) +* **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) +* **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) +* **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) +* **typeahead:** add title to matches ([f523361](https://github.com/angular-ui/bootstrap/commit/f523361)), closes [#5252](https://github.com/angular-ui/bootstrap/issues/5252) +* **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) + + +## BREAKING CHANGES + +* tab: This undoes the prior change to the template using div +elements. If one wishes to use div elements, one must override the +template in one's app and provide the necessary CSS + + + + +# [1.0.3](https://github.com/angular-ui/bootstrap/compare/v1.0.2...v1.0.3) (2016-01-12) + + +## Bug Fixes + +* **timepicker:** fix injection ([627a451](https://github.com/angular-ui/bootstrap/commit/627a451)) + + + + +# [1.0.2](https://github.com/angular-ui/bootstrap/compare/v1.0.1...v1.0.2) (2016-01-12) + + +## Bug Fixes + +* **tabs:** fix csp check ([74be568](https://github.com/angular-ui/bootstrap/commit/74be568)), closes [#5214](https://github.com/angular-ui/bootstrap/issues/5214) + + + + +# [1.0.1](https://github.com/angular-ui/bootstrap/compare/1.0.0...v1.0.1) (2016-01-12) + + +## Bug Fixes + +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) + +## Features + +* **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) +* **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) + + + + +# [1.0.0](https://github.com/angular-ui/bootstrap/compare/0.14.3...v1.0.0) (2016-01-08) + + +## Bug Fixes + +* **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) +* **accordion:** fix unexpected routing ([df211bd](https://github.com/angular-ui/bootstrap/commit/df211bd)), closes [#4792](https://github.com/angular-ui/bootstrap/issues/4792) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **datepicker:** update with alternative format ([fd88dcb](https://github.com/angular-ui/bootstrap/commit/fd88dcb)), closes [#5014](https://github.com/angular-ui/bootstrap/issues/5014) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **timepicker:** correct meridian toggle condition ([09ad740](https://github.com/angular-ui/bootstrap/commit/09ad740)), closes [#4435](https://github.com/angular-ui/bootstrap/issues/4435) +* **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) +* **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) +* **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) +* **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) +* **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) +* 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) + +## Features + +* **accordion:** remove deprecated code ([0010aff](https://github.com/angular-ui/bootstrap/commit/0010aff)), closes [#4706](https://github.com/angular-ui/bootstrap/issues/4706) +* **alert:** remove deprecated code ([21e852b](https://github.com/angular-ui/bootstrap/commit/21e852b)), closes [#4714](https://github.com/angular-ui/bootstrap/issues/4714) +* **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) +* **buttons:** remove deprecated code ([b549263](https://github.com/angular-ui/bootstrap/commit/b549263)), closes [#4716](https://github.com/angular-ui/bootstrap/issues/4716) +* **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) +* **carousel:** remove deprecated code ([b159b21](https://github.com/angular-ui/bootstrap/commit/b159b21)), closes [#4717](https://github.com/angular-ui/bootstrap/issues/4717) +* **collapse:** remove deprecated code ([bc004df](https://github.com/angular-ui/bootstrap/commit/bc004df)), closes [#4715](https://github.com/angular-ui/bootstrap/issues/4715) +* **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) +* **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) +* **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) +* **dateparser:** add Z support ([2fb812b](https://github.com/angular-ui/bootstrap/commit/2fb812b)), closes [#4831](https://github.com/angular-ui/bootstrap/issues/4831) +* **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) +* **dateparser:** remove deprecated code ([2d68f41](https://github.com/angular-ui/bootstrap/commit/2d68f41)), closes [#4718](https://github.com/angular-ui/bootstrap/issues/4718) +* **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) +* **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) +* **datepicker:** add semantic classes ([97c4333](https://github.com/angular-ui/bootstrap/commit/97c4333)), closes [#4761](https://github.com/angular-ui/bootstrap/issues/4761) +* **datepicker:** add timezone support ([09098f8](https://github.com/angular-ui/bootstrap/commit/09098f8)), closes [#5062](https://github.com/angular-ui/bootstrap/issues/5062) +* **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) +* **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) +* **datepicker:** remove deprecated code ([2fc3f21](https://github.com/angular-ui/bootstrap/commit/2fc3f21)), closes [#4708](https://github.com/angular-ui/bootstrap/issues/4708) +* **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) +* **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) +* **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) +* **dropdown:** remove deprecated code ([ca3a343](https://github.com/angular-ui/bootstrap/commit/ca3a343)), closes [#4719](https://github.com/angular-ui/bootstrap/issues/4719) +* **modal:** add appendTo support ([16d854c](https://github.com/angular-ui/bootstrap/commit/16d854c)), closes [#4599](https://github.com/angular-ui/bootstrap/issues/4599) +* **modal:** add closed promise ([e9c4977](https://github.com/angular-ui/bootstrap/commit/e9c4977)), closes [#4979](https://github.com/angular-ui/bootstrap/issues/4979) +* **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) +* **modal:** allow appending outside iframe ([80df015](https://github.com/angular-ui/bootstrap/commit/80df015)), closes [#4818](https://github.com/angular-ui/bootstrap/issues/4818) +* **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) +* **modal:** remove deprecated code ([a85d499](https://github.com/angular-ui/bootstrap/commit/a85d499)), closes [#4709](https://github.com/angular-ui/bootstrap/issues/4709) +* **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) +* **pager:** change controllerAs to pager ([5890248](https://github.com/angular-ui/bootstrap/commit/5890248)), closes [#4961](https://github.com/angular-ui/bootstrap/issues/4961) +* **pager:** move to separate component ([2a3314d](https://github.com/angular-ui/bootstrap/commit/2a3314d)), closes [#4935](https://github.com/angular-ui/bootstrap/issues/4935) +* **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) +* **pagination:** remove deprecated code ([75e493a](https://github.com/angular-ui/bootstrap/commit/75e493a)), closes [#4720](https://github.com/angular-ui/bootstrap/issues/4720) +* **pagination:** Show ellipsis when rotating ([3f307e4](https://github.com/angular-ui/bootstrap/commit/3f307e4)) +* **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) +* **position:** implement auto positioning ([d265113](https://github.com/angular-ui/bootstrap/commit/d265113)) +* **position:** remove deprecated code ([42fa28f](https://github.com/angular-ui/bootstrap/commit/42fa28f)), closes [#4721](https://github.com/angular-ui/bootstrap/issues/4721) +* **progressbar:** remove deprecated code ([0669b06](https://github.com/angular-ui/bootstrap/commit/0669b06)), closes [#4722](https://github.com/angular-ui/bootstrap/issues/4722) +* **rating:** remove deprecated code ([d844623](https://github.com/angular-ui/bootstrap/commit/d844623)), closes [#4723](https://github.com/angular-ui/bootstrap/issues/4723) +* **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) +* **tabs:** remove deprecated code ([1b75164](https://github.com/angular-ui/bootstrap/commit/1b75164)), closes [#4710](https://github.com/angular-ui/bootstrap/issues/4710) +* **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) +* **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) +* **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) +* **timepicker:** add support for seconds ([c7fa845](https://github.com/angular-ui/bootstrap/commit/c7fa845)), closes [#4768](https://github.com/angular-ui/bootstrap/issues/4768) +* **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) +* **timepicker:** remove deprecated code ([feb2b73](https://github.com/angular-ui/bootstrap/commit/feb2b73)), closes [#4712](https://github.com/angular-ui/bootstrap/issues/4712) +* **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) +* **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) +* **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) +* **tooltip:** remove deprecated code ([187f64c](https://github.com/angular-ui/bootstrap/commit/187f64c)), closes [#4713](https://github.com/angular-ui/bootstrap/issues/4713) +* **typeahead:** change to `appendTo` ([8637afc](https://github.com/angular-ui/bootstrap/commit/8637afc)), closes [#4797](https://github.com/angular-ui/bootstrap/issues/4797) +* 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) +* 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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **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) +* **typeahead:** remove deprecated code ([606d419](https://github.com/angular-ui/bootstrap/commit/606d419)), closes [#4711](https://github.com/angular-ui/bootstrap/issues/4711) + +## Reverts + +* **dateparser:** change template literal to ' ([f40066a](https://github.com/angular-ui/bootstrap/commit/f40066a)), closes [#5091](https://github.com/angular-ui/bootstrap/issues/5091) +* **progressbar:** remove min-width ([ed7e460](https://github.com/angular-ui/bootstrap/commit/ed7e460)), closes [#5141](https://github.com/angular-ui/bootstrap/issues/5141) + + +## BREAKING CHANGES + +* all: All of the deprecated services/directives/etc. are removed - one must use all prefixed versions instead +* pager: As part of the split of the pager component from the +pagination component, this changes the controllerAs use to `pager` from +`pagination` +* 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`. +* dropdown: remove class support for `uib-dropdown-menu` directive. +* All virtual templates in UI Bootstrap now are prefixed +with `uib/` - if one is overriding the templates via `$templateCache` path +or manually building the templates from the UI Bootstrap repository, one +needs to change the string used in the `$templateCache` representation +to have the new prefix +* typeahead: Usage before +```html +
+ +``` +After +```html +
+ +``` +```js +$scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadContainer')); +``` +* tab: This causes the cursor style to be removed from the tab - implement CSS on the `.uib-tab > div` selector, or similar, accordingly +* accordion: This causes the cursor style to be removed from the heading - implement CSS on the `accordion-toggle` class accordingly +* 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 + + + + +# [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...v0.14.3) (2015-10-23) + + +## Bug Fixes + * **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) * **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) * **collapse:** fix collapse animation timing ([6d1cd0f](https://github.com/angular-ui/bootstrap/commit/6d1cd0f)), closes [#4493](https://github.com/angular-ui/bootstrap/issues/4493) @@ -15,7 +510,7 @@ * **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) * **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) -### Features +## Features * **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) * **datepicker:** preserve timezone with model ([0d64aad](https://github.com/angular-ui/bootstrap/commit/0d64aad)), closes [#4676](https://github.com/angular-ui/bootstrap/issues/4676) @@ -24,10 +519,10 @@ -## [0.14.2](https://github.com/angular-ui/bootstrap/compare/0.14.1...v0.14.2) (2015-10-14) +# [0.14.2](https://github.com/angular-ui/bootstrap/compare/0.14.1...v0.14.2) (2015-10-14) -### Bug Fixes +## Bug Fixes * **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) * **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) @@ -36,10 +531,10 @@ -## [0.14.1](https://github.com/angular-ui/bootstrap/compare/0.14.0...v0.14.1) (2015-10-11) +# [0.14.1](https://github.com/angular-ui/bootstrap/compare/0.14.0...v0.14.1) (2015-10-11) -### Bug Fixes +## Bug Fixes * **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) * **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) @@ -53,7 +548,7 @@ * **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) * **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) -### Features +## Features * **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) @@ -63,7 +558,7 @@ # [0.14.0](https://github.com/angular-ui/bootstrap/compare/0.13.4...0.14.0) (2015-10-09) -### Bug Fixes +## Bug Fixes * **accordion:** coerce to boolean ([b864aa9](https://github.com/angular-ui/bootstrap/commit/b864aa9)), closes [#4385](https://github.com/angular-ui/bootstrap/issues/4385) * **accordion:** re-expose AccordionController ([5382226](https://github.com/angular-ui/bootstrap/commit/5382226)), closes [#4524](https://github.com/angular-ui/bootstrap/issues/4524) @@ -95,7 +590,7 @@ * **tooltip:** properly gc popupTimeout ([ff52f52](https://github.com/angular-ui/bootstrap/commit/ff52f52)), closes [#2786](https://github.com/angular-ui/bootstrap/issues/2786) * **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) -### Features +## Features * **accordion:** use uib- prefix ([0328a76](https://github.com/angular-ui/bootstrap/commit/0328a76)), closes [#4389](https://github.com/angular-ui/bootstrap/issues/4389) * **accordion:** use uib- prefix ([298ec8c](https://github.com/angular-ui/bootstrap/commit/298ec8c)), closes [#4503](https://github.com/angular-ui/bootstrap/issues/4503) @@ -124,21 +619,21 @@ * **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) * **typeahead:** add uib- prefix ([9e5e1a2](https://github.com/angular-ui/bootstrap/commit/9e5e1a2)), closes [#4542](https://github.com/angular-ui/bootstrap/issues/4542) -### Reverts +## Reverts * **dropdown:** undo adding of `open` class on body ([6f9f1fc](https://github.com/angular-ui/bootstrap/commit/6f9f1fc)) -### BREAKING CHANGES +## BREAKING CHANGES * Removes focus on datepicker on selection of a date via keyboard for accessibility reasons -## [0.13.4](https://github.com/angular-ui/bootstrap/compare/0.13.3...0.13.4) (2015-09-03) +# [0.13.4](https://github.com/angular-ui/bootstrap/compare/0.13.3...0.13.4) (2015-09-03) -### Bug Fixes +## Bug Fixes * **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) * **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) @@ -158,7 +653,7 @@ * **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) * **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) -### Features +## Features * **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) * **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) @@ -178,7 +673,7 @@ * **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) -### Breaking Changes +## Breaking Changes * **buttons** * hide nested `` elements on `btn-radio` and `btn-checkbox` directives. @@ -219,10 +714,10 @@ -### 0.13.3 (2015-08-09) +# 0.13.3 (2015-08-09) -#### Bug Fixes +## Bug Fixes * **accordion:** * 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)) @@ -251,7 +746,7 @@ * **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)) -#### Features +## Features * **accordion:** * add `controllerAs` support ([9865ee8e](https://github.com/angular-ui/bootstrap/commit/9865ee8e), closes [#4138](https://github.com/angular-ui/bootstrap/issues/4138)) @@ -283,7 +778,7 @@ * 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)) -#### Breaking Changes +## Breaking Changes * add `open` class to accordion group when expanded @@ -306,10 +801,10 @@ Closes #4080 -### 0.13.2 (2015-08-02) +# 0.13.2 (2015-08-02) -#### Bug Fixes +## Bug Fixes * **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)) * **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)) @@ -340,7 +835,7 @@ Closes #4080 * 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)) -#### Features +## Features * **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)) * **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)) @@ -352,10 +847,10 @@ Closes #4080 -### 0.13.1 (2015-07-23) +# 0.13.1 (2015-07-23) -#### Bug Fixes +## Bug Fixes * **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)) * **alert:** @@ -393,7 +888,7 @@ Closes #4080 * 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)) -#### Features +## Features * **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)) * **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)) @@ -413,10 +908,10 @@ Closes #4080 -## 0.13.0 (2015-05-02) +# 0.13.0 (2015-05-02) -#### Bug Fixes +## Bug Fixes * **accordion:** * Made accordion heading tab-able for IE9-10 ([6abad509](https://github.com/angular-ui/bootstrap/commit/6abad509cd4d44c3ca432f2f21c9ecea0a206b53)) @@ -477,7 +972,7 @@ Closes #4080 * $compile match template after adding to DOM ([03446c56](https://github.com/angular-ui/bootstrap/commit/03446c56335d5ee0970f9730af215fea1dd53f48)) -#### Features +## Features * **dateparser:** * 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)) @@ -516,8 +1011,8 @@ Closes #4080 ## Bug Fixes -- **tooltip:** - - incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef)) +- **tooltip:** + - incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef)) # 0.12.0 (2014-11-16) @@ -557,7 +1052,7 @@ Closes #4080 * `tooltip-trigger` and `popover-trigger` are no longer watched attributes. -([a65bea95](http://github.com/angular-ui/bootstrap/commit/a65bea95338802b026fd213805b095b5a0b5b393)) +([a65bea95](http://github.com/angular-ui/bootstrap/commit/a65bea95338802b026fd213805b095b5a0b5b393)) This affects both popovers and tooltips. The *triggers are now set up once* and can no longer be changed after initialization. @@ -581,125 +1076,125 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b ## Features -- **modal:** - - add backdropClass option, similar to windowClass option ([353e6127](http://github.com/angular-ui/bootstrap/commit/353e6127)) - - support alternative controllerAs syntax ([8d7c2a26](http://github.com/angular-ui/bootstrap/commit/8d7c2a26)) - - allow templateUrl to be a function ([990015fb](http://github.com/angular-ui/bootstrap/commit/990015fb)) +- **modal:** + - add backdropClass option, similar to windowClass option ([353e6127](http://github.com/angular-ui/bootstrap/commit/353e6127)) + - support alternative controllerAs syntax ([8d7c2a26](http://github.com/angular-ui/bootstrap/commit/8d7c2a26)) + - allow templateUrl to be a function ([990015fb](http://github.com/angular-ui/bootstrap/commit/990015fb)) ## Bug Fixes -- **alert:** - - correct binding of alert type class ([aa188aec](http://github.com/angular-ui/bootstrap/commit/aa188aec)) -- **dateparser:** - - do not parse if no format specified ([42cc3f26](http://github.com/angular-ui/bootstrap/commit/42cc3f26)) -- **datepicker:** - - correct `datepicker-mode` binding for popup ([63ae06c9](http://github.com/angular-ui/bootstrap/commit/63ae06c9)) - - memory leak fix for datepicker ([08c150e1](http://github.com/angular-ui/bootstrap/commit/08c150e1)) -- **dropdown:** - - close after selecting an item ([3ac3b487](http://github.com/angular-ui/bootstrap/commit/3ac3b487)) - - remove `C` restrictions to avoid conflicts ([7512b93f](http://github.com/angular-ui/bootstrap/commit/7512b93f)) -- **modal:** - - allow modal.{dismiss,close} to be called again ([1590920c](http://github.com/angular-ui/bootstrap/commit/1590920c)) - - add a work-around for transclusion scope ([0b31e865](http://github.com/angular-ui/bootstrap/commit/0b31e865)) - - allow in-lined controller-as controllers ([79105368](http://github.com/angular-ui/bootstrap/commit/79105368)) - - respect autofocus on child elements ([e62ab94a](http://github.com/angular-ui/bootstrap/commit/e62ab94a)) - - controllerAs not checked ([7b7cdf84](http://github.com/angular-ui/bootstrap/commit/7b7cdf84)) -- **tabs:** - - remove leading newline from a template ([a708fe6d](http://github.com/angular-ui/bootstrap/commit/a708fe6d)) -- **typeahead:** - - timeout cancellation when deleting characters ([5dc57927](http://github.com/angular-ui/bootstrap/commit/5dc57927)) - - allow multiple line expression ([c7db0df4](http://github.com/angular-ui/bootstrap/commit/c7db0df4)) - - replace ng-if with ng-show in matches popup ([a0be450d](http://github.com/angular-ui/bootstrap/commit/a0be450d)) +- **alert:** + - correct binding of alert type class ([aa188aec](http://github.com/angular-ui/bootstrap/commit/aa188aec)) +- **dateparser:** + - do not parse if no format specified ([42cc3f26](http://github.com/angular-ui/bootstrap/commit/42cc3f26)) +- **datepicker:** + - correct `datepicker-mode` binding for popup ([63ae06c9](http://github.com/angular-ui/bootstrap/commit/63ae06c9)) + - memory leak fix for datepicker ([08c150e1](http://github.com/angular-ui/bootstrap/commit/08c150e1)) +- **dropdown:** + - close after selecting an item ([3ac3b487](http://github.com/angular-ui/bootstrap/commit/3ac3b487)) + - remove `C` restrictions to avoid conflicts ([7512b93f](http://github.com/angular-ui/bootstrap/commit/7512b93f)) +- **modal:** + - allow modal.{dismiss,close} to be called again ([1590920c](http://github.com/angular-ui/bootstrap/commit/1590920c)) + - add a work-around for transclusion scope ([0b31e865](http://github.com/angular-ui/bootstrap/commit/0b31e865)) + - allow in-lined controller-as controllers ([79105368](http://github.com/angular-ui/bootstrap/commit/79105368)) + - respect autofocus on child elements ([e62ab94a](http://github.com/angular-ui/bootstrap/commit/e62ab94a)) + - controllerAs not checked ([7b7cdf84](http://github.com/angular-ui/bootstrap/commit/7b7cdf84)) +- **tabs:** + - remove leading newline from a template ([a708fe6d](http://github.com/angular-ui/bootstrap/commit/a708fe6d)) +- **typeahead:** + - timeout cancellation when deleting characters ([5dc57927](http://github.com/angular-ui/bootstrap/commit/5dc57927)) + - allow multiple line expression ([c7db0df4](http://github.com/angular-ui/bootstrap/commit/c7db0df4)) + - replace ng-if with ng-show in matches popup ([a0be450d](http://github.com/angular-ui/bootstrap/commit/a0be450d)) # 0.11.0 (2014-05-01) ## Features -- **accordion:** - - support `is-disabled` state ([9c43ae7c](http://github.com/angular-ui/bootstrap/commit/9c43ae7c)) -- **alert:** - - add WAI-ARIA markup ([9a2638bf](http://github.com/angular-ui/bootstrap/commit/9a2638bf)) -- **button:** - - allow uncheckable radio button ([82df4fb1](http://github.com/angular-ui/bootstrap/commit/82df4fb1)) -- **carousel:** - - Support swipe for touchscreen devices ([85140f84](http://github.com/angular-ui/bootstrap/commit/85140f84)) -- **dateParser:** - - add `dateParser` service ([bd2ae0ee](http://github.com/angular-ui/bootstrap/commit/bd2ae0ee)) -- **datepicker:** - - add `datepicker-mode`, `init-date` & today hint ([7f4b40eb](http://github.com/angular-ui/bootstrap/commit/7f4b40eb)) - - make widget accessible ([2423f6d4](http://github.com/angular-ui/bootstrap/commit/2423f6d4)) - - full six-week calendar ([b0b14343](http://github.com/angular-ui/bootstrap/commit/b0b14343)) -- **dropdown:** - - add WAI-ARIA attributes ([22ebd230](http://github.com/angular-ui/bootstrap/commit/22ebd230)) - - focus toggle element when opening or closing with Esc` ([f715d052](http://github.com/angular-ui/bootstrap/commit/f715d052)) -- **dropdownToggle:** - - support programmatic trigger & toggle callback ([ae31079c](http://github.com/angular-ui/bootstrap/commit/ae31079c)) - - add support for `escape` key ([1417c548](http://github.com/angular-ui/bootstrap/commit/1417c548)) -- **modal:** - - support custom template for modal window ([96def3d6](http://github.com/angular-ui/bootstrap/commit/96def3d6)) - - support modal window sizes ([976f6083](http://github.com/angular-ui/bootstrap/commit/976f6083)) - - improve accessibility - add role='dialog' ([60cee9dc](http://github.com/angular-ui/bootstrap/commit/60cee9dc)) -- **pagination:** - - plug into `ngModel` controller ([d65901cf](http://github.com/angular-ui/bootstrap/commit/d65901cf)) -- **progressbar:** - - make widget accessible ([9dfe3157](http://github.com/angular-ui/bootstrap/commit/9dfe3157)) -- **rating:** - - plug into `ngModel` controller ([47e227f6](http://github.com/angular-ui/bootstrap/commit/47e227f6)) - - make widget accessible ([4f56e60e](http://github.com/angular-ui/bootstrap/commit/4f56e60e)) -- **tooltip:** - - support more positioning options ([3704db9a](http://github.com/angular-ui/bootstrap/commit/3704db9a)) -- **typeahead:** - - add WAI-ARIA markup ([5ca23e97](http://github.com/angular-ui/bootstrap/commit/5ca23e97)) - - add `aria-owns` & `aria-activedescendant` roles ([4c76a858](http://github.com/angular-ui/bootstrap/commit/4c76a858)) +- **accordion:** + - support `is-disabled` state ([9c43ae7c](http://github.com/angular-ui/bootstrap/commit/9c43ae7c)) +- **alert:** + - add WAI-ARIA markup ([9a2638bf](http://github.com/angular-ui/bootstrap/commit/9a2638bf)) +- **button:** + - allow uncheckable radio button ([82df4fb1](http://github.com/angular-ui/bootstrap/commit/82df4fb1)) +- **carousel:** + - Support swipe for touchscreen devices ([85140f84](http://github.com/angular-ui/bootstrap/commit/85140f84)) +- **dateParser:** + - add `dateParser` service ([bd2ae0ee](http://github.com/angular-ui/bootstrap/commit/bd2ae0ee)) +- **datepicker:** + - add `datepicker-mode`, `init-date` & today hint ([7f4b40eb](http://github.com/angular-ui/bootstrap/commit/7f4b40eb)) + - make widget accessible ([2423f6d4](http://github.com/angular-ui/bootstrap/commit/2423f6d4)) + - full six-week calendar ([b0b14343](http://github.com/angular-ui/bootstrap/commit/b0b14343)) +- **dropdown:** + - add WAI-ARIA attributes ([22ebd230](http://github.com/angular-ui/bootstrap/commit/22ebd230)) + - focus toggle element when opening or closing with Esc` ([f715d052](http://github.com/angular-ui/bootstrap/commit/f715d052)) +- **dropdownToggle:** + - support programmatic trigger & toggle callback ([ae31079c](http://github.com/angular-ui/bootstrap/commit/ae31079c)) + - add support for `escape` key ([1417c548](http://github.com/angular-ui/bootstrap/commit/1417c548)) +- **modal:** + - support custom template for modal window ([96def3d6](http://github.com/angular-ui/bootstrap/commit/96def3d6)) + - support modal window sizes ([976f6083](http://github.com/angular-ui/bootstrap/commit/976f6083)) + - improve accessibility - add role='dialog' ([60cee9dc](http://github.com/angular-ui/bootstrap/commit/60cee9dc)) +- **pagination:** + - plug into `ngModel` controller ([d65901cf](http://github.com/angular-ui/bootstrap/commit/d65901cf)) +- **progressbar:** + - make widget accessible ([9dfe3157](http://github.com/angular-ui/bootstrap/commit/9dfe3157)) +- **rating:** + - plug into `ngModel` controller ([47e227f6](http://github.com/angular-ui/bootstrap/commit/47e227f6)) + - make widget accessible ([4f56e60e](http://github.com/angular-ui/bootstrap/commit/4f56e60e)) +- **tooltip:** + - support more positioning options ([3704db9a](http://github.com/angular-ui/bootstrap/commit/3704db9a)) +- **typeahead:** + - add WAI-ARIA markup ([5ca23e97](http://github.com/angular-ui/bootstrap/commit/5ca23e97)) + - add `aria-owns` & `aria-activedescendant` roles ([4c76a858](http://github.com/angular-ui/bootstrap/commit/4c76a858)) ## Bug Fixes -- **alert:** - - use interpolation for type attribute ([f0a129ad](http://github.com/angular-ui/bootstrap/commit/f0a129ad)) - - add `alert-dismissable` class ([794954af](http://github.com/angular-ui/bootstrap/commit/794954af)) -- **carousel:** - - correct glyphicon ([3b6ab25b](http://github.com/angular-ui/bootstrap/commit/3b6ab25b)) -- **datepicker:** - - remove unneeded date creation ([68cb2e5a](http://github.com/angular-ui/bootstrap/commit/68cb2e5a)) - - `Today` button should not set time ([e1993491](http://github.com/angular-ui/bootstrap/commit/e1993491)) - - mark input field as invalid if the date is invalid ([467dd159](http://github.com/angular-ui/bootstrap/commit/467dd159)) - - rename `dateFormat` to `datepickerPopup` in datepickerPopupConfig ([93da30d5](http://github.com/angular-ui/bootstrap/commit/93da30d5)) - - parse input using dateParser ([e0eb1bce](http://github.com/angular-ui/bootstrap/commit/e0eb1bce)) +- **alert:** + - use interpolation for type attribute ([f0a129ad](http://github.com/angular-ui/bootstrap/commit/f0a129ad)) + - add `alert-dismissable` class ([794954af](http://github.com/angular-ui/bootstrap/commit/794954af)) +- **carousel:** + - correct glyphicon ([3b6ab25b](http://github.com/angular-ui/bootstrap/commit/3b6ab25b)) +- **datepicker:** + - remove unneeded date creation ([68cb2e5a](http://github.com/angular-ui/bootstrap/commit/68cb2e5a)) + - `Today` button should not set time ([e1993491](http://github.com/angular-ui/bootstrap/commit/e1993491)) + - mark input field as invalid if the date is invalid ([467dd159](http://github.com/angular-ui/bootstrap/commit/467dd159)) + - rename `dateFormat` to `datepickerPopup` in datepickerPopupConfig ([93da30d5](http://github.com/angular-ui/bootstrap/commit/93da30d5)) + - parse input using dateParser ([e0eb1bce](http://github.com/angular-ui/bootstrap/commit/e0eb1bce)) - **dropdown:** - - use $animate for adding and removing classes ([e8d5fefc](http://github.com/angular-ui/bootstrap/commit/e8d5fefc)) - - unbind toggle element event on scope destroy ([890e2d37](http://github.com/angular-ui/bootstrap/commit/890e2d37)) - - do not call `on-toggle` initially ([004dd1de](http://github.com/angular-ui/bootstrap/commit/004dd1de)) - - ensure `on-toggle` works when `is-open` is not used ([06ad3bd5](http://github.com/angular-ui/bootstrap/commit/06ad3bd5)) + - use $animate for adding and removing classes ([e8d5fefc](http://github.com/angular-ui/bootstrap/commit/e8d5fefc)) + - unbind toggle element event on scope destroy ([890e2d37](http://github.com/angular-ui/bootstrap/commit/890e2d37)) + - do not call `on-toggle` initially ([004dd1de](http://github.com/angular-ui/bootstrap/commit/004dd1de)) + - ensure `on-toggle` works when `is-open` is not used ([06ad3bd5](http://github.com/angular-ui/bootstrap/commit/06ad3bd5)) - **modal:** - - destroy modal scope after animation end ([dfc36fd9](http://github.com/angular-ui/bootstrap/commit/dfc36fd9)) - - backdrop z-index when stacking modals ([94a7f593](http://github.com/angular-ui/bootstrap/commit/94a7f593)) - - give a reason of rejection when escape key pressed ([cb31b875](http://github.com/angular-ui/bootstrap/commit/cb31b875)) + - destroy modal scope after animation end ([dfc36fd9](http://github.com/angular-ui/bootstrap/commit/dfc36fd9)) + - backdrop z-index when stacking modals ([94a7f593](http://github.com/angular-ui/bootstrap/commit/94a7f593)) + - give a reason of rejection when escape key pressed ([cb31b875](http://github.com/angular-ui/bootstrap/commit/cb31b875)) - prevent default event when closing via escape key ([da951222](http://github.com/angular-ui/bootstrap/commit/da951222)) - toggle 'modal-open' class after animation ([4d641ca7](http://github.com/angular-ui/bootstrap/commit/4d641ca7)) -- **pagination:** - - take maxSize defaults into account ([a294c87f](http://github.com/angular-ui/bootstrap/commit/a294c87f)) -- **position:** - - remove deprecated body scrollTop and scrollLeft ([1ba07c1b](http://github.com/angular-ui/bootstrap/commit/1ba07c1b)) -- **progressbar:** - - allow fractional values for bar width ([0daa7a74](http://github.com/angular-ui/bootstrap/commit/0daa7a74)) - - number filter in bar template and only for percent ([378a9337](http://github.com/angular-ui/bootstrap/commit/378a9337)) -- **tabs:** - - fire deselect before select callback ([7474c47b](http://github.com/angular-ui/bootstrap/commit/7474c47b)) - - use interpolation for type attribute ([83ceb78a](http://github.com/angular-ui/bootstrap/commit/83ceb78a)) - - remove `tabbable` class required for left/right tabs ([19468331](http://github.com/angular-ui/bootstrap/commit/19468331)) -- **timepicker:** - - evaluate correctly the `readonly-input` attribute ([f9b6c496](http://github.com/angular-ui/bootstrap/commit/f9b6c496)) -- **tooltip:** - - animation causes tooltip to hide on show ([2b429f5d](http://github.com/angular-ui/bootstrap/commit/2b429f5d)) -- **typeahead:** - - correctly handle append to body attribute ([10785736](http://github.com/angular-ui/bootstrap/commit/10785736)) - - correctly higlight numeric matches ([09678b12](http://github.com/angular-ui/bootstrap/commit/09678b12)) - - loading callback updates after blur ([6a830116](http://github.com/angular-ui/bootstrap/commit/6a830116)) - - incompatibility with ng-focus ([d0024931](http://github.com/angular-ui/bootstrap/commit/d0024931)) +- **pagination:** + - take maxSize defaults into account ([a294c87f](http://github.com/angular-ui/bootstrap/commit/a294c87f)) +- **position:** + - remove deprecated body scrollTop and scrollLeft ([1ba07c1b](http://github.com/angular-ui/bootstrap/commit/1ba07c1b)) +- **progressbar:** + - allow fractional values for bar width ([0daa7a74](http://github.com/angular-ui/bootstrap/commit/0daa7a74)) + - number filter in bar template and only for percent ([378a9337](http://github.com/angular-ui/bootstrap/commit/378a9337)) +- **tabs:** + - fire deselect before select callback ([7474c47b](http://github.com/angular-ui/bootstrap/commit/7474c47b)) + - use interpolation for type attribute ([83ceb78a](http://github.com/angular-ui/bootstrap/commit/83ceb78a)) + - remove `tabbable` class required for left/right tabs ([19468331](http://github.com/angular-ui/bootstrap/commit/19468331)) +- **timepicker:** + - evaluate correctly the `readonly-input` attribute ([f9b6c496](http://github.com/angular-ui/bootstrap/commit/f9b6c496)) +- **tooltip:** + - animation causes tooltip to hide on show ([2b429f5d](http://github.com/angular-ui/bootstrap/commit/2b429f5d)) +- **typeahead:** + - correctly handle append to body attribute ([10785736](http://github.com/angular-ui/bootstrap/commit/10785736)) + - correctly higlight numeric matches ([09678b12](http://github.com/angular-ui/bootstrap/commit/09678b12)) + - loading callback updates after blur ([6a830116](http://github.com/angular-ui/bootstrap/commit/6a830116)) + - incompatibility with ng-focus ([d0024931](http://github.com/angular-ui/bootstrap/commit/d0024931)) ## Breaking Changes -- **alert:** +- **alert:** Use interpolation for type attribute. Before: @@ -722,13 +1217,13 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b ``` -- **datepicker:** +- **datepicker:** `show-weeks` is no longer a watched attribute `*-format` attributes have been renamed to `format-*` `min` attribute has been renamed to `min-date` `max` attribute has been renamed to `max-date` -`Open on focus` has been removed. Read more on this ([comment](https://github.com/angular-ui/bootstrap/pull/1922#issuecomment-40491716)). +`Open on focus` has been removed. Read more on this ([comment](https://github.com/angular-ui/bootstrap/pull/1922#issuecomment-40491716)). `dateFormat` renamed to `datepickerPopup` in datepickerPopupConfig - **dropdown:** @@ -742,18 +1237,18 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b * `on-select-page` is removed since `ng-change` can now be used. Before: - + ```html ``` - + After: ```html ``` - -- **rating:** + +- **rating:** `rating` is now integrated with `ngModelController`. * `value` is replaced from `ng-model`. @@ -762,53 +1257,53 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b ```html ``` - + After: - + ```html ``` - + - **tabs:** Use interpolation for type attribute. Before: - + ```html ``` - + After: - + ```html ``` - + # 0.10.0 (2014-01-13) _This release adds AngularJS 1.2 support_ ## Features -- **modal:** - - expose dismissAll on $modalStack ([bc8d21c1](http://github.com/angular-ui/bootstrap/commit/bc8d21c1)) +- **modal:** + - expose dismissAll on $modalStack ([bc8d21c1](http://github.com/angular-ui/bootstrap/commit/bc8d21c1)) ## Bug Fixes -- **datepicker:** - - evaluate `show-weeks` from `datepicker-options` ([92c1715f](http://github.com/angular-ui/bootstrap/commit/92c1715f)) -- **modal:** - - leaking watchers due to scope re-use ([0754ad7b](http://github.com/angular-ui/bootstrap/commit/0754ad7b)) - - support close animation ([1933488c](http://github.com/angular-ui/bootstrap/commit/1933488c)) -- **timepicker:** - - add correct type for meridian button ([bcf39efe](http://github.com/angular-ui/bootstrap/commit/bcf39efe)) -- **tooltip:** - - performance and scope fixes ([c0df3201](http://github.com/angular-ui/bootstrap/commit/c0df3201)) +- **datepicker:** + - evaluate `show-weeks` from `datepicker-options` ([92c1715f](http://github.com/angular-ui/bootstrap/commit/92c1715f)) +- **modal:** + - leaking watchers due to scope re-use ([0754ad7b](http://github.com/angular-ui/bootstrap/commit/0754ad7b)) + - support close animation ([1933488c](http://github.com/angular-ui/bootstrap/commit/1933488c)) +- **timepicker:** + - add correct type for meridian button ([bcf39efe](http://github.com/angular-ui/bootstrap/commit/bcf39efe)) +- **tooltip:** + - performance and scope fixes ([c0df3201](http://github.com/angular-ui/bootstrap/commit/c0df3201)) # 0.9.0 (2013-12-28) @@ -816,88 +1311,88 @@ _This release adds Bootstrap3 support_ ## Features -- **accordion:** - - convert to bootstrap3 panel styling ([458a9bd3](http://github.com/angular-ui/bootstrap/commit/458a9bd3)) -- **carousel:** - - some changes for Bootstrap3 ([1f632b65](http://github.com/angular-ui/bootstrap/commit/1f632b65)) -- **collapse:** - - make collapse work with bootstrap3 ([517dff6e](http://github.com/angular-ui/bootstrap/commit/517dff6e)) -- **datepicker:** - - update to Bootstrap 3 ([37684330](http://github.com/angular-ui/bootstrap/commit/37684330)) -- **modal:** - - added bootstrap3 support ([444c488d](http://github.com/angular-ui/bootstrap/commit/444c488d)) -- **pagination:** - - support bootstrap3 ([3db699d7](http://github.com/angular-ui/bootstrap/commit/3db699d7)) -- **progressbar:** - - update to bootstrap3 ([5bcff623](http://github.com/angular-ui/bootstrap/commit/5bcff623)) -- **rating:** - - update rating to bootstrap3 ([7e60284e](http://github.com/angular-ui/bootstrap/commit/7e60284e)) -- **tabs:** - - add nav-justified ([3199dd88](http://github.com/angular-ui/bootstrap/commit/3199dd88)) -- **timepicker:** - - restyled for bootstrap 3 ([6724a721](http://github.com/angular-ui/bootstrap/commit/6724a721)) -- **typeahead:** - - update to Bootstrap 3 ([eadf934a](http://github.com/angular-ui/bootstrap/commit/eadf934a)) +- **accordion:** + - convert to bootstrap3 panel styling ([458a9bd3](http://github.com/angular-ui/bootstrap/commit/458a9bd3)) +- **carousel:** + - some changes for Bootstrap3 ([1f632b65](http://github.com/angular-ui/bootstrap/commit/1f632b65)) +- **collapse:** + - make collapse work with bootstrap3 ([517dff6e](http://github.com/angular-ui/bootstrap/commit/517dff6e)) +- **datepicker:** + - update to Bootstrap 3 ([37684330](http://github.com/angular-ui/bootstrap/commit/37684330)) +- **modal:** + - added bootstrap3 support ([444c488d](http://github.com/angular-ui/bootstrap/commit/444c488d)) +- **pagination:** + - support bootstrap3 ([3db699d7](http://github.com/angular-ui/bootstrap/commit/3db699d7)) +- **progressbar:** + - update to bootstrap3 ([5bcff623](http://github.com/angular-ui/bootstrap/commit/5bcff623)) +- **rating:** + - update rating to bootstrap3 ([7e60284e](http://github.com/angular-ui/bootstrap/commit/7e60284e)) +- **tabs:** + - add nav-justified ([3199dd88](http://github.com/angular-ui/bootstrap/commit/3199dd88)) +- **timepicker:** + - restyled for bootstrap 3 ([6724a721](http://github.com/angular-ui/bootstrap/commit/6724a721)) +- **typeahead:** + - update to Bootstrap 3 ([eadf934a](http://github.com/angular-ui/bootstrap/commit/eadf934a)) ## Bug Fixes -- **alert:** - - update template to Bootstrap 3 ([dfc3b0bd](http://github.com/angular-ui/bootstrap/commit/dfc3b0bd)) -- **collapse:** - - Prevent consecutive transitions & tidy up code ([b0032d68](http://github.com/angular-ui/bootstrap/commit/b0032d68)) - - fixes after rebase ([dc02ad1d](http://github.com/angular-ui/bootstrap/commit/dc02ad1d)) -- **rating:** - - user glyhicon classes ([d221d517](http://github.com/angular-ui/bootstrap/commit/d221d517)) -- **timepicker:** - - fix look with bootstrap3 ([9613b61b](http://github.com/angular-ui/bootstrap/commit/9613b61b)) -- **tooltip:** - - re-position tooltip after draw ([a99b3608](http://github.com/angular-ui/bootstrap/commit/a99b3608)) +- **alert:** + - update template to Bootstrap 3 ([dfc3b0bd](http://github.com/angular-ui/bootstrap/commit/dfc3b0bd)) +- **collapse:** + - Prevent consecutive transitions & tidy up code ([b0032d68](http://github.com/angular-ui/bootstrap/commit/b0032d68)) + - fixes after rebase ([dc02ad1d](http://github.com/angular-ui/bootstrap/commit/dc02ad1d)) +- **rating:** + - user glyhicon classes ([d221d517](http://github.com/angular-ui/bootstrap/commit/d221d517)) +- **timepicker:** + - fix look with bootstrap3 ([9613b61b](http://github.com/angular-ui/bootstrap/commit/9613b61b)) +- **tooltip:** + - re-position tooltip after draw ([a99b3608](http://github.com/angular-ui/bootstrap/commit/a99b3608)) # 0.8.0 (2013-12-28) ## Features -- **datepicker:** - - option whether to display button bar in popup ([4d158e0d](http://github.com/angular-ui/bootstrap/commit/4d158e0d)) -- **modal:** - - add modal-open class to body on modal open ([e76512fa](http://github.com/angular-ui/bootstrap/commit/e76512fa)) -- **progressbar:** - - add `max` attribute & support transclusion ([365573ab](http://github.com/angular-ui/bootstrap/commit/365573ab)) -- **timepicker:** - - default meridian labels based on locale ([8b1ab79a](http://github.com/angular-ui/bootstrap/commit/8b1ab79a)) -- **typeahead:** - - add typeahead-append-to-body option ([dd8eac22](http://github.com/angular-ui/bootstrap/commit/dd8eac22)) +- **datepicker:** + - option whether to display button bar in popup ([4d158e0d](http://github.com/angular-ui/bootstrap/commit/4d158e0d)) +- **modal:** + - add modal-open class to body on modal open ([e76512fa](http://github.com/angular-ui/bootstrap/commit/e76512fa)) +- **progressbar:** + - add `max` attribute & support transclusion ([365573ab](http://github.com/angular-ui/bootstrap/commit/365573ab)) +- **timepicker:** + - default meridian labels based on locale ([8b1ab79a](http://github.com/angular-ui/bootstrap/commit/8b1ab79a)) +- **typeahead:** + - add typeahead-append-to-body option ([dd8eac22](http://github.com/angular-ui/bootstrap/commit/dd8eac22)) ## Bug Fixes -- **accordion:** - - correct `is-open` handling for dynamic groups ([9ec21286](http://github.com/angular-ui/bootstrap/commit/9ec21286)) -- **carousel:** - - cancel timer on scope destruction ([5b9d929c](http://github.com/angular-ui/bootstrap/commit/5b9d929c)) - - cancel goNext on scope destruction ([7515df45](http://github.com/angular-ui/bootstrap/commit/7515df45)) -- **collapse:** - - dont animate height changes from 0 to 0 ([81e014a8](http://github.com/angular-ui/bootstrap/commit/81e014a8)) -- **datepicker:** - - set default zero time after no date selected ([93cd0df8](http://github.com/angular-ui/bootstrap/commit/93cd0df8)) - - fire `ngChange` on today/clear button press ([6b1c68fb](http://github.com/angular-ui/bootstrap/commit/6b1c68fb)) - - remove datepicker's popup on scope destroy ([48955d69](http://github.com/angular-ui/bootstrap/commit/48955d69)) - - remove edge case position updates ([1fbcb5d6](http://github.com/angular-ui/bootstrap/commit/1fbcb5d6)) -- **modal:** - - put backdrop in before window ([d64f4a97](http://github.com/angular-ui/bootstrap/commit/d64f4a97)) +- **accordion:** + - correct `is-open` handling for dynamic groups ([9ec21286](http://github.com/angular-ui/bootstrap/commit/9ec21286)) +- **carousel:** + - cancel timer on scope destruction ([5b9d929c](http://github.com/angular-ui/bootstrap/commit/5b9d929c)) + - cancel goNext on scope destruction ([7515df45](http://github.com/angular-ui/bootstrap/commit/7515df45)) +- **collapse:** + - dont animate height changes from 0 to 0 ([81e014a8](http://github.com/angular-ui/bootstrap/commit/81e014a8)) +- **datepicker:** + - set default zero time after no date selected ([93cd0df8](http://github.com/angular-ui/bootstrap/commit/93cd0df8)) + - fire `ngChange` on today/clear button press ([6b1c68fb](http://github.com/angular-ui/bootstrap/commit/6b1c68fb)) + - remove datepicker's popup on scope destroy ([48955d69](http://github.com/angular-ui/bootstrap/commit/48955d69)) + - remove edge case position updates ([1fbcb5d6](http://github.com/angular-ui/bootstrap/commit/1fbcb5d6)) +- **modal:** + - put backdrop in before window ([d64f4a97](http://github.com/angular-ui/bootstrap/commit/d64f4a97)) - 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)) - - focus freshly opened modal ([709e679c](http://github.com/angular-ui/bootstrap/commit/709e679c)) - - properly animate backdrops on each modal opening ([672a557a](http://github.com/angular-ui/bootstrap/commit/672a557a)) -- **tabs:** - - make nested tabs work ([c9acebbe](http://github.com/angular-ui/bootstrap/commit/c9acebbe)) -- **tooltip:** - - update tooltip content when empty ([60515ae1](http://github.com/angular-ui/bootstrap/commit/60515ae1)) - - support IE8 ([5dd98238](http://github.com/angular-ui/bootstrap/commit/5dd98238)) - - unbind element events on scope destroy ([3fe7aa8c](http://github.com/angular-ui/bootstrap/commit/3fe7aa8c)) - - respect animate attribute ([54e614a8](http://github.com/angular-ui/bootstrap/commit/54e614a8)) + - focus freshly opened modal ([709e679c](http://github.com/angular-ui/bootstrap/commit/709e679c)) + - properly animate backdrops on each modal opening ([672a557a](http://github.com/angular-ui/bootstrap/commit/672a557a)) +- **tabs:** + - make nested tabs work ([c9acebbe](http://github.com/angular-ui/bootstrap/commit/c9acebbe)) +- **tooltip:** + - update tooltip content when empty ([60515ae1](http://github.com/angular-ui/bootstrap/commit/60515ae1)) + - support IE8 ([5dd98238](http://github.com/angular-ui/bootstrap/commit/5dd98238)) + - unbind element events on scope destroy ([3fe7aa8c](http://github.com/angular-ui/bootstrap/commit/3fe7aa8c)) + - respect animate attribute ([54e614a8](http://github.com/angular-ui/bootstrap/commit/54e614a8)) ## Breaking Changes -- **progressbar:** +- **progressbar:** The onFull/onEmpty handlers & auto/stacked types have been removed. To migrate your code change your markup like below. @@ -924,72 +1419,72 @@ _This release adds Bootstrap3 support_ ## Features -- **datepicker:** - - add i18n support for bar buttons in popup ([c6ba8d7f](http://github.com/angular-ui/bootstrap/commit/c6ba8d7f)) - - dynamic date format for popup ([aa3eaa91](http://github.com/angular-ui/bootstrap/commit/aa3eaa91)) - - datepicker-append-to-body attribute ([0cdc4609](http://github.com/angular-ui/bootstrap/commit/0cdc4609)) -- **dropdownToggle:** - - disable dropdown when it has the disabled class ([104bdd1b](http://github.com/angular-ui/bootstrap/commit/104bdd1b)) -- **tooltip:** - - add ability to enable / disable tooltip ([5d9bd058](http://github.com/angular-ui/bootstrap/commit/5d9bd058)) +- **datepicker:** + - add i18n support for bar buttons in popup ([c6ba8d7f](http://github.com/angular-ui/bootstrap/commit/c6ba8d7f)) + - dynamic date format for popup ([aa3eaa91](http://github.com/angular-ui/bootstrap/commit/aa3eaa91)) + - datepicker-append-to-body attribute ([0cdc4609](http://github.com/angular-ui/bootstrap/commit/0cdc4609)) +- **dropdownToggle:** + - disable dropdown when it has the disabled class ([104bdd1b](http://github.com/angular-ui/bootstrap/commit/104bdd1b)) +- **tooltip:** + - add ability to enable / disable tooltip ([5d9bd058](http://github.com/angular-ui/bootstrap/commit/5d9bd058)) ## Bug Fixes -- **accordion:** - - assign `is-open` to correct scope ([157f614a](http://github.com/angular-ui/bootstrap/commit/157f614a)) -- **collapse:** - - remove element height watching ([a72c635c](http://github.com/angular-ui/bootstrap/commit/a72c635c)) - - add the "in" class for expanded panels ([9eca35a8](http://github.com/angular-ui/bootstrap/commit/9eca35a8)) +- **accordion:** + - assign `is-open` to correct scope ([157f614a](http://github.com/angular-ui/bootstrap/commit/157f614a)) +- **collapse:** + - remove element height watching ([a72c635c](http://github.com/angular-ui/bootstrap/commit/a72c635c)) + - add the "in" class for expanded panels ([9eca35a8](http://github.com/angular-ui/bootstrap/commit/9eca35a8)) - **datepicker:** - - some IE8 compatibility improvements ([4540476f](http://github.com/angular-ui/bootstrap/commit/4540476f)) + - some IE8 compatibility improvements ([4540476f](http://github.com/angular-ui/bootstrap/commit/4540476f)) - set popup initial position in append-to-body case ([78a1e9d7](http://github.com/angular-ui/bootstrap/commit/78a1e9d7)) - - properly handle showWeeks config option ([570dba90](http://github.com/angular-ui/bootstrap/commit/570dba90)) -- **modal:** - - correctly close modals with no backdrop ([e55c2de3](http://github.com/angular-ui/bootstrap/commit/e55c2de3)) -- **pagination:** - - fix altering of current page caused by totals change ([81164dae](http://github.com/angular-ui/bootstrap/commit/81164dae)) - - handle extreme values for `total-items` ([8ecf93ed](http://github.com/angular-ui/bootstrap/commit/8ecf93ed)) -- **position:** - - correct positioning for SVG elements ([968e5407](http://github.com/angular-ui/bootstrap/commit/968e5407)) -- **tabs:** - - initial tab selection ([a08173ec](http://github.com/angular-ui/bootstrap/commit/a08173ec)) -- **timepicker:** - - use html5 for input elements ([53709f0f](http://github.com/angular-ui/bootstrap/commit/53709f0f)) -- **tooltip:** - - restore html-unsafe compatibility with AngularJS 1.2 ([08d8b21d](http://github.com/angular-ui/bootstrap/commit/08d8b21d)) - - hide tooltips when content becomes empty ([cf5c27ae](http://github.com/angular-ui/bootstrap/commit/cf5c27ae)) - - tackle DOM node and event handlers leak ([0d810acd](http://github.com/angular-ui/bootstrap/commit/0d810acd)) -- **typeahead:** - - do not set editable error when input is empty ([006986db](http://github.com/angular-ui/bootstrap/commit/006986db)) - - remove popup flickering ([dde804b6](http://github.com/angular-ui/bootstrap/commit/dde804b6)) - - don't show matches if an element is not focused ([d1f94530](http://github.com/angular-ui/bootstrap/commit/d1f94530)) - - fix loading callback when deleting characters ([0149eff6](http://github.com/angular-ui/bootstrap/commit/0149eff6)) - - prevent accidental form submission on ENTER ([253c49ff](http://github.com/angular-ui/bootstrap/commit/253c49ff)) - - evaluate matches source against a correct scope ([fd21214d](http://github.com/angular-ui/bootstrap/commit/fd21214d)) - - support IE8 ([0e9f9980](http://github.com/angular-ui/bootstrap/commit/0e9f9980)) + - properly handle showWeeks config option ([570dba90](http://github.com/angular-ui/bootstrap/commit/570dba90)) +- **modal:** + - correctly close modals with no backdrop ([e55c2de3](http://github.com/angular-ui/bootstrap/commit/e55c2de3)) +- **pagination:** + - fix altering of current page caused by totals change ([81164dae](http://github.com/angular-ui/bootstrap/commit/81164dae)) + - handle extreme values for `total-items` ([8ecf93ed](http://github.com/angular-ui/bootstrap/commit/8ecf93ed)) +- **position:** + - correct positioning for SVG elements ([968e5407](http://github.com/angular-ui/bootstrap/commit/968e5407)) +- **tabs:** + - initial tab selection ([a08173ec](http://github.com/angular-ui/bootstrap/commit/a08173ec)) +- **timepicker:** + - use html5 for input elements ([53709f0f](http://github.com/angular-ui/bootstrap/commit/53709f0f)) +- **tooltip:** + - restore html-unsafe compatibility with AngularJS 1.2 ([08d8b21d](http://github.com/angular-ui/bootstrap/commit/08d8b21d)) + - hide tooltips when content becomes empty ([cf5c27ae](http://github.com/angular-ui/bootstrap/commit/cf5c27ae)) + - tackle DOM node and event handlers leak ([0d810acd](http://github.com/angular-ui/bootstrap/commit/0d810acd)) +- **typeahead:** + - do not set editable error when input is empty ([006986db](http://github.com/angular-ui/bootstrap/commit/006986db)) + - remove popup flickering ([dde804b6](http://github.com/angular-ui/bootstrap/commit/dde804b6)) + - don't show matches if an element is not focused ([d1f94530](http://github.com/angular-ui/bootstrap/commit/d1f94530)) + - fix loading callback when deleting characters ([0149eff6](http://github.com/angular-ui/bootstrap/commit/0149eff6)) + - prevent accidental form submission on ENTER ([253c49ff](http://github.com/angular-ui/bootstrap/commit/253c49ff)) + - evaluate matches source against a correct scope ([fd21214d](http://github.com/angular-ui/bootstrap/commit/fd21214d)) + - support IE8 ([0e9f9980](http://github.com/angular-ui/bootstrap/commit/0e9f9980)) # 0.6.0 (2013-09-08) ## Features -- **modal:** - - rewrite $dialog as $modal ([d7a48523](http://github.com/angular-ui/bootstrap/commit/d7a48523)) - - add support for custom window settings ([015625d1](http://github.com/angular-ui/bootstrap/commit/015625d1)) - - expose $close and $dismiss options on modal's scope ([8d153acb](http://github.com/angular-ui/bootstrap/commit/8d153acb)) -- **pagination:** - - `total-items` & optional `items-per-page` API ([e55d9063](http://github.com/angular-ui/bootstrap/commit/e55d9063)) -- **rating:** - - add support for custom icons per instance ([20ab01ad](http://github.com/angular-ui/bootstrap/commit/20ab01ad)) -- **timepicker:** - - plug into `ngModel` controller ([b08e993f](http://github.com/angular-ui/bootstrap/commit/b08e993f)) +- **modal:** + - rewrite $dialog as $modal ([d7a48523](http://github.com/angular-ui/bootstrap/commit/d7a48523)) + - add support for custom window settings ([015625d1](http://github.com/angular-ui/bootstrap/commit/015625d1)) + - expose $close and $dismiss options on modal's scope ([8d153acb](http://github.com/angular-ui/bootstrap/commit/8d153acb)) +- **pagination:** + - `total-items` & optional `items-per-page` API ([e55d9063](http://github.com/angular-ui/bootstrap/commit/e55d9063)) +- **rating:** + - add support for custom icons per instance ([20ab01ad](http://github.com/angular-ui/bootstrap/commit/20ab01ad)) +- **timepicker:** + - plug into `ngModel` controller ([b08e993f](http://github.com/angular-ui/bootstrap/commit/b08e993f)) ## Bug Fixes -- **carousel:** - - correct reflow triggering on FFox and Safari ([d34f2de1](http://github.com/angular-ui/bootstrap/commit/d34f2de1)) -- **datepicker:** - - correctly manage focus without jQuery present ([d474824b](http://github.com/angular-ui/bootstrap/commit/d474824b)) - - compatibility with angular 1.1.5 and no jquery ([bf30898d](http://github.com/angular-ui/bootstrap/commit/bf30898d)) +- **carousel:** + - correct reflow triggering on FFox and Safari ([d34f2de1](http://github.com/angular-ui/bootstrap/commit/d34f2de1)) +- **datepicker:** + - correctly manage focus without jQuery present ([d474824b](http://github.com/angular-ui/bootstrap/commit/d474824b)) + - compatibility with angular 1.1.5 and no jquery ([bf30898d](http://github.com/angular-ui/bootstrap/commit/bf30898d)) - use $setViewValue for inner changes ([dd99f35d](http://github.com/angular-ui/bootstrap/commit/dd99f35d)) - **modal:** - insert backdrop before modal window ([d870f212](http://github.com/angular-ui/bootstrap/commit/d870f212)) @@ -998,21 +1493,21 @@ _This release adds Bootstrap3 support_ - backdrop should cover previously opened modals ([7fce2fe8](http://github.com/angular-ui/bootstrap/commit/7fce2fe8)) - allow replacing object with default options ([8e7fbf06](http://github.com/angular-ui/bootstrap/commit/8e7fbf06)) - **position:** - - fallback for IE8's scrollTop/Left for offset ([9aecd4ed](http://github.com/angular-ui/bootstrap/commit/9aecd4ed)) -- **tabs:** - - add DI array-style annotations ([aac4a0dd](http://github.com/angular-ui/bootstrap/commit/aac4a0dd)) - - evaluate `vertical` on parent scope ([9af6f96e](http://github.com/angular-ui/bootstrap/commit/9af6f96e)) -- **timepicker:** - - add type attribute for meridian button ([1f89fd4b](http://github.com/angular-ui/bootstrap/commit/1f89fd4b)) -- **tooltip:** - - remove placement='mouse' option ([17163c22](http://github.com/angular-ui/bootstrap/commit/17163c22)) -- **typeahead:** - - fix label rendering for equal model and items names ([5de71216](http://github.com/angular-ui/bootstrap/commit/5de71216)) - - set validity flag for non-editable inputs ([366e0c8a](http://github.com/angular-ui/bootstrap/commit/366e0c8a)) - - plug in front of existing parsers ([80cef614](http://github.com/angular-ui/bootstrap/commit/80cef614)) - - highlight return match if no query ([45dd9be1](http://github.com/angular-ui/bootstrap/commit/45dd9be1)) - - keep pop-up on clicking input ([5f9e270d](http://github.com/angular-ui/bootstrap/commit/5f9e270d)) - - remove dependency on ng-bind-html-unsafe ([75893393](http://github.com/angular-ui/bootstrap/commit/75893393)) + - fallback for IE8's scrollTop/Left for offset ([9aecd4ed](http://github.com/angular-ui/bootstrap/commit/9aecd4ed)) +- **tabs:** + - add DI array-style annotations ([aac4a0dd](http://github.com/angular-ui/bootstrap/commit/aac4a0dd)) + - evaluate `vertical` on parent scope ([9af6f96e](http://github.com/angular-ui/bootstrap/commit/9af6f96e)) +- **timepicker:** + - add type attribute for meridian button ([1f89fd4b](http://github.com/angular-ui/bootstrap/commit/1f89fd4b)) +- **tooltip:** + - remove placement='mouse' option ([17163c22](http://github.com/angular-ui/bootstrap/commit/17163c22)) +- **typeahead:** + - fix label rendering for equal model and items names ([5de71216](http://github.com/angular-ui/bootstrap/commit/5de71216)) + - set validity flag for non-editable inputs ([366e0c8a](http://github.com/angular-ui/bootstrap/commit/366e0c8a)) + - plug in front of existing parsers ([80cef614](http://github.com/angular-ui/bootstrap/commit/80cef614)) + - highlight return match if no query ([45dd9be1](http://github.com/angular-ui/bootstrap/commit/45dd9be1)) + - keep pop-up on clicking input ([5f9e270d](http://github.com/angular-ui/bootstrap/commit/5f9e270d)) + - remove dependency on ng-bind-html-unsafe ([75893393](http://github.com/angular-ui/bootstrap/commit/75893393)) ## Breaking Changes @@ -1023,7 +1518,7 @@ _This release adds Bootstrap3 support_ Check the documentation for the `$modal` service to migrate from `$dialog` -- **pagination:** +- **pagination:** API has undergone some changes in order to be easier to use. * `current-page` is replaced from `page`. * Number of pages is not defined by `num-pages`, but from `total-items` & @@ -1042,65 +1537,65 @@ Check the documentation for the `$modal` service to migrate from `$dialog` ``` -- **tooltip:** +- **tooltip:** The placment='mouse' is gone with no equivalent - + # 0.5.0 (2013-08-04) ## Features -- **buttons:** - - support dynamic true / false values in btn-checkbox ([3e30cd94](http://github.com/angular-ui/bootstrap/commit/3e30cd94)) -- **datepicker:** - - `ngModelController` plug & new `datepickerPopup` ([dab18336](http://github.com/angular-ui/bootstrap/commit/dab18336)) -- **rating:** - - added onHover and onLeave. ([5b1115e3](http://github.com/angular-ui/bootstrap/commit/5b1115e3)) -- **tabs:** - - added onDeselect callback, used similarly as onSelect ([fe47c9bb](http://github.com/angular-ui/bootstrap/commit/fe47c9bb)) - - add the ability to set the direction of the tabs ([220e7b60](http://github.com/angular-ui/bootstrap/commit/220e7b60)) -- **typeahead:** - - support custom templates for matched items ([e2238174](http://github.com/angular-ui/bootstrap/commit/e2238174)) - - expose index to custom templates ([5ffae83d](http://github.com/angular-ui/bootstrap/commit/5ffae83d)) +- **buttons:** + - support dynamic true / false values in btn-checkbox ([3e30cd94](http://github.com/angular-ui/bootstrap/commit/3e30cd94)) +- **datepicker:** + - `ngModelController` plug & new `datepickerPopup` ([dab18336](http://github.com/angular-ui/bootstrap/commit/dab18336)) +- **rating:** + - added onHover and onLeave. ([5b1115e3](http://github.com/angular-ui/bootstrap/commit/5b1115e3)) +- **tabs:** + - added onDeselect callback, used similarly as onSelect ([fe47c9bb](http://github.com/angular-ui/bootstrap/commit/fe47c9bb)) + - add the ability to set the direction of the tabs ([220e7b60](http://github.com/angular-ui/bootstrap/commit/220e7b60)) +- **typeahead:** + - support custom templates for matched items ([e2238174](http://github.com/angular-ui/bootstrap/commit/e2238174)) + - expose index to custom templates ([5ffae83d](http://github.com/angular-ui/bootstrap/commit/5ffae83d)) ## Bug Fixes -- **datepicker:** - - handle correctly `min`/`max` when cleared ([566bdd16](http://github.com/angular-ui/bootstrap/commit/566bdd16)) - - add type attribute for buttons ([25caf5fb](http://github.com/angular-ui/bootstrap/commit/25caf5fb)) -- **pagination:** - - handle `currentPage` number as string ([b1fa7bb8](http://github.com/angular-ui/bootstrap/commit/b1fa7bb8)) - - use interpolation for text attributes ([f45815cb](http://github.com/angular-ui/bootstrap/commit/f45815cb)) -- **popover:** - - don't unbind event handlers created by other directives ([56f624a2](http://github.com/angular-ui/bootstrap/commit/56f624a2)) - - correctly position popovers appended to body ([93a82af0](http://github.com/angular-ui/bootstrap/commit/93a82af0)) -- **rating:** - - evaluate `max` attribute on parent scope ([60619d51](http://github.com/angular-ui/bootstrap/commit/60619d51)) -- **tabs:** - - make tab contents be correctly connected to parent (#524) ([be7ecff0](http://github.com/angular-ui/bootstrap/commit/be7ecff0)) - - Make tabset template correctly use tabset attributes (#584) ([8868f236](http://github.com/angular-ui/bootstrap/commit/8868f236)) - - fix tab content compiling wrong (Closes #599, #631, #574) ([224bc2f5](http://github.com/angular-ui/bootstrap/commit/224bc2f5)) - - make tabs added with active=true be selected ([360cd5ca](http://github.com/angular-ui/bootstrap/commit/360cd5ca)) - - if tab is active at start, always select it ([ba1f741d](http://github.com/angular-ui/bootstrap/commit/ba1f741d)) -- **timepicker:** - - prevent date change ([ee741707](http://github.com/angular-ui/bootstrap/commit/ee741707)) - - added wheel event to enable mousewheel on Firefox ([8dc92afa](http://github.com/angular-ui/bootstrap/commit/8dc92afa)) -- **tooltip:** - - fix positioning inside scrolling element ([63ae7e12](http://github.com/angular-ui/bootstrap/commit/63ae7e12)) - - triggers should be local to tooltip instances ([58e8ef4f](http://github.com/angular-ui/bootstrap/commit/58e8ef4f)) - - correctly handle initial events unbinding ([4fd5bf43](http://github.com/angular-ui/bootstrap/commit/4fd5bf43)) - - bind correct 'hide' event handler ([d50b0547](http://github.com/angular-ui/bootstrap/commit/d50b0547)) -- **typeahead:** - - play nicelly with existing formatters ([d2df0b35](http://github.com/angular-ui/bootstrap/commit/d2df0b35)) - - properly render initial input value ([c4e169cb](http://github.com/angular-ui/bootstrap/commit/c4e169cb)) - - separate text field rendering and drop down rendering ([ea1e858a](http://github.com/angular-ui/bootstrap/commit/ea1e858a)) - - fixed waitTime functionality ([90a8aa79](http://github.com/angular-ui/bootstrap/commit/90a8aa79)) - - correctly close popup on match selection ([624fd5f5](http://github.com/angular-ui/bootstrap/commit/624fd5f5)) +- **datepicker:** + - handle correctly `min`/`max` when cleared ([566bdd16](http://github.com/angular-ui/bootstrap/commit/566bdd16)) + - add type attribute for buttons ([25caf5fb](http://github.com/angular-ui/bootstrap/commit/25caf5fb)) +- **pagination:** + - handle `currentPage` number as string ([b1fa7bb8](http://github.com/angular-ui/bootstrap/commit/b1fa7bb8)) + - use interpolation for text attributes ([f45815cb](http://github.com/angular-ui/bootstrap/commit/f45815cb)) +- **popover:** + - don't unbind event handlers created by other directives ([56f624a2](http://github.com/angular-ui/bootstrap/commit/56f624a2)) + - correctly position popovers appended to body ([93a82af0](http://github.com/angular-ui/bootstrap/commit/93a82af0)) +- **rating:** + - evaluate `max` attribute on parent scope ([60619d51](http://github.com/angular-ui/bootstrap/commit/60619d51)) +- **tabs:** + - make tab contents be correctly connected to parent (#524) ([be7ecff0](http://github.com/angular-ui/bootstrap/commit/be7ecff0)) + - Make tabset template correctly use tabset attributes (#584) ([8868f236](http://github.com/angular-ui/bootstrap/commit/8868f236)) + - fix tab content compiling wrong (Closes #599, #631, #574) ([224bc2f5](http://github.com/angular-ui/bootstrap/commit/224bc2f5)) + - make tabs added with active=true be selected ([360cd5ca](http://github.com/angular-ui/bootstrap/commit/360cd5ca)) + - if tab is active at start, always select it ([ba1f741d](http://github.com/angular-ui/bootstrap/commit/ba1f741d)) +- **timepicker:** + - prevent date change ([ee741707](http://github.com/angular-ui/bootstrap/commit/ee741707)) + - added wheel event to enable mousewheel on Firefox ([8dc92afa](http://github.com/angular-ui/bootstrap/commit/8dc92afa)) +- **tooltip:** + - fix positioning inside scrolling element ([63ae7e12](http://github.com/angular-ui/bootstrap/commit/63ae7e12)) + - triggers should be local to tooltip instances ([58e8ef4f](http://github.com/angular-ui/bootstrap/commit/58e8ef4f)) + - correctly handle initial events unbinding ([4fd5bf43](http://github.com/angular-ui/bootstrap/commit/4fd5bf43)) + - bind correct 'hide' event handler ([d50b0547](http://github.com/angular-ui/bootstrap/commit/d50b0547)) +- **typeahead:** + - play nicelly with existing formatters ([d2df0b35](http://github.com/angular-ui/bootstrap/commit/d2df0b35)) + - properly render initial input value ([c4e169cb](http://github.com/angular-ui/bootstrap/commit/c4e169cb)) + - separate text field rendering and drop down rendering ([ea1e858a](http://github.com/angular-ui/bootstrap/commit/ea1e858a)) + - fixed waitTime functionality ([90a8aa79](http://github.com/angular-ui/bootstrap/commit/90a8aa79)) + - correctly close popup on match selection ([624fd5f5](http://github.com/angular-ui/bootstrap/commit/624fd5f5)) ## Breaking Changes -- **pagination:** +- **pagination:** The 'first-text', 'previous-text', 'next-text' and 'last-text' attributes are now interpolated. @@ -1134,89 +1629,89 @@ The placment='mouse' is gone with no equivalent ## Features -- **buttons:** - - support dynamic values in btn-radio ([e8c5b548](http://github.com/angular-ui/bootstrap/commit/e8c5b548)) -- **carousel:** - - add option to prevent pause ([5f895c13](http://github.com/angular-ui/bootstrap/commit/5f895c13)) -- **datepicker:** - - add datepicker directive ([30a00a07](http://github.com/angular-ui/bootstrap/commit/30a00a07)) -- **pagination:** - - option for different mode when maxSize ([a023d082](http://github.com/angular-ui/bootstrap/commit/a023d082)) - - add pager directive ([d9526475](http://github.com/angular-ui/bootstrap/commit/d9526475)) -- **tabs:** - - Change directive name, add features ([c5326595](http://github.com/angular-ui/bootstrap/commit/c5326595)) - - support disabled state ([2b78dd16](http://github.com/angular-ui/bootstrap/commit/2b78dd16)) - - add support for vertical option ([88d17a75](http://github.com/angular-ui/bootstrap/commit/88d17a75)) - - add support for other navigation types, like 'pills' ([53e0a39f](http://github.com/angular-ui/bootstrap/commit/53e0a39f)) -- **timepicker:** - - add timepicker directive ([9bc5207b](http://github.com/angular-ui/bootstrap/commit/9bc5207b)) -- **tooltip:** +- **buttons:** + - support dynamic values in btn-radio ([e8c5b548](http://github.com/angular-ui/bootstrap/commit/e8c5b548)) +- **carousel:** + - add option to prevent pause ([5f895c13](http://github.com/angular-ui/bootstrap/commit/5f895c13)) +- **datepicker:** + - add datepicker directive ([30a00a07](http://github.com/angular-ui/bootstrap/commit/30a00a07)) +- **pagination:** + - option for different mode when maxSize ([a023d082](http://github.com/angular-ui/bootstrap/commit/a023d082)) + - add pager directive ([d9526475](http://github.com/angular-ui/bootstrap/commit/d9526475)) +- **tabs:** + - Change directive name, add features ([c5326595](http://github.com/angular-ui/bootstrap/commit/c5326595)) + - support disabled state ([2b78dd16](http://github.com/angular-ui/bootstrap/commit/2b78dd16)) + - add support for vertical option ([88d17a75](http://github.com/angular-ui/bootstrap/commit/88d17a75)) + - add support for other navigation types, like 'pills' ([53e0a39f](http://github.com/angular-ui/bootstrap/commit/53e0a39f)) +- **timepicker:** + - add timepicker directive ([9bc5207b](http://github.com/angular-ui/bootstrap/commit/9bc5207b)) +- **tooltip:** - add mouse placement option ([ace7bc60](http://github.com/angular-ui/bootstrap/commit/ace7bc60)) - - add *-append-to-body attribute ([d0896263](http://github.com/angular-ui/bootstrap/commit/d0896263)) - - add custom trigger support ([dfa53155](http://github.com/angular-ui/bootstrap/commit/dfa53155)) -- **typeahead:** - - support typeahead-on-select callback ([91ac17c9](http://github.com/angular-ui/bootstrap/commit/91ac17c9)) - - support wait-ms option ([7f35a3f2](http://github.com/angular-ui/bootstrap/commit/7f35a3f2)) + - add *-append-to-body attribute ([d0896263](http://github.com/angular-ui/bootstrap/commit/d0896263)) + - add custom trigger support ([dfa53155](http://github.com/angular-ui/bootstrap/commit/dfa53155)) +- **typeahead:** + - support typeahead-on-select callback ([91ac17c9](http://github.com/angular-ui/bootstrap/commit/91ac17c9)) + - support wait-ms option ([7f35a3f2](http://github.com/angular-ui/bootstrap/commit/7f35a3f2)) ## Bug Fixes -- **accordion:** +- **accordion:** - allow accordion heading directives as attributes. ([25f6e55c](http://github.com/angular-ui/bootstrap/commit/25f6e55c)) -- **carousel:** - - do not allow user to change slide if transitioning ([1d19663f](http://github.com/angular-ui/bootstrap/commit/1d19663f)) - - make slide 'active' binding optional ([17d6c3b5](http://github.com/angular-ui/bootstrap/commit/17d6c3b5)) - - fix error with deleting multiple slides at once ([3fcb70f0](http://github.com/angular-ui/bootstrap/commit/3fcb70f0)) -- **dialog:** - - remove dialogOpenClass to get in line with v2.3 ([f009b23f](http://github.com/angular-ui/bootstrap/commit/f009b23f)) -- **pagination:** - - bind *-text attributes ([e1bff6b7](http://github.com/angular-ui/bootstrap/commit/e1bff6b7)) -- **progressbar:** - - user `percent` attribute instead of `value`. ([58efec80](http://github.com/angular-ui/bootstrap/commit/58efec80)) -- **tooltip:** - - fix positioning error when appendToBody is set to true ([76fee1f9](http://github.com/angular-ui/bootstrap/commit/76fee1f9)) - - close tooltips appended to body on location change ([041261b5](http://github.com/angular-ui/bootstrap/commit/041261b5)) - - tooltips will hide on scope.$destroy ([3e5a58e5](http://github.com/angular-ui/bootstrap/commit/3e5a58e5)) - - support of custom $interpolate.startSymbol ([88c94ee6](http://github.com/angular-ui/bootstrap/commit/88c94ee6)) - - make sure tooltip scope is evicted from cache ([9246905a](http://github.com/angular-ui/bootstrap/commit/9246905a)) -- **typeahead:** - - return focus to the input after selecting a suggestion ([04a21e33](http://github.com/angular-ui/bootstrap/commit/04a21e33)) +- **carousel:** + - do not allow user to change slide if transitioning ([1d19663f](http://github.com/angular-ui/bootstrap/commit/1d19663f)) + - make slide 'active' binding optional ([17d6c3b5](http://github.com/angular-ui/bootstrap/commit/17d6c3b5)) + - fix error with deleting multiple slides at once ([3fcb70f0](http://github.com/angular-ui/bootstrap/commit/3fcb70f0)) +- **dialog:** + - remove dialogOpenClass to get in line with v2.3 ([f009b23f](http://github.com/angular-ui/bootstrap/commit/f009b23f)) +- **pagination:** + - bind *-text attributes ([e1bff6b7](http://github.com/angular-ui/bootstrap/commit/e1bff6b7)) +- **progressbar:** + - user `percent` attribute instead of `value`. ([58efec80](http://github.com/angular-ui/bootstrap/commit/58efec80)) +- **tooltip:** + - fix positioning error when appendToBody is set to true ([76fee1f9](http://github.com/angular-ui/bootstrap/commit/76fee1f9)) + - close tooltips appended to body on location change ([041261b5](http://github.com/angular-ui/bootstrap/commit/041261b5)) + - tooltips will hide on scope.$destroy ([3e5a58e5](http://github.com/angular-ui/bootstrap/commit/3e5a58e5)) + - support of custom $interpolate.startSymbol ([88c94ee6](http://github.com/angular-ui/bootstrap/commit/88c94ee6)) + - make sure tooltip scope is evicted from cache ([9246905a](http://github.com/angular-ui/bootstrap/commit/9246905a)) +- **typeahead:** + - return focus to the input after selecting a suggestion ([04a21e33](http://github.com/angular-ui/bootstrap/commit/04a21e33)) ## Breaking Changes -- **pagination:** +- **pagination:** The 'first-text', 'previous-text', 'next-text' and 'last-text' attributes are now binded to parent scope. To migrate your code, surround the text of these attributes with quotes. Before: - + ```html ``` After: - + ```html ``` -- **progressbar:** +- **progressbar:** The 'value' is replaced by 'percent'. Before: - + ```html ``` After: - + ```html ``` -- **tabs:** +- **tabs:** The 'tabs' directive has been renamed to 'tabset', and the 'pane' directive has been renamed to 'tab'. @@ -1248,7 +1743,7 @@ The placment='mouse' is gone with no equivalent ``` - + # 0.3.0 (2013-04-30) ## Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8db18e8..40be9ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Firstly, please go over our FAQ: https://github.com/angular-ui/bootstrap/wiki/FAQ -Please, 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 questions tagged with `angular-ui-bootstrap`. +Please, 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`. StackOverflow is a much better place to ask questions since: * there are hundreds of people willing to help on StackOverflow @@ -28,7 +28,7 @@ The best part is that you don't need to create plunks from scratch - you can use Unfortunately 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. -## You want to contribute some code? +## You want to contribute some code? We 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: @@ -42,4 +42,4 @@ We are always looking for the quality contributions and will be happy to accept * Please assure that you are submitting quality code, specifically make sure that: * 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 * 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 - * your commits conform to the conventions established [here](https://github.com/ajoslin/conventional-changelog/blob/master/conventions/angular.md) + * your commits conform to the conventions established [here](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md) diff --git a/Gruntfile.js b/Gruntfile.js index 350b307..783344e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,6 @@ -var markdown = require('node-markdown').Markdown; +var marked = require('marked'); var fs = require('fs'); +var _ = require('lodash'); module.exports = function(grunt) { require('load-grunt-tasks')(grunt); @@ -8,8 +9,8 @@ module.exports = function(grunt) { grunt.util.linefeed = '\n'; grunt.initConfig({ - ngversion: '1.4.7', - bsversion: '3.3.5', + ngversion: '1.5.3', + bsversion: '3.3.6', modules: [],//to be filled in by build task pkg: grunt.file.readJSON('package.json'), dist: 'dist', @@ -22,12 +23,14 @@ module.exports = function(grunt) { cssInclude: '', cssFileBanner: '/* Include this file in your html if you are using the CSP mode. */\n\n', cssFileDest: '<%= dist %>/<%= filename %>-<%= pkg.version %>-csp.css', - banner: ['/*', - ' * <%= pkg.name %>', - ' * <%= pkg.homepage %>\n', - ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>', - ' * License: <%= pkg.license %>', - ' */\n'].join('\n') + banner: [ + '/*', + ' * <%= pkg.name %>', + ' * <%= pkg.homepage %>\n', + ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>', + ' * License: <%= pkg.license %>', + ' */' + ].join('\n') }, delta: { docs: { @@ -39,7 +42,7 @@ module.exports = function(grunt) { tasks: ['html2js', 'karma:watch:run'] }, js: { - files: ['src/**/*.js'], + files: ['src/**/*.js', '!src/**/index.js'], tasks: ['karma:watch:run'] } }, @@ -103,7 +106,7 @@ module.exports = function(grunt) { module: null, // no bundle module for all the html2js templates base: '.', rename: function(moduleName) { - return 'uib/' + moduleName; + return `uib/${moduleName}`; } }, files: [{ @@ -158,7 +161,7 @@ module.exports = function(grunt) { } }, shell: { - //We use %version% and evluate it at run-time, because <%= pkg.version %> + //We use %version% and evaluate it at run-time, because <%= pkg.version %> //is only evaluated once 'release-prepare': [ 'grunt before-test after-test', @@ -194,7 +197,7 @@ module.exports = function(grunt) { // Default task. grunt.registerTask('default', ['before-test', 'test', 'after-test']); - grunt.registerTask('enforce', 'Install commit message enforce script if it doesn\'t exist', function() { + grunt.registerTask('enforce', `Install commit message enforce script if it doesn't exist`, function() { if (!grunt.file.exists('.git/hooks/commit-msg')) { grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg'); require('fs').chmodSync('.git/hooks/commit-msg', '0755'); @@ -219,28 +222,28 @@ module.exports = function(grunt) { }); } function enquote(str) { - return '"' + str + '"'; + return `"${str}"`; } function enquoteUibDir(str) { - return enquote('uib/' + str); + return enquote(`uib/${str}`); } var module = { name: name, - moduleName: enquote('ui.bootstrap.' + name), + moduleName: enquote(`ui.bootstrap.${name}`), displayName: ucwords(breakup(name, ' ')), - srcFiles: grunt.file.expand('src/'+name+'/*.js'), - cssFiles: grunt.file.expand('src/'+name+'/*.css'), - tplFiles: grunt.file.expand('template/'+name+'/*.html'), - tpljsFiles: grunt.file.expand('template/'+name+'/*.html.js'), - tplModules: grunt.file.expand('template/'+name+'/*.html').map(enquoteUibDir), + srcFiles: grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`]), + cssFiles: grunt.file.expand(`src/${name}/*.css`), + tplFiles: grunt.file.expand(`template/${name}/*.html`), + tpljsFiles: grunt.file.expand(`template/${name}/*.html.js`), + tplModules: grunt.file.expand(`template/${name}/*.html`).map(enquoteUibDir), dependencies: dependenciesForModule(name), docs: { - md: grunt.file.expand('src/'+name+'/docs/*.md') - .map(grunt.file.read).map(markdown).join('\n'), - js: grunt.file.expand('src/'+name+'/docs/*.js') + md: grunt.file.expand(`src/${name}/docs/*.md`) + .map(grunt.file.read).map((str) => marked(str)).join('\n'), + js: grunt.file.expand(`src/${name}/docs/*.js`) .map(grunt.file.read).join('\n'), - html: grunt.file.expand('src/'+name+'/docs/*.html') + html: grunt.file.expand(`src/${name}/docs/*.html`) .map(grunt.file.read).join('\n') } }; @@ -249,7 +252,7 @@ module.exports = function(grunt) { css: [], js: [] }; - module.cssFiles.forEach(processCSS.bind(null, styles, true)); + module.cssFiles.forEach(processCSS.bind(null, module.name, styles, true)); if (styles.css.length) { module.css = styles.css.join('\n'); module.cssJs = styles.js.join('\n'); @@ -261,7 +264,7 @@ module.exports = function(grunt) { function dependenciesForModule(name) { var deps = []; - grunt.file.expand('src/' + name + '/*.js') + grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`]) .map(grunt.file.read) .forEach(function(contents) { //Strategy: find where module is declared, @@ -299,19 +302,17 @@ module.exports = function(grunt) { } else { grunt.file.expand({ filter: 'isDirectory', cwd: '.' - }, 'src/*').forEach(function(dir) { + }, 'src/*').forEach((dir) => { findModule(dir.split('/')[1]); }); } var modules = grunt.config('modules'); grunt.config('srcModules', _.pluck(modules, 'moduleName')); - grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} )); + grunt.config('tplModules', _.pluck(modules, 'tplModules').filter((tpls) => tpls.length > 0)); grunt.config('demoModules', modules - .filter(function(module) { - return module.docs.md && module.docs.js && module.docs.html; - }) - .sort(function(a, b) { + .filter((module) => module.docs.md && module.docs.js && module.docs.html) + .sort((a, b) => { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; @@ -330,9 +331,7 @@ module.exports = function(grunt) { } var moduleFileMapping = _.clone(modules, true); - moduleFileMapping.forEach(function (module) { - delete module.docs; - }); + moduleFileMapping.forEach((module) => delete module.docs); grunt.config('moduleFileMapping', moduleFileMapping); @@ -348,14 +347,14 @@ module.exports = function(grunt) { grunt.task.run(['concat', 'uglify', 'makeModuleMappingFile', 'makeRawFilesJs', 'makeVersionsMappingFile']); }); - grunt.registerTask('test', 'Run tests on singleRun karma server', function () { + grunt.registerTask('test', 'Run tests on singleRun karma server', function() { //this task can be executed in 3 different environments: local, Travis-CI and Jenkins-CI //we need to take settings for each one into account if (process.env.TRAVIS) { grunt.task.run('karma:travis'); } else { var isToRunJenkinsTask = !!this.args.length; - if(grunt.option('coverage')) { + if (grunt.option('coverage')) { var karmaOptions = grunt.config.get('karma.options'), coverageOpts = grunt.config.get('karma.coverage'); grunt.util._.extend(karmaOptions, coverageOpts); @@ -365,7 +364,7 @@ module.exports = function(grunt) { } }); - grunt.registerTask('makeModuleMappingFile', function () { + grunt.registerTask('makeModuleMappingFile', function() { var _ = grunt.util._; var moduleMappingJs = 'dist/assets/module-mapping.json'; var moduleMappings = grunt.config('moduleFileMapping'); @@ -375,7 +374,7 @@ module.exports = function(grunt) { grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.'); }); - grunt.registerTask('makeRawFilesJs', function () { + grunt.registerTask('makeRawFilesJs', function() { var _ = grunt.util._; var jsFilename = 'dist/assets/raw-files.json'; var genRawFilesJs = require('./misc/raw-files-generator'); @@ -384,7 +383,7 @@ module.exports = function(grunt) { grunt.config('meta.banner'), grunt.config('meta.cssFileBanner')); }); - grunt.registerTask('makeVersionsMappingFile', function () { + grunt.registerTask('makeVersionsMappingFile', function() { var done = this.async(); var exec = require('child_process').exec; @@ -395,17 +394,19 @@ module.exports = function(grunt) { // Let's remove the oldest 14 versions. var versions = stdout.split('\n').slice(0, -14); var jsContent = versions.map(function(version) { + version = version.replace(/^v/, ''); return { version: version, - url: '/bootstrap/versioned-docs/' + version + url: `/bootstrap/versioned-docs/${version}` }; }); + jsContent = _.sortBy(jsContent, 'version').reverse(); jsContent.unshift({ version: 'Current', url: '/bootstrap' }); grunt.file.write(versionsMappingFile, JSON.stringify(jsContent)); - grunt.log.writeln('File ' + versionsMappingFile.cyan + ' created.'); + grunt.log.writeln(`File ${versionsMappingFile.cyan} created.`); done(); }); @@ -415,12 +416,12 @@ module.exports = function(grunt) { * Logic from AngularJS * https://github.com/angular/angular.js/blob/36831eccd1da37c089f2141a2c073a6db69f3e1d/lib/grunt/utils.js#L121-L145 */ - function processCSS(state, minify, file) { + function processCSS(moduleName, state, minify, file) { var css = fs.readFileSync(file).toString(), js; state.css.push(css); - if(minify){ + if (minify) { css = css .replace(/\r?\n/g, '') .replace(/\/\*.*?\*\//g, '') @@ -435,7 +436,7 @@ module.exports = function(grunt) { .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/\r?\n/g, '\\n'); - js = "angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp() && angular.element(document).find('head').prepend(''); })"; + js = `angular.module('ui.bootstrap.${moduleName}').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uib${_.capitalize(moduleName)}Css && angular.element(document).find('head').prepend(''); angular.$$uib${_.capitalize(moduleName)}Css = true; });`; state.js.push(js); return state; diff --git a/LICENSE b/LICENSE index 18db5a5..64050c0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2012-2015 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112 +Copyright (c) 2012-2016 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4544c6b..a2e08be 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - [NuGet](#install-with-nuget) - [Custom](#custom-build) - [Manual](#manual-download) +- [Webpack / JSPM](#webpack--jspm) - [Support](#support) - [FAQ](#faq) - [Code of Conduct](#code-of-conduct) @@ -30,14 +31,23 @@ Do you want to see directives in action? Visit http://angular-ui.github.io/boots # Angular 2 -Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core). +Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core). # Installation Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required. -Note: 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. +*Notes:* +* 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. +* 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. -Additionally, it is strongly recommended that for UI Bootstrap 0.13.3 and higher you use Angular 1.3.18 or higher due to animation fixes. +## Angular Requirements +* UI Bootstrap 1.0 and higher _requires_ Angular 1.4.x or higher and it has been tested with Angular 1.4.8. +* UI Bootstrap 0.14.3 is the _last_ version that supports Angular 1.3.x. +* UI Bootstrap 0.12.0 is the _last_ version that supports Angular 1.2.x. + +## Bootstrap Requirements +* UI Bootstrap requires Bootstrap CSS version 3.x or higher and it has been tested with Bootstrap CSS 3.3.6. +* UI Bootstrap 0.8 is the _last_ version that supports Bootstrap CSS 2.3.x. #### Install with NPM @@ -79,12 +89,48 @@ When you are done downloading all the dependencies and project files the only re angular.module('myModule', ['ui.bootstrap']); ``` -If you're a Browserify or Webpack user, you can do: +# Webpack / JSPM + +To use this project with webpack, follow the [NPM](#install-with-npm) instructions. +Now, if you want to use only the accordion, you can do: + +```js +import accordion from 'angular-ui-bootstrap/src/accordion'; + +angular.module('myModule', [accordion]); +``` + +You can import all the pieces you need in the same way: + +```js +import accordion from 'angular-ui-bootstrap/src/accordion'; +import datepicker from 'angular-ui-bootstrap/src/datepicker'; + +angular.module('myModule', [accordion, datepicker]); +``` + +This will load all the dependencies (if any) and also the templates (if any). + +Be sure to have a loader able to process `css` files like `css-loader`. + +If 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: +* carousel +* datepicker +* datepickerPopup +* dropdown +* popover +* position +* timepicker +* tooltip +* typeahead + +The 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: ```js -var uibs = require('angular-ui-bootstrap'); +import accordion from 'angular-ui-bootstrap/src/accordion'; +import typeahead from 'angular-ui-bootstrap/src/typeahead/index-nocss.js'; -angular.module('myModule', [uibs]); +angular.module('myModule', [accordion, datepicker]); ``` # Support diff --git a/dist/ui-bootstrap-1.3.3-csp.css b/dist/ui-bootstrap-1.3.3-csp.css new file mode 100644 index 0000000..3b69cf6 --- /dev/null +++ b/dist/ui-bootstrap-1.3.3-csp.css @@ -0,0 +1,115 @@ +/* Include this file in your html if you are using the CSP mode. */ + +.ng-animate.item:not(.left):not(.right) { + -webkit-transition: 0s ease-in-out left; + transition: 0s ease-in-out left +} +.uib-datepicker .uib-title { + width: 100%; +} + +.uib-day button, .uib-month button, .uib-year button { + min-width: 100%; +} + +.uib-left, .uib-right { + width: 100% +} + +.uib-position-measure { + display: block !important; + visibility: hidden !important; + position: absolute !important; + top: -9999px !important; + left: -9999px !important; +} + +.uib-position-scrollbar-measure { + position: absolute !important; + top: -9999px !important; + width: 50px !important; + height: 50px !important; + overflow: scroll !important; +} + +.uib-position-body-scrollbar-measure { + overflow: scroll !important; +} +.uib-datepicker-popup.dropdown-menu { + display: block; + float: none; + margin: 0; +} + +.uib-button-bar { + padding: 10px 9px 2px; +} + +[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-popover-popup].popover.top-left > .arrow, +[uib-popover-popup].popover.top-right > .arrow, +[uib-popover-popup].popover.bottom-left > .arrow, +[uib-popover-popup].popover.bottom-right > .arrow, +[uib-popover-popup].popover.left-top > .arrow, +[uib-popover-popup].popover.left-bottom > .arrow, +[uib-popover-popup].popover.right-top > .arrow, +[uib-popover-popup].popover.right-bottom > .arrow, +[uib-popover-html-popup].popover.top-left > .arrow, +[uib-popover-html-popup].popover.top-right > .arrow, +[uib-popover-html-popup].popover.bottom-left > .arrow, +[uib-popover-html-popup].popover.bottom-right > .arrow, +[uib-popover-html-popup].popover.left-top > .arrow, +[uib-popover-html-popup].popover.left-bottom > .arrow, +[uib-popover-html-popup].popover.right-top > .arrow, +[uib-popover-html-popup].popover.right-bottom > .arrow, +[uib-popover-template-popup].popover.top-left > .arrow, +[uib-popover-template-popup].popover.top-right > .arrow, +[uib-popover-template-popup].popover.bottom-left > .arrow, +[uib-popover-template-popup].popover.bottom-right > .arrow, +[uib-popover-template-popup].popover.left-top > .arrow, +[uib-popover-template-popup].popover.left-bottom > .arrow, +[uib-popover-template-popup].popover.right-top > .arrow, +[uib-popover-template-popup].popover.right-bottom > .arrow { + top: auto; + bottom: auto; + left: auto; + right: auto; + margin: 0; +} + +[uib-popover-popup].popover, +[uib-popover-html-popup].popover, +[uib-popover-template-popup].popover { + display: block !important; +} + +.uib-time input { + width: 50px; +} + +[uib-typeahead-popup].dropdown-menu { + display: block; +} diff --git a/dist/ui-bootstrap-custom-1.3.3-csp.css b/dist/ui-bootstrap-custom-1.3.3-csp.css new file mode 100644 index 0000000..4bfe70e --- /dev/null +++ b/dist/ui-bootstrap-custom-1.3.3-csp.css @@ -0,0 +1,107 @@ +/* Include this file in your html if you are using the CSP mode. */ + +.uib-position-measure { + display: block !important; + visibility: hidden !important; + position: absolute !important; + top: -9999px !important; + left: -9999px !important; +} + +.uib-position-scrollbar-measure { + position: absolute !important; + top: -9999px !important; + width: 50px !important; + height: 50px !important; + overflow: scroll !important; +} + +.uib-position-body-scrollbar-measure { + overflow: scroll !important; +} +[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-popover-popup].popover.top-left > .arrow, +[uib-popover-popup].popover.top-right > .arrow, +[uib-popover-popup].popover.bottom-left > .arrow, +[uib-popover-popup].popover.bottom-right > .arrow, +[uib-popover-popup].popover.left-top > .arrow, +[uib-popover-popup].popover.left-bottom > .arrow, +[uib-popover-popup].popover.right-top > .arrow, +[uib-popover-popup].popover.right-bottom > .arrow, +[uib-popover-html-popup].popover.top-left > .arrow, +[uib-popover-html-popup].popover.top-right > .arrow, +[uib-popover-html-popup].popover.bottom-left > .arrow, +[uib-popover-html-popup].popover.bottom-right > .arrow, +[uib-popover-html-popup].popover.left-top > .arrow, +[uib-popover-html-popup].popover.left-bottom > .arrow, +[uib-popover-html-popup].popover.right-top > .arrow, +[uib-popover-html-popup].popover.right-bottom > .arrow, +[uib-popover-template-popup].popover.top-left > .arrow, +[uib-popover-template-popup].popover.top-right > .arrow, +[uib-popover-template-popup].popover.bottom-left > .arrow, +[uib-popover-template-popup].popover.bottom-right > .arrow, +[uib-popover-template-popup].popover.left-top > .arrow, +[uib-popover-template-popup].popover.left-bottom > .arrow, +[uib-popover-template-popup].popover.right-top > .arrow, +[uib-popover-template-popup].popover.right-bottom > .arrow { + top: auto; + bottom: auto; + left: auto; + right: auto; + margin: 0; +} + +[uib-popover-popup].popover, +[uib-popover-html-popup].popover, +[uib-popover-template-popup].popover { + display: block !important; +} + +.uib-time input { + width: 50px; +} + +.uib-datepicker .uib-title { + width: 100%; +} + +.uib-day button, .uib-month button, .uib-year button { + min-width: 100%; +} + +.uib-left, .uib-right { + width: 100% +} + +.uib-datepicker-popup.dropdown-menu { + display: block; + float: none; + margin: 0; +} + +.uib-button-bar { + padding: 10px 9px 2px; +} diff --git a/dist/ui-bootstrap-custom-1.3.3.js b/dist/ui-bootstrap-custom-1.3.3.js new file mode 100644 index 0000000..490e77a --- /dev/null +++ b/dist/ui-bootstrap-custom-1.3.3.js @@ -0,0 +1,5261 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 1.3.3 - 2017-07-07 + * License: MIT + */angular.module("ui.bootstrap", ["ui.bootstrap.stackedMap","ui.bootstrap.position","ui.bootstrap.modal","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.timepicker","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.popover","ui.bootstrap.datepickerPopup"]); +angular.module('ui.bootstrap.stackedMap', []) +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function() { + return { + createNew: function() { + var stack = []; + + return { + add: function(key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function(key) { + for (var i = 0; i < stack.length; i++) { + if (key === stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function() { + return stack[stack.length - 1]; + }, + remove: function(key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key === stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function() { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function() { + return stack.length; + } + }; + } + }; + }); +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods for working with the DOM. + * It is meant to be used where we need to absolute-position elements in + * relation to another element (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$uibPosition', ['$document', '$window', function($document, $window) { + /** + * Used by scrollbarWidth() function to cache scrollbar's width. + * Do not access this variable directly, use scrollbarWidth() instead. + */ + var SCROLLBAR_WIDTH; + /** + * scrollbar on body and html element in IE and Edge overlay + * content and should be considered 0 width. + */ + var BODY_SCROLLBAR_WIDTH; + var OVERFLOW_REGEX = { + normal: /(auto|scroll)/, + hidden: /(auto|scroll|hidden)/ + }; + var PLACEMENT_REGEX = { + auto: /\s?auto?\s?/i, + primary: /^(top|bottom|left|right)$/, + secondary: /^(top|bottom|left|right|center)$/, + vertical: /^(top|bottom)$/ + }; + var BODY_REGEX = /(HTML|BODY)/; + + return { + + /** + * Provides a raw DOM element from a jQuery/jQLite element. + * + * @param {element} elem - The element to convert. + * + * @returns {element} A HTML element. + */ + getRawNode: function(elem) { + return elem.nodeName ? elem : elem[0] || elem; + }, + + /** + * Provides a parsed number for a style property. Strips + * units and casts invalid numbers to 0. + * + * @param {string} value - The style value to parse. + * + * @returns {number} A valid number. + */ + parseStyle: function(value) { + value = parseFloat(value); + return isFinite(value) ? value : 0; + }, + + /** + * Provides the closest positioned ancestor. + * + * @param {element} element - The element to get the offest parent for. + * + * @returns {element} The closest positioned ancestor. + */ + offsetParent: function(elem) { + elem = this.getRawNode(elem); + + var offsetParent = elem.offsetParent || $document[0].documentElement; + + function isStaticPositioned(el) { + return ($window.getComputedStyle(el).position || 'static') === 'static'; + } + + while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + + return offsetParent || $document[0].documentElement; + }, + + /** + * Provides the scrollbar width, concept from TWBS measureScrollbar() + * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js + * In IE and Edge, scollbar on body and html element overlay and should + * return a width of 0. + * + * @returns {number} The width of the browser scollbar. + */ + scrollbarWidth: function(isBody) { + if (isBody) { + if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) { + var bodyElem = $document.find('body'); + bodyElem.addClass('uib-position-body-scrollbar-measure'); + BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth; + BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0; + bodyElem.removeClass('uib-position-body-scrollbar-measure'); + } + return BODY_SCROLLBAR_WIDTH; + } + + if (angular.isUndefined(SCROLLBAR_WIDTH)) { + var scrollElem = angular.element('
'); + $document.find('body').append(scrollElem); + SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth; + SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0; + scrollElem.remove(); + } + + return SCROLLBAR_WIDTH; + }, + + /** + * Provides the padding required on an element to replace the scrollbar. + * + * @returns {object} An object with the following properties: + * + */ + scrollbarPadding: function(elem) { + elem = this.getRawNode(elem); + + var elemStyle = $window.getComputedStyle(elem); + var paddingRight = this.parseStyle(elemStyle.paddingRight); + var paddingBottom = this.parseStyle(elemStyle.paddingBottom); + var scrollParent = this.scrollParent(elem, false, true); + var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName)); + + return { + scrollbarWidth: scrollbarWidth, + widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth, + right: paddingRight + scrollbarWidth, + originalRight: paddingRight, + heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight, + bottom: paddingBottom + scrollbarWidth, + originalBottom: paddingBottom + }; + }, + + /** + * Checks to see if the element is scrollable. + * + * @param {element} elem - The element to check. + * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, + * default is false. + * + * @returns {boolean} Whether the element is scrollable. + */ + isScrollable: function(elem, includeHidden) { + elem = this.getRawNode(elem); + + var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; + var elemStyle = $window.getComputedStyle(elem); + return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX); + }, + + /** + * Provides the closest scrollable ancestor. + * A port of the jQuery UI scrollParent method: + * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js + * + * @param {element} elem - The element to find the scroll parent of. + * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, + * default is false. + * @param {boolean=} [includeSelf=false] - Should the element being passed be + * included in the scrollable llokup. + * + * @returns {element} A HTML element. + */ + scrollParent: function(elem, includeHidden, includeSelf) { + elem = this.getRawNode(elem); + + var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; + var documentEl = $document[0].documentElement; + var elemStyle = $window.getComputedStyle(elem); + if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) { + return elem; + } + var excludeStatic = elemStyle.position === 'absolute'; + var scrollParent = elem.parentElement || documentEl; + + if (scrollParent === documentEl || elemStyle.position === 'fixed') { + return documentEl; + } + + while (scrollParent.parentElement && scrollParent !== documentEl) { + var spStyle = $window.getComputedStyle(scrollParent); + if (excludeStatic && spStyle.position !== 'static') { + excludeStatic = false; + } + + if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) { + break; + } + scrollParent = scrollParent.parentElement; + } + + return scrollParent; + }, + + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ - distance to closest positioned + * ancestor. Does not account for margins by default like jQuery position. + * + * @param {element} elem - The element to caclulate the position on. + * @param {boolean=} [includeMargins=false] - Should margins be accounted + * for, default is false. + * + * @returns {object} An object with the following properties: + * + */ + position: function(elem, includeMagins) { + elem = this.getRawNode(elem); + + var elemOffset = this.offset(elem); + if (includeMagins) { + var elemStyle = $window.getComputedStyle(elem); + elemOffset.top -= this.parseStyle(elemStyle.marginTop); + elemOffset.left -= this.parseStyle(elemStyle.marginLeft); + } + var parent = this.offsetParent(elem); + var parentOffset = {top: 0, left: 0}; + + if (parent !== $document[0].documentElement) { + parentOffset = this.offset(parent); + parentOffset.top += parent.clientTop - parent.scrollTop; + parentOffset.left += parent.clientLeft - parent.scrollLeft; + } + + return { + width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth), + height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight), + top: Math.round(elemOffset.top - parentOffset.top), + left: Math.round(elemOffset.left - parentOffset.left) + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ - distance to viewport. Does + * not account for borders, margins, or padding on the body + * element. + * + * @param {element} elem - The element to calculate the offset on. + * + * @returns {object} An object with the following properties: + * + */ + offset: function(elem) { + elem = this.getRawNode(elem); + + var elemBCR = elem.getBoundingClientRect(); + return { + width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth), + height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight), + top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)), + left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)) + }; + }, + + /** + * Provides offset distance to the closest scrollable ancestor + * or viewport. Accounts for border and scrollbar width. + * + * Right and bottom dimensions represent the distance to the + * respective edge of the viewport element. If the element + * edge extends beyond the viewport, a negative value will be + * reported. + * + * @param {element} elem - The element to get the viewport offset for. + * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead + * of the first scrollable element, default is false. + * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element + * be accounted for, default is true. + * + * @returns {object} An object with the following properties: + * + */ + viewportOffset: function(elem, useDocument, includePadding) { + elem = this.getRawNode(elem); + includePadding = includePadding !== false ? true : false; + + var elemBCR = elem.getBoundingClientRect(); + var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0}; + + var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem); + var offsetParentBCR = offsetParent.getBoundingClientRect(); + + offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop; + offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft; + if (offsetParent === $document[0].documentElement) { + offsetBCR.top += $window.pageYOffset; + offsetBCR.left += $window.pageXOffset; + } + offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight; + offsetBCR.right = offsetBCR.left + offsetParent.clientWidth; + + if (includePadding) { + var offsetParentStyle = $window.getComputedStyle(offsetParent); + offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop); + offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom); + offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft); + offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight); + } + + return { + top: Math.round(elemBCR.top - offsetBCR.top), + bottom: Math.round(offsetBCR.bottom - elemBCR.bottom), + left: Math.round(elemBCR.left - offsetBCR.left), + right: Math.round(offsetBCR.right - elemBCR.right) + }; + }, + + /** + * Provides an array of placement values parsed from a placement string. + * Along with the 'auto' indicator, supported placement strings are: + * + * A placement string with an 'auto' indicator is expected to be + * space separated from the placement, i.e: 'auto bottom-left' If + * the primary and secondary placement values do not match 'top, + * bottom, left, right' then 'top' will be the primary placement and + * 'center' will be the secondary placement. If 'auto' is passed, true + * will be returned as the 3rd value of the array. + * + * @param {string} placement - The placement string to parse. + * + * @returns {array} An array with the following values + * + */ + parsePlacement: function(placement) { + var autoPlace = PLACEMENT_REGEX.auto.test(placement); + if (autoPlace) { + placement = placement.replace(PLACEMENT_REGEX.auto, ''); + } + + placement = placement.split('-'); + + placement[0] = placement[0] || 'top'; + if (!PLACEMENT_REGEX.primary.test(placement[0])) { + placement[0] = 'top'; + } + + placement[1] = placement[1] || 'center'; + if (!PLACEMENT_REGEX.secondary.test(placement[1])) { + placement[1] = 'center'; + } + + if (autoPlace) { + placement[2] = true; + } else { + placement[2] = false; + } + + return placement; + }, + + /** + * Provides coordinates for an element to be positioned relative to + * another element. Passing 'auto' as part of the placement parameter + * will enable smart placement - where the element fits. i.e: + * 'auto left-top' will check to see if there is enough space to the left + * of the hostElem to fit the targetElem, if not place right (same for secondary + * top placement). Available space is calculated using the viewportOffset + * function. + * + * @param {element} hostElem - The element to position against. + * @param {element} targetElem - The element to position. + * @param {string=} [placement=top] - The placement for the targetElem, + * default is 'top'. 'center' is assumed as secondary placement for + * 'top', 'left', 'right', and 'bottom' placements. Available placements are: + * + * @param {boolean=} [appendToBody=false] - Should the top and left values returned + * be calculated from the body element, default is false. + * + * @returns {object} An object with the following properties: + * + */ + positionElements: function(hostElem, targetElem, placement, appendToBody) { + hostElem = this.getRawNode(hostElem); + targetElem = this.getRawNode(targetElem); + + // need to read from prop to support tests. + var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth'); + var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight'); + + placement = this.parsePlacement(placement); + + var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem); + var targetElemPos = {top: 0, left: 0, placement: ''}; + + if (placement[2]) { + var viewportOffset = this.viewportOffset(hostElem, appendToBody); + + var targetElemStyle = $window.getComputedStyle(targetElem); + var adjustedSize = { + width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))), + height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom))) + }; + + placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' : + placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' : + placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' : + placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' : + placement[0]; + + placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' : + placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' : + placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' : + placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' : + placement[1]; + + if (placement[1] === 'center') { + if (PLACEMENT_REGEX.vertical.test(placement[0])) { + var xOverflow = hostElemPos.width / 2 - targetWidth / 2; + if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) { + placement[1] = 'left'; + } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) { + placement[1] = 'right'; + } + } else { + var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2; + if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) { + placement[1] = 'top'; + } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) { + placement[1] = 'bottom'; + } + } + } + } + + switch (placement[0]) { + case 'top': + targetElemPos.top = hostElemPos.top - targetHeight; + break; + case 'bottom': + targetElemPos.top = hostElemPos.top + hostElemPos.height; + break; + case 'left': + targetElemPos.left = hostElemPos.left - targetWidth; + break; + case 'right': + targetElemPos.left = hostElemPos.left + hostElemPos.width; + break; + } + + switch (placement[1]) { + case 'top': + targetElemPos.top = hostElemPos.top; + break; + case 'bottom': + targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight; + break; + case 'left': + targetElemPos.left = hostElemPos.left; + break; + case 'right': + targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth; + break; + case 'center': + if (PLACEMENT_REGEX.vertical.test(placement[0])) { + targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2; + } else { + targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2; + } + break; + } + + targetElemPos.top = Math.round(targetElemPos.top); + targetElemPos.left = Math.round(targetElemPos.left); + targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1]; + + return targetElemPos; + }, + + /** + * Provides a way for positioning tooltip & dropdown + * arrows when using placement options beyond the standard + * left, right, top, or bottom. + * + * @param {element} elem - The tooltip/dropdown element. + * @param {string} placement - The placement for the elem. + */ + positionArrow: function(elem, placement) { + elem = this.getRawNode(elem); + + var innerElem = elem.querySelector('.tooltip-inner, .popover-inner'); + if (!innerElem) { + return; + } + + var isTooltip = angular.element(innerElem).hasClass('tooltip-inner'); + + var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow'); + if (!arrowElem) { + return; + } + + var arrowCss = { + top: '', + bottom: '', + left: '', + right: '' + }; + + placement = this.parsePlacement(placement); + if (placement[1] === 'center') { + // no adjustment necessary - just reset styles + angular.element(arrowElem).css(arrowCss); + return; + } + + var borderProp = 'border-' + placement[0] + '-width'; + var borderWidth = $window.getComputedStyle(arrowElem)[borderProp]; + + var borderRadiusProp = 'border-'; + if (PLACEMENT_REGEX.vertical.test(placement[0])) { + borderRadiusProp += placement[0] + '-' + placement[1]; + } else { + borderRadiusProp += placement[1] + '-' + placement[0]; + } + borderRadiusProp += '-radius'; + var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp]; + + switch (placement[0]) { + case 'top': + arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth; + break; + case 'bottom': + arrowCss.top = isTooltip ? '0' : '-' + borderWidth; + break; + case 'left': + arrowCss.right = isTooltip ? '0' : '-' + borderWidth; + break; + case 'right': + arrowCss.left = isTooltip ? '0' : '-' + borderWidth; + break; + } + + arrowCss[placement[1]] = borderRadius; + + angular.element(arrowElem).css(arrowCss); + } + }; + }]); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position']) +/** + * A helper, internal data structure that stores all references attached to key + */ + .factory('$$multiMap', function() { + return { + createNew: function() { + var map = {}; + + return { + entries: function() { + return Object.keys(map).map(function(key) { + return { + key: key, + value: map[key] + }; + }); + }, + get: function(key) { + return map[key]; + }, + hasKey: function(key) { + return !!map[key]; + }, + keys: function() { + return Object.keys(map); + }, + put: function(key, value) { + if (!map[key]) { + map[key] = []; + } + + map[key].push(value); + }, + remove: function(key, value) { + var values = map[key]; + + if (!values) { + return; + } + + var idx = values.indexOf(value); + + if (idx !== -1) { + values.splice(idx, 1); + } + + if (!values.length) { + delete map[key]; + } + } + }; + } + }; + }) + +/** + * Pluggable resolve mechanism for the modal resolve resolution + * Supports UI Router's $resolve service + */ + .provider('$uibResolve', function() { + var resolve = this; + this.resolver = null; + + this.setResolver = function(resolver) { + this.resolver = resolver; + }; + + this.$get = ['$injector', '$q', function($injector, $q) { + var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null; + return { + resolve: function(invocables, locals, parent, self) { + if (resolver) { + return resolver.resolve(invocables, locals, parent, self); + } + + var promises = []; + + angular.forEach(invocables, function(value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promises.push($q.resolve($injector.invoke(value))); + } else if (angular.isString(value)) { + promises.push($q.resolve($injector.get(value))); + } else { + promises.push($q.resolve(value)); + } + }); + + return $q.all(promises).then(function(resolves) { + var resolveObj = {}; + var resolveIter = 0; + angular.forEach(invocables, function(value, key) { + resolveObj[key] = resolves[resolveIter++]; + }); + + return resolveObj; + }); + } + }; + }]; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack', + function($animate, $injector, $modalStack) { + return { + replace: true, + templateUrl: 'uib/template/modal/backdrop.html', + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; + } + }; + + function linkFn(scope, element, attrs) { + if (attrs.modalInClass) { + $animate.addClass(element, attrs.modalInClass); + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if (scope.modalOptions.animation) { + $animate.removeClass(element, attrs.modalInClass).then(done); + } else { + done(); + } + }); + } + } + }]) + + .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document', + function($modalStack, $q, $animateCss, $document) { + return { + scope: { + index: '@' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'uib/template/modal/window.html'; + }, + link: function(scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); + scope.size = attrs.size; + + scope.close = function(evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && + modal.value.backdrop !== 'static' && + evt.target === evt.currentTarget) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value === 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + }); + } + + + $q.when(animationPromise).then(function() { + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + + /** + * If something within the freshly-opened modal already has focus (perhaps via a + * directive that causes focus). then no need to try and focus anything. + */ + if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) { + var inputWithAutofocus = element[0].querySelector('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + element[0].focus(); + } + } + }); + }); + } + }; + }]) + + .directive('uibModalAnimationClass', function() { + return { + compile: function(tElement, tAttrs) { + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.uibModalAnimationClass); + } + } + }; + }) + + .directive('uibModalTransclude', function() { + return { + link: function(scope, element, attrs, controller, transclude) { + transclude(scope.$parent, function(clone) { + element.empty(); + element.append(clone); + }); + } + }; + }) + + .factory('$uibModalStack', ['$animate', '$animateCss', '$document', + '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition', + function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) { + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var openedClasses = $$multiMap.createNew(); + var $modalStack = { + NOW_CLOSING_EVENT: 'modal.stack.now-closing' + }; + var topModalIndex = 0; + var previousTopOpenedModal = null; + + //Modal focus behavior + var tabableSelector = 'a[href], area[href], input:not([disabled]), ' + + 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + + 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; + var scrollbarPadding; + + function isVisible(element) { + return !!(element.offsetWidth || + element.offsetHeight || + element.getClientRects().length); + } + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + + // If any backdrop exist, ensure that it's index is always + // right below the top modal + if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) { + topBackdropIndex = topModalIndex; + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex) { + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance, elementToReceiveFocus) { + var modalWindow = openedWindows.get(modalInstance).value; + var appendToElement = modalWindow.appendTo; + + //clean up the stack + openedWindows.remove(modalInstance); + previousTopOpenedModal = openedWindows.top(); + if (previousTopOpenedModal) { + topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10); + } + + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { + var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; + openedClasses.remove(modalBodyClass, modalInstance); + var areAnyOpen = openedClasses.hasKey(modalBodyClass); + appendToElement.toggleClass(modalBodyClass, areAnyOpen); + if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + if (scrollbarPadding.originalRight) { + appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'}); + } else { + appendToElement.css({paddingRight: ''}); + } + scrollbarPadding = null; + } + toggleTopWindowClass(true); + }, modalWindow.closedDeferred); + checkRemoveBackdrop(); + + //move focus to specified element if available, or else to body + if (elementToReceiveFocus && elementToReceiveFocus.focus) { + elementToReceiveFocus.focus(); + } else if (appendToElement.focus) { + appendToElement.focus(); + } + } + + // Add or remove "windowTopClass" from the top window in the stack + function toggleTopWindowClass(toggleSwitch) { + var modalWindow; + + if (openedWindows.length() > 0) { + modalWindow = openedWindows.top().value; + modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); + } + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() === -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, function() { + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, done, closedDeferred) { + var asyncDeferred; + var asyncPromise = null; + var setIsAsync = function() { + if (!asyncDeferred) { + asyncDeferred = $q.defer(); + asyncPromise = asyncDeferred.promise; + } + + return function asyncDone() { + asyncDeferred.resolve(); + }; + }; + scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); + + // Note that it's intentional that asyncPromise might be null. + // That's when setIsAsync has not been called during the + // NOW_CLOSING_EVENT broadcast. + return $q.when(asyncPromise).then(afterAnimating); + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + $animate.leave(domEl).then(function() { + domEl.remove(); + if (closedDeferred) { + closedDeferred.resolve(); + } + }); + + scope.$destroy(); + if (done) { + done(); + } + } + } + + $document.on('keydown', keydownListener); + + $rootScope.$on('$destroy', function() { + $document.off('keydown', keydownListener); + }); + + function keydownListener(evt) { + if (evt.isDefaultPrevented()) { + return evt; + } + + var modal = openedWindows.top(); + if (modal) { + switch (evt.which) { + case 27: { + if (modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function() { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + break; + } + case 9: { + var list = $modalStack.loadFocusElementList(modal); + var focusChanged = false; + if (evt.shiftKey) { + if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { + focusChanged = $modalStack.focusLastFocusableElement(list); + } + } else { + if ($modalStack.isFocusInLastItem(evt, list)) { + focusChanged = $modalStack.focusFirstFocusableElement(list); + } + } + + if (focusChanged) { + evt.preventDefault(); + evt.stopPropagation(); + } + + break; + } + } + } + } + + $modalStack.open = function(modalInstance, modal) { + var modalOpener = $document[0].activeElement, + modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; + + toggleTopWindowClass(false); + + // Store the current top first, to determine what index we ought to use + // for the current top modal + previousTopOpenedModal = openedWindows.top(); + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + renderDeferred: modal.renderDeferred, + closedDeferred: modal.closedDeferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard, + openedClass: modal.openedClass, + windowTopClass: modal.windowTopClass, + animation: modal.animation, + appendTo: modal.appendTo + }); + + openedClasses.put(modalBodyClass, modalInstance); + + var appendToElement = modal.appendTo, + currBackdropIndex = backdropIndex(); + + if (!appendToElement.length) { + throw new Error('appendTo element not found. Make sure that the element passed is in DOM.'); + } + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.modalOptions = modal; + backdropScope.index = currBackdropIndex; + backdropDomEl = angular.element('
'); + backdropDomEl.attr('backdrop-class', modal.backdropClass); + if (modal.animation) { + backdropDomEl.attr('modal-animation', 'true'); + } + $compile(backdropDomEl)(backdropScope); + $animate.enter(backdropDomEl, appendToElement); + scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement); + if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + appendToElement.css({paddingRight: scrollbarPadding.right + 'px'}); + } + } + + // Set the top modal index based on the index of the previous top modal + topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0; + var angularDomEl = angular.element('
'); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'window-top-class': modal.windowTopClass, + 'size': modal.size, + 'index': topModalIndex, + 'animate': 'animate' + }).html(modal.content); + if (modal.animation) { + angularDomEl.attr('modal-animation', 'true'); + } + + appendToElement.addClass(modalBodyClass); + $animate.enter($compile(angularDomEl)(modal.scope), appendToElement); + + openedWindows.top().value.modalDomEl = angularDomEl; + openedWindows.top().value.modalOpener = modalOpener; + }; + + function broadcastClosing(modalWindow, resultOrReason, closing) { + return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; + } + + $modalStack.close = function(modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow && broadcastClosing(modalWindow, result, true)) { + modalInstance.callCloseFn(result); + modalWindow.value.modalScope.$$uibDestructionScheduled = true; + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; + } + return !modalWindow; + }; + + $modalStack.dismiss = function(modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow && broadcastClosing(modalWindow, reason, false)) { + modalInstance.callDismissFn(reason); + modalWindow.value.modalScope.$$uibDestructionScheduled = true; + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; + } + return !modalWindow; + }; + + $modalStack.dismissAll = function(reason) { + var topModal = this.getTop(); + while (topModal && this.dismiss(topModal.key, reason)) { + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function() { + return openedWindows.top(); + }; + + $modalStack.modalRendered = function(modalInstance) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.renderDeferred.resolve(); + } + }; + + $modalStack.focusFirstFocusableElement = function(list) { + if (list.length > 0) { + list[0].focus(); + return true; + } + return false; + }; + + $modalStack.focusLastFocusableElement = function(list) { + if (list.length > 0) { + list[list.length - 1].focus(); + return true; + } + return false; + }; + + $modalStack.isModalFocused = function(evt, modalWindow) { + if (evt && modalWindow) { + var modalDomEl = modalWindow.value.modalDomEl; + if (modalDomEl && modalDomEl.length) { + return (evt.target || evt.srcElement) === modalDomEl[0]; + } + } + return false; + }; + + $modalStack.isFocusInFirstItem = function(evt, list) { + if (list.length > 0) { + return (evt.target || evt.srcElement) === list[0]; + } + return false; + }; + + $modalStack.isFocusInLastItem = function(evt, list) { + if (list.length > 0) { + return (evt.target || evt.srcElement) === list[list.length - 1]; + } + return false; + }; + + $modalStack.loadFocusElementList = function(modalWindow) { + if (modalWindow) { + var modalDomE1 = modalWindow.value.modalDomEl; + if (modalDomE1 && modalDomE1.length) { + var elements = modalDomE1[0].querySelectorAll(tabableSelector); + return elements ? + Array.prototype.filter.call(elements, function(element) { + return isVisible(element); + }) : elements; + } + } + }; + + $modalStack.length = function() { + return openedWindows.length; + }; + + return $modalStack; + }]) + + .provider('$uibModal', function() { + var $modalProvider = { + options: { + animation: true, + backdrop: true, //can also be false or 'static' + keyboard: true, + modalOpenFn: function() {}, + modalCloseFn: function() {}, + modalDismissFn: function() {} + }, + $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack', + function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) { + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $templateRequest(angular.isFunction(options.templateUrl) ? + options.templateUrl() : options.templateUrl); + } + + var promiseChain = null; + $modal.getPromiseChain = function() { + return promiseChain; + }; + + $modal.setCallbacks = function(openFn, closeFn, dismissFn) { + angular.extend($modalProvider.options, { + modalOpenFn: openFn, + modalCloseFn: closeFn, + modalDismissFn: dismissFn + }); + }; + + $modal.open = function(modalOptions) { + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + var modalClosedDeferred = $q.defer(); + var modalRenderDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + closed: modalClosedDeferred.promise, + rendered: modalRenderDeferred.promise, + close: function (result) { + return $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + return $modalStack.dismiss(modalInstance, reason); + }, + callDismissFn: function(reason) { + modalOptions.modalDismissFn(modalOptions, reason); + return true; + }, + callCloseFn: function(result) { + modalOptions.modalCloseFn(modalOptions, result); + return true; + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0); + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]); + + function resolveWithTemplate() { + return templateAndResolvePromise; + } + + // Wait for the resolution of the existing promise chain. + // Then switch to our own combined promise dependency (regardless of how the previous modal fared). + // Then add to $modalStack and resolve opened. + // Finally clean up the chain variable if no subsequent modal has overwritten it. + var samePromise; + samePromise = promiseChain = $q.all([promiseChain]) + .then(resolveWithTemplate, resolveWithTemplate) + .then(function resolveSuccess(tplAndVars) { + var providedScope = modalOptions.scope || $rootScope; + + var modalScope = providedScope.$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + modalScope.$on('$destroy', function() { + if (!modalScope.$$uibDestructionScheduled) { + modalScope.$dismiss('$uibUnscheduledDestruction'); + } + }); + + var ctrlInstance, ctrlInstantiate, ctrlLocals = {}; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$scope.$resolve = {}; + ctrlLocals.$uibModalInstance = modalInstance; + angular.forEach(tplAndVars[1], function(value, key) { + ctrlLocals[key] = value; + ctrlLocals.$scope.$resolve[key] = value; + }); + + // the third param will make the controller instantiate later,private api + // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126 + ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs); + if (modalOptions.controllerAs && modalOptions.bindToController) { + ctrlInstance = ctrlInstantiate.instance; + ctrlInstance.$close = modalScope.$close; + ctrlInstance.$dismiss = modalScope.$dismiss; + angular.extend(ctrlInstance, { + $resolve: ctrlLocals.$scope.$resolve + }, providedScope); + } + + ctrlInstance = ctrlInstantiate(); + + if (angular.isFunction(ctrlInstance.$onInit)) { + ctrlInstance.$onInit(); + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + renderDeferred: modalRenderDeferred, + closedDeferred: modalClosedDeferred, + content: tplAndVars[0], + animation: modalOptions.animation, + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowTopClass: modalOptions.windowTopClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size, + openedClass: modalOptions.openedClass, + appendTo: modalOptions.appendTo + }); + + modalOptions.modalOpenFn(modalOptions); + modalOpenedDeferred.resolve(true); + + }, function resolveError(reason) { + modalOpenedDeferred.reject(reason); + modalResultDeferred.reject(reason); + })['finally'](function() { + if (promiseChain === samePromise) { + promiseChain = null; + } + }); + + return modalInstance; + }; + + return $modal; + } + ] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.tabs', []) + +.controller('UibTabsetController', ['$scope', function ($scope) { + var ctrl = this, + oldIndex; + ctrl.tabs = []; + + ctrl.select = function(index, evt) { + if (!destroyed) { + var previousIndex = findTabIndex(oldIndex); + var previousSelected = ctrl.tabs[previousIndex]; + if (previousSelected) { + previousSelected.tab.onDeselect({ + $event: evt, + $selectedIndex: index + }); + if (evt && evt.isDefaultPrevented()) { + return; + } + previousSelected.tab.active = false; + } + + var selected = ctrl.tabs[index]; + if (selected) { + selected.tab.onSelect({ + $event: evt + }); + selected.tab.active = true; + ctrl.active = selected.index; + oldIndex = selected.index; + } else if (!selected && angular.isDefined(oldIndex)) { + ctrl.active = null; + oldIndex = null; + } + } + }; + + ctrl.addTab = function addTab(tab) { + ctrl.tabs.push({ + tab: tab, + index: tab.index + }); + ctrl.tabs.sort(function(t1, t2) { + if (t1.index > t2.index) { + return 1; + } + + if (t1.index < t2.index) { + return -1; + } + + return 0; + }); + + if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) { + var newActiveIndex = findTabIndex(tab.index); + ctrl.select(newActiveIndex); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index; + for (var i = 0; i < ctrl.tabs.length; i++) { + if (ctrl.tabs[i].tab === tab) { + index = i; + break; + } + } + + if (ctrl.tabs[index].index === ctrl.active) { + var newActiveTabIndex = index === ctrl.tabs.length - 1 ? + index - 1 : index + 1 % ctrl.tabs.length; + ctrl.select(newActiveTabIndex); + } + + ctrl.tabs.splice(index, 1); + }; + + $scope.$watch('tabset.active', function(val) { + if (angular.isDefined(val) && val !== oldIndex) { + ctrl.select(findTabIndex(val)); + } + }); + + var destroyed; + $scope.$on('$destroy', function() { + destroyed = true; + }); + + function findTabIndex(index) { + for (var i = 0; i < ctrl.tabs.length; i++) { + if (ctrl.tabs[i].index === index) { + return i; + } + } + } +}]) + +.directive('uibTabset', function() { + return { + transclude: true, + replace: true, + scope: {}, + bindToController: { + active: '=?', + type: '@' + }, + controller: 'UibTabsetController', + controllerAs: 'tabset', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/tabs/tabset.html'; + }, + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? + scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? + scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +.directive('uibTab', ['$parse', function($parse) { + return { + require: '^uibTabset', + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/tabs/tab.html'; + }, + transclude: true, + scope: { + heading: '@', + index: '=?', + classes: '@?', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + controllerAs: 'tab', + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !! value; + }); + } + + if (angular.isUndefined(attrs.index)) { + if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) { + scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1; + } else { + scope.index = 0; + } + } + + if (angular.isUndefined(attrs.classes)) { + scope.classes = ''; + } + + scope.select = function(evt) { + if (!scope.disabled) { + var index; + for (var i = 0; i < tabsetCtrl.tabs.length; i++) { + if (tabsetCtrl.tabs[i].tab === scope) { + index = i; + break; + } + } + + tabsetCtrl.select(index, evt); + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + } + }; +}]) + +.directive('uibTabHeadingTransclude', function() { + return { + restrict: 'A', + require: '^uibTab', + link: function(scope, elm) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}) + +.directive('uibTabContentTransclude', function() { + return { + restrict: 'A', + require: '^uibTabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.uibTabContentTransclude).tab; + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('uib-tab-heading') || + node.hasAttribute('data-uib-tab-heading') || + node.hasAttribute('x-uib-tab-heading') || + node.tagName.toLowerCase() === 'uib-tab-heading' || + node.tagName.toLowerCase() === 'data-uib-tab-heading' || + node.tagName.toLowerCase() === 'x-uib-tab-heading' || + node.tagName.toLowerCase() === 'uib:tab-heading' + ); + } +}); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap']) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider('$uibTooltip', function() { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + placementClassPrefix: '', + animation: true, + popupDelay: 0, + popupCloseDelay: 0, + useContentExp: false + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'outsideClick': 'outsideClick', + 'focus': 'blur', + 'none': '' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function(value) { + angular.extend(globalOptions, value); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } ); + */ + this.setTriggers = function setTriggers(triggers) { + angular.extend(triggerMap, triggers); + }; + + /** + * This is a helper function for translating camel-case to snake_case. + */ + function snake_case(name) { + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) { + var openedTooltips = $$stackedMap.createNew(); + $document.on('keypress', keypressListener); + + $rootScope.$on('$destroy', function() { + $document.off('keypress', keypressListener); + }); + + function keypressListener(e) { + if (e.which === 27) { + var last = openedTooltips.top(); + if (last) { + last.value.close(); + openedTooltips.removeTop(); + last = null; + } + } + } + + return function $tooltip(ttType, prefix, defaultTriggerShow, options) { + options = angular.extend({}, defaultOptions, globalOptions, options); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers(trigger) { + var show = (trigger || options.trigger || defaultTriggerShow).split(' '); + var hide = show.map(function(trigger) { + return triggerMap[trigger] || trigger; + }); + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case(ttType); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
' + + '
'; + + return { + compile: function(tElem, tAttrs) { + var tooltipLinker = $compile(template); + + return function link(scope, element, attrs, tooltipCtrl) { + var tooltip; + var tooltipLinkedScope; + var transitionTimeout; + var showTimeout; + var hideTimeout; + var positionTimeout; + var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false; + var triggers = getTriggers(undefined); + var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); + var ttScope = scope.$new(true); + var repositionScheduled = false; + var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; + var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; + var observers = []; + var lastPlacement; + + var positionTooltip = function() { + // check if tooltip exists and is not empty + if (!tooltip || !tooltip.html()) { return; } + + if (!positionTimeout) { + positionTimeout = $timeout(function() { + var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); + tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' }); + + if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) { + tooltip.removeClass(lastPlacement.split('-')[0]); + tooltip.addClass(ttPosition.placement.split('-')[0]); + } + + if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) { + tooltip.removeClass(options.placementClassPrefix + lastPlacement); + tooltip.addClass(options.placementClassPrefix + ttPosition.placement); + } + + // first time through tt element will have the + // uib-position-measure class or if the placement + // has changed we need to position the arrow. + if (tooltip.hasClass('uib-position-measure')) { + $position.positionArrow(tooltip, ttPosition.placement); + tooltip.removeClass('uib-position-measure'); + } else if (lastPlacement !== ttPosition.placement) { + $position.positionArrow(tooltip, ttPosition.placement); + } + lastPlacement = ttPosition.placement; + + positionTimeout = null; + }, 0, false); + } + }; + + // Set up the correct scope to allow transclusion later + ttScope.origScope = scope; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + ttScope.isOpen = false; + openedTooltips.add(ttScope, { + close: hide + }); + + function toggleTooltipBind() { + if (!ttScope.isOpen) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { + return; + } + + cancelHide(); + prepareTooltip(); + + if (ttScope.popupDelay) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!showTimeout) { + showTimeout = $timeout(show, ttScope.popupDelay, false); + } + } else { + show(); + } + } + + function hideTooltipBind() { + cancelShow(); + + if (ttScope.popupCloseDelay) { + if (!hideTimeout) { + hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false); + } + } else { + hide(); + } + } + + // Show the tooltip popup element. + function show() { + cancelShow(); + cancelHide(); + + // Don't show empty tooltips. + if (!ttScope.content) { + return angular.noop; + } + + createTooltip(); + + // And show the tooltip. + ttScope.$evalAsync(function() { + ttScope.isOpen = true; + assignIsOpen(true); + positionTooltip(); + }); + } + + function cancelShow() { + if (showTimeout) { + $timeout.cancel(showTimeout); + showTimeout = null; + } + + if (positionTimeout) { + $timeout.cancel(positionTimeout); + positionTimeout = null; + } + } + + // Hide the tooltip popup element. + function hide() { + if (!ttScope) { + return; + } + + // First things first: we don't show it anymore. + ttScope.$evalAsync(function() { + if (ttScope) { + ttScope.isOpen = false; + assignIsOpen(false); + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + // The fade transition in TWBS is 150ms. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 150, false); + } + } else { + removeTooltip(); + } + } + }); + } + + function cancelHide() { + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = null; + } + + if (transitionTimeout) { + $timeout.cancel(transitionTimeout); + transitionTimeout = null; + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + return; + } + + tooltipLinkedScope = ttScope.$new(); + tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { + if (appendToBody) { + $document.find('body').append(tooltip); + } else { + element.after(tooltip); + } + }); + + prepObservers(); + } + + function removeTooltip() { + cancelShow(); + cancelHide(); + unregisterObservers(); + + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + if (tooltipLinkedScope) { + tooltipLinkedScope.$destroy(); + tooltipLinkedScope = null; + } + } + + /** + * Set the initial scope values. Once + * the tooltip is created, the observers + * will be added to keep things in sync. + */ + function prepareTooltip() { + ttScope.title = attrs[prefix + 'Title']; + if (contentParse) { + ttScope.content = contentParse(scope); + } else { + ttScope.content = attrs[ttType]; + } + + ttScope.popupClass = attrs[prefix + 'Class']; + ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; + var placement = $position.parsePlacement(ttScope.placement); + lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; + + var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); + var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); + ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; + ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay; + } + + function assignIsOpen(isOpen) { + if (isOpenParse && angular.isFunction(isOpenParse.assign)) { + isOpenParse.assign(scope, isOpen); + } + } + + ttScope.contentExp = function() { + return ttScope.content; + }; + + /** + * Observe the relevant attributes. + */ + attrs.$observe('disabled', function(val) { + if (val) { + cancelShow(); + } + + if (val && ttScope.isOpen) { + hide(); + } + }); + + if (isOpenParse) { + scope.$watch(isOpenParse, function(val) { + if (ttScope && !val === ttScope.isOpen) { + toggleTooltipBind(); + } + }); + } + + function prepObservers() { + observers.length = 0; + + if (contentParse) { + observers.push( + scope.$watch(contentParse, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } + }) + ); + + observers.push( + tooltipLinkedScope.$watch(function() { + if (!repositionScheduled) { + repositionScheduled = true; + tooltipLinkedScope.$$postDigest(function() { + repositionScheduled = false; + if (ttScope && ttScope.isOpen) { + positionTooltip(); + } + }); + } + }) + ); + } else { + observers.push( + attrs.$observe(ttType, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } else { + positionTooltip(); + } + }) + ); + } + + observers.push( + attrs.$observe(prefix + 'Title', function(val) { + ttScope.title = val; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); + + observers.push( + attrs.$observe(prefix + 'Placement', function(val) { + ttScope.placement = val ? val : options.placement; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); + } + + function unregisterObservers() { + if (observers.length) { + angular.forEach(observers, function(observer) { + observer(); + }); + observers.length = 0; + } + } + + // hide tooltips/popovers for outsideClick trigger + function bodyHideTooltipBind(e) { + if (!ttScope || !ttScope.isOpen || !tooltip) { + return; + } + // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked + if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) { + hideTooltipBind(); + } + } + + var unregisterTriggers = function() { + triggers.show.forEach(function(trigger) { + if (trigger === 'outsideClick') { + element.off('click', toggleTooltipBind); + } else { + element.off(trigger, showTooltipBind); + element.off(trigger, toggleTooltipBind); + } + }); + triggers.hide.forEach(function(trigger) { + if (trigger === 'outsideClick') { + $document.off('click', bodyHideTooltipBind); + } else { + element.off(trigger, hideTooltipBind); + } + }); + }; + + function prepTriggers() { + var val = attrs[prefix + 'Trigger']; + unregisterTriggers(); + + triggers = getTriggers(val); + + if (triggers.show !== 'none') { + triggers.show.forEach(function(trigger, idx) { + if (trigger === 'outsideClick') { + element.on('click', toggleTooltipBind); + $document.on('click', bodyHideTooltipBind); + } else if (trigger === triggers.hide[idx]) { + element.on(trigger, toggleTooltipBind); + } else if (trigger) { + element.on(trigger, showTooltipBind); + element.on(triggers.hide[idx], hideTooltipBind); + } + + element.on('keypress', function(e) { + if (e.which === 27) { + hideTooltipBind(); + } + }); + }); + } + } + + prepTriggers(); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; + + var appendToBodyVal; + var appendKey = prefix + 'AppendToBody'; + if (appendKey in attrs && attrs[appendKey] === undefined) { + appendToBodyVal = true; + } else { + appendToBodyVal = scope.$eval(attrs[appendKey]); + } + + appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + unregisterTriggers(); + removeTooltip(); + openedTooltips.remove(ttScope); + ttScope = null; + }); + }; + } + }; + }; + }]; +}) + +// This is mostly ngInclude code but with a custom scope +.directive('uibTooltipTemplateTransclude', [ + '$animate', '$sce', '$compile', '$templateRequest', +function ($animate, $sce, $compile, $templateRequest) { + return { + link: function(scope, elem, attrs) { + var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); + + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if (previousElement) { + previousElement.remove(); + previousElement = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + + if (currentElement) { + $animate.leave(currentElement).then(function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) { + var thisChangeId = ++changeCounter; + + if (src) { + //set the 2nd param to true to ignore the template request error so that the inner + //contents and scope can be cleaned up. + $templateRequest(src, true).then(function(response) { + if (thisChangeId !== changeCounter) { return; } + var newScope = origScope.$new(); + var template = response; + + var clone = $compile(template)(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, elem); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded', src); + }, function() { + if (thisChangeId === changeCounter) { + cleanupLastIncludeContent(); + scope.$emit('$includeContentError', src); + } + }); + scope.$emit('$includeContentRequested', src); + } else { + cleanupLastIncludeContent(); + } + }); + + scope.$on('$destroy', cleanupLastIncludeContent); + } + }; +}]) + +/** + * Note that it's intentional that these classes are *not* applied through $animate. + * They must not be animated as they're expected to be present on the tooltip on + * initialization. + */ +.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + // need to set the primary position so the + // arrow has space during position measure. + // tooltip.positionTooltip() + if (scope.placement) { + // // There are no top-left etc... classes + // // in TWBS, so we need the primary position. + var position = $uibPosition.parsePlacement(scope.placement); + element.addClass(position[0]); + } + + if (scope.popupClass) { + element.addClass(scope.popupClass); + } + + if (scope.animation()) { + element.addClass(attrs.tooltipAnimationClass); + } + } + }; +}]) + +.directive('uibTooltipPopup', function() { + return { + replace: true, + scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/tooltip/tooltip-popup.html' + }; +}) + +.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter'); +}]) + +.directive('uibTooltipTemplatePopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'uib/template/tooltip/tooltip-template-popup.html' + }; +}) + +.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]) + +.directive('uibTooltipHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/tooltip/tooltip-html-popup.html' + }; +}) + +.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]); + +angular.module('ui.bootstrap.timepicker', []) + +.constant('uibTimepickerConfig', { + hourStep: 1, + minuteStep: 1, + secondStep: 1, + showMeridian: true, + showSeconds: false, + meridians: null, + readonlyInput: false, + mousewheel: true, + arrowkeys: true, + showSpinners: true, + templateUrl: 'uib/template/timepicker/timepicker.html' +}) + +.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + watchers = [], + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS, + padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true; + + $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; + $element.removeAttr('tabindex'); + + this.init = function(ngModelCtrl_, inputs) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + ngModelCtrl.$formatters.unshift(function(modelValue) { + return modelValue ? new Date(modelValue) : null; + }); + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1), + secondsInputEl = inputs.eq(2); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + + if (mousewheel) { + this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl); + } + + var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; + if (arrowkeys) { + this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = +value; + })); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = +value; + })); + } + + var min; + watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) { + var dt = new Date(value); + min = isNaN(dt) ? undefined : dt; + })); + + var max; + watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) { + var dt = new Date(value); + max = isNaN(dt) ? undefined : dt; + })); + + var disabled = false; + if ($attrs.ngDisabled) { + watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) { + disabled = value; + })); + } + + $scope.noIncrementHours = function() { + var incrementedSelected = addMinutes(selected, hourStep * 60); + return disabled || incrementedSelected > max || + incrementedSelected < selected && incrementedSelected < min; + }; + + $scope.noDecrementHours = function() { + var decrementedSelected = addMinutes(selected, -hourStep * 60); + return disabled || decrementedSelected < min || + decrementedSelected > selected && decrementedSelected > max; + }; + + $scope.noIncrementMinutes = function() { + var incrementedSelected = addMinutes(selected, minuteStep); + return disabled || incrementedSelected > max || + incrementedSelected < selected && incrementedSelected < min; + }; + + $scope.noDecrementMinutes = function() { + var decrementedSelected = addMinutes(selected, -minuteStep); + return disabled || decrementedSelected < min || + decrementedSelected > selected && decrementedSelected > max; + }; + + $scope.noIncrementSeconds = function() { + var incrementedSelected = addSeconds(selected, secondStep); + return disabled || incrementedSelected > max || + incrementedSelected < selected && incrementedSelected < min; + }; + + $scope.noDecrementSeconds = function() { + var decrementedSelected = addSeconds(selected, -secondStep); + return disabled || decrementedSelected < min || + decrementedSelected > selected && decrementedSelected > max; + }; + + $scope.noToggleMeridian = function() { + if (selected.getHours() < 12) { + return disabled || addMinutes(selected, 12 * 60) > max; + } + + return disabled || addMinutes(selected, -12 * 60) < min; + }; + + var secondStep = timepickerConfig.secondStep; + if ($attrs.secondStep) { + watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) { + secondStep = +value; + })); + } + + $scope.showSeconds = timepickerConfig.showSeconds; + if ($attrs.showSeconds) { + watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) { + $scope.showSeconds = !!value; + })); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if (ngModelCtrl.$error.time) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); + refresh(); + } + } else { + updateTemplate(); + } + })); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate() { + var hours = +$scope.hours; + var valid = $scope.showMeridian ? hours > 0 && hours < 13 : + hours >= 0 && hours < 24; + if (!valid || $scope.hours === '') { + return undefined; + } + + if ($scope.showMeridian) { + if (hours === 12) { + hours = 0; + } + if ($scope.meridian === meridians[1]) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = +$scope.minutes; + var valid = minutes >= 0 && minutes < 60; + if (!valid || $scope.minutes === '') { + return undefined; + } + return minutes; + } + + function getSecondsFromTemplate() { + var seconds = +$scope.seconds; + return seconds >= 0 && seconds < 60 ? seconds : undefined; + } + + function pad(value, noPad) { + if (value === null) { + return ''; + } + + return angular.isDefined(value) && value.toString().length < 2 && !noPad ? + '0' + value : value.toString(); + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY; + return e.detail || delta > 0; + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + if (!disabled) { + $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); + } + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + if (!disabled) { + $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); + } + e.preventDefault(); + }); + + secondsInputEl.bind('mousewheel wheel', function(e) { + if (!disabled) { + $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds()); + } + e.preventDefault(); + }); + }; + + // Respond on up/down arrowkeys + this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { + hoursInputEl.bind('keydown', function(e) { + if (!disabled) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementHours(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementHours(); + $scope.$apply(); + } + } + }); + + minutesInputEl.bind('keydown', function(e) { + if (!disabled) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementMinutes(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementMinutes(); + $scope.$apply(); + } + } + }); + + secondsInputEl.bind('keydown', function(e) { + if (!disabled) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementSeconds(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementSeconds(); + $scope.$apply(); + } + } + }); + }; + + this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { + if ($scope.readonlyInput) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + $scope.updateSeconds = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) { + ngModelCtrl.$setViewValue(null); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + + if (angular.isDefined(invalidSeconds)) { + $scope.invalidSeconds = invalidSeconds; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(), + minutes = getMinutesFromTemplate(); + + ngModelCtrl.$setDirty(); + + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); + selected.setMinutes(minutes); + if (selected < min || selected > max) { + invalidate(true); + } else { + refresh('h'); + } + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + ngModelCtrl.$setTouched(); + if (modelIsEmpty()) { + makeValid(); + } else if ($scope.hours === null || $scope.hours === '') { + invalidate(true); + } else if (!$scope.invalidHours && $scope.hours < 10) { + $scope.$apply(function() { + $scope.hours = pad($scope.hours, !padHours); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(), + hours = getHoursFromTemplate(); + + ngModelCtrl.$setDirty(); + + if (angular.isDefined(minutes) && angular.isDefined(hours)) { + selected.setHours(hours); + selected.setMinutes(minutes); + if (selected < min || selected > max) { + invalidate(undefined, true); + } else { + refresh('m'); + } + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + ngModelCtrl.$setTouched(); + if (modelIsEmpty()) { + makeValid(); + } else if ($scope.minutes === null) { + invalidate(undefined, true); + } else if (!$scope.invalidMinutes && $scope.minutes < 10) { + $scope.$apply(function() { + $scope.minutes = pad($scope.minutes); + }); + } + }); + + $scope.updateSeconds = function() { + var seconds = getSecondsFromTemplate(); + + ngModelCtrl.$setDirty(); + + if (angular.isDefined(seconds)) { + selected.setSeconds(seconds); + refresh('s'); + } else { + invalidate(undefined, undefined, true); + } + }; + + secondsInputEl.bind('blur', function(e) { + if (modelIsEmpty()) { + makeValid(); + } else if (!$scope.invalidSeconds && $scope.seconds < 10) { + $scope.$apply( function() { + $scope.seconds = pad($scope.seconds); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$viewValue; + + if (isNaN(date)) { + ngModelCtrl.$setValidity('time', false); + $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.'); + } else { + if (date) { + selected = date; + } + + if (selected < min || selected > max) { + ngModelCtrl.$setValidity('time', false); + $scope.invalidHours = true; + $scope.invalidMinutes = true; + } else { + makeValid(); + } + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh(keyboardChange) { + makeValid(); + ngModelCtrl.$setViewValue(new Date(selected)); + updateTemplate(keyboardChange); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + $scope.invalidSeconds = false; + } + + function updateTemplate(keyboardChange) { + if (!ngModelCtrl.$modelValue) { + $scope.hours = null; + $scope.minutes = null; + $scope.seconds = null; + $scope.meridian = meridians[0]; + } else { + var hours = selected.getHours(), + minutes = selected.getMinutes(), + seconds = selected.getSeconds(); + + if ($scope.showMeridian) { + hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours); + if (keyboardChange !== 'm') { + $scope.minutes = pad(minutes); + } + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + + if (keyboardChange !== 's') { + $scope.seconds = pad(seconds); + } + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + } + + function addSecondsToSelected(seconds) { + selected = addSeconds(selected, seconds); + refresh(); + } + + function addMinutes(selected, minutes) { + return addSeconds(selected, minutes*60); + } + + function addSeconds(date, seconds) { + var dt = new Date(date.getTime() + seconds * 1000); + var newDate = new Date(date); + newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds()); + return newDate; + } + + function modelIsEmpty() { + return ($scope.hours === null || $scope.hours === '') && + ($scope.minutes === null || $scope.minutes === '') && + (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === '')); + } + + $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? + $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; + + $scope.incrementHours = function() { + if (!$scope.noIncrementHours()) { + addSecondsToSelected(hourStep * 60 * 60); + } + }; + + $scope.decrementHours = function() { + if (!$scope.noDecrementHours()) { + addSecondsToSelected(-hourStep * 60 * 60); + } + }; + + $scope.incrementMinutes = function() { + if (!$scope.noIncrementMinutes()) { + addSecondsToSelected(minuteStep * 60); + } + }; + + $scope.decrementMinutes = function() { + if (!$scope.noDecrementMinutes()) { + addSecondsToSelected(-minuteStep * 60); + } + }; + + $scope.incrementSeconds = function() { + if (!$scope.noIncrementSeconds()) { + addSecondsToSelected(secondStep); + } + }; + + $scope.decrementSeconds = function() { + if (!$scope.noDecrementSeconds()) { + addSecondsToSelected(-secondStep); + } + }; + + $scope.toggleMeridian = function() { + var minutes = getMinutesFromTemplate(), + hours = getHoursFromTemplate(); + + if (!$scope.noToggleMeridian()) { + if (angular.isDefined(minutes) && angular.isDefined(hours)) { + addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60)); + } else { + $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0]; + } + } + }; + + $scope.blur = function() { + ngModelCtrl.$setTouched(); + }; + + $scope.$on('$destroy', function() { + while (watchers.length) { + watchers.shift()(); + } + }); +}]) + +.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) { + return { + require: ['uibTimepicker', '?^ngModel'], + controller: 'UibTimepickerController', + controllerAs: 'timepicker', + replace: true, + scope: {}, + templateUrl: function(element, attrs) { + return attrs.templateUrl || uibTimepickerConfig.templateUrl; + }, + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (ngModelCtrl) { + timepickerCtrl.init(ngModelCtrl, element.find('input')); + } + } + }; +}]); + +angular.module('ui.bootstrap.dateparser', []) + +.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) { + // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js + var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + + var localeId; + var formatCodeToRegex; + + this.init = function() { + localeId = $locale.id; + + this.parsers = {}; + this.formatters = {}; + + formatCodeToRegex = [ + { + key: 'yyyy', + regex: '\\d{4}', + apply: function(value) { this.year = +value; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'yyyy'); + } + }, + { + key: 'yy', + regex: '\\d{2}', + apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'yy'); + } + }, + { + key: 'y', + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'y'); + } + }, + { + key: 'M!', + regex: '0?[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { + var value = date.getMonth(); + if (/^[0-9]$/.test(value)) { + return dateFilter(date, 'MM'); + } + + return dateFilter(date, 'M'); + } + }, + { + key: 'MMMM', + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, + formatter: function(date) { return dateFilter(date, 'MMMM'); } + }, + { + key: 'MMM', + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, + formatter: function(date) { return dateFilter(date, 'MMM'); } + }, + { + key: 'MM', + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { return dateFilter(date, 'MM'); } + }, + { + key: 'M', + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { return dateFilter(date, 'M'); } + }, + { + key: 'd!', + regex: '[0-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; }, + formatter: function(date) { + var value = date.getDate(); + if (/^[1-9]$/.test(value)) { + return dateFilter(date, 'dd'); + } + + return dateFilter(date, 'd'); + } + }, + { + key: 'dd', + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; }, + formatter: function(date) { return dateFilter(date, 'dd'); } + }, + { + key: 'd', + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; }, + formatter: function(date) { return dateFilter(date, 'd'); } + }, + { + key: 'EEEE', + regex: $locale.DATETIME_FORMATS.DAY.join('|'), + formatter: function(date) { return dateFilter(date, 'EEEE'); } + }, + { + key: 'EEE', + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), + formatter: function(date) { return dateFilter(date, 'EEE'); } + }, + { + key: 'HH', + regex: '(?:0|1)[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'HH'); } + }, + { + key: 'hh', + regex: '0[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'hh'); } + }, + { + key: 'H', + regex: '1?[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'H'); } + }, + { + key: 'h', + regex: '[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'h'); } + }, + { + key: 'mm', + regex: '[0-5][0-9]', + apply: function(value) { this.minutes = +value; }, + formatter: function(date) { return dateFilter(date, 'mm'); } + }, + { + key: 'm', + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.minutes = +value; }, + formatter: function(date) { return dateFilter(date, 'm'); } + }, + { + key: 'sss', + regex: '[0-9][0-9][0-9]', + apply: function(value) { this.milliseconds = +value; }, + formatter: function(date) { return dateFilter(date, 'sss'); } + }, + { + key: 'ss', + regex: '[0-5][0-9]', + apply: function(value) { this.seconds = +value; }, + formatter: function(date) { return dateFilter(date, 'ss'); } + }, + { + key: 's', + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.seconds = +value; }, + formatter: function(date) { return dateFilter(date, 's'); } + }, + { + key: 'a', + regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), + apply: function(value) { + if (this.hours === 12) { + this.hours = 0; + } + + if (value === 'PM') { + this.hours += 12; + } + }, + formatter: function(date) { return dateFilter(date, 'a'); } + }, + { + key: 'Z', + regex: '[+-]\\d{4}', + apply: function(value) { + var matches = value.match(/([+-])(\d{2})(\d{2})/), + sign = matches[1], + hours = matches[2], + minutes = matches[3]; + this.hours += toInt(sign + hours); + this.minutes += toInt(sign + minutes); + }, + formatter: function(date) { + return dateFilter(date, 'Z'); + } + }, + { + key: 'ww', + regex: '[0-4][0-9]|5[0-3]', + formatter: function(date) { return dateFilter(date, 'ww'); } + }, + { + key: 'w', + regex: '[0-9]|[1-4][0-9]|5[0-3]', + formatter: function(date) { return dateFilter(date, 'w'); } + }, + { + key: 'GGGG', + regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), + formatter: function(date) { return dateFilter(date, 'GGGG'); } + }, + { + key: 'GGG', + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'GGG'); } + }, + { + key: 'GG', + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'GG'); } + }, + { + key: 'G', + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'G'); } + } + ]; + }; + + this.init(); + + function createParser(format, func) { + var map = [], regex = format.split(''); + + // check for literal values + var quoteIndex = format.indexOf('\''); + if (quoteIndex > -1) { + var inLiteral = false; + format = format.split(''); + for (var i = quoteIndex; i < format.length; i++) { + if (inLiteral) { + if (format[i] === '\'') { + if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote + format[i+1] = '$'; + regex[i+1] = ''; + } else { // end of literal + regex[i] = ''; + inLiteral = false; + } + } + format[i] = '$'; + } else { + if (format[i] === '\'') { // start of literal + format[i] = '$'; + regex[i] = ''; + inLiteral = true; + } + } + } + + format = format.join(''); + } + + angular.forEach(formatCodeToRegex, function(data) { + var index = format.indexOf(data.key); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + data.key.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ + index: index, + key: data.key, + apply: data[func], + matcher: data.regex + }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + } + + this.filter = function(date, format) { + if (!angular.isDate(date) || isNaN(date) || !format) { + return ''; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.formatters[format]) { + this.formatters[format] = createParser(format, 'formatter'); + } + + var parser = this.formatters[format], + map = parser.map; + + var _format = format; + + return map.reduce(function(str, mapper, i) { + var match = _format.match(new RegExp('(.*)' + mapper.key)); + if (match && angular.isString(match[1])) { + str += match[1]; + _format = _format.replace(match[1] + mapper.key, ''); + } + + var endStr = i === map.length - 1 ? _format : ''; + + if (mapper.apply) { + return str + mapper.apply.call(null, date) + endStr; + } + + return str + endStr; + }, ''); + }; + + this.parse = function(input, format, baseDate) { + if (!angular.isString(input) || !format) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); + + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.parsers[format]) { + this.parsers[format] = createParser(format, 'apply'); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex), + tzOffset = false; + if (results && results.length) { + var fields, dt; + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { + fields = { + year: baseDate.getFullYear(), + month: baseDate.getMonth(), + date: baseDate.getDate(), + hours: baseDate.getHours(), + minutes: baseDate.getMinutes(), + seconds: baseDate.getSeconds(), + milliseconds: baseDate.getMilliseconds() + }; + } else { + if (baseDate) { + $log.warn('dateparser:', 'baseDate is not a valid date'); + } + fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; + } + + for (var i = 1, n = results.length; i < n; i++) { + var mapper = map[i - 1]; + if (mapper.matcher === 'Z') { + tzOffset = true; + } + + if (mapper.apply) { + mapper.apply.call(fields, results[i]); + } + } + + var datesetter = tzOffset ? Date.prototype.setUTCFullYear : + Date.prototype.setFullYear; + var timesetter = tzOffset ? Date.prototype.setUTCHours : + Date.prototype.setHours; + + if (isValid(fields.year, fields.month, fields.date)) { + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) { + dt = new Date(baseDate); + datesetter.call(dt, fields.year, fields.month, fields.date); + timesetter.call(dt, fields.hours, fields.minutes, + fields.seconds, fields.milliseconds); + } else { + dt = new Date(0); + datesetter.call(dt, fields.year, fields.month, fields.date); + timesetter.call(dt, fields.hours || 0, fields.minutes || 0, + fields.seconds || 0, fields.milliseconds || 0); + } + } + + return dt; + } + }; + + // Check if date is valid for specific month (and year for February). + // Month: 0 = Jan, 1 = Feb, etc + function isValid(year, month, date) { + if (date < 1) { + return false; + } + + if (month === 1 && date > 28) { + return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0); + } + + if (month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; + } + + return true; + } + + function toInt(str) { + return parseInt(str, 10); + } + + this.toTimezone = toTimezone; + this.fromTimezone = fromTimezone; + this.timezoneToOffset = timezoneToOffset; + this.addDateMinutes = addDateMinutes; + this.convertTimezoneToLocal = convertTimezoneToLocal; + + function toTimezone(date, timezone) { + return date && timezone ? convertTimezoneToLocal(date, timezone) : date; + } + + function fromTimezone(date, timezone) { + return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; + } + + //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207 + function timezoneToOffset(timezone, fallback) { + timezone = timezone.replace(/:/g, ''); + var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; + return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; + } + + function addDateMinutes(date, minutes) { + date = new Date(date.getTime()); + date.setMinutes(date.getMinutes() + minutes); + return date; + } + + function convertTimezoneToLocal(date, timezone, reverse) { + reverse = reverse ? -1 : 1; + var dateTimezoneOffset = date.getTimezoneOffset(); + var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); + return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); + } +}]); + +// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to +// at most one element. +angular.module('ui.bootstrap.isClass', []) +.directive('uibIsClass', [ + '$animate', +function ($animate) { + // 11111111 22222222 + var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/; + // 11111111 22222222 + var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/; + + var dataPerTracked = {}; + + return { + restrict: 'A', + compile: function(tElement, tAttrs) { + var linkedScopes = []; + var instances = []; + var expToData = {}; + var lastActivated = null; + var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP); + var onExp = onExpMatches[2]; + var expsStr = onExpMatches[1]; + var exps = expsStr.split(','); + + return linkFn; + + function linkFn(scope, element, attrs) { + linkedScopes.push(scope); + instances.push({ + scope: scope, + element: element + }); + + exps.forEach(function(exp, k) { + addForExp(exp, scope); + }); + + scope.$on('$destroy', removeScope); + } + + function addForExp(exp, scope) { + var matches = exp.match(IS_REGEXP); + var clazz = scope.$eval(matches[1]); + var compareWithExp = matches[2]; + var data = expToData[exp]; + if (!data) { + var watchFn = function(compareWithVal) { + var newActivated = null; + instances.some(function(instance) { + var thisVal = instance.scope.$eval(onExp); + if (thisVal === compareWithVal) { + newActivated = instance; + return true; + } + }); + if (data.lastActivated !== newActivated) { + if (data.lastActivated) { + $animate.removeClass(data.lastActivated.element, clazz); + } + if (newActivated) { + $animate.addClass(newActivated.element, clazz); + } + data.lastActivated = newActivated; + } + }; + expToData[exp] = data = { + lastActivated: null, + scope: scope, + watchFn: watchFn, + compareWithExp: compareWithExp, + watcher: scope.$watch(compareWithExp, watchFn) + }; + } + data.watchFn(scope.$eval(compareWithExp)); + } + + function removeScope(e) { + var removedScope = e.targetScope; + var index = linkedScopes.indexOf(removedScope); + linkedScopes.splice(index, 1); + instances.splice(index, 1); + if (linkedScopes.length) { + var newWatchScope = linkedScopes[0]; + angular.forEach(expToData, function(data) { + if (data.scope === removedScope) { + data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn); + data.scope = newWatchScope; + } + }); + } else { + expToData = {}; + } + } + } + }; +}]); +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass']) + +.value('$datepickerSuppressError', false) + +.value('$datepickerLiteralWarning', true) + +.constant('uibDatepickerConfig', { + datepickerMode: 'day', + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + maxDate: null, + maxMode: 'year', + minDate: null, + minMode: 'day', + ngModelOptions: {}, + shortcutPropagation: false, + showWeeks: true, + yearColumns: 5, + yearRows: 4 +}) + +.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser', + function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl; + ngModelOptions = {}, + watchListeners = [], + optionsUsed = !!$attrs.datepickerOptions; + + if (!$scope.datepickerOptions) { + $scope.datepickerOptions = {}; + } + + // Modes chain + this.modes = ['day', 'month', 'year']; + + [ + 'customClass', + 'dateDisabled', + 'datepickerMode', + 'formatDay', + 'formatDayHeader', + 'formatDayTitle', + 'formatMonth', + 'formatMonthTitle', + 'formatYear', + 'maxDate', + 'maxMode', + 'minDate', + 'minMode', + 'showWeeks', + 'shortcutPropagation', + 'startingDay', + 'yearColumns', + 'yearRows' + ].forEach(function(key) { + switch (key) { + case 'customClass': + case 'dateDisabled': + $scope[key] = $scope.datepickerOptions[key] || angular.noop; + break; + case 'datepickerMode': + $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ? + $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode; + break; + case 'formatDay': + case 'formatDayHeader': + case 'formatDayTitle': + case 'formatMonth': + case 'formatMonthTitle': + case 'formatYear': + self[key] = angular.isDefined($scope.datepickerOptions[key]) ? + $interpolate($scope.datepickerOptions[key])($scope.$parent) : + datepickerConfig[key]; + break; + case 'showWeeks': + case 'shortcutPropagation': + case 'yearColumns': + case 'yearRows': + self[key] = angular.isDefined($scope.datepickerOptions[key]) ? + $scope.datepickerOptions[key] : datepickerConfig[key]; + break; + case 'startingDay': + if (angular.isDefined($scope.datepickerOptions.startingDay)) { + self.startingDay = $scope.datepickerOptions.startingDay; + } else if (angular.isNumber(datepickerConfig.startingDay)) { + self.startingDay = datepickerConfig.startingDay; + } else { + self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; + } + + break; + case 'maxDate': + case 'minDate': + $scope.$watch('datepickerOptions.' + key, function(value) { + if (value) { + if (angular.isDate(value)) { + self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); + } else { + if ($datepickerLiteralWarning) { + $log.warn('Literal date support has been deprecated, please switch to date object usage'); + } + + self[key] = new Date(dateFilter(value, 'medium')); + } + } else { + self[key] = datepickerConfig[key] ? + dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : + null; + } + + self.refreshView(); + }); + + break; + case 'maxMode': + case 'minMode': + if ($scope.datepickerOptions[key]) { + $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) { + self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key]; + if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) || + key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) { + $scope.datepickerMode = self[key]; + $scope.datepickerOptions.datepickerMode = self[key]; + } + }); + } else { + self[key] = $scope[key] = datepickerConfig[key] || null; + } + + break; + } + }); + + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + + $scope.disabled = angular.isDefined($attrs.disabled) || false; + if (angular.isDefined($attrs.ngDisabled)) { + watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) { + $scope.disabled = disabled; + self.refreshView(); + })); + } + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions; + if ($scope.datepickerOptions.initDate) { + self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date(); + $scope.$watch('datepickerOptions.initDate', function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); + self.refreshView(); + } + }); + } else { + self.activeDate = new Date(); + } + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date(); + this.activeDate = !isNaN(date) ? + dateParser.fromTimezone(date, ngModelOptions.timezone) : + dateParser.fromTimezone(new Date(), ngModelOptions.timezone); + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if (ngModelCtrl.$viewValue) { + var date = new Date(ngModelCtrl.$viewValue), + isValid = !isNaN(date); + + if (isValid) { + this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone); + } else if (!$datepickerSuppressError) { + $log.error('Datepicker directive: "ng-model" value must be a Date object'); + } + } + this.refreshView(); + }; + + this.refreshView = function() { + if (this.element) { + $scope.selectedDt = null; + this._refreshView(); + if ($scope.activeDt) { + $scope.activeDateId = $scope.activeDt.uid; + } + + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + date = dateParser.fromTimezone(date, ngModelOptions.timezone); + ngModelCtrl.$setValidity('dateDisabled', !date || + this.element && !this.isDisabled(date)); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + model = dateParser.fromTimezone(model, ngModelOptions.timezone); + var today = new Date(); + today = dateParser.fromTimezone(today, ngModelOptions.timezone); + var time = this.compare(date, today); + var dt = { + date: date, + label: dateParser.filter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + past: time < 0, + current: time === 0, + future: time > 0, + customClass: this.customClass(date) || null + }; + + if (model && this.compare(date, model) === 0) { + $scope.selectedDt = dt; + } + + if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) { + $scope.activeDt = dt; + } + + return dt; + }; + + this.isDisabled = function(date) { + return $scope.disabled || + this.minDate && this.compare(date, this.minDate) < 0 || + this.maxDate && this.compare(date, this.maxDate) > 0 || + $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}); + }; + + this.customClass = function(date) { + return $scope.customClass({date: date, mode: $scope.datepickerMode}); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + $scope.select = function(date) { + if ($scope.datepickerMode === self.minMode) { + var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + dt = dateParser.toTimezone(dt, ngModelOptions.timezone); + ngModelCtrl.$setViewValue(dt); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]); + + $scope.$emit('uib:datepicker.mode'); + } + + $scope.$broadcast('uib:datepicker.focus'); + }; + + $scope.move = function(direction) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function(direction) { + direction = direction || 1; + + if ($scope.datepickerMode === self.maxMode && direction === 1 || + $scope.datepickerMode === self.minMode && direction === -1) { + return; + } + + setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]); + + $scope.$emit('uib:datepicker.mode'); + }; + + // Key event mapper + $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; + + var focusElement = function() { + self.element[0].focus(); + }; + + // Listen for focus requests from popup directive + $scope.$on('uib:datepicker.focus', focusElement); + + $scope.keydown = function(evt) { + var key = $scope.keys[evt.which]; + + if (!key || evt.shiftKey || evt.altKey || $scope.disabled) { + return; + } + + evt.preventDefault(); + if (!self.shortcutPropagation) { + evt.stopPropagation(); + } + + if (key === 'enter' || key === 'space') { + if (self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; + + $scope.$on('$destroy', function() { + //Clear all watch listeners on destroy + while (watchListeners.length) { + watchListeners.shift()(); + } + }); + + function setMode(mode) { + $scope.datepickerMode = mode; + $scope.datepickerOptions.datepickerMode = mode; + } +}]) + +.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + this.step = { months: 1 }; + this.element = $element; + function getDaysInMonth(year, month) { + return month === 1 && year % 4 === 0 && + (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month]; + } + + this.init = function(ctrl) { + angular.extend(ctrl, this); + scope.showWeeks = ctrl.showWeeks; + ctrl.refreshView(); + }; + + this.getDates = function(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0, date; + while (i < n) { + date = new Date(current); + dates[i++] = date; + current.setDate(current.getDate() + 1); + } + return dates; + }; + + this._refreshView = function() { + var year = this.activeDate.getFullYear(), + month = this.activeDate.getMonth(), + firstDayOfMonth = new Date(this.activeDate); + + firstDayOfMonth.setFullYear(year, month, 1); + + var difference = this.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = difference > 0 ? + 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if (numDisplayedFromPreviousMonth > 0) { + firstDate.setDate(-numDisplayedFromPreviousMonth + 1); + } + + // 42 is the number of days on a six-week calendar + var days = this.getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, this.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(this.activeDate, this.formatDayTitle); + scope.rows = this.split(days, 7); + + if (scope.showWeeks) { + scope.weekNumbers = []; + var thursdayIndex = (4 + 7 - this.startingDay) % 7, + numWeeks = scope.rows.length; + for (var curWeek = 0; curWeek < numWeeks; curWeek++) { + scope.weekNumbers.push( + getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); + } + } + }; + + this.compare = function(date1, date2) { + var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); + var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); + _date1.setFullYear(date1.getFullYear()); + _date2.setFullYear(date2.getFullYear()); + return _date1 - _date2; + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; + } else if (key === 'up') { + date = date - 7; + } else if (key === 'right') { + date = date + 1; + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()); + } + this.activeDate.setDate(date); + }; +}]) + +.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + this.step = { years: 1 }; + this.element = $element; + + this.init = function(ctrl) { + angular.extend(ctrl, this); + ctrl.refreshView(); + }; + + this._refreshView = function() { + var months = new Array(12), + year = this.activeDate.getFullYear(), + date; + + for (var i = 0; i < 12; i++) { + date = new Date(this.activeDate); + date.setFullYear(year, i, 1); + months[i] = angular.extend(this.createDateObject(date, this.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(this.activeDate, this.formatMonthTitle); + scope.rows = this.split(months, 3); + }; + + this.compare = function(date1, date2) { + var _date1 = new Date(date1.getFullYear(), date1.getMonth()); + var _date2 = new Date(date2.getFullYear(), date2.getMonth()); + _date1.setFullYear(date1.getFullYear()); + _date2.setFullYear(date2.getFullYear()); + return _date1 - _date2; + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; + } else if (key === 'up') { + date = date - 3; + } else if (key === 'right') { + date = date + 1; + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + this.activeDate.setMonth(date); + }; +}]) + +.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var columns, range; + this.element = $element; + + function getStartingYear(year) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + this.yearpickerInit = function() { + columns = this.yearColumns; + range = this.yearRows * columns; + this.step = { years: range }; + }; + + this._refreshView = function() { + var years = new Array(range), date; + + for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) { + date = new Date(this.activeDate); + date.setFullYear(start + i, 0, 1); + years[i] = angular.extend(this.createDateObject(date, this.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = this.split(years, columns); + scope.columns = columns; + }; + + this.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; + } else if (key === 'up') { + date = date - columns; + } else if (key === 'right') { + date = date + 1; + } else if (key === 'down') { + date = date + columns; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * range; + } else if (key === 'home') { + date = getStartingYear(this.activeDate.getFullYear()); + } else if (key === 'end') { + date = getStartingYear(this.activeDate.getFullYear()) + range - 1; + } + this.activeDate.setFullYear(date); + }; +}]) + +.directive('uibDatepicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/datepicker.html'; + }, + scope: { + datepickerOptions: '=?' + }, + require: ['uibDatepicker', '^ngModel'], + controller: 'UibDatepickerController', + controllerAs: 'datepicker', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + datepickerCtrl.init(ngModelCtrl); + } + }; +}) + +.directive('uibDaypicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/day.html'; + }, + require: ['^uibDatepicker', 'uibDaypicker'], + controller: 'UibDaypickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], + daypickerCtrl = ctrls[1]; + + daypickerCtrl.init(datepickerCtrl); + } + }; +}) + +.directive('uibMonthpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/month.html'; + }, + require: ['^uibDatepicker', 'uibMonthpicker'], + controller: 'UibMonthpickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], + monthpickerCtrl = ctrls[1]; + + monthpickerCtrl.init(datepickerCtrl); + } + }; +}) + +.directive('uibYearpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/year.html'; + }, + require: ['^uibDatepicker', 'uibYearpicker'], + controller: 'UibYearpickerController', + link: function(scope, element, attrs, ctrls) { + var ctrl = ctrls[0]; + angular.extend(ctrl, ctrls[1]); + ctrl.yearpickerInit(); + + ctrl.refreshView(); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) + +.constant('uibDropdownConfig', { + appendToOpenClass: 'uib-dropdown-open', + openClass: 'open' +}) + +.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) { + var openScope = null; + + this.open = function(dropdownScope, element) { + if (!openScope) { + $document.on('click', closeDropdown); + element.on('keydown', keybindFilter); + } + + if (openScope && openScope !== dropdownScope) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function(dropdownScope, element) { + if (openScope === dropdownScope) { + openScope = null; + $document.off('click', closeDropdown); + element.off('keydown', keybindFilter); + } + }; + + var closeDropdown = function(evt) { + // This method may still be called during the same mouse event that + // unbound this event handler. So check openScope before proceeding. + if (!openScope) { return; } + + if (evt && openScope.getAutoClose() === 'disabled') { return; } + + if (evt && evt.which === 3) { return; } + + var toggleElement = openScope.getToggleElement(); + if (evt && toggleElement && toggleElement[0].contains(evt.target)) { + return; + } + + var dropdownElement = openScope.getDropdownElement(); + if (evt && openScope.getAutoClose() === 'outsideClick' && + dropdownElement && dropdownElement[0].contains(evt.target)) { + return; + } + + openScope.isOpen = false; + + if (!$rootScope.$$phase) { + openScope.$apply(); + } + }; + + var keybindFilter = function(evt) { + if (evt.which === 27) { + evt.stopPropagation(); + openScope.focusToggleElement(); + closeDropdown(); + } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + openScope.focusDropdownEntry(evt.which); + } + }; +}]) + +.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) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + appendToOpenClass = dropdownConfig.appendToOpenClass, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + appendToBody = false, + appendTo = null, + keynavEnabled = false, + selectedOption = null, + body = $document.find('body'); + + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + if (angular.isDefined($attrs.dropdownAppendTo)) { + var appendToEl = $parse($attrs.dropdownAppendTo)(scope); + if (appendToEl) { + appendTo = angular.element(appendToEl); + } + } + + appendToBody = angular.isDefined($attrs.dropdownAppendToBody); + keynavEnabled = angular.isDefined($attrs.keyboardNav); + + if (appendToBody && !appendTo) { + appendTo = body; + } + + if (appendTo && self.dropdownMenu) { + appendTo.append(self.dropdownMenu); + $element.on('$destroy', function handleDestroyEvent() { + self.dropdownMenu.remove(); + }); + } + }; + + this.toggle = function(open) { + scope.isOpen = arguments.length ? !!open : !scope.isOpen; + if (angular.isFunction(setIsOpen)) { + setIsOpen(scope, scope.isOpen); + } + + return scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + angular.element(self.dropdownMenu).find('a') : + $element.find('ul').eq(0).find('a'); + + switch (keyCode) { + case 40: { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = self.selectedOption === elems.length - 1 ? + self.selectedOption : + self.selectedOption + 1; + } + break; + } + case 38: { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + + scope.focusToggleElement = function() { + if (self.toggleElement) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function(isOpen, wasOpen) { + if (appendTo && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true), + css, + rightalign, + scrollbarPadding, + scrollbarWidth = 0; + + css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; + + rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + scrollbarPadding = $position.scrollbarPadding(appendTo); + + if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + scrollbarWidth = scrollbarPadding.scrollbarWidth; + } + + css.right = window.innerWidth - scrollbarWidth - + (pos.left + $element.prop('offsetWidth')) + 'px'; + } + + // Need to adjust our positioning to be relative to the appendTo container + // if it's not the body element + if (!appendToBody) { + var appendOffset = $position.offset(appendTo); + + css.top = pos.top - appendOffset.top + 'px'; + + if (!rightalign) { + css.left = pos.left - appendOffset.left + 'px'; + } else { + css.right = window.innerWidth - + (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px'; + } + } + + self.dropdownMenu.css(css); + } + + var openContainer = appendTo ? appendTo : $element; + var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass); + + if (hasOpenClass === !isOpen) { + $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + } + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + }); + }); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope, $element); + } else { + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element(''); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + uibDropdownService.close(scope, $element); + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); + } + }); +}]) + +.directive('uibDropdown', function() { + return { + controller: 'UibDropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init(); + } + }; +}) + +.directive('uibDropdownMenu', function() { + return { + restrict: 'A', + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { + return; + } + + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; +}) + +.directive('uibDropdownToggle', function() { + return { + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if (!element.hasClass('disabled') && !attrs.disabled) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.collapse', []) + + .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) { + var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; + return { + link: function(scope, element, attrs) { + var expandingExpr = $parse(attrs.expanding), + expandedExpr = $parse(attrs.expanded), + collapsingExpr = $parse(attrs.collapsing), + collapsedExpr = $parse(attrs.collapsed); + + if (!scope.$eval(attrs.uibCollapse)) { + element.addClass('in') + .addClass('collapse') + .attr('aria-expanded', true) + .attr('aria-hidden', false) + .css({height: 'auto'}); + } + + function expand() { + if (element.hasClass('collapse') && element.hasClass('in')) { + return; + } + + $q.resolve(expandingExpr(scope)) + .then(function() { + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); + + if ($animateCss) { + $animateCss(element, { + addClass: 'in', + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start()['finally'](expandDone); + } else { + $animate.addClass(element, 'in', { + to: { height: element[0].scrollHeight + 'px' } + }).then(expandDone); + } + }); + } + + function expandDone() { + element.removeClass('collapsing') + .addClass('collapse') + .css({height: 'auto'}); + expandedExpr(scope); + } + + function collapse() { + if (!element.hasClass('collapse') && !element.hasClass('in')) { + return collapseDone(); + } + + $q.resolve(collapsingExpr(scope)) + .then(function() { + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); + + if ($animateCss) { + $animateCss(element, { + removeClass: 'in', + to: {height: '0'} + }).start()['finally'](collapseDone); + } else { + $animate.removeClass(element, 'in', { + to: {height: '0'} + }).then(collapseDone); + } + }); + } + + function collapseDone() { + element.css({height: '0'}); // Required so that collapse works when animation is disabled + element.removeClass('collapsing') + .addClass('collapse'); + collapsedExpr(scope); + } + + scope.$watch(attrs.uibCollapse, function(shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('uibAccordionConfig', { + closeOthers: true +}) + +.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) { + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? + $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if (closeOthers) { + angular.forEach(this.groups, function(group) { + if (group !== openGroup) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function(event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if (index !== -1) { + this.groups.splice(index, 1); + } + }; +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('uibAccordion', function() { + return { + controller: 'UibAccordionController', + controllerAs: 'accordion', + transclude: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/accordion/accordion.html'; + } + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('uibAccordionGroup', function() { + return { + require: '^uibAccordion', // We need this directive to be inside an accordion + transclude: true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/accordion/accordion-group.html'; + }, + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + panelClass: '@?', // Ditto with panelClass + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.openClass = attrs.openClass || 'panel-open'; + scope.panelClass = attrs.panelClass || 'panel-default'; + scope.$watch('isOpen', function(value) { + element.toggleClass(scope.openClass, !!value); + if (value) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function($event) { + if (!scope.isDisabled) { + if (!$event || $event.which === 32) { + scope.isOpen = !scope.isOpen; + } + } + }; + + var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + scope.headingId = id + '-tab'; + scope.panelId = id + '-panel'; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +.directive('uibAccordionHeading', function() { + return { + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^uibAccordionGroup', + link: function(scope, element, attrs, accordionGroupCtrl, transclude) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +.directive('uibAccordionTransclude', function() { + return { + require: '^uibAccordionGroup', + link: function(scope, element, attrs, controller) { + scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) { + if (heading) { + var elem = angular.element(element[0].querySelector(getHeaderSelectors())); + elem.html(''); + elem.append(heading); + } + }); + } + }; + + function getHeaderSelectors() { + return 'uib-accordion-header,' + + 'data-uib-accordion-header,' + + 'x-uib-accordion-header,' + + 'uib\\:accordion-header,' + + '[uib-accordion-header],' + + '[data-uib-accordion-header],' + + '[x-uib-accordion-header]'; + } +}); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, and selector delegatation. + */ +angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip']) + +.directive('uibPopoverTemplatePopup', function() { + return { + replace: true, + scope: { uibTitle: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'uib/template/popover/popover-template.html' + }; +}) + +.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverTemplate', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', uibTitle: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/popover/popover-html.html' + }; +}) + +.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverHtml', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverPopup', function() { + return { + replace: true, + scope: { uibTitle: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/popover/popover.html' + }; +}) + +.directive('uibPopover', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopover', 'popover', 'click'); +}]); + +angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position']) + +.value('$datepickerPopupLiteralWarning', true) + +.constant('uibDatepickerPopupConfig', { + altInputFormats: [], + appendToBody: false, + clearText: 'Clear', + closeOnDateSelection: true, + closeText: 'Done', + currentText: 'Today', + datepickerPopup: 'yyyy-MM-dd', + datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html', + datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html', + html5Types: { + date: 'yyyy-MM-dd', + 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', + 'month': 'yyyy-MM' + }, + onOpenFocus: true, + showButtonBar: true, + placement: 'auto bottom-left' +}) + +.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning', +function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) { + var cache = {}, + isHtml5DateInput = false; + var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, + datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, + ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [], + timezone; + + this.init = function(_ngModel_) { + ngModel = _ngModel_; + ngModelOptions = _ngModel_.$options; + closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? + $scope.$parent.$eval($attrs.closeOnDateSelection) : + datepickerPopupConfig.closeOnDateSelection; + appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? + $scope.$parent.$eval($attrs.datepickerAppendToBody) : + datepickerPopupConfig.appendToBody; + onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? + $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; + datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? + $attrs.datepickerPopupTemplateUrl : + datepickerPopupConfig.datepickerPopupTemplateUrl; + datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? + $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; + altInputFormats = angular.isDefined($attrs.altInputFormats) ? + $scope.$parent.$eval($attrs.altInputFormats) : + datepickerPopupConfig.altInputFormats; + + $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? + $scope.$parent.$eval($attrs.showButtonBar) : + datepickerPopupConfig.showButtonBar; + + if (datepickerPopupConfig.html5Types[$attrs.type]) { + dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; + isHtml5DateInput = true; + } else { + dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; + $attrs.$observe('uibDatepickerPopup', function(value, oldValue) { + var newDateFormat = value || datepickerPopupConfig.datepickerPopup; + // Invalidate the $modelValue to ensure that formatters re-run + // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 + if (newDateFormat !== dateFormat) { + dateFormat = newDateFormat; + ngModel.$modelValue = null; + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + } + }); + } + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + + if (isHtml5DateInput && $attrs.uibDatepickerPopup) { + throw new Error('HTML5 date input types do not support custom formats.'); + } + + // popup element used to display calendar + popupEl = angular.element('
'); + if (ngModelOptions) { + timezone = ngModelOptions.timezone; + $scope.ngModelOptions = angular.copy(ngModelOptions); + $scope.ngModelOptions.timezone = null; + if ($scope.ngModelOptions.updateOnDefault === true) { + $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ? + $scope.ngModelOptions.updateOn + ' default' : 'default'; + } + + popupEl.attr('ng-model-options', 'ngModelOptions'); + } else { + timezone = null; + } + + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection(date)', + 'template-url': datepickerPopupTemplateUrl + }); + + // datepicker element + datepickerEl = angular.element(popupEl.children()[0]); + datepickerEl.attr('template-url', datepickerTemplateUrl); + + if (!$scope.datepickerOptions) { + $scope.datepickerOptions = {}; + } + + if (isHtml5DateInput) { + if ($attrs.type === 'month') { + $scope.datepickerOptions.datepickerMode = 'month'; + $scope.datepickerOptions.minMode = 'month'; + } + } + + datepickerEl.attr('datepicker-options', 'datepickerOptions'); + + if (!isHtml5DateInput) { + // Internal API to maintain the correct ng-invalid-[key] class + ngModel.$$parserName = 'date'; + ngModel.$validators.date = validator; + ngModel.$parsers.unshift(parseDate); + ngModel.$formatters.push(function(value) { + if (ngModel.$isEmpty(value)) { + $scope.date = value; + return value; + } + + if (angular.isNumber(value)) { + value = new Date(value); + } + + $scope.date = dateParser.fromTimezone(value, timezone); + + return dateParser.filter($scope.date, dateFormat); + }); + } else { + ngModel.$formatters.push(function(value) { + $scope.date = dateParser.fromTimezone(value, timezone); + return value; + }); + } + + // Detect changes in the view from the text box + ngModel.$viewChangeListeners.push(function() { + $scope.date = parseDateString(ngModel.$viewValue); + }); + + $element.on('keydown', inputKeydownBind); + + $popup = $compile(popupEl)($scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if (appendToBody) { + $document.find('body').append($popup); + } else { + $element.after($popup); + } + + $scope.$on('$destroy', function() { + if ($scope.isOpen === true) { + if (!$rootScope.$$phase) { + $scope.$apply(function() { + $scope.isOpen = false; + }); + } + } + + $popup.remove(); + $element.off('keydown', inputKeydownBind); + $document.off('click', documentClickBind); + if (scrollParentEl) { + scrollParentEl.off('scroll', positionPopup); + } + angular.element($window).off('resize', positionPopup); + + //Clear all watch listeners on destroy + while (watchListeners.length) { + watchListeners.shift()(); + } + }); + }; + + $scope.getText = function(key) { + return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + $scope.isDisabled = function(date) { + if (date === 'today') { + date = dateParser.fromTimezone(new Date(), timezone); + } + + var dates = {}; + angular.forEach(['minDate', 'maxDate'], function(key) { + if (!$scope.datepickerOptions[key]) { + dates[key] = null; + } else if (angular.isDate($scope.datepickerOptions[key])) { + dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone); + } else { + if ($datepickerPopupLiteralWarning) { + $log.warn('Literal date support has been deprecated, please switch to date object usage'); + } + + dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium')); + } + }); + + return $scope.datepickerOptions && + dates.minDate && $scope.compare(date, dates.minDate) < 0 || + dates.maxDate && $scope.compare(date, dates.maxDate) > 0; + }; + + $scope.compare = function(date1, date2) { + return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); + }; + + // Inner change + $scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + $scope.date = dt; + } + var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function + $element.val(date); + ngModel.$setViewValue(date); + + if (closeOnDateSelection) { + $scope.isOpen = false; + $element[0].focus(); + } + }; + + $scope.keydown = function(evt) { + if (evt.which === 27) { + evt.stopPropagation(); + $scope.isOpen = false; + $element[0].focus(); + } + }; + + $scope.select = function(date, evt) { + evt.stopPropagation(); + + if (date === 'today') { + var today = new Date(); + if (angular.isDate($scope.date)) { + date = new Date($scope.date); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + $scope.dateSelection(date); + }; + + $scope.close = function(evt) { + evt.stopPropagation(); + + $scope.isOpen = false; + $element[0].focus(); + }; + + $scope.disabled = angular.isDefined($attrs.disabled) || false; + if ($attrs.ngDisabled) { + watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { + $scope.disabled = disabled; + })); + } + + $scope.$watch('isOpen', function(value) { + if (value) { + if (!$scope.disabled) { + $timeout(function() { + positionPopup(); + + if (onOpenFocus) { + $scope.$broadcast('uib:datepicker.focus'); + } + + $document.on('click', documentClickBind); + + var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; + if (appendToBody || $position.parsePlacement(placement)[2]) { + scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); + if (scrollParentEl) { + scrollParentEl.on('scroll', positionPopup); + } + } else { + scrollParentEl = null; + } + + angular.element($window).on('resize', positionPopup); + }, 0, false); + } else { + $scope.isOpen = false; + } + } else { + $document.off('click', documentClickBind); + if (scrollParentEl) { + scrollParentEl.off('scroll', positionPopup); + } + angular.element($window).off('resize', positionPopup); + } + }); + + function cameltoDash(string) { + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + function parseDateString(viewValue) { + var date = dateParser.parse(viewValue, dateFormat, $scope.date); + if (isNaN(date)) { + for (var i = 0; i < altInputFormats.length; i++) { + date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); + if (!isNaN(date)) { + return date; + } + } + } + return date; + } + + function parseDate(viewValue) { + if (angular.isNumber(viewValue)) { + // presumably timestamp to date object + viewValue = new Date(viewValue); + } + + if (!viewValue) { + return null; + } + + if (angular.isDate(viewValue) && !isNaN(viewValue)) { + return viewValue; + } + + if (angular.isString(viewValue)) { + var date = parseDateString(viewValue); + if (!isNaN(date)) { + return dateParser.toTimezone(date, timezone); + } + } + + return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined; + } + + function validator(modelValue, viewValue) { + var value = modelValue || viewValue; + + if (!$attrs.ngRequired && !value) { + return true; + } + + if (angular.isNumber(value)) { + value = new Date(value); + } + + if (!value) { + return true; + } + + if (angular.isDate(value) && !isNaN(value)) { + return true; + } + + if (angular.isString(value)) { + return !isNaN(parseDateString(viewValue)); + } + + return false; + } + + function documentClickBind(event) { + if (!$scope.isOpen && $scope.disabled) { + return; + } + + var popup = $popup[0]; + var dpContainsTarget = $element[0].contains(event.target); + // The popup node may not be an element node + // In some browsers (IE) only element nodes have the 'contains' function + var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); + if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { + $scope.$apply(function() { + $scope.isOpen = false; + }); + } + } + + function inputKeydownBind(evt) { + if (evt.which === 27 && $scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.$apply(function() { + $scope.isOpen = false; + }); + $element[0].focus(); + } else if (evt.which === 40 && !$scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.$apply(function() { + $scope.isOpen = true; + }); + } + } + + function positionPopup() { + if ($scope.isOpen) { + var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); + var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; + var position = $position.positionElements($element, dpElement, placement, appendToBody); + dpElement.css({top: position.top + 'px', left: position.left + 'px'}); + if (dpElement.hasClass('uib-position-measure')) { + dpElement.removeClass('uib-position-measure'); + } + } + } + + $scope.$on('uib:datepicker.mode', function() { + $timeout(positionPopup, 0, false); + }); +}]) + +.directive('uibDatepickerPopup', function() { + return { + require: ['ngModel', 'uibDatepickerPopup'], + controller: 'UibDatepickerPopupController', + scope: { + datepickerOptions: '=?', + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@' + }, + link: function(scope, element, attrs, ctrls) { + var ngModel = ctrls[0], + ctrl = ctrls[1]; + + ctrl.init(ngModel); + } + }; +}) + +.directive('uibDatepickerPopupWrap', function() { + return { + replace: true, + transclude: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html'; + } + }; +}); +angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend(''); angular.$$uibPositionCss = true; }); +angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend(''); angular.$$uibTooltipCss = true; }); +angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibTimepickerCss = true; }); +angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibDatepickerCss = true; }); +angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend(''); angular.$$uibDatepickerpopupCss = true; }); \ No newline at end of file diff --git a/dist/ui-bootstrap-custom-1.3.3.min.js b/dist/ui-bootstrap-custom-1.3.3.min.js new file mode 100644 index 0000000..5142877 --- /dev/null +++ b/dist/ui-bootstrap-custom-1.3.3.min.js @@ -0,0 +1,9 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 1.3.3 - 2017-07-07 + * License: MIT + */angular.module("ui.bootstrap",["ui.bootstrap.stackedMap","ui.bootstrap.position","ui.bootstrap.modal","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.timepicker","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.popover","ui.bootstrap.datepickerPopup"]),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c');a.find("body").append(g),c=g[0].offsetWidth-g[0].clientWidth,c=isFinite(c)?c:0,g.remove()}return c},scrollbarPadding:function(a){a=this.getRawNode(a);var c=b.getComputedStyle(a),d=this.parseStyle(c.paddingRight),e=this.parseStyle(c.paddingBottom),f=this.scrollParent(a,!1,!0),h=this.scrollbarWidth(f,g.test(f.tagName));return{scrollbarWidth:h,widthOverflow:f.scrollWidth>f.clientWidth,right:d+h,originalRight:d,heightOverflow:f.scrollHeight>f.clientHeight,bottom:e+h,originalBottom:e}},isScrollable:function(a,c){a=this.getRawNode(a);var d=c?e.hidden:e.normal,f=b.getComputedStyle(a);return d.test(f.overflow+f.overflowY+f.overflowX)},scrollParent:function(c,d,f){c=this.getRawNode(c);var g=d?e.hidden:e.normal,h=a[0].documentElement,i=b.getComputedStyle(c);if(f&&g.test(i.overflow+i.overflowY+i.overflowX))return c;var j="absolute"===i.position,k=c.parentElement||h;if(k===h||"fixed"===i.position)return h;for(;k.parentElement&&k!==h;){var l=b.getComputedStyle(k);if(j&&"static"!==l.position&&(j=!1),!j&&g.test(l.overflow+l.overflowY+l.overflowX))break;k=k.parentElement}return k},position:function(c,d){c=this.getRawNode(c);var e=this.offset(c);if(d){var f=b.getComputedStyle(c);e.top-=this.parseStyle(f.marginTop),e.left-=this.parseStyle(f.marginLeft)}var g=this.offsetParent(c),h={top:0,left:0};return g!==a[0].documentElement&&(h=this.offset(g),h.top+=g.clientTop-g.scrollTop,h.left+=g.clientLeft-g.scrollLeft),{width:Math.round(angular.isNumber(e.width)?e.width:c.offsetWidth),height:Math.round(angular.isNumber(e.height)?e.height:c.offsetHeight),top:Math.round(e.top-h.top),left:Math.round(e.left-h.left)}},offset:function(c){c=this.getRawNode(c);var d=c.getBoundingClientRect();return{width:Math.round(angular.isNumber(d.width)?d.width:c.offsetWidth),height:Math.round(angular.isNumber(d.height)?d.height:c.offsetHeight),top:Math.round(d.top+(b.pageYOffset||a[0].documentElement.scrollTop)),left:Math.round(d.left+(b.pageXOffset||a[0].documentElement.scrollLeft))}},viewportOffset:function(c,d,e){c=this.getRawNode(c),e=e!==!1;var f=c.getBoundingClientRect(),g={top:0,left:0,bottom:0,right:0},h=d?a[0].documentElement:this.scrollParent(c),i=h.getBoundingClientRect();if(g.top=i.top+h.clientTop,g.left=i.left+h.clientLeft,h===a[0].documentElement&&(g.top+=b.pageYOffset,g.left+=b.pageXOffset),g.bottom=g.top+h.clientHeight,g.right=g.left+h.clientWidth,e){var j=b.getComputedStyle(h);g.top+=this.parseStyle(j.paddingTop),g.bottom-=this.parseStyle(j.paddingBottom),g.left+=this.parseStyle(j.paddingLeft),g.right-=this.parseStyle(j.paddingRight)}return{top:Math.round(f.top-g.top),bottom:Math.round(g.bottom-f.bottom),left:Math.round(f.left-g.left),right:Math.round(g.right-f.right)}},parsePlacement:function(a){var b=f.auto.test(a);return b&&(a=a.replace(f.auto,"")),a=a.split("-"),a[0]=a[0]||"top",f.primary.test(a[0])||(a[0]="top"),a[1]=a[1]||"center",f.secondary.test(a[1])||(a[1]="center"),b?a[2]=!0:a[2]=!1,a},positionElements:function(a,c,d,e){a=this.getRawNode(a),c=this.getRawNode(c);var g=angular.isDefined(c.offsetWidth)?c.offsetWidth:c.prop("offsetWidth"),h=angular.isDefined(c.offsetHeight)?c.offsetHeight:c.prop("offsetHeight");d=this.parsePlacement(d);var i=e?this.offset(a):this.position(a),j={top:0,left:0,placement:""};if(d[2]){var k=this.viewportOffset(a,e),l=b.getComputedStyle(c),m={width:g+Math.round(Math.abs(this.parseStyle(l.marginLeft)+this.parseStyle(l.marginRight))),height:h+Math.round(Math.abs(this.parseStyle(l.marginTop)+this.parseStyle(l.marginBottom)))};if(d[0]="top"===d[0]&&m.height>k.top&&m.height<=k.bottom?"bottom":"bottom"===d[0]&&m.height>k.bottom&&m.height<=k.top?"top":"left"===d[0]&&m.width>k.left&&m.width<=k.right?"right":"right"===d[0]&&m.width>k.right&&m.width<=k.left?"left":d[0],d[1]="top"===d[1]&&m.height-i.height>k.bottom&&m.height-i.height<=k.top?"bottom":"bottom"===d[1]&&m.height-i.height>k.top&&m.height-i.height<=k.bottom?"top":"left"===d[1]&&m.width-i.width>k.right&&m.width-i.width<=k.left?"right":"right"===d[1]&&m.width-i.width>k.left&&m.width-i.width<=k.right?"left":d[1],"center"===d[1])if(f.vertical.test(d[0])){var n=i.width/2-g/2;k.left+n<0&&m.width-i.width<=k.right?d[1]="left":k.right+n<0&&m.width-i.width<=k.left&&(d[1]="right")}else{var o=i.height/2-m.height/2;k.top+o<0&&m.height-i.height<=k.bottom?d[1]="top":k.bottom+o<0&&m.height-i.height<=k.top&&(d[1]="bottom")}}switch(d[0]){case"top":j.top=i.top-h;break;case"bottom":j.top=i.top+i.height;break;case"left":j.left=i.left-g;break;case"right":j.left=i.left+i.width}switch(d[1]){case"top":j.top=i.top;break;case"bottom":j.top=i.top+i.height-h;break;case"left":j.left=i.left;break;case"right":j.left=i.left+i.width-g;break;case"center":f.vertical.test(d[0])?j.left=i.left+i.width/2-g/2:j.top=i.top+i.height/2-h/2}return j.top=Math.round(j.top),j.left=Math.round(j.left),j.placement="center"===d[1]?d[0]:d[0]+"-"+d[1],j},positionArrow:function(a,c){a=this.getRawNode(a);var d=a.querySelector(".tooltip-inner, .popover-inner");if(d){var e=angular.element(d).hasClass("tooltip-inner"),g=e?a.querySelector(".tooltip-arrow"):a.querySelector(".arrow");if(g){var h={top:"",bottom:"",left:"",right:""};if(c=this.parsePlacement(c),"center"===c[1])return void angular.element(g).css(h);var i="border-"+c[0]+"-width",j=b.getComputedStyle(g)[i],k="border-";k+=f.vertical.test(c[0])?c[0]+"-"+c[1]:c[1]+"-"+c[0],k+="-radius";var l=b.getComputedStyle(e?d:a)[k];switch(c[0]){case"top":h.bottom=e?"0":"-"+j;break;case"bottom":h.top=e?"0":"-"+j;break;case"left":h.right=e?"0":"-"+j;break;case"right":h.left=e?"0":"-"+j}h[c[1]]=l,angular.element(g).css(h)}}}}}]),angular.module("ui.bootstrap.modal",["ui.bootstrap.stackedMap","ui.bootstrap.position"]).factory("$$multiMap",function(){return{createNew:function(){var a={};return{entries:function(){return Object.keys(a).map(function(b){return{key:b,value:a[b]}})},get:function(b){return a[b]},hasKey:function(b){return!!a[b]},keys:function(){return Object.keys(a)},put:function(b,c){a[b]||(a[b]=[]),a[b].push(c)},remove:function(b,c){var d=a[b];if(d){var e=d.indexOf(c);e!==-1&&d.splice(e,1),d.length||delete a[b]}}}}}}).provider("$uibResolve",function(){var a=this;this.resolver=null,this.setResolver=function(a){this.resolver=a},this.$get=["$injector","$q",function(b,c){var d=a.resolver?b.get(a.resolver):null;return{resolve:function(a,e,f,g){if(d)return d.resolve(a,e,f,g);var h=[];return angular.forEach(a,function(a){angular.isFunction(a)||angular.isArray(a)?h.push(c.resolve(b.invoke(a))):angular.isString(a)?h.push(c.resolve(b.get(a))):h.push(c.resolve(a))}),c.all(h).then(function(b){var c={},d=0;return angular.forEach(a,function(a,e){c[e]=b[d++]}),c})}}}]}).directive("uibModalBackdrop",["$animate","$injector","$uibModalStack",function(a,b,c){function d(b,d,e){e.modalInClass&&(a.addClass(d,e.modalInClass),b.$on(c.NOW_CLOSING_EVENT,function(c,f){var g=f();b.modalOptions.animation?a.removeClass(d,e.modalInClass).then(g):g()}))}return{replace:!0,templateUrl:"uib/template/modal/backdrop.html",compile:function(a,b){return a.addClass(b.backdropClass),d}}}]).directive("uibModalWindow",["$uibModalStack","$q","$animateCss","$document",function(a,b,c,d){return{scope:{index:"@"},replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/modal/window.html"},link:function(e,f,g){f.addClass(g.windowClass||""),f.addClass(g.windowTopClass||""),e.size=g.size,e.close=function(b){var c=a.getTop();c&&c.value.backdrop&&"static"!==c.value.backdrop&&b.target===b.currentTarget&&(b.preventDefault(),b.stopPropagation(),a.dismiss(c.key,"backdrop click"))},f.on("click",e.close),e.$isRendered=!0;var h=b.defer();g.$observe("modalRender",function(a){"true"===a&&h.resolve()}),h.promise.then(function(){var h=null;g.modalInClass&&(h=c(f,{addClass:g.modalInClass}).start(),e.$on(a.NOW_CLOSING_EVENT,function(a,b){var d=b();c(f,{removeClass:g.modalInClass}).start().then(d)})),b.when(h).then(function(){var b=a.getTop();if(b&&a.modalRendered(b.key),!d[0].activeElement||!f[0].contains(d[0].activeElement)){var c=f[0].querySelector("[autofocus]");c?c.focus():f[0].focus()}})})}}}]).directive("uibModalAnimationClass",function(){return{compile:function(a,b){b.modalAnimation&&a.addClass(b.uibModalAnimationClass)}}}).directive("uibModalTransclude",function(){return{link:function(a,b,c,d,e){e(a.$parent,function(a){b.empty(),b.append(a)})}}}).factory("$uibModalStack",["$animate","$animateCss","$document","$compile","$rootScope","$q","$$multiMap","$$stackedMap","$uibPosition",function(a,b,c,d,e,f,g,h,i){function j(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)}function k(){for(var a=-1,b=v.keys(),c=0;c-1&&a0&&(b=v.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function n(){if(r&&k()===-1){var a=s;o(r,s,function(){a=null}),r=void 0,s=void 0}}function o(b,c,d,e){function g(){g.done||(g.done=!0,a.leave(b).then(function(){b.remove(),e&&e.resolve()}),c.$destroy(),d&&d())}var h,i=null,j=function(){return h||(h=f.defer(),i=h.promise),function(){h.resolve()}};return c.$broadcast(x.NOW_CLOSING_EVENT,j),f.when(i).then(g)}function p(a){if(a.isDefaultPrevented())return a;var b=v.top();if(b)switch(a.which){case 27:b.value.keyboard&&(a.preventDefault(),e.$apply(function(){x.dismiss(b.key,"escape key press")}));break;case 9:var c=x.loadFocusElementList(b),d=!1;a.shiftKey?(x.isFocusInFirstItem(a,c)||x.isModalFocused(a,b))&&(d=x.focusLastFocusableElement(c)):x.isFocusInLastItem(a,c)&&(d=x.focusFirstFocusableElement(c)),d&&(a.preventDefault(),a.stopPropagation())}}function q(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}var r,s,t,u="modal-open",v=h.createNew(),w=g.createNew(),x={NOW_CLOSING_EVENT:"modal.stack.now-closing"},y=0,z=null,A="a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable=true]";return e.$watch(k,function(a){s&&(s.index=a)}),c.on("keydown",p),e.$on("$destroy",function(){c.off("keydown",p)}),x.open=function(b,f){var g=c[0].activeElement,h=f.openedClass||u;m(!1),z=v.top(),v.add(b,{deferred:f.deferred,renderDeferred:f.renderDeferred,closedDeferred:f.closedDeferred,modalScope:f.scope,backdrop:f.backdrop,keyboard:f.keyboard,openedClass:f.openedClass,windowTopClass:f.windowTopClass,animation:f.animation,appendTo:f.appendTo}),w.put(h,b);var j=f.appendTo,l=k();if(!j.length)throw new Error("appendTo element not found. Make sure that the element passed is in DOM.");l>=0&&!r&&(s=e.$new(!0),s.modalOptions=f,s.index=l,r=angular.element('
'),r.attr("backdrop-class",f.backdropClass),f.animation&&r.attr("modal-animation","true"),d(r)(s),a.enter(r,j),t=i.scrollbarPadding(j),t.heightOverflow&&t.scrollbarWidth&&j.css({paddingRight:t.right+"px"})),y=z?parseInt(z.value.modalDomEl.attr("index"),10)+1:0;var n=angular.element('
');n.attr({"template-url":f.windowTemplateUrl,"window-class":f.windowClass,"window-top-class":f.windowTopClass,size:f.size,index:y,animate:"animate"}).html(f.content),f.animation&&n.attr("modal-animation","true"),j.addClass(h),a.enter(d(n)(f.scope),j),v.top().value.modalDomEl=n,v.top().value.modalOpener=g},x.close=function(a,b){var c=v.get(a);return c&&q(c,b,!0)?(a.callCloseFn(b),c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),l(a,c.value.modalOpener),!0):!c},x.dismiss=function(a,b){var c=v.get(a);return c&&q(c,b,!1)?(a.callDismissFn(b),c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),l(a,c.value.modalOpener),!0):!c},x.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},x.getTop=function(){return v.top()},x.modalRendered=function(a){var b=v.get(a);b&&b.value.renderDeferred.resolve()},x.focusFirstFocusableElement=function(a){return a.length>0&&(a[0].focus(),!0)},x.focusLastFocusableElement=function(a){return a.length>0&&(a[a.length-1].focus(),!0)},x.isModalFocused=function(a,b){if(a&&b){var c=b.value.modalDomEl;if(c&&c.length)return(a.target||a.srcElement)===c[0]}return!1},x.isFocusInFirstItem=function(a,b){return b.length>0&&(a.target||a.srcElement)===b[0]},x.isFocusInLastItem=function(a,b){return b.length>0&&(a.target||a.srcElement)===b[b.length-1]},x.loadFocusElementList=function(a){if(a){var b=a.value.modalDomEl;if(b&&b.length){var c=b[0].querySelectorAll(A);return c?Array.prototype.filter.call(c,function(a){return j(a)}):c}}},x.length=function(){return v.length},x}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0,modalOpenFn:function(){},modalCloseFn:function(){},modalDismissFn:function(){}},$get:["$rootScope","$q","$document","$templateRequest","$controller","$uibResolve","$uibModalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?c.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}var j={},k=null;return j.getPromiseChain=function(){return k},j.setCallbacks=function(b,c,d){angular.extend(a.options,{modalOpenFn:b,modalCloseFn:c,modalDismissFn:d})},j.open=function(e){function j(){return r}var l=c.defer(),m=c.defer(),n=c.defer(),o=c.defer(),p={result:l.promise,opened:m.promise,closed:n.promise,rendered:o.promise,close:function(a){return h.close(p,a)},dismiss:function(a){return h.dismiss(p,a)},callDismissFn:function(a){return e.modalDismissFn(e,a),!0},callCloseFn:function(a){return e.modalCloseFn(e,a),!0}};if(e=angular.extend({},a.options,e),e.resolve=e.resolve||{},e.appendTo=e.appendTo||d.find("body").eq(0),!e.template&&!e.templateUrl)throw new Error("One of template or templateUrl options is required.");var q,r=c.all([i(e),g.resolve(e.resolve,{},null,null)]);return q=k=c.all([k]).then(j,j).then(function(a){var c=e.scope||b,d=c.$new();d.$close=p.close,d.$dismiss=p.dismiss,d.$on("$destroy",function(){d.$$uibDestructionScheduled||d.$dismiss("$uibUnscheduledDestruction")});var g,i,j={};e.controller&&(j.$scope=d,j.$scope.$resolve={},j.$uibModalInstance=p,angular.forEach(a[1],function(a,b){j[b]=a,j.$scope.$resolve[b]=a}),i=f(e.controller,j,!0,e.controllerAs),e.controllerAs&&e.bindToController&&(g=i.instance,g.$close=d.$close,g.$dismiss=d.$dismiss,angular.extend(g,{$resolve:j.$scope.$resolve},c)),g=i(),angular.isFunction(g.$onInit)&&g.$onInit()),h.open(p,{scope:d,deferred:l,renderDeferred:o,closedDeferred:n,content:a[0],animation:e.animation,backdrop:e.backdrop,keyboard:e.keyboard,backdropClass:e.backdropClass,windowTopClass:e.windowTopClass,windowClass:e.windowClass,windowTemplateUrl:e.windowTemplateUrl,size:e.size,openedClass:e.openedClass,appendTo:e.appendTo}),e.modalOpenFn(e),m.resolve(!0)},function(a){m.reject(a),l.reject(a)})["finally"](function(){k===q&&(k=null)}),p},j}]};return a}),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){function b(a){for(var b=0;bb.index?1:a.index';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){N.isOpen?q():m()}function m(){M&&!a.$eval(d[k+"Enable"])||(u(),x(),N.popupDelay?G||(G=g(r,N.popupDelay,!1)):r())}function q(){s(),N.popupCloseDelay?H||(H=g(t,N.popupCloseDelay,!1)):t()}function r(){return s(),u(),N.content?(v(),void N.$evalAsync(function(){N.isOpen=!0,y(!0),S()})):angular.noop}function s(){G&&(g.cancel(G),G=null),I&&(g.cancel(I),I=null)}function t(){N&&N.$evalAsync(function(){N&&(N.isOpen=!1,y(!1),N.animation?F||(F=g(w,150,!1)):w())})}function u(){H&&(g.cancel(H),H=null),F&&(g.cancel(F),F=null)}function v(){D||(E=N.$new(),D=c(E,function(a){K?h.find("body").append(a):b.after(a)}),z())}function w(){s(),u(),A(),D&&(D.remove(),D=null),E&&(E.$destroy(),E=null)}function x(){N.title=d[k+"Title"],Q?N.content=Q(a):N.content=d[e],N.popupClass=d[k+"Class"],N.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:n.placement;var b=i.parsePlacement(N.placement);J=b[1]?b[0]+"-"+b[1]:b[0];var c=parseInt(d[k+"PopupDelay"],10),f=parseInt(d[k+"PopupCloseDelay"],10);N.popupDelay=isNaN(c)?n.popupDelay:c,N.popupCloseDelay=isNaN(f)?n.popupCloseDelay:f}function y(b){P&&angular.isFunction(P.assign)&&P.assign(a,b)}function z(){R.length=0,Q?(R.push(a.$watch(Q,function(a){N.content=a,!a&&N.isOpen&&t()})),R.push(E.$watch(function(){O||(O=!0,E.$$postDigest(function(){O=!1,N&&N.isOpen&&S()}))}))):R.push(d.$observe(e,function(a){N.content=a,!a&&N.isOpen?t():S()})),R.push(d.$observe(k+"Title",function(a){N.title=a,N.isOpen&&S()})),R.push(d.$observe(k+"Placement",function(a){N.placement=a?a:n.placement,N.isOpen&&S()}))}function A(){R.length&&(angular.forEach(R,function(a){a()}),R.length=0)}function B(a){N&&N.isOpen&&D&&(b[0].contains(a.target)||D[0].contains(a.target)||q())}function C(){var a=d[k+"Trigger"];T(),L=p(a),"none"!==L.show&&L.show.forEach(function(a,c){"outsideClick"===a?(b.on("click",j),h.on("click",B)):a===L.hide[c]?b.on(a,j):a&&(b.on(a,m),b.on(L.hide[c],q)),b.on("keypress",function(a){27===a.which&&q()})})}var D,E,F,G,H,I,J,K=!!angular.isDefined(n.appendToBody)&&n.appendToBody,L=p(void 0),M=angular.isDefined(d[k+"Enable"]),N=a.$new(!0),O=!1,P=!!angular.isDefined(d[k+"IsOpen"])&&l(d[k+"IsOpen"]),Q=!!n.useContentExp&&l(d[e]),R=[],S=function(){D&&D.html()&&(I||(I=g(function(){var a=i.positionElements(b,D,N.placement,K);D.css({top:a.top+"px",left:a.left+"px"}),D.hasClass(a.placement.split("-")[0])||(D.removeClass(J.split("-")[0]),D.addClass(a.placement.split("-")[0])),D.hasClass(n.placementClassPrefix+a.placement)||(D.removeClass(n.placementClassPrefix+J),D.addClass(n.placementClassPrefix+a.placement)),D.hasClass("uib-position-measure")?(i.positionArrow(D,a.placement),D.removeClass("uib-position-measure")):J!==a.placement&&i.positionArrow(D,a.placement),J=a.placement,I=null},0,!1)))};N.origScope=a,N.isOpen=!1,o.add(N,{close:t}),N.contentExp=function(){return N.content},d.$observe("disabled",function(a){a&&s(),a&&N.isOpen&&t()}),P&&a.$watch(P,function(a){N&&!a===N.isOpen&&j()});var T=function(){L.show.forEach(function(a){"outsideClick"===a?b.off("click",j):(b.off(a,m),b.off(a,j))}),L.hide.forEach(function(a){"outsideClick"===a?h.off("click",B):b.off(a,q)})};C();var U=a.$eval(d[k+"Animation"]);N.animation=angular.isDefined(U)?!!U:n.animation;var V,W=k+"AppendToBody";V=W in d&&void 0===d[W]||a.$eval(d[W]),K=angular.isDefined(V)?V:K,a.$on("$destroy",function(){T(),w(),o.remove(N),N=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",["$uibPosition",function(a){return{restrict:"A",link:function(b,c,d){if(b.placement){var e=a.parsePlacement(b.placement);c.addClass(e[0])}b.popupClass&&c.addClass(b.popupClass),b.animation()&&c.addClass(d.tooltipAnimationClass)}}}]).directive("uibTooltipPopup",function(){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/tooltip/tooltip-popup.html"}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"uib/template/tooltip/tooltip-template-popup.html"}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/tooltip/tooltip-html-popup.html"}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.timepicker",[]).constant("uibTimepickerConfig",{hourStep:1,minuteStep:1,secondStep:1,showMeridian:!0,showSeconds:!1,meridians:null,readonlyInput:!1,mousewheel:!0,arrowkeys:!0,showSpinners:!0,templateUrl:"uib/template/timepicker/timepicker.html"}).controller("UibTimepickerController",["$scope","$element","$attrs","$parse","$log","$locale","uibTimepickerConfig",function(a,b,c,d,e,f,g){function h(){var b=+a.hours,c=a.showMeridian?b>0&&b<13:b>=0&&b<24;if(c&&""!==a.hours)return a.showMeridian&&(12===b&&(b=0),a.meridian===v[1]&&(b+=12)),b}function i(){var b=+a.minutes,c=b>=0&&b<60;if(c&&""!==a.minutes)return b}function j(){var b=+a.seconds;return b>=0&&b<60?b:void 0}function k(a,b){return null===a?"":angular.isDefined(a)&&a.toString().length<2&&!b?"0"+a:a.toString()}function l(a){m(),u.$setViewValue(new Date(s)),n(a)}function m(){u.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1,a.invalidSeconds=!1}function n(b){if(u.$modelValue){var c=s.getHours(),d=s.getMinutes(),e=s.getSeconds();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:k(c,!w),"m"!==b&&(a.minutes=k(d)),a.meridian=s.getHours()<12?v[0]:v[1],"s"!==b&&(a.seconds=k(e)),a.meridian=s.getHours()<12?v[0]:v[1]}else a.hours=null,a.minutes=null,a.seconds=null,a.meridian=v[0]}function o(a){s=q(s,a),l()}function p(a,b){return q(a,60*b)}function q(a,b){var c=new Date(a.getTime()+1e3*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes(),c.getSeconds()),d}function r(){return(null===a.hours||""===a.hours)&&(null===a.minutes||""===a.minutes)&&(!a.showSeconds||a.showSeconds&&(null===a.seconds||""===a.seconds))}var s=new Date,t=[],u={$setViewValue:angular.noop},v=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS,w=!angular.isDefined(c.padHours)||a.$parent.$eval(c.padHours);a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){u=b,u.$render=this.render,u.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=d.eq(2),i=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;i&&this.setupMousewheelEvents(e,f,h);var j=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;j&&this.setupArrowkeyEvents(e,f,h),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f,h)};var x=g.hourStep;c.hourStep&&t.push(a.$parent.$watch(d(c.hourStep),function(a){x=+a}));var y=g.minuteStep;c.minuteStep&&t.push(a.$parent.$watch(d(c.minuteStep),function(a){y=+a}));var z;t.push(a.$parent.$watch(d(c.min),function(a){var b=new Date(a);z=isNaN(b)?void 0:b}));var A;t.push(a.$parent.$watch(d(c.max),function(a){var b=new Date(a);A=isNaN(b)?void 0:b}));var B=!1;c.ngDisabled&&t.push(a.$parent.$watch(d(c.ngDisabled),function(a){B=a})),a.noIncrementHours=function(){var a=p(s,60*x);return B||a>A||as&&a>A},a.noIncrementMinutes=function(){var a=p(s,y);return B||a>A||as&&a>A},a.noIncrementSeconds=function(){var a=q(s,C);return B||a>A||as&&a>A},a.noToggleMeridian=function(){return s.getHours()<12?B||p(s,720)>A:B||p(s,-720)0};b.bind("mousewheel wheel",function(b){B||a.$apply(e(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){B||a.$apply(e(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()}),d.bind("mousewheel wheel",function(b){B||a.$apply(e(b)?a.incrementSeconds():a.decrementSeconds()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c,d){b.bind("keydown",function(b){B||(38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply()))}),c.bind("keydown",function(b){B||(38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply()))}),d.bind("keydown",function(b){B||(38===b.which?(b.preventDefault(),a.incrementSeconds(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementSeconds(),a.$apply()))})},this.setupInputEvents=function(b,c,d){if(a.readonlyInput)return a.updateHours=angular.noop,a.updateMinutes=angular.noop,void(a.updateSeconds=angular.noop); +var e=function(b,c,d){u.$setViewValue(null),u.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c),angular.isDefined(d)&&(a.invalidSeconds=d)};a.updateHours=function(){var a=h(),b=i();u.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(s.setHours(a),s.setMinutes(b),sA?e(!0):l("h")):e(!0)},b.bind("blur",function(b){u.$setTouched(),r()?m():null===a.hours||""===a.hours?e(!0):!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=k(a.hours,!w)})}),a.updateMinutes=function(){var a=i(),b=h();u.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(s.setHours(b),s.setMinutes(a),sA?e(void 0,!0):l("m")):e(void 0,!0)},c.bind("blur",function(b){u.$setTouched(),r()?m():null===a.minutes?e(void 0,!0):!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=k(a.minutes)})}),a.updateSeconds=function(){var a=j();u.$setDirty(),angular.isDefined(a)?(s.setSeconds(a),l("s")):e(void 0,void 0,!0)},d.bind("blur",function(b){r()?m():!a.invalidSeconds&&a.seconds<10&&a.$apply(function(){a.seconds=k(a.seconds)})})},this.render=function(){var b=u.$viewValue;isNaN(b)?(u.$setValidity("time",!1),e.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.')):(b&&(s=b),sA?(u.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):m(),n())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*x*60)},a.decrementHours=function(){a.noDecrementHours()||o(60*-x*60)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(60*y)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(60*-y)},a.incrementSeconds=function(){a.noIncrementSeconds()||o(C)},a.decrementSeconds=function(){a.noDecrementSeconds()||o(-C)},a.toggleMeridian=function(){var b=i(),c=h();a.noToggleMeridian()||(angular.isDefined(b)&&angular.isDefined(c)?o(720*(s.getHours()<12?60:-60)):a.meridian=a.meridian===v[0]?v[1]:v[0])},a.blur=function(){u.$setTouched()},a.$on("$destroy",function(){for(;t.length;)t.shift()()})}]).directive("uibTimepicker",["uibTimepickerConfig",function(a){return{require:["uibTimepicker","?^ngModel"],controller:"UibTimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(b,c){return c.templateUrl||a.templateUrl},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}]),angular.module("ui.bootstrap.dateparser",[]).service("uibDateParser",["$log","$locale","dateFilter","orderByFilter",function(a,b,c,d){function e(a,b){var c=[],e=a.split(""),f=a.indexOf("'");if(f>-1){var g=!1;a=a.split("");for(var h=f;h-1){a=a.split(""),e[f]="("+d.regex+")",a[f]="$";for(var g=f+1,h=f+d.key.length;g28?29===c&&(a%4===0&&a%100!==0||a%400===0):3!==b&&5!==b&&8!==b&&10!==b||c<31)}function g(a){return parseInt(a,10)}function h(a,b){return a&&b?l(a,b):a}function i(a,b){return a&&b?l(a,b,!0):a}function j(a,b){a=a.replace(/:/g,"");var c=Date.parse("Jan 01, 1970 00:00:00 "+a)/6e4;return isNaN(c)?b:c}function k(a,b){return a=new Date(a.getTime()),a.setMinutes(a.getMinutes()+b),a}function l(a,b,c){c=c?-1:1;var d=a.getTimezoneOffset(),e=j(b,d);return k(a,c*(e-d))}var m,n,o=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){m=b.id,this.parsers={},this.formatters={},n=[{key:"yyyy",regex:"\\d{4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yyyy")}},{key:"yy",regex:"\\d{2}",apply:function(a){a=+a,this.year=a<69?a+2e3:a+1900},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yy")}},{key:"y",regex:"\\d{1,4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"y")}},{key:"M!",regex:"0?[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){var b=a.getMonth();return/^[0-9]$/.test(b)?c(a,"MM"):c(a,"M")}},{key:"MMMM",regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)},formatter:function(a){return c(a,"MMMM")}},{key:"MMM",regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)},formatter:function(a){return c(a,"MMM")}},{key:"MM",regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"MM")}},{key:"M",regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"M")}},{key:"d!",regex:"[0-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){var b=a.getDate();return/^[1-9]$/.test(b)?c(a,"dd"):c(a,"d")}},{key:"dd",regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"dd")}},{key:"d",regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"d")}},{key:"EEEE",regex:b.DATETIME_FORMATS.DAY.join("|"),formatter:function(a){return c(a,"EEEE")}},{key:"EEE",regex:b.DATETIME_FORMATS.SHORTDAY.join("|"),formatter:function(a){return c(a,"EEE")}},{key:"HH",regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"HH")}},{key:"hh",regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"hh")}},{key:"H",regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"H")}},{key:"h",regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"h")}},{key:"mm",regex:"[0-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"mm")}},{key:"m",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"m")}},{key:"sss",regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a},formatter:function(a){return c(a,"sss")}},{key:"ss",regex:"[0-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"ss")}},{key:"s",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"s")}},{key:"a",regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)},formatter:function(a){return c(a,"a")}},{key:"Z",regex:"[+-]\\d{4}",apply:function(a){var b=a.match(/([+-])(\d{2})(\d{2})/),c=b[1],d=b[2],e=b[3];this.hours+=g(c+d),this.minutes+=g(c+e)},formatter:function(a){return c(a,"Z")}},{key:"ww",regex:"[0-4][0-9]|5[0-3]",formatter:function(a){return c(a,"ww")}},{key:"w",regex:"[0-9]|[1-4][0-9]|5[0-3]",formatter:function(a){return c(a,"w")}},{key:"GGGG",regex:b.DATETIME_FORMATS.ERANAMES.join("|").replace(/\s/g,"\\s"),formatter:function(a){return c(a,"GGGG")}},{key:"GGG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GGG")}},{key:"GG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GG")}},{key:"G",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"G")}}]},this.init(),this.filter=function(a,c){if(!angular.isDate(a)||isNaN(a)||!c)return"";c=b.DATETIME_FORMATS[c]||c,b.id!==m&&this.init(),this.formatters[c]||(this.formatters[c]=e(c,"formatter"));var d=this.formatters[c],f=d.map,g=c;return f.reduce(function(b,c,d){var e=g.match(new RegExp("(.*)"+c.key));e&&angular.isString(e[1])&&(b+=e[1],g=g.replace(e[1]+c.key,""));var h=d===f.length-1?g:"";return c.apply?b+c.apply.call(null,a)+h:b+h},"")},this.parse=function(c,d,g){if(!angular.isString(c)||!d)return c;d=b.DATETIME_FORMATS[d]||d,d=d.replace(o,"\\$&"),b.id!==m&&this.init(),this.parsers[d]||(this.parsers[d]=e(d,"apply"));var h=this.parsers[d],i=h.regex,j=h.map,k=c.match(i),l=!1;if(k&&k.length){var n,p;angular.isDate(g)&&!isNaN(g.getTime())?n={year:g.getFullYear(),month:g.getMonth(),date:g.getDate(),hours:g.getHours(),minutes:g.getMinutes(),seconds:g.getSeconds(),milliseconds:g.getMilliseconds()}:(g&&a.warn("dateparser:","baseDate is not a valid date"),n={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var q=1,r=k.length;qm.modes.indexOf(m[b]))&&(a.datepickerMode=m[b],a.datepickerOptions.datepickerMode=m[b])}):m[b]=a[b]=h[b]||null}}),a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),a.disabled=angular.isDefined(b.disabled)||!1,angular.isDefined(b.ngDisabled)&&p.push(a.$parent.$watch(b.ngDisabled,function(b){a.disabled=b,m.refreshView()})),a.isActive=function(b){return 0===m.compare(b.date,m.activeDate)&&(a.activeDateId=b.uid,!0)},this.init=function(b){n=b,o=b.$options||h.ngModelOptions,a.datepickerOptions.initDate?(m.activeDate=k.fromTimezone(a.datepickerOptions.initDate,o.timezone)||new Date,a.$watch("datepickerOptions.initDate",function(a){a&&(n.$isEmpty(n.$modelValue)||n.$invalid)&&(m.activeDate=k.fromTimezone(a,o.timezone),m.refreshView())})):m.activeDate=new Date;var c=n.$modelValue?new Date(n.$modelValue):new Date;this.activeDate=isNaN(c)?k.fromTimezone(new Date,o.timezone):k.fromTimezone(c,o.timezone),n.$render=function(){m.render()}},this.render=function(){if(n.$viewValue){var a=new Date(n.$viewValue),b=!isNaN(a);b?this.activeDate=k.fromTimezone(a,o.timezone):j||f.error('Datepicker directive: "ng-model" value must be a Date object')}this.refreshView()},this.refreshView=function(){if(this.element){a.selectedDt=null,this._refreshView(),a.activeDt&&(a.activeDateId=a.activeDt.uid);var b=n.$viewValue?new Date(n.$viewValue):null;b=k.fromTimezone(b,o.timezone),n.$setValidity("dateDisabled",!b||this.element&&!this.isDisabled(b))}},this.createDateObject=function(b,c){var d=n.$viewValue?new Date(n.$viewValue):null;d=k.fromTimezone(d,o.timezone);var e=new Date;e=k.fromTimezone(e,o.timezone);var f=this.compare(b,e),g={date:b,label:k.filter(b,c),selected:d&&0===this.compare(b,d),disabled:this.isDisabled(b),past:f<0,current:0===f,future:f>0,customClass:this.customClass(b)||null};return d&&0===this.compare(b,d)&&(a.selectedDt=g),m.activeDate&&0===this.compare(g.date,m.activeDate)&&(a.activeDt=g),g},this.isDisabled=function(b){return a.disabled||this.minDate&&this.compare(b,this.minDate)<0||this.maxDate&&this.compare(b,this.maxDate)>0||a.dateDisabled&&a.dateDisabled({date:b,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===m.minMode){var c=n.$viewValue?k.fromTimezone(new Date(n.$viewValue),o.timezone):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),c=k.toTimezone(c,o.timezone),n.$setViewValue(c),n.$render()}else m.activeDate=b,l(m.modes[m.modes.indexOf(a.datepickerMode)-1]),a.$emit("uib:datepicker.mode");a.$broadcast("uib:datepicker.focus")},a.move=function(a){var b=m.activeDate.getFullYear()+a*(m.step.years||0),c=m.activeDate.getMonth()+a*(m.step.months||0);m.activeDate.setFullYear(b,c,1),m.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===m.maxMode&&1===b||a.datepickerMode===m.minMode&&b===-1||(l(m.modes[m.modes.indexOf(a.datepickerMode)+b]),a.$emit("uib:datepicker.mode"))},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var q=function(){m.element[0].focus()};a.$on("uib:datepicker.focus",q),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey&&!a.disabled)if(b.preventDefault(),m.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(m.isDisabled(m.activeDate))return;a.select(m.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(m.handleKeyDown(c,b),m.refreshView()):a.toggleMode("up"===c?1:-1)},a.$on("$destroy",function(){for(;p.length;)p.shift()()})}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;f0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;k<42;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;l<7;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;o');n.dropdownMenu.replaceWith(A),n.dropdownMenu=A}f.close(o,b),n.selectedOption=null}angular.isFunction(r)&&r(a,c)})}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$q","$parse","$injector",function(a,b,c,d){var e=d.has("$animateCss")?d.get("$animateCss"):null;return{link:function(d,f,g){function h(){f.hasClass("collapse")&&f.hasClass("in")||b.resolve(l(d)).then(function(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{addClass:"in",easing:"ease",to:{height:f[0].scrollHeight+"px"}}).start()["finally"](i):a.addClass(f,"in",{to:{height:f[0].scrollHeight+"px"}}).then(i)})}function i(){f.removeClass("collapsing").addClass("collapse").css({height:"auto"}),m(d)}function j(){return f.hasClass("collapse")||f.hasClass("in")?void b.resolve(n(d)).then(function(){f.css({height:f[0].scrollHeight+"px"}).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),e?e(f,{removeClass:"in",to:{height:"0"}}).start()["finally"](k):a.removeClass(f,"in",{to:{height:"0"}}).then(k)}):k()}function k(){f.css({height:"0"}),f.removeClass("collapsing").addClass("collapse"),o(d)}var l=c(g.expanding),m=c(g.expanded),n=c(g.collapsing),o=c(g.collapsed);d.$eval(g.uibCollapse)||f.addClass("in").addClass("collapse").attr("aria-expanded",!0).attr("aria-hidden",!1).css({height:"auto"}),d.$watch(g.uibCollapse,function(a){a?j():h()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);b!==-1&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion-group.html"},scope:{heading:"@",panelClass:"@?",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass||"panel-default",a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)};var e="accordiongroup-"+a.$id+"-"+Math.floor(1e4*Math.random());a.headingId=e+"-tab",a.panelId=e+"-panel"}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){function a(){return"uib-accordion-header,data-uib-accordion-header,x-uib-accordion-header,uib\\:accordion-header,[uib-accordion-header],[data-uib-accordion-header],[x-uib-accordion-header]"}return{require:"^uibAccordionGroup",link:function(b,c,d,e){b.$watch(function(){return e[d.uibAccordionTransclude]},function(b){if(b){var d=angular.element(c[0].querySelector(a()));d.html(""),d.append(b)}})}}}),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{replace:!0,scope:{uibTitle:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"uib/template/popover/popover-template.html"}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",uibTitle:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/popover/popover-html.html"}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{replace:!0,scope:{uibTitle:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/popover/popover.html"}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.datepickerPopup",["ui.bootstrap.datepicker","ui.bootstrap.position"]).value("$datepickerPopupLiteralWarning",!0).constant("uibDatepickerPopupConfig",{altInputFormats:[],appendToBody:!1,clearText:"Clear",closeOnDateSelection:!0,closeText:"Done",currentText:"Today",datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"uib/template/datepickerPopup/popup.html",datepickerTemplateUrl:"uib/template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},onOpenFocus:!0,showButtonBar:!0,placement:"auto bottom-left"}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$log","$parse","$window","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout","uibDatepickerConfig","$datepickerPopupLiteralWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){function q(b){var c=l.parse(b,w,a.date);if(isNaN(c))for(var d=0;d
"),G?(J=G.timezone,a.ngModelOptions=angular.copy(G),a.ngModelOptions.timezone=null,a.ngModelOptions.updateOnDefault===!0&&(a.ngModelOptions.updateOn=a.ngModelOptions.updateOn?a.ngModelOptions.updateOn+" default":"default"),C.attr("ng-model-options","ngModelOptions")):J=null,C.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":A}),D=angular.element(C.children()[0]),D.attr("template-url",B),a.datepickerOptions||(a.datepickerOptions={}),K&&"month"===c.type&&(a.datepickerOptions.datepickerMode="month",a.datepickerOptions.minMode="month"),D.attr("datepicker-options","datepickerOptions"),K?F.$formatters.push(function(b){return a.date=l.fromTimezone(b,J),b}):(F.$$parserName="date",F.$validators.date=s,F.$parsers.unshift(r),F.$formatters.push(function(b){return F.$isEmpty(b)?(a.date=b,b):(angular.isNumber(b)&&(b=new Date(b)),a.date=l.fromTimezone(b,J),l.filter(a.date,w))})),F.$viewChangeListeners.push(function(){a.date=q(F.$viewValue)}),b.on("keydown",u),H=d(C)(a),C.remove(),y?h.find("body").append(H):b.after(H),a.$on("$destroy",function(){for(a.isOpen===!0&&(i.$$phase||a.$apply(function(){a.isOpen=!1})),H.remove(),b.off("keydown",u),h.off("click",t),E&&E.off("scroll",v),angular.element(g).off("resize",v);L.length;)L.shift()()})},a.getText=function(b){return a[b+"Text"]||m[b+"Text"]},a.isDisabled=function(b){"today"===b&&(b=l.fromTimezone(new Date,J));var c={};return angular.forEach(["minDate","maxDate"],function(b){a.datepickerOptions[b]?angular.isDate(a.datepickerOptions[b])?c[b]=l.fromTimezone(new Date(a.datepickerOptions[b]),J):(p&&e.warn("Literal date support has been deprecated, please switch to date object usage"),c[b]=new Date(k(a.datepickerOptions[b],"medium"))):c[b]=null}),a.datepickerOptions&&c.minDate&&a.compare(b,c.minDate)<0||c.maxDate&&a.compare(b,c.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){angular.isDefined(c)&&(a.date=c);var d=a.date?l.filter(a.date,w):null;b.val(d),F.$setViewValue(d),x&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(c.stopPropagation(),a.isOpen=!1,b[0].focus())},a.select=function(b,c){if(c.stopPropagation(),"today"===b){var d=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(d.getFullYear(),d.getMonth(),d.getDate())):b=new Date(d.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(c){c.stopPropagation(),a.isOpen=!1,b[0].focus()},a.disabled=angular.isDefined(c.disabled)||!1,c.ngDisabled&&L.push(a.$parent.$watch(f(c.ngDisabled),function(b){a.disabled=b})),a.$watch("isOpen",function(d){d?a.disabled?a.isOpen=!1:n(function(){v(),z&&a.$broadcast("uib:datepicker.focus"),h.on("click",t);var d=c.popupPlacement?c.popupPlacement:m.placement;y||j.parsePlacement(d)[2]?(E=E||angular.element(j.scrollParent(b)),E&&E.on("scroll",v)):E=null,angular.element(g).on("resize",v)},0,!1):(h.off("click",t),E&&E.off("scroll",v),angular.element(g).off("resize",v))}),a.$on("uib:datepicker.mode",function(){n(v,0,!1)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{datepickerOptions:"=?",isOpen:"=?",currentText:"@",clearText:"@",closeText:"@"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepickerPopup/popup.html"}}}),angular.module("ui.bootstrap.position").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibPositionCss&&angular.element(document).find("head").prepend(''),angular.$$uibPositionCss=!0}),angular.module("ui.bootstrap.tooltip").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTooltipCss&&angular.element(document).find("head").prepend(''),angular.$$uibTooltipCss=!0}),angular.module("ui.bootstrap.timepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTimepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibTimepickerCss=!0}),angular.module("ui.bootstrap.datepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerCss=!0}),angular.module("ui.bootstrap.datepickerPopup").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerpopupCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerpopupCss=!0}); \ No newline at end of file diff --git a/dist/ui-bootstrap-custom-tpls-1.3.3.js b/dist/ui-bootstrap-custom-tpls-1.3.3.js new file mode 100644 index 0000000..fedeea0 --- /dev/null +++ b/dist/ui-bootstrap-custom-tpls-1.3.3.js @@ -0,0 +1,5590 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 1.3.3 - 2017-07-07 + * License: MIT + */angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.stackedMap","ui.bootstrap.position","ui.bootstrap.modal","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.timepicker","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.popover","ui.bootstrap.datepickerPopup"]); +angular.module("ui.bootstrap.tpls", ["uib/template/modal/backdrop.html","uib/template/modal/window.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/timepicker/timepicker.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/datepickerPopup/popup.html"]); +angular.module('ui.bootstrap.stackedMap', []) +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function() { + return { + createNew: function() { + var stack = []; + + return { + add: function(key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function(key) { + for (var i = 0; i < stack.length; i++) { + if (key === stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function() { + return stack[stack.length - 1]; + }, + remove: function(key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key === stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function() { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function() { + return stack.length; + } + }; + } + }; + }); +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods for working with the DOM. + * It is meant to be used where we need to absolute-position elements in + * relation to another element (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$uibPosition', ['$document', '$window', function($document, $window) { + /** + * Used by scrollbarWidth() function to cache scrollbar's width. + * Do not access this variable directly, use scrollbarWidth() instead. + */ + var SCROLLBAR_WIDTH; + /** + * scrollbar on body and html element in IE and Edge overlay + * content and should be considered 0 width. + */ + var BODY_SCROLLBAR_WIDTH; + var OVERFLOW_REGEX = { + normal: /(auto|scroll)/, + hidden: /(auto|scroll|hidden)/ + }; + var PLACEMENT_REGEX = { + auto: /\s?auto?\s?/i, + primary: /^(top|bottom|left|right)$/, + secondary: /^(top|bottom|left|right|center)$/, + vertical: /^(top|bottom)$/ + }; + var BODY_REGEX = /(HTML|BODY)/; + + return { + + /** + * Provides a raw DOM element from a jQuery/jQLite element. + * + * @param {element} elem - The element to convert. + * + * @returns {element} A HTML element. + */ + getRawNode: function(elem) { + return elem.nodeName ? elem : elem[0] || elem; + }, + + /** + * Provides a parsed number for a style property. Strips + * units and casts invalid numbers to 0. + * + * @param {string} value - The style value to parse. + * + * @returns {number} A valid number. + */ + parseStyle: function(value) { + value = parseFloat(value); + return isFinite(value) ? value : 0; + }, + + /** + * Provides the closest positioned ancestor. + * + * @param {element} element - The element to get the offest parent for. + * + * @returns {element} The closest positioned ancestor. + */ + offsetParent: function(elem) { + elem = this.getRawNode(elem); + + var offsetParent = elem.offsetParent || $document[0].documentElement; + + function isStaticPositioned(el) { + return ($window.getComputedStyle(el).position || 'static') === 'static'; + } + + while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + + return offsetParent || $document[0].documentElement; + }, + + /** + * Provides the scrollbar width, concept from TWBS measureScrollbar() + * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js + * In IE and Edge, scollbar on body and html element overlay and should + * return a width of 0. + * + * @returns {number} The width of the browser scollbar. + */ + scrollbarWidth: function(isBody) { + if (isBody) { + if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) { + var bodyElem = $document.find('body'); + bodyElem.addClass('uib-position-body-scrollbar-measure'); + BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth; + BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0; + bodyElem.removeClass('uib-position-body-scrollbar-measure'); + } + return BODY_SCROLLBAR_WIDTH; + } + + if (angular.isUndefined(SCROLLBAR_WIDTH)) { + var scrollElem = angular.element('
'); + $document.find('body').append(scrollElem); + SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth; + SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0; + scrollElem.remove(); + } + + return SCROLLBAR_WIDTH; + }, + + /** + * Provides the padding required on an element to replace the scrollbar. + * + * @returns {object} An object with the following properties: + *
    + *
  • **scrollbarWidth**: the width of the scrollbar
  • + *
  • **widthOverflow**: whether the the width is overflowing
  • + *
  • **right**: the amount of right padding on the element needed to replace the scrollbar
  • + *
  • **rightOriginal**: the amount of right padding currently on the element
  • + *
  • **heightOverflow**: whether the the height is overflowing
  • + *
  • **bottom**: the amount of bottom padding on the element needed to replace the scrollbar
  • + *
  • **bottomOriginal**: the amount of bottom padding currently on the element
  • + *
+ */ + scrollbarPadding: function(elem) { + elem = this.getRawNode(elem); + + var elemStyle = $window.getComputedStyle(elem); + var paddingRight = this.parseStyle(elemStyle.paddingRight); + var paddingBottom = this.parseStyle(elemStyle.paddingBottom); + var scrollParent = this.scrollParent(elem, false, true); + var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName)); + + return { + scrollbarWidth: scrollbarWidth, + widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth, + right: paddingRight + scrollbarWidth, + originalRight: paddingRight, + heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight, + bottom: paddingBottom + scrollbarWidth, + originalBottom: paddingBottom + }; + }, + + /** + * Checks to see if the element is scrollable. + * + * @param {element} elem - The element to check. + * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, + * default is false. + * + * @returns {boolean} Whether the element is scrollable. + */ + isScrollable: function(elem, includeHidden) { + elem = this.getRawNode(elem); + + var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; + var elemStyle = $window.getComputedStyle(elem); + return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX); + }, + + /** + * Provides the closest scrollable ancestor. + * A port of the jQuery UI scrollParent method: + * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js + * + * @param {element} elem - The element to find the scroll parent of. + * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, + * default is false. + * @param {boolean=} [includeSelf=false] - Should the element being passed be + * included in the scrollable llokup. + * + * @returns {element} A HTML element. + */ + scrollParent: function(elem, includeHidden, includeSelf) { + elem = this.getRawNode(elem); + + var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; + var documentEl = $document[0].documentElement; + var elemStyle = $window.getComputedStyle(elem); + if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) { + return elem; + } + var excludeStatic = elemStyle.position === 'absolute'; + var scrollParent = elem.parentElement || documentEl; + + if (scrollParent === documentEl || elemStyle.position === 'fixed') { + return documentEl; + } + + while (scrollParent.parentElement && scrollParent !== documentEl) { + var spStyle = $window.getComputedStyle(scrollParent); + if (excludeStatic && spStyle.position !== 'static') { + excludeStatic = false; + } + + if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) { + break; + } + scrollParent = scrollParent.parentElement; + } + + return scrollParent; + }, + + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ - distance to closest positioned + * ancestor. Does not account for margins by default like jQuery position. + * + * @param {element} elem - The element to caclulate the position on. + * @param {boolean=} [includeMargins=false] - Should margins be accounted + * for, default is false. + * + * @returns {object} An object with the following properties: + *
    + *
  • **width**: the width of the element
  • + *
  • **height**: the height of the element
  • + *
  • **top**: distance to top edge of offset parent
  • + *
  • **left**: distance to left edge of offset parent
  • + *
+ */ + position: function(elem, includeMagins) { + elem = this.getRawNode(elem); + + var elemOffset = this.offset(elem); + if (includeMagins) { + var elemStyle = $window.getComputedStyle(elem); + elemOffset.top -= this.parseStyle(elemStyle.marginTop); + elemOffset.left -= this.parseStyle(elemStyle.marginLeft); + } + var parent = this.offsetParent(elem); + var parentOffset = {top: 0, left: 0}; + + if (parent !== $document[0].documentElement) { + parentOffset = this.offset(parent); + parentOffset.top += parent.clientTop - parent.scrollTop; + parentOffset.left += parent.clientLeft - parent.scrollLeft; + } + + return { + width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth), + height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight), + top: Math.round(elemOffset.top - parentOffset.top), + left: Math.round(elemOffset.left - parentOffset.left) + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ - distance to viewport. Does + * not account for borders, margins, or padding on the body + * element. + * + * @param {element} elem - The element to calculate the offset on. + * + * @returns {object} An object with the following properties: + *
    + *
  • **width**: the width of the element
  • + *
  • **height**: the height of the element
  • + *
  • **top**: distance to top edge of viewport
  • + *
  • **right**: distance to bottom edge of viewport
  • + *
+ */ + offset: function(elem) { + elem = this.getRawNode(elem); + + var elemBCR = elem.getBoundingClientRect(); + return { + width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth), + height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight), + top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)), + left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)) + }; + }, + + /** + * Provides offset distance to the closest scrollable ancestor + * or viewport. Accounts for border and scrollbar width. + * + * Right and bottom dimensions represent the distance to the + * respective edge of the viewport element. If the element + * edge extends beyond the viewport, a negative value will be + * reported. + * + * @param {element} elem - The element to get the viewport offset for. + * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead + * of the first scrollable element, default is false. + * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element + * be accounted for, default is true. + * + * @returns {object} An object with the following properties: + *
    + *
  • **top**: distance to the top content edge of viewport element
  • + *
  • **bottom**: distance to the bottom content edge of viewport element
  • + *
  • **left**: distance to the left content edge of viewport element
  • + *
  • **right**: distance to the right content edge of viewport element
  • + *
+ */ + viewportOffset: function(elem, useDocument, includePadding) { + elem = this.getRawNode(elem); + includePadding = includePadding !== false ? true : false; + + var elemBCR = elem.getBoundingClientRect(); + var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0}; + + var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem); + var offsetParentBCR = offsetParent.getBoundingClientRect(); + + offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop; + offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft; + if (offsetParent === $document[0].documentElement) { + offsetBCR.top += $window.pageYOffset; + offsetBCR.left += $window.pageXOffset; + } + offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight; + offsetBCR.right = offsetBCR.left + offsetParent.clientWidth; + + if (includePadding) { + var offsetParentStyle = $window.getComputedStyle(offsetParent); + offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop); + offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom); + offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft); + offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight); + } + + return { + top: Math.round(elemBCR.top - offsetBCR.top), + bottom: Math.round(offsetBCR.bottom - elemBCR.bottom), + left: Math.round(elemBCR.left - offsetBCR.left), + right: Math.round(offsetBCR.right - elemBCR.right) + }; + }, + + /** + * Provides an array of placement values parsed from a placement string. + * Along with the 'auto' indicator, supported placement strings are: + *
    + *
  • top: element on top, horizontally centered on host element.
  • + *
  • top-left: element on top, left edge aligned with host element left edge.
  • + *
  • top-right: element on top, lerightft edge aligned with host element right edge.
  • + *
  • bottom: element on bottom, horizontally centered on host element.
  • + *
  • bottom-left: element on bottom, left edge aligned with host element left edge.
  • + *
  • bottom-right: element on bottom, right edge aligned with host element right edge.
  • + *
  • left: element on left, vertically centered on host element.
  • + *
  • left-top: element on left, top edge aligned with host element top edge.
  • + *
  • left-bottom: element on left, bottom edge aligned with host element bottom edge.
  • + *
  • right: element on right, vertically centered on host element.
  • + *
  • right-top: element on right, top edge aligned with host element top edge.
  • + *
  • right-bottom: element on right, bottom edge aligned with host element bottom edge.
  • + *
+ * A placement string with an 'auto' indicator is expected to be + * space separated from the placement, i.e: 'auto bottom-left' If + * the primary and secondary placement values do not match 'top, + * bottom, left, right' then 'top' will be the primary placement and + * 'center' will be the secondary placement. If 'auto' is passed, true + * will be returned as the 3rd value of the array. + * + * @param {string} placement - The placement string to parse. + * + * @returns {array} An array with the following values + *
    + *
  • **[0]**: The primary placement.
  • + *
  • **[1]**: The secondary placement.
  • + *
  • **[2]**: If auto is passed: true, else undefined.
  • + *
+ */ + parsePlacement: function(placement) { + var autoPlace = PLACEMENT_REGEX.auto.test(placement); + if (autoPlace) { + placement = placement.replace(PLACEMENT_REGEX.auto, ''); + } + + placement = placement.split('-'); + + placement[0] = placement[0] || 'top'; + if (!PLACEMENT_REGEX.primary.test(placement[0])) { + placement[0] = 'top'; + } + + placement[1] = placement[1] || 'center'; + if (!PLACEMENT_REGEX.secondary.test(placement[1])) { + placement[1] = 'center'; + } + + if (autoPlace) { + placement[2] = true; + } else { + placement[2] = false; + } + + return placement; + }, + + /** + * Provides coordinates for an element to be positioned relative to + * another element. Passing 'auto' as part of the placement parameter + * will enable smart placement - where the element fits. i.e: + * 'auto left-top' will check to see if there is enough space to the left + * of the hostElem to fit the targetElem, if not place right (same for secondary + * top placement). Available space is calculated using the viewportOffset + * function. + * + * @param {element} hostElem - The element to position against. + * @param {element} targetElem - The element to position. + * @param {string=} [placement=top] - The placement for the targetElem, + * default is 'top'. 'center' is assumed as secondary placement for + * 'top', 'left', 'right', and 'bottom' placements. Available placements are: + *
    + *
  • top
  • + *
  • top-right
  • + *
  • top-left
  • + *
  • bottom
  • + *
  • bottom-left
  • + *
  • bottom-right
  • + *
  • left
  • + *
  • left-top
  • + *
  • left-bottom
  • + *
  • right
  • + *
  • right-top
  • + *
  • right-bottom
  • + *
+ * @param {boolean=} [appendToBody=false] - Should the top and left values returned + * be calculated from the body element, default is false. + * + * @returns {object} An object with the following properties: + *
    + *
  • **top**: Value for targetElem top.
  • + *
  • **left**: Value for targetElem left.
  • + *
  • **placement**: The resolved placement.
  • + *
+ */ + positionElements: function(hostElem, targetElem, placement, appendToBody) { + hostElem = this.getRawNode(hostElem); + targetElem = this.getRawNode(targetElem); + + // need to read from prop to support tests. + var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth'); + var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight'); + + placement = this.parsePlacement(placement); + + var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem); + var targetElemPos = {top: 0, left: 0, placement: ''}; + + if (placement[2]) { + var viewportOffset = this.viewportOffset(hostElem, appendToBody); + + var targetElemStyle = $window.getComputedStyle(targetElem); + var adjustedSize = { + width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))), + height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom))) + }; + + placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' : + placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' : + placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' : + placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' : + placement[0]; + + placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' : + placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' : + placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' : + placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' : + placement[1]; + + if (placement[1] === 'center') { + if (PLACEMENT_REGEX.vertical.test(placement[0])) { + var xOverflow = hostElemPos.width / 2 - targetWidth / 2; + if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) { + placement[1] = 'left'; + } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) { + placement[1] = 'right'; + } + } else { + var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2; + if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) { + placement[1] = 'top'; + } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) { + placement[1] = 'bottom'; + } + } + } + } + + switch (placement[0]) { + case 'top': + targetElemPos.top = hostElemPos.top - targetHeight; + break; + case 'bottom': + targetElemPos.top = hostElemPos.top + hostElemPos.height; + break; + case 'left': + targetElemPos.left = hostElemPos.left - targetWidth; + break; + case 'right': + targetElemPos.left = hostElemPos.left + hostElemPos.width; + break; + } + + switch (placement[1]) { + case 'top': + targetElemPos.top = hostElemPos.top; + break; + case 'bottom': + targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight; + break; + case 'left': + targetElemPos.left = hostElemPos.left; + break; + case 'right': + targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth; + break; + case 'center': + if (PLACEMENT_REGEX.vertical.test(placement[0])) { + targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2; + } else { + targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2; + } + break; + } + + targetElemPos.top = Math.round(targetElemPos.top); + targetElemPos.left = Math.round(targetElemPos.left); + targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1]; + + return targetElemPos; + }, + + /** + * Provides a way for positioning tooltip & dropdown + * arrows when using placement options beyond the standard + * left, right, top, or bottom. + * + * @param {element} elem - The tooltip/dropdown element. + * @param {string} placement - The placement for the elem. + */ + positionArrow: function(elem, placement) { + elem = this.getRawNode(elem); + + var innerElem = elem.querySelector('.tooltip-inner, .popover-inner'); + if (!innerElem) { + return; + } + + var isTooltip = angular.element(innerElem).hasClass('tooltip-inner'); + + var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow'); + if (!arrowElem) { + return; + } + + var arrowCss = { + top: '', + bottom: '', + left: '', + right: '' + }; + + placement = this.parsePlacement(placement); + if (placement[1] === 'center') { + // no adjustment necessary - just reset styles + angular.element(arrowElem).css(arrowCss); + return; + } + + var borderProp = 'border-' + placement[0] + '-width'; + var borderWidth = $window.getComputedStyle(arrowElem)[borderProp]; + + var borderRadiusProp = 'border-'; + if (PLACEMENT_REGEX.vertical.test(placement[0])) { + borderRadiusProp += placement[0] + '-' + placement[1]; + } else { + borderRadiusProp += placement[1] + '-' + placement[0]; + } + borderRadiusProp += '-radius'; + var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp]; + + switch (placement[0]) { + case 'top': + arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth; + break; + case 'bottom': + arrowCss.top = isTooltip ? '0' : '-' + borderWidth; + break; + case 'left': + arrowCss.right = isTooltip ? '0' : '-' + borderWidth; + break; + case 'right': + arrowCss.left = isTooltip ? '0' : '-' + borderWidth; + break; + } + + arrowCss[placement[1]] = borderRadius; + + angular.element(arrowElem).css(arrowCss); + } + }; + }]); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position']) +/** + * A helper, internal data structure that stores all references attached to key + */ + .factory('$$multiMap', function() { + return { + createNew: function() { + var map = {}; + + return { + entries: function() { + return Object.keys(map).map(function(key) { + return { + key: key, + value: map[key] + }; + }); + }, + get: function(key) { + return map[key]; + }, + hasKey: function(key) { + return !!map[key]; + }, + keys: function() { + return Object.keys(map); + }, + put: function(key, value) { + if (!map[key]) { + map[key] = []; + } + + map[key].push(value); + }, + remove: function(key, value) { + var values = map[key]; + + if (!values) { + return; + } + + var idx = values.indexOf(value); + + if (idx !== -1) { + values.splice(idx, 1); + } + + if (!values.length) { + delete map[key]; + } + } + }; + } + }; + }) + +/** + * Pluggable resolve mechanism for the modal resolve resolution + * Supports UI Router's $resolve service + */ + .provider('$uibResolve', function() { + var resolve = this; + this.resolver = null; + + this.setResolver = function(resolver) { + this.resolver = resolver; + }; + + this.$get = ['$injector', '$q', function($injector, $q) { + var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null; + return { + resolve: function(invocables, locals, parent, self) { + if (resolver) { + return resolver.resolve(invocables, locals, parent, self); + } + + var promises = []; + + angular.forEach(invocables, function(value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promises.push($q.resolve($injector.invoke(value))); + } else if (angular.isString(value)) { + promises.push($q.resolve($injector.get(value))); + } else { + promises.push($q.resolve(value)); + } + }); + + return $q.all(promises).then(function(resolves) { + var resolveObj = {}; + var resolveIter = 0; + angular.forEach(invocables, function(value, key) { + resolveObj[key] = resolves[resolveIter++]; + }); + + return resolveObj; + }); + } + }; + }]; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack', + function($animate, $injector, $modalStack) { + return { + replace: true, + templateUrl: 'uib/template/modal/backdrop.html', + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; + } + }; + + function linkFn(scope, element, attrs) { + if (attrs.modalInClass) { + $animate.addClass(element, attrs.modalInClass); + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if (scope.modalOptions.animation) { + $animate.removeClass(element, attrs.modalInClass).then(done); + } else { + done(); + } + }); + } + } + }]) + + .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document', + function($modalStack, $q, $animateCss, $document) { + return { + scope: { + index: '@' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'uib/template/modal/window.html'; + }, + link: function(scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); + scope.size = attrs.size; + + scope.close = function(evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && + modal.value.backdrop !== 'static' && + evt.target === evt.currentTarget) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value === 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + }); + } + + + $q.when(animationPromise).then(function() { + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + + /** + * If something within the freshly-opened modal already has focus (perhaps via a + * directive that causes focus). then no need to try and focus anything. + */ + if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) { + var inputWithAutofocus = element[0].querySelector('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + element[0].focus(); + } + } + }); + }); + } + }; + }]) + + .directive('uibModalAnimationClass', function() { + return { + compile: function(tElement, tAttrs) { + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.uibModalAnimationClass); + } + } + }; + }) + + .directive('uibModalTransclude', function() { + return { + link: function(scope, element, attrs, controller, transclude) { + transclude(scope.$parent, function(clone) { + element.empty(); + element.append(clone); + }); + } + }; + }) + + .factory('$uibModalStack', ['$animate', '$animateCss', '$document', + '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition', + function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) { + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var openedClasses = $$multiMap.createNew(); + var $modalStack = { + NOW_CLOSING_EVENT: 'modal.stack.now-closing' + }; + var topModalIndex = 0; + var previousTopOpenedModal = null; + + //Modal focus behavior + var tabableSelector = 'a[href], area[href], input:not([disabled]), ' + + 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + + 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; + var scrollbarPadding; + + function isVisible(element) { + return !!(element.offsetWidth || + element.offsetHeight || + element.getClientRects().length); + } + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + + // If any backdrop exist, ensure that it's index is always + // right below the top modal + if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) { + topBackdropIndex = topModalIndex; + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex) { + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance, elementToReceiveFocus) { + var modalWindow = openedWindows.get(modalInstance).value; + var appendToElement = modalWindow.appendTo; + + //clean up the stack + openedWindows.remove(modalInstance); + previousTopOpenedModal = openedWindows.top(); + if (previousTopOpenedModal) { + topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10); + } + + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { + var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; + openedClasses.remove(modalBodyClass, modalInstance); + var areAnyOpen = openedClasses.hasKey(modalBodyClass); + appendToElement.toggleClass(modalBodyClass, areAnyOpen); + if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + if (scrollbarPadding.originalRight) { + appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'}); + } else { + appendToElement.css({paddingRight: ''}); + } + scrollbarPadding = null; + } + toggleTopWindowClass(true); + }, modalWindow.closedDeferred); + checkRemoveBackdrop(); + + //move focus to specified element if available, or else to body + if (elementToReceiveFocus && elementToReceiveFocus.focus) { + elementToReceiveFocus.focus(); + } else if (appendToElement.focus) { + appendToElement.focus(); + } + } + + // Add or remove "windowTopClass" from the top window in the stack + function toggleTopWindowClass(toggleSwitch) { + var modalWindow; + + if (openedWindows.length() > 0) { + modalWindow = openedWindows.top().value; + modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); + } + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() === -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, function() { + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, done, closedDeferred) { + var asyncDeferred; + var asyncPromise = null; + var setIsAsync = function() { + if (!asyncDeferred) { + asyncDeferred = $q.defer(); + asyncPromise = asyncDeferred.promise; + } + + return function asyncDone() { + asyncDeferred.resolve(); + }; + }; + scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); + + // Note that it's intentional that asyncPromise might be null. + // That's when setIsAsync has not been called during the + // NOW_CLOSING_EVENT broadcast. + return $q.when(asyncPromise).then(afterAnimating); + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + $animate.leave(domEl).then(function() { + domEl.remove(); + if (closedDeferred) { + closedDeferred.resolve(); + } + }); + + scope.$destroy(); + if (done) { + done(); + } + } + } + + $document.on('keydown', keydownListener); + + $rootScope.$on('$destroy', function() { + $document.off('keydown', keydownListener); + }); + + function keydownListener(evt) { + if (evt.isDefaultPrevented()) { + return evt; + } + + var modal = openedWindows.top(); + if (modal) { + switch (evt.which) { + case 27: { + if (modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function() { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + break; + } + case 9: { + var list = $modalStack.loadFocusElementList(modal); + var focusChanged = false; + if (evt.shiftKey) { + if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { + focusChanged = $modalStack.focusLastFocusableElement(list); + } + } else { + if ($modalStack.isFocusInLastItem(evt, list)) { + focusChanged = $modalStack.focusFirstFocusableElement(list); + } + } + + if (focusChanged) { + evt.preventDefault(); + evt.stopPropagation(); + } + + break; + } + } + } + } + + $modalStack.open = function(modalInstance, modal) { + var modalOpener = $document[0].activeElement, + modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; + + toggleTopWindowClass(false); + + // Store the current top first, to determine what index we ought to use + // for the current top modal + previousTopOpenedModal = openedWindows.top(); + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + renderDeferred: modal.renderDeferred, + closedDeferred: modal.closedDeferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard, + openedClass: modal.openedClass, + windowTopClass: modal.windowTopClass, + animation: modal.animation, + appendTo: modal.appendTo + }); + + openedClasses.put(modalBodyClass, modalInstance); + + var appendToElement = modal.appendTo, + currBackdropIndex = backdropIndex(); + + if (!appendToElement.length) { + throw new Error('appendTo element not found. Make sure that the element passed is in DOM.'); + } + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.modalOptions = modal; + backdropScope.index = currBackdropIndex; + backdropDomEl = angular.element('
'); + backdropDomEl.attr('backdrop-class', modal.backdropClass); + if (modal.animation) { + backdropDomEl.attr('modal-animation', 'true'); + } + $compile(backdropDomEl)(backdropScope); + $animate.enter(backdropDomEl, appendToElement); + scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement); + if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + appendToElement.css({paddingRight: scrollbarPadding.right + 'px'}); + } + } + + // Set the top modal index based on the index of the previous top modal + topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0; + var angularDomEl = angular.element('
'); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'window-top-class': modal.windowTopClass, + 'size': modal.size, + 'index': topModalIndex, + 'animate': 'animate' + }).html(modal.content); + if (modal.animation) { + angularDomEl.attr('modal-animation', 'true'); + } + + appendToElement.addClass(modalBodyClass); + $animate.enter($compile(angularDomEl)(modal.scope), appendToElement); + + openedWindows.top().value.modalDomEl = angularDomEl; + openedWindows.top().value.modalOpener = modalOpener; + }; + + function broadcastClosing(modalWindow, resultOrReason, closing) { + return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; + } + + $modalStack.close = function(modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow && broadcastClosing(modalWindow, result, true)) { + modalInstance.callCloseFn(result); + modalWindow.value.modalScope.$$uibDestructionScheduled = true; + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; + } + return !modalWindow; + }; + + $modalStack.dismiss = function(modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow && broadcastClosing(modalWindow, reason, false)) { + modalInstance.callDismissFn(reason); + modalWindow.value.modalScope.$$uibDestructionScheduled = true; + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; + } + return !modalWindow; + }; + + $modalStack.dismissAll = function(reason) { + var topModal = this.getTop(); + while (topModal && this.dismiss(topModal.key, reason)) { + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function() { + return openedWindows.top(); + }; + + $modalStack.modalRendered = function(modalInstance) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.renderDeferred.resolve(); + } + }; + + $modalStack.focusFirstFocusableElement = function(list) { + if (list.length > 0) { + list[0].focus(); + return true; + } + return false; + }; + + $modalStack.focusLastFocusableElement = function(list) { + if (list.length > 0) { + list[list.length - 1].focus(); + return true; + } + return false; + }; + + $modalStack.isModalFocused = function(evt, modalWindow) { + if (evt && modalWindow) { + var modalDomEl = modalWindow.value.modalDomEl; + if (modalDomEl && modalDomEl.length) { + return (evt.target || evt.srcElement) === modalDomEl[0]; + } + } + return false; + }; + + $modalStack.isFocusInFirstItem = function(evt, list) { + if (list.length > 0) { + return (evt.target || evt.srcElement) === list[0]; + } + return false; + }; + + $modalStack.isFocusInLastItem = function(evt, list) { + if (list.length > 0) { + return (evt.target || evt.srcElement) === list[list.length - 1]; + } + return false; + }; + + $modalStack.loadFocusElementList = function(modalWindow) { + if (modalWindow) { + var modalDomE1 = modalWindow.value.modalDomEl; + if (modalDomE1 && modalDomE1.length) { + var elements = modalDomE1[0].querySelectorAll(tabableSelector); + return elements ? + Array.prototype.filter.call(elements, function(element) { + return isVisible(element); + }) : elements; + } + } + }; + + $modalStack.length = function() { + return openedWindows.length; + }; + + return $modalStack; + }]) + + .provider('$uibModal', function() { + var $modalProvider = { + options: { + animation: true, + backdrop: true, //can also be false or 'static' + keyboard: true, + modalOpenFn: function() {}, + modalCloseFn: function() {}, + modalDismissFn: function() {} + }, + $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack', + function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) { + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $templateRequest(angular.isFunction(options.templateUrl) ? + options.templateUrl() : options.templateUrl); + } + + var promiseChain = null; + $modal.getPromiseChain = function() { + return promiseChain; + }; + + $modal.setCallbacks = function(openFn, closeFn, dismissFn) { + angular.extend($modalProvider.options, { + modalOpenFn: openFn, + modalCloseFn: closeFn, + modalDismissFn: dismissFn + }); + }; + + $modal.open = function(modalOptions) { + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + var modalClosedDeferred = $q.defer(); + var modalRenderDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + closed: modalClosedDeferred.promise, + rendered: modalRenderDeferred.promise, + close: function (result) { + return $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + return $modalStack.dismiss(modalInstance, reason); + }, + callDismissFn: function(reason) { + modalOptions.modalDismissFn(modalOptions, reason); + return true; + }, + callCloseFn: function(result) { + modalOptions.modalCloseFn(modalOptions, result); + return true; + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0); + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]); + + function resolveWithTemplate() { + return templateAndResolvePromise; + } + + // Wait for the resolution of the existing promise chain. + // Then switch to our own combined promise dependency (regardless of how the previous modal fared). + // Then add to $modalStack and resolve opened. + // Finally clean up the chain variable if no subsequent modal has overwritten it. + var samePromise; + samePromise = promiseChain = $q.all([promiseChain]) + .then(resolveWithTemplate, resolveWithTemplate) + .then(function resolveSuccess(tplAndVars) { + var providedScope = modalOptions.scope || $rootScope; + + var modalScope = providedScope.$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + modalScope.$on('$destroy', function() { + if (!modalScope.$$uibDestructionScheduled) { + modalScope.$dismiss('$uibUnscheduledDestruction'); + } + }); + + var ctrlInstance, ctrlInstantiate, ctrlLocals = {}; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$scope.$resolve = {}; + ctrlLocals.$uibModalInstance = modalInstance; + angular.forEach(tplAndVars[1], function(value, key) { + ctrlLocals[key] = value; + ctrlLocals.$scope.$resolve[key] = value; + }); + + // the third param will make the controller instantiate later,private api + // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126 + ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs); + if (modalOptions.controllerAs && modalOptions.bindToController) { + ctrlInstance = ctrlInstantiate.instance; + ctrlInstance.$close = modalScope.$close; + ctrlInstance.$dismiss = modalScope.$dismiss; + angular.extend(ctrlInstance, { + $resolve: ctrlLocals.$scope.$resolve + }, providedScope); + } + + ctrlInstance = ctrlInstantiate(); + + if (angular.isFunction(ctrlInstance.$onInit)) { + ctrlInstance.$onInit(); + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + renderDeferred: modalRenderDeferred, + closedDeferred: modalClosedDeferred, + content: tplAndVars[0], + animation: modalOptions.animation, + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowTopClass: modalOptions.windowTopClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size, + openedClass: modalOptions.openedClass, + appendTo: modalOptions.appendTo + }); + + modalOptions.modalOpenFn(modalOptions); + modalOpenedDeferred.resolve(true); + + }, function resolveError(reason) { + modalOpenedDeferred.reject(reason); + modalResultDeferred.reject(reason); + })['finally'](function() { + if (promiseChain === samePromise) { + promiseChain = null; + } + }); + + return modalInstance; + }; + + return $modal; + } + ] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.tabs', []) + +.controller('UibTabsetController', ['$scope', function ($scope) { + var ctrl = this, + oldIndex; + ctrl.tabs = []; + + ctrl.select = function(index, evt) { + if (!destroyed) { + var previousIndex = findTabIndex(oldIndex); + var previousSelected = ctrl.tabs[previousIndex]; + if (previousSelected) { + previousSelected.tab.onDeselect({ + $event: evt, + $selectedIndex: index + }); + if (evt && evt.isDefaultPrevented()) { + return; + } + previousSelected.tab.active = false; + } + + var selected = ctrl.tabs[index]; + if (selected) { + selected.tab.onSelect({ + $event: evt + }); + selected.tab.active = true; + ctrl.active = selected.index; + oldIndex = selected.index; + } else if (!selected && angular.isDefined(oldIndex)) { + ctrl.active = null; + oldIndex = null; + } + } + }; + + ctrl.addTab = function addTab(tab) { + ctrl.tabs.push({ + tab: tab, + index: tab.index + }); + ctrl.tabs.sort(function(t1, t2) { + if (t1.index > t2.index) { + return 1; + } + + if (t1.index < t2.index) { + return -1; + } + + return 0; + }); + + if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) { + var newActiveIndex = findTabIndex(tab.index); + ctrl.select(newActiveIndex); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index; + for (var i = 0; i < ctrl.tabs.length; i++) { + if (ctrl.tabs[i].tab === tab) { + index = i; + break; + } + } + + if (ctrl.tabs[index].index === ctrl.active) { + var newActiveTabIndex = index === ctrl.tabs.length - 1 ? + index - 1 : index + 1 % ctrl.tabs.length; + ctrl.select(newActiveTabIndex); + } + + ctrl.tabs.splice(index, 1); + }; + + $scope.$watch('tabset.active', function(val) { + if (angular.isDefined(val) && val !== oldIndex) { + ctrl.select(findTabIndex(val)); + } + }); + + var destroyed; + $scope.$on('$destroy', function() { + destroyed = true; + }); + + function findTabIndex(index) { + for (var i = 0; i < ctrl.tabs.length; i++) { + if (ctrl.tabs[i].index === index) { + return i; + } + } + } +}]) + +.directive('uibTabset', function() { + return { + transclude: true, + replace: true, + scope: {}, + bindToController: { + active: '=?', + type: '@' + }, + controller: 'UibTabsetController', + controllerAs: 'tabset', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/tabs/tabset.html'; + }, + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? + scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? + scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +.directive('uibTab', ['$parse', function($parse) { + return { + require: '^uibTabset', + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/tabs/tab.html'; + }, + transclude: true, + scope: { + heading: '@', + index: '=?', + classes: '@?', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + controllerAs: 'tab', + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !! value; + }); + } + + if (angular.isUndefined(attrs.index)) { + if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) { + scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1; + } else { + scope.index = 0; + } + } + + if (angular.isUndefined(attrs.classes)) { + scope.classes = ''; + } + + scope.select = function(evt) { + if (!scope.disabled) { + var index; + for (var i = 0; i < tabsetCtrl.tabs.length; i++) { + if (tabsetCtrl.tabs[i].tab === scope) { + index = i; + break; + } + } + + tabsetCtrl.select(index, evt); + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + } + }; +}]) + +.directive('uibTabHeadingTransclude', function() { + return { + restrict: 'A', + require: '^uibTab', + link: function(scope, elm) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}) + +.directive('uibTabContentTransclude', function() { + return { + restrict: 'A', + require: '^uibTabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.uibTabContentTransclude).tab; + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('uib-tab-heading') || + node.hasAttribute('data-uib-tab-heading') || + node.hasAttribute('x-uib-tab-heading') || + node.tagName.toLowerCase() === 'uib-tab-heading' || + node.tagName.toLowerCase() === 'data-uib-tab-heading' || + node.tagName.toLowerCase() === 'x-uib-tab-heading' || + node.tagName.toLowerCase() === 'uib:tab-heading' + ); + } +}); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap']) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider('$uibTooltip', function() { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + placementClassPrefix: '', + animation: true, + popupDelay: 0, + popupCloseDelay: 0, + useContentExp: false + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'outsideClick': 'outsideClick', + 'focus': 'blur', + 'none': '' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function(value) { + angular.extend(globalOptions, value); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } ); + */ + this.setTriggers = function setTriggers(triggers) { + angular.extend(triggerMap, triggers); + }; + + /** + * This is a helper function for translating camel-case to snake_case. + */ + function snake_case(name) { + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) { + var openedTooltips = $$stackedMap.createNew(); + $document.on('keypress', keypressListener); + + $rootScope.$on('$destroy', function() { + $document.off('keypress', keypressListener); + }); + + function keypressListener(e) { + if (e.which === 27) { + var last = openedTooltips.top(); + if (last) { + last.value.close(); + openedTooltips.removeTop(); + last = null; + } + } + } + + return function $tooltip(ttType, prefix, defaultTriggerShow, options) { + options = angular.extend({}, defaultOptions, globalOptions, options); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers(trigger) { + var show = (trigger || options.trigger || defaultTriggerShow).split(' '); + var hide = show.map(function(trigger) { + return triggerMap[trigger] || trigger; + }); + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case(ttType); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
' + + '
'; + + return { + compile: function(tElem, tAttrs) { + var tooltipLinker = $compile(template); + + return function link(scope, element, attrs, tooltipCtrl) { + var tooltip; + var tooltipLinkedScope; + var transitionTimeout; + var showTimeout; + var hideTimeout; + var positionTimeout; + var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false; + var triggers = getTriggers(undefined); + var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); + var ttScope = scope.$new(true); + var repositionScheduled = false; + var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; + var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; + var observers = []; + var lastPlacement; + + var positionTooltip = function() { + // check if tooltip exists and is not empty + if (!tooltip || !tooltip.html()) { return; } + + if (!positionTimeout) { + positionTimeout = $timeout(function() { + var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); + tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' }); + + if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) { + tooltip.removeClass(lastPlacement.split('-')[0]); + tooltip.addClass(ttPosition.placement.split('-')[0]); + } + + if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) { + tooltip.removeClass(options.placementClassPrefix + lastPlacement); + tooltip.addClass(options.placementClassPrefix + ttPosition.placement); + } + + // first time through tt element will have the + // uib-position-measure class or if the placement + // has changed we need to position the arrow. + if (tooltip.hasClass('uib-position-measure')) { + $position.positionArrow(tooltip, ttPosition.placement); + tooltip.removeClass('uib-position-measure'); + } else if (lastPlacement !== ttPosition.placement) { + $position.positionArrow(tooltip, ttPosition.placement); + } + lastPlacement = ttPosition.placement; + + positionTimeout = null; + }, 0, false); + } + }; + + // Set up the correct scope to allow transclusion later + ttScope.origScope = scope; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + ttScope.isOpen = false; + openedTooltips.add(ttScope, { + close: hide + }); + + function toggleTooltipBind() { + if (!ttScope.isOpen) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { + return; + } + + cancelHide(); + prepareTooltip(); + + if (ttScope.popupDelay) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!showTimeout) { + showTimeout = $timeout(show, ttScope.popupDelay, false); + } + } else { + show(); + } + } + + function hideTooltipBind() { + cancelShow(); + + if (ttScope.popupCloseDelay) { + if (!hideTimeout) { + hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false); + } + } else { + hide(); + } + } + + // Show the tooltip popup element. + function show() { + cancelShow(); + cancelHide(); + + // Don't show empty tooltips. + if (!ttScope.content) { + return angular.noop; + } + + createTooltip(); + + // And show the tooltip. + ttScope.$evalAsync(function() { + ttScope.isOpen = true; + assignIsOpen(true); + positionTooltip(); + }); + } + + function cancelShow() { + if (showTimeout) { + $timeout.cancel(showTimeout); + showTimeout = null; + } + + if (positionTimeout) { + $timeout.cancel(positionTimeout); + positionTimeout = null; + } + } + + // Hide the tooltip popup element. + function hide() { + if (!ttScope) { + return; + } + + // First things first: we don't show it anymore. + ttScope.$evalAsync(function() { + if (ttScope) { + ttScope.isOpen = false; + assignIsOpen(false); + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + // The fade transition in TWBS is 150ms. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 150, false); + } + } else { + removeTooltip(); + } + } + }); + } + + function cancelHide() { + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = null; + } + + if (transitionTimeout) { + $timeout.cancel(transitionTimeout); + transitionTimeout = null; + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + return; + } + + tooltipLinkedScope = ttScope.$new(); + tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { + if (appendToBody) { + $document.find('body').append(tooltip); + } else { + element.after(tooltip); + } + }); + + prepObservers(); + } + + function removeTooltip() { + cancelShow(); + cancelHide(); + unregisterObservers(); + + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + if (tooltipLinkedScope) { + tooltipLinkedScope.$destroy(); + tooltipLinkedScope = null; + } + } + + /** + * Set the initial scope values. Once + * the tooltip is created, the observers + * will be added to keep things in sync. + */ + function prepareTooltip() { + ttScope.title = attrs[prefix + 'Title']; + if (contentParse) { + ttScope.content = contentParse(scope); + } else { + ttScope.content = attrs[ttType]; + } + + ttScope.popupClass = attrs[prefix + 'Class']; + ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; + var placement = $position.parsePlacement(ttScope.placement); + lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; + + var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); + var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); + ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; + ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay; + } + + function assignIsOpen(isOpen) { + if (isOpenParse && angular.isFunction(isOpenParse.assign)) { + isOpenParse.assign(scope, isOpen); + } + } + + ttScope.contentExp = function() { + return ttScope.content; + }; + + /** + * Observe the relevant attributes. + */ + attrs.$observe('disabled', function(val) { + if (val) { + cancelShow(); + } + + if (val && ttScope.isOpen) { + hide(); + } + }); + + if (isOpenParse) { + scope.$watch(isOpenParse, function(val) { + if (ttScope && !val === ttScope.isOpen) { + toggleTooltipBind(); + } + }); + } + + function prepObservers() { + observers.length = 0; + + if (contentParse) { + observers.push( + scope.$watch(contentParse, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } + }) + ); + + observers.push( + tooltipLinkedScope.$watch(function() { + if (!repositionScheduled) { + repositionScheduled = true; + tooltipLinkedScope.$$postDigest(function() { + repositionScheduled = false; + if (ttScope && ttScope.isOpen) { + positionTooltip(); + } + }); + } + }) + ); + } else { + observers.push( + attrs.$observe(ttType, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } else { + positionTooltip(); + } + }) + ); + } + + observers.push( + attrs.$observe(prefix + 'Title', function(val) { + ttScope.title = val; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); + + observers.push( + attrs.$observe(prefix + 'Placement', function(val) { + ttScope.placement = val ? val : options.placement; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); + } + + function unregisterObservers() { + if (observers.length) { + angular.forEach(observers, function(observer) { + observer(); + }); + observers.length = 0; + } + } + + // hide tooltips/popovers for outsideClick trigger + function bodyHideTooltipBind(e) { + if (!ttScope || !ttScope.isOpen || !tooltip) { + return; + } + // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked + if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) { + hideTooltipBind(); + } + } + + var unregisterTriggers = function() { + triggers.show.forEach(function(trigger) { + if (trigger === 'outsideClick') { + element.off('click', toggleTooltipBind); + } else { + element.off(trigger, showTooltipBind); + element.off(trigger, toggleTooltipBind); + } + }); + triggers.hide.forEach(function(trigger) { + if (trigger === 'outsideClick') { + $document.off('click', bodyHideTooltipBind); + } else { + element.off(trigger, hideTooltipBind); + } + }); + }; + + function prepTriggers() { + var val = attrs[prefix + 'Trigger']; + unregisterTriggers(); + + triggers = getTriggers(val); + + if (triggers.show !== 'none') { + triggers.show.forEach(function(trigger, idx) { + if (trigger === 'outsideClick') { + element.on('click', toggleTooltipBind); + $document.on('click', bodyHideTooltipBind); + } else if (trigger === triggers.hide[idx]) { + element.on(trigger, toggleTooltipBind); + } else if (trigger) { + element.on(trigger, showTooltipBind); + element.on(triggers.hide[idx], hideTooltipBind); + } + + element.on('keypress', function(e) { + if (e.which === 27) { + hideTooltipBind(); + } + }); + }); + } + } + + prepTriggers(); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; + + var appendToBodyVal; + var appendKey = prefix + 'AppendToBody'; + if (appendKey in attrs && attrs[appendKey] === undefined) { + appendToBodyVal = true; + } else { + appendToBodyVal = scope.$eval(attrs[appendKey]); + } + + appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + unregisterTriggers(); + removeTooltip(); + openedTooltips.remove(ttScope); + ttScope = null; + }); + }; + } + }; + }; + }]; +}) + +// This is mostly ngInclude code but with a custom scope +.directive('uibTooltipTemplateTransclude', [ + '$animate', '$sce', '$compile', '$templateRequest', +function ($animate, $sce, $compile, $templateRequest) { + return { + link: function(scope, elem, attrs) { + var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); + + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if (previousElement) { + previousElement.remove(); + previousElement = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + + if (currentElement) { + $animate.leave(currentElement).then(function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) { + var thisChangeId = ++changeCounter; + + if (src) { + //set the 2nd param to true to ignore the template request error so that the inner + //contents and scope can be cleaned up. + $templateRequest(src, true).then(function(response) { + if (thisChangeId !== changeCounter) { return; } + var newScope = origScope.$new(); + var template = response; + + var clone = $compile(template)(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, elem); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded', src); + }, function() { + if (thisChangeId === changeCounter) { + cleanupLastIncludeContent(); + scope.$emit('$includeContentError', src); + } + }); + scope.$emit('$includeContentRequested', src); + } else { + cleanupLastIncludeContent(); + } + }); + + scope.$on('$destroy', cleanupLastIncludeContent); + } + }; +}]) + +/** + * Note that it's intentional that these classes are *not* applied through $animate. + * They must not be animated as they're expected to be present on the tooltip on + * initialization. + */ +.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + // need to set the primary position so the + // arrow has space during position measure. + // tooltip.positionTooltip() + if (scope.placement) { + // // There are no top-left etc... classes + // // in TWBS, so we need the primary position. + var position = $uibPosition.parsePlacement(scope.placement); + element.addClass(position[0]); + } + + if (scope.popupClass) { + element.addClass(scope.popupClass); + } + + if (scope.animation()) { + element.addClass(attrs.tooltipAnimationClass); + } + } + }; +}]) + +.directive('uibTooltipPopup', function() { + return { + replace: true, + scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/tooltip/tooltip-popup.html' + }; +}) + +.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter'); +}]) + +.directive('uibTooltipTemplatePopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'uib/template/tooltip/tooltip-template-popup.html' + }; +}) + +.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]) + +.directive('uibTooltipHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/tooltip/tooltip-html-popup.html' + }; +}) + +.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]); + +angular.module('ui.bootstrap.timepicker', []) + +.constant('uibTimepickerConfig', { + hourStep: 1, + minuteStep: 1, + secondStep: 1, + showMeridian: true, + showSeconds: false, + meridians: null, + readonlyInput: false, + mousewheel: true, + arrowkeys: true, + showSpinners: true, + templateUrl: 'uib/template/timepicker/timepicker.html' +}) + +.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + watchers = [], + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS, + padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true; + + $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; + $element.removeAttr('tabindex'); + + this.init = function(ngModelCtrl_, inputs) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + ngModelCtrl.$formatters.unshift(function(modelValue) { + return modelValue ? new Date(modelValue) : null; + }); + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1), + secondsInputEl = inputs.eq(2); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + + if (mousewheel) { + this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl); + } + + var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; + if (arrowkeys) { + this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = +value; + })); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = +value; + })); + } + + var min; + watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) { + var dt = new Date(value); + min = isNaN(dt) ? undefined : dt; + })); + + var max; + watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) { + var dt = new Date(value); + max = isNaN(dt) ? undefined : dt; + })); + + var disabled = false; + if ($attrs.ngDisabled) { + watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) { + disabled = value; + })); + } + + $scope.noIncrementHours = function() { + var incrementedSelected = addMinutes(selected, hourStep * 60); + return disabled || incrementedSelected > max || + incrementedSelected < selected && incrementedSelected < min; + }; + + $scope.noDecrementHours = function() { + var decrementedSelected = addMinutes(selected, -hourStep * 60); + return disabled || decrementedSelected < min || + decrementedSelected > selected && decrementedSelected > max; + }; + + $scope.noIncrementMinutes = function() { + var incrementedSelected = addMinutes(selected, minuteStep); + return disabled || incrementedSelected > max || + incrementedSelected < selected && incrementedSelected < min; + }; + + $scope.noDecrementMinutes = function() { + var decrementedSelected = addMinutes(selected, -minuteStep); + return disabled || decrementedSelected < min || + decrementedSelected > selected && decrementedSelected > max; + }; + + $scope.noIncrementSeconds = function() { + var incrementedSelected = addSeconds(selected, secondStep); + return disabled || incrementedSelected > max || + incrementedSelected < selected && incrementedSelected < min; + }; + + $scope.noDecrementSeconds = function() { + var decrementedSelected = addSeconds(selected, -secondStep); + return disabled || decrementedSelected < min || + decrementedSelected > selected && decrementedSelected > max; + }; + + $scope.noToggleMeridian = function() { + if (selected.getHours() < 12) { + return disabled || addMinutes(selected, 12 * 60) > max; + } + + return disabled || addMinutes(selected, -12 * 60) < min; + }; + + var secondStep = timepickerConfig.secondStep; + if ($attrs.secondStep) { + watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) { + secondStep = +value; + })); + } + + $scope.showSeconds = timepickerConfig.showSeconds; + if ($attrs.showSeconds) { + watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) { + $scope.showSeconds = !!value; + })); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if (ngModelCtrl.$error.time) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); + refresh(); + } + } else { + updateTemplate(); + } + })); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate() { + var hours = +$scope.hours; + var valid = $scope.showMeridian ? hours > 0 && hours < 13 : + hours >= 0 && hours < 24; + if (!valid || $scope.hours === '') { + return undefined; + } + + if ($scope.showMeridian) { + if (hours === 12) { + hours = 0; + } + if ($scope.meridian === meridians[1]) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = +$scope.minutes; + var valid = minutes >= 0 && minutes < 60; + if (!valid || $scope.minutes === '') { + return undefined; + } + return minutes; + } + + function getSecondsFromTemplate() { + var seconds = +$scope.seconds; + return seconds >= 0 && seconds < 60 ? seconds : undefined; + } + + function pad(value, noPad) { + if (value === null) { + return ''; + } + + return angular.isDefined(value) && value.toString().length < 2 && !noPad ? + '0' + value : value.toString(); + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY; + return e.detail || delta > 0; + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + if (!disabled) { + $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); + } + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + if (!disabled) { + $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); + } + e.preventDefault(); + }); + + secondsInputEl.bind('mousewheel wheel', function(e) { + if (!disabled) { + $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds()); + } + e.preventDefault(); + }); + }; + + // Respond on up/down arrowkeys + this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { + hoursInputEl.bind('keydown', function(e) { + if (!disabled) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementHours(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementHours(); + $scope.$apply(); + } + } + }); + + minutesInputEl.bind('keydown', function(e) { + if (!disabled) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementMinutes(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementMinutes(); + $scope.$apply(); + } + } + }); + + secondsInputEl.bind('keydown', function(e) { + if (!disabled) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementSeconds(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementSeconds(); + $scope.$apply(); + } + } + }); + }; + + this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { + if ($scope.readonlyInput) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + $scope.updateSeconds = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) { + ngModelCtrl.$setViewValue(null); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + + if (angular.isDefined(invalidSeconds)) { + $scope.invalidSeconds = invalidSeconds; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(), + minutes = getMinutesFromTemplate(); + + ngModelCtrl.$setDirty(); + + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); + selected.setMinutes(minutes); + if (selected < min || selected > max) { + invalidate(true); + } else { + refresh('h'); + } + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + ngModelCtrl.$setTouched(); + if (modelIsEmpty()) { + makeValid(); + } else if ($scope.hours === null || $scope.hours === '') { + invalidate(true); + } else if (!$scope.invalidHours && $scope.hours < 10) { + $scope.$apply(function() { + $scope.hours = pad($scope.hours, !padHours); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(), + hours = getHoursFromTemplate(); + + ngModelCtrl.$setDirty(); + + if (angular.isDefined(minutes) && angular.isDefined(hours)) { + selected.setHours(hours); + selected.setMinutes(minutes); + if (selected < min || selected > max) { + invalidate(undefined, true); + } else { + refresh('m'); + } + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + ngModelCtrl.$setTouched(); + if (modelIsEmpty()) { + makeValid(); + } else if ($scope.minutes === null) { + invalidate(undefined, true); + } else if (!$scope.invalidMinutes && $scope.minutes < 10) { + $scope.$apply(function() { + $scope.minutes = pad($scope.minutes); + }); + } + }); + + $scope.updateSeconds = function() { + var seconds = getSecondsFromTemplate(); + + ngModelCtrl.$setDirty(); + + if (angular.isDefined(seconds)) { + selected.setSeconds(seconds); + refresh('s'); + } else { + invalidate(undefined, undefined, true); + } + }; + + secondsInputEl.bind('blur', function(e) { + if (modelIsEmpty()) { + makeValid(); + } else if (!$scope.invalidSeconds && $scope.seconds < 10) { + $scope.$apply( function() { + $scope.seconds = pad($scope.seconds); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$viewValue; + + if (isNaN(date)) { + ngModelCtrl.$setValidity('time', false); + $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.'); + } else { + if (date) { + selected = date; + } + + if (selected < min || selected > max) { + ngModelCtrl.$setValidity('time', false); + $scope.invalidHours = true; + $scope.invalidMinutes = true; + } else { + makeValid(); + } + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh(keyboardChange) { + makeValid(); + ngModelCtrl.$setViewValue(new Date(selected)); + updateTemplate(keyboardChange); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + $scope.invalidSeconds = false; + } + + function updateTemplate(keyboardChange) { + if (!ngModelCtrl.$modelValue) { + $scope.hours = null; + $scope.minutes = null; + $scope.seconds = null; + $scope.meridian = meridians[0]; + } else { + var hours = selected.getHours(), + minutes = selected.getMinutes(), + seconds = selected.getSeconds(); + + if ($scope.showMeridian) { + hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours); + if (keyboardChange !== 'm') { + $scope.minutes = pad(minutes); + } + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + + if (keyboardChange !== 's') { + $scope.seconds = pad(seconds); + } + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + } + + function addSecondsToSelected(seconds) { + selected = addSeconds(selected, seconds); + refresh(); + } + + function addMinutes(selected, minutes) { + return addSeconds(selected, minutes*60); + } + + function addSeconds(date, seconds) { + var dt = new Date(date.getTime() + seconds * 1000); + var newDate = new Date(date); + newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds()); + return newDate; + } + + function modelIsEmpty() { + return ($scope.hours === null || $scope.hours === '') && + ($scope.minutes === null || $scope.minutes === '') && + (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === '')); + } + + $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? + $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; + + $scope.incrementHours = function() { + if (!$scope.noIncrementHours()) { + addSecondsToSelected(hourStep * 60 * 60); + } + }; + + $scope.decrementHours = function() { + if (!$scope.noDecrementHours()) { + addSecondsToSelected(-hourStep * 60 * 60); + } + }; + + $scope.incrementMinutes = function() { + if (!$scope.noIncrementMinutes()) { + addSecondsToSelected(minuteStep * 60); + } + }; + + $scope.decrementMinutes = function() { + if (!$scope.noDecrementMinutes()) { + addSecondsToSelected(-minuteStep * 60); + } + }; + + $scope.incrementSeconds = function() { + if (!$scope.noIncrementSeconds()) { + addSecondsToSelected(secondStep); + } + }; + + $scope.decrementSeconds = function() { + if (!$scope.noDecrementSeconds()) { + addSecondsToSelected(-secondStep); + } + }; + + $scope.toggleMeridian = function() { + var minutes = getMinutesFromTemplate(), + hours = getHoursFromTemplate(); + + if (!$scope.noToggleMeridian()) { + if (angular.isDefined(minutes) && angular.isDefined(hours)) { + addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60)); + } else { + $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0]; + } + } + }; + + $scope.blur = function() { + ngModelCtrl.$setTouched(); + }; + + $scope.$on('$destroy', function() { + while (watchers.length) { + watchers.shift()(); + } + }); +}]) + +.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) { + return { + require: ['uibTimepicker', '?^ngModel'], + controller: 'UibTimepickerController', + controllerAs: 'timepicker', + replace: true, + scope: {}, + templateUrl: function(element, attrs) { + return attrs.templateUrl || uibTimepickerConfig.templateUrl; + }, + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (ngModelCtrl) { + timepickerCtrl.init(ngModelCtrl, element.find('input')); + } + } + }; +}]); + +angular.module('ui.bootstrap.dateparser', []) + +.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) { + // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js + var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + + var localeId; + var formatCodeToRegex; + + this.init = function() { + localeId = $locale.id; + + this.parsers = {}; + this.formatters = {}; + + formatCodeToRegex = [ + { + key: 'yyyy', + regex: '\\d{4}', + apply: function(value) { this.year = +value; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'yyyy'); + } + }, + { + key: 'yy', + regex: '\\d{2}', + apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'yy'); + } + }, + { + key: 'y', + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'y'); + } + }, + { + key: 'M!', + regex: '0?[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { + var value = date.getMonth(); + if (/^[0-9]$/.test(value)) { + return dateFilter(date, 'MM'); + } + + return dateFilter(date, 'M'); + } + }, + { + key: 'MMMM', + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, + formatter: function(date) { return dateFilter(date, 'MMMM'); } + }, + { + key: 'MMM', + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, + formatter: function(date) { return dateFilter(date, 'MMM'); } + }, + { + key: 'MM', + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { return dateFilter(date, 'MM'); } + }, + { + key: 'M', + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { return dateFilter(date, 'M'); } + }, + { + key: 'd!', + regex: '[0-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; }, + formatter: function(date) { + var value = date.getDate(); + if (/^[1-9]$/.test(value)) { + return dateFilter(date, 'dd'); + } + + return dateFilter(date, 'd'); + } + }, + { + key: 'dd', + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; }, + formatter: function(date) { return dateFilter(date, 'dd'); } + }, + { + key: 'd', + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; }, + formatter: function(date) { return dateFilter(date, 'd'); } + }, + { + key: 'EEEE', + regex: $locale.DATETIME_FORMATS.DAY.join('|'), + formatter: function(date) { return dateFilter(date, 'EEEE'); } + }, + { + key: 'EEE', + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), + formatter: function(date) { return dateFilter(date, 'EEE'); } + }, + { + key: 'HH', + regex: '(?:0|1)[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'HH'); } + }, + { + key: 'hh', + regex: '0[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'hh'); } + }, + { + key: 'H', + regex: '1?[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'H'); } + }, + { + key: 'h', + regex: '[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'h'); } + }, + { + key: 'mm', + regex: '[0-5][0-9]', + apply: function(value) { this.minutes = +value; }, + formatter: function(date) { return dateFilter(date, 'mm'); } + }, + { + key: 'm', + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.minutes = +value; }, + formatter: function(date) { return dateFilter(date, 'm'); } + }, + { + key: 'sss', + regex: '[0-9][0-9][0-9]', + apply: function(value) { this.milliseconds = +value; }, + formatter: function(date) { return dateFilter(date, 'sss'); } + }, + { + key: 'ss', + regex: '[0-5][0-9]', + apply: function(value) { this.seconds = +value; }, + formatter: function(date) { return dateFilter(date, 'ss'); } + }, + { + key: 's', + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.seconds = +value; }, + formatter: function(date) { return dateFilter(date, 's'); } + }, + { + key: 'a', + regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), + apply: function(value) { + if (this.hours === 12) { + this.hours = 0; + } + + if (value === 'PM') { + this.hours += 12; + } + }, + formatter: function(date) { return dateFilter(date, 'a'); } + }, + { + key: 'Z', + regex: '[+-]\\d{4}', + apply: function(value) { + var matches = value.match(/([+-])(\d{2})(\d{2})/), + sign = matches[1], + hours = matches[2], + minutes = matches[3]; + this.hours += toInt(sign + hours); + this.minutes += toInt(sign + minutes); + }, + formatter: function(date) { + return dateFilter(date, 'Z'); + } + }, + { + key: 'ww', + regex: '[0-4][0-9]|5[0-3]', + formatter: function(date) { return dateFilter(date, 'ww'); } + }, + { + key: 'w', + regex: '[0-9]|[1-4][0-9]|5[0-3]', + formatter: function(date) { return dateFilter(date, 'w'); } + }, + { + key: 'GGGG', + regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), + formatter: function(date) { return dateFilter(date, 'GGGG'); } + }, + { + key: 'GGG', + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'GGG'); } + }, + { + key: 'GG', + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'GG'); } + }, + { + key: 'G', + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'G'); } + } + ]; + }; + + this.init(); + + function createParser(format, func) { + var map = [], regex = format.split(''); + + // check for literal values + var quoteIndex = format.indexOf('\''); + if (quoteIndex > -1) { + var inLiteral = false; + format = format.split(''); + for (var i = quoteIndex; i < format.length; i++) { + if (inLiteral) { + if (format[i] === '\'') { + if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote + format[i+1] = '$'; + regex[i+1] = ''; + } else { // end of literal + regex[i] = ''; + inLiteral = false; + } + } + format[i] = '$'; + } else { + if (format[i] === '\'') { // start of literal + format[i] = '$'; + regex[i] = ''; + inLiteral = true; + } + } + } + + format = format.join(''); + } + + angular.forEach(formatCodeToRegex, function(data) { + var index = format.indexOf(data.key); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + data.key.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ + index: index, + key: data.key, + apply: data[func], + matcher: data.regex + }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + } + + this.filter = function(date, format) { + if (!angular.isDate(date) || isNaN(date) || !format) { + return ''; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.formatters[format]) { + this.formatters[format] = createParser(format, 'formatter'); + } + + var parser = this.formatters[format], + map = parser.map; + + var _format = format; + + return map.reduce(function(str, mapper, i) { + var match = _format.match(new RegExp('(.*)' + mapper.key)); + if (match && angular.isString(match[1])) { + str += match[1]; + _format = _format.replace(match[1] + mapper.key, ''); + } + + var endStr = i === map.length - 1 ? _format : ''; + + if (mapper.apply) { + return str + mapper.apply.call(null, date) + endStr; + } + + return str + endStr; + }, ''); + }; + + this.parse = function(input, format, baseDate) { + if (!angular.isString(input) || !format) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); + + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.parsers[format]) { + this.parsers[format] = createParser(format, 'apply'); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex), + tzOffset = false; + if (results && results.length) { + var fields, dt; + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { + fields = { + year: baseDate.getFullYear(), + month: baseDate.getMonth(), + date: baseDate.getDate(), + hours: baseDate.getHours(), + minutes: baseDate.getMinutes(), + seconds: baseDate.getSeconds(), + milliseconds: baseDate.getMilliseconds() + }; + } else { + if (baseDate) { + $log.warn('dateparser:', 'baseDate is not a valid date'); + } + fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; + } + + for (var i = 1, n = results.length; i < n; i++) { + var mapper = map[i - 1]; + if (mapper.matcher === 'Z') { + tzOffset = true; + } + + if (mapper.apply) { + mapper.apply.call(fields, results[i]); + } + } + + var datesetter = tzOffset ? Date.prototype.setUTCFullYear : + Date.prototype.setFullYear; + var timesetter = tzOffset ? Date.prototype.setUTCHours : + Date.prototype.setHours; + + if (isValid(fields.year, fields.month, fields.date)) { + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) { + dt = new Date(baseDate); + datesetter.call(dt, fields.year, fields.month, fields.date); + timesetter.call(dt, fields.hours, fields.minutes, + fields.seconds, fields.milliseconds); + } else { + dt = new Date(0); + datesetter.call(dt, fields.year, fields.month, fields.date); + timesetter.call(dt, fields.hours || 0, fields.minutes || 0, + fields.seconds || 0, fields.milliseconds || 0); + } + } + + return dt; + } + }; + + // Check if date is valid for specific month (and year for February). + // Month: 0 = Jan, 1 = Feb, etc + function isValid(year, month, date) { + if (date < 1) { + return false; + } + + if (month === 1 && date > 28) { + return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0); + } + + if (month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; + } + + return true; + } + + function toInt(str) { + return parseInt(str, 10); + } + + this.toTimezone = toTimezone; + this.fromTimezone = fromTimezone; + this.timezoneToOffset = timezoneToOffset; + this.addDateMinutes = addDateMinutes; + this.convertTimezoneToLocal = convertTimezoneToLocal; + + function toTimezone(date, timezone) { + return date && timezone ? convertTimezoneToLocal(date, timezone) : date; + } + + function fromTimezone(date, timezone) { + return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; + } + + //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207 + function timezoneToOffset(timezone, fallback) { + timezone = timezone.replace(/:/g, ''); + var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; + return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; + } + + function addDateMinutes(date, minutes) { + date = new Date(date.getTime()); + date.setMinutes(date.getMinutes() + minutes); + return date; + } + + function convertTimezoneToLocal(date, timezone, reverse) { + reverse = reverse ? -1 : 1; + var dateTimezoneOffset = date.getTimezoneOffset(); + var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); + return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); + } +}]); + +// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to +// at most one element. +angular.module('ui.bootstrap.isClass', []) +.directive('uibIsClass', [ + '$animate', +function ($animate) { + // 11111111 22222222 + var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/; + // 11111111 22222222 + var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/; + + var dataPerTracked = {}; + + return { + restrict: 'A', + compile: function(tElement, tAttrs) { + var linkedScopes = []; + var instances = []; + var expToData = {}; + var lastActivated = null; + var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP); + var onExp = onExpMatches[2]; + var expsStr = onExpMatches[1]; + var exps = expsStr.split(','); + + return linkFn; + + function linkFn(scope, element, attrs) { + linkedScopes.push(scope); + instances.push({ + scope: scope, + element: element + }); + + exps.forEach(function(exp, k) { + addForExp(exp, scope); + }); + + scope.$on('$destroy', removeScope); + } + + function addForExp(exp, scope) { + var matches = exp.match(IS_REGEXP); + var clazz = scope.$eval(matches[1]); + var compareWithExp = matches[2]; + var data = expToData[exp]; + if (!data) { + var watchFn = function(compareWithVal) { + var newActivated = null; + instances.some(function(instance) { + var thisVal = instance.scope.$eval(onExp); + if (thisVal === compareWithVal) { + newActivated = instance; + return true; + } + }); + if (data.lastActivated !== newActivated) { + if (data.lastActivated) { + $animate.removeClass(data.lastActivated.element, clazz); + } + if (newActivated) { + $animate.addClass(newActivated.element, clazz); + } + data.lastActivated = newActivated; + } + }; + expToData[exp] = data = { + lastActivated: null, + scope: scope, + watchFn: watchFn, + compareWithExp: compareWithExp, + watcher: scope.$watch(compareWithExp, watchFn) + }; + } + data.watchFn(scope.$eval(compareWithExp)); + } + + function removeScope(e) { + var removedScope = e.targetScope; + var index = linkedScopes.indexOf(removedScope); + linkedScopes.splice(index, 1); + instances.splice(index, 1); + if (linkedScopes.length) { + var newWatchScope = linkedScopes[0]; + angular.forEach(expToData, function(data) { + if (data.scope === removedScope) { + data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn); + data.scope = newWatchScope; + } + }); + } else { + expToData = {}; + } + } + } + }; +}]); +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass']) + +.value('$datepickerSuppressError', false) + +.value('$datepickerLiteralWarning', true) + +.constant('uibDatepickerConfig', { + datepickerMode: 'day', + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + maxDate: null, + maxMode: 'year', + minDate: null, + minMode: 'day', + ngModelOptions: {}, + shortcutPropagation: false, + showWeeks: true, + yearColumns: 5, + yearRows: 4 +}) + +.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser', + function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl; + ngModelOptions = {}, + watchListeners = [], + optionsUsed = !!$attrs.datepickerOptions; + + if (!$scope.datepickerOptions) { + $scope.datepickerOptions = {}; + } + + // Modes chain + this.modes = ['day', 'month', 'year']; + + [ + 'customClass', + 'dateDisabled', + 'datepickerMode', + 'formatDay', + 'formatDayHeader', + 'formatDayTitle', + 'formatMonth', + 'formatMonthTitle', + 'formatYear', + 'maxDate', + 'maxMode', + 'minDate', + 'minMode', + 'showWeeks', + 'shortcutPropagation', + 'startingDay', + 'yearColumns', + 'yearRows' + ].forEach(function(key) { + switch (key) { + case 'customClass': + case 'dateDisabled': + $scope[key] = $scope.datepickerOptions[key] || angular.noop; + break; + case 'datepickerMode': + $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ? + $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode; + break; + case 'formatDay': + case 'formatDayHeader': + case 'formatDayTitle': + case 'formatMonth': + case 'formatMonthTitle': + case 'formatYear': + self[key] = angular.isDefined($scope.datepickerOptions[key]) ? + $interpolate($scope.datepickerOptions[key])($scope.$parent) : + datepickerConfig[key]; + break; + case 'showWeeks': + case 'shortcutPropagation': + case 'yearColumns': + case 'yearRows': + self[key] = angular.isDefined($scope.datepickerOptions[key]) ? + $scope.datepickerOptions[key] : datepickerConfig[key]; + break; + case 'startingDay': + if (angular.isDefined($scope.datepickerOptions.startingDay)) { + self.startingDay = $scope.datepickerOptions.startingDay; + } else if (angular.isNumber(datepickerConfig.startingDay)) { + self.startingDay = datepickerConfig.startingDay; + } else { + self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; + } + + break; + case 'maxDate': + case 'minDate': + $scope.$watch('datepickerOptions.' + key, function(value) { + if (value) { + if (angular.isDate(value)) { + self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); + } else { + if ($datepickerLiteralWarning) { + $log.warn('Literal date support has been deprecated, please switch to date object usage'); + } + + self[key] = new Date(dateFilter(value, 'medium')); + } + } else { + self[key] = datepickerConfig[key] ? + dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : + null; + } + + self.refreshView(); + }); + + break; + case 'maxMode': + case 'minMode': + if ($scope.datepickerOptions[key]) { + $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) { + self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key]; + if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) || + key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) { + $scope.datepickerMode = self[key]; + $scope.datepickerOptions.datepickerMode = self[key]; + } + }); + } else { + self[key] = $scope[key] = datepickerConfig[key] || null; + } + + break; + } + }); + + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + + $scope.disabled = angular.isDefined($attrs.disabled) || false; + if (angular.isDefined($attrs.ngDisabled)) { + watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) { + $scope.disabled = disabled; + self.refreshView(); + })); + } + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions; + if ($scope.datepickerOptions.initDate) { + self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date(); + $scope.$watch('datepickerOptions.initDate', function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); + self.refreshView(); + } + }); + } else { + self.activeDate = new Date(); + } + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date(); + this.activeDate = !isNaN(date) ? + dateParser.fromTimezone(date, ngModelOptions.timezone) : + dateParser.fromTimezone(new Date(), ngModelOptions.timezone); + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if (ngModelCtrl.$viewValue) { + var date = new Date(ngModelCtrl.$viewValue), + isValid = !isNaN(date); + + if (isValid) { + this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone); + } else if (!$datepickerSuppressError) { + $log.error('Datepicker directive: "ng-model" value must be a Date object'); + } + } + this.refreshView(); + }; + + this.refreshView = function() { + if (this.element) { + $scope.selectedDt = null; + this._refreshView(); + if ($scope.activeDt) { + $scope.activeDateId = $scope.activeDt.uid; + } + + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + date = dateParser.fromTimezone(date, ngModelOptions.timezone); + ngModelCtrl.$setValidity('dateDisabled', !date || + this.element && !this.isDisabled(date)); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + model = dateParser.fromTimezone(model, ngModelOptions.timezone); + var today = new Date(); + today = dateParser.fromTimezone(today, ngModelOptions.timezone); + var time = this.compare(date, today); + var dt = { + date: date, + label: dateParser.filter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + past: time < 0, + current: time === 0, + future: time > 0, + customClass: this.customClass(date) || null + }; + + if (model && this.compare(date, model) === 0) { + $scope.selectedDt = dt; + } + + if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) { + $scope.activeDt = dt; + } + + return dt; + }; + + this.isDisabled = function(date) { + return $scope.disabled || + this.minDate && this.compare(date, this.minDate) < 0 || + this.maxDate && this.compare(date, this.maxDate) > 0 || + $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}); + }; + + this.customClass = function(date) { + return $scope.customClass({date: date, mode: $scope.datepickerMode}); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + $scope.select = function(date) { + if ($scope.datepickerMode === self.minMode) { + var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + dt = dateParser.toTimezone(dt, ngModelOptions.timezone); + ngModelCtrl.$setViewValue(dt); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]); + + $scope.$emit('uib:datepicker.mode'); + } + + $scope.$broadcast('uib:datepicker.focus'); + }; + + $scope.move = function(direction) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function(direction) { + direction = direction || 1; + + if ($scope.datepickerMode === self.maxMode && direction === 1 || + $scope.datepickerMode === self.minMode && direction === -1) { + return; + } + + setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]); + + $scope.$emit('uib:datepicker.mode'); + }; + + // Key event mapper + $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; + + var focusElement = function() { + self.element[0].focus(); + }; + + // Listen for focus requests from popup directive + $scope.$on('uib:datepicker.focus', focusElement); + + $scope.keydown = function(evt) { + var key = $scope.keys[evt.which]; + + if (!key || evt.shiftKey || evt.altKey || $scope.disabled) { + return; + } + + evt.preventDefault(); + if (!self.shortcutPropagation) { + evt.stopPropagation(); + } + + if (key === 'enter' || key === 'space') { + if (self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; + + $scope.$on('$destroy', function() { + //Clear all watch listeners on destroy + while (watchListeners.length) { + watchListeners.shift()(); + } + }); + + function setMode(mode) { + $scope.datepickerMode = mode; + $scope.datepickerOptions.datepickerMode = mode; + } +}]) + +.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + this.step = { months: 1 }; + this.element = $element; + function getDaysInMonth(year, month) { + return month === 1 && year % 4 === 0 && + (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month]; + } + + this.init = function(ctrl) { + angular.extend(ctrl, this); + scope.showWeeks = ctrl.showWeeks; + ctrl.refreshView(); + }; + + this.getDates = function(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0, date; + while (i < n) { + date = new Date(current); + dates[i++] = date; + current.setDate(current.getDate() + 1); + } + return dates; + }; + + this._refreshView = function() { + var year = this.activeDate.getFullYear(), + month = this.activeDate.getMonth(), + firstDayOfMonth = new Date(this.activeDate); + + firstDayOfMonth.setFullYear(year, month, 1); + + var difference = this.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = difference > 0 ? + 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if (numDisplayedFromPreviousMonth > 0) { + firstDate.setDate(-numDisplayedFromPreviousMonth + 1); + } + + // 42 is the number of days on a six-week calendar + var days = this.getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, this.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(this.activeDate, this.formatDayTitle); + scope.rows = this.split(days, 7); + + if (scope.showWeeks) { + scope.weekNumbers = []; + var thursdayIndex = (4 + 7 - this.startingDay) % 7, + numWeeks = scope.rows.length; + for (var curWeek = 0; curWeek < numWeeks; curWeek++) { + scope.weekNumbers.push( + getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); + } + } + }; + + this.compare = function(date1, date2) { + var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); + var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); + _date1.setFullYear(date1.getFullYear()); + _date2.setFullYear(date2.getFullYear()); + return _date1 - _date2; + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; + } else if (key === 'up') { + date = date - 7; + } else if (key === 'right') { + date = date + 1; + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()); + } + this.activeDate.setDate(date); + }; +}]) + +.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + this.step = { years: 1 }; + this.element = $element; + + this.init = function(ctrl) { + angular.extend(ctrl, this); + ctrl.refreshView(); + }; + + this._refreshView = function() { + var months = new Array(12), + year = this.activeDate.getFullYear(), + date; + + for (var i = 0; i < 12; i++) { + date = new Date(this.activeDate); + date.setFullYear(year, i, 1); + months[i] = angular.extend(this.createDateObject(date, this.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(this.activeDate, this.formatMonthTitle); + scope.rows = this.split(months, 3); + }; + + this.compare = function(date1, date2) { + var _date1 = new Date(date1.getFullYear(), date1.getMonth()); + var _date2 = new Date(date2.getFullYear(), date2.getMonth()); + _date1.setFullYear(date1.getFullYear()); + _date2.setFullYear(date2.getFullYear()); + return _date1 - _date2; + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; + } else if (key === 'up') { + date = date - 3; + } else if (key === 'right') { + date = date + 1; + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + this.activeDate.setMonth(date); + }; +}]) + +.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var columns, range; + this.element = $element; + + function getStartingYear(year) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + this.yearpickerInit = function() { + columns = this.yearColumns; + range = this.yearRows * columns; + this.step = { years: range }; + }; + + this._refreshView = function() { + var years = new Array(range), date; + + for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) { + date = new Date(this.activeDate); + date.setFullYear(start + i, 0, 1); + years[i] = angular.extend(this.createDateObject(date, this.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = this.split(years, columns); + scope.columns = columns; + }; + + this.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; + } else if (key === 'up') { + date = date - columns; + } else if (key === 'right') { + date = date + 1; + } else if (key === 'down') { + date = date + columns; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * range; + } else if (key === 'home') { + date = getStartingYear(this.activeDate.getFullYear()); + } else if (key === 'end') { + date = getStartingYear(this.activeDate.getFullYear()) + range - 1; + } + this.activeDate.setFullYear(date); + }; +}]) + +.directive('uibDatepicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/datepicker.html'; + }, + scope: { + datepickerOptions: '=?' + }, + require: ['uibDatepicker', '^ngModel'], + controller: 'UibDatepickerController', + controllerAs: 'datepicker', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + datepickerCtrl.init(ngModelCtrl); + } + }; +}) + +.directive('uibDaypicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/day.html'; + }, + require: ['^uibDatepicker', 'uibDaypicker'], + controller: 'UibDaypickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], + daypickerCtrl = ctrls[1]; + + daypickerCtrl.init(datepickerCtrl); + } + }; +}) + +.directive('uibMonthpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/month.html'; + }, + require: ['^uibDatepicker', 'uibMonthpicker'], + controller: 'UibMonthpickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], + monthpickerCtrl = ctrls[1]; + + monthpickerCtrl.init(datepickerCtrl); + } + }; +}) + +.directive('uibYearpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepicker/year.html'; + }, + require: ['^uibDatepicker', 'uibYearpicker'], + controller: 'UibYearpickerController', + link: function(scope, element, attrs, ctrls) { + var ctrl = ctrls[0]; + angular.extend(ctrl, ctrls[1]); + ctrl.yearpickerInit(); + + ctrl.refreshView(); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) + +.constant('uibDropdownConfig', { + appendToOpenClass: 'uib-dropdown-open', + openClass: 'open' +}) + +.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) { + var openScope = null; + + this.open = function(dropdownScope, element) { + if (!openScope) { + $document.on('click', closeDropdown); + element.on('keydown', keybindFilter); + } + + if (openScope && openScope !== dropdownScope) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function(dropdownScope, element) { + if (openScope === dropdownScope) { + openScope = null; + $document.off('click', closeDropdown); + element.off('keydown', keybindFilter); + } + }; + + var closeDropdown = function(evt) { + // This method may still be called during the same mouse event that + // unbound this event handler. So check openScope before proceeding. + if (!openScope) { return; } + + if (evt && openScope.getAutoClose() === 'disabled') { return; } + + if (evt && evt.which === 3) { return; } + + var toggleElement = openScope.getToggleElement(); + if (evt && toggleElement && toggleElement[0].contains(evt.target)) { + return; + } + + var dropdownElement = openScope.getDropdownElement(); + if (evt && openScope.getAutoClose() === 'outsideClick' && + dropdownElement && dropdownElement[0].contains(evt.target)) { + return; + } + + openScope.isOpen = false; + + if (!$rootScope.$$phase) { + openScope.$apply(); + } + }; + + var keybindFilter = function(evt) { + if (evt.which === 27) { + evt.stopPropagation(); + openScope.focusToggleElement(); + closeDropdown(); + } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + openScope.focusDropdownEntry(evt.which); + } + }; +}]) + +.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) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + appendToOpenClass = dropdownConfig.appendToOpenClass, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + appendToBody = false, + appendTo = null, + keynavEnabled = false, + selectedOption = null, + body = $document.find('body'); + + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + if (angular.isDefined($attrs.dropdownAppendTo)) { + var appendToEl = $parse($attrs.dropdownAppendTo)(scope); + if (appendToEl) { + appendTo = angular.element(appendToEl); + } + } + + appendToBody = angular.isDefined($attrs.dropdownAppendToBody); + keynavEnabled = angular.isDefined($attrs.keyboardNav); + + if (appendToBody && !appendTo) { + appendTo = body; + } + + if (appendTo && self.dropdownMenu) { + appendTo.append(self.dropdownMenu); + $element.on('$destroy', function handleDestroyEvent() { + self.dropdownMenu.remove(); + }); + } + }; + + this.toggle = function(open) { + scope.isOpen = arguments.length ? !!open : !scope.isOpen; + if (angular.isFunction(setIsOpen)) { + setIsOpen(scope, scope.isOpen); + } + + return scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + angular.element(self.dropdownMenu).find('a') : + $element.find('ul').eq(0).find('a'); + + switch (keyCode) { + case 40: { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = self.selectedOption === elems.length - 1 ? + self.selectedOption : + self.selectedOption + 1; + } + break; + } + case 38: { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + + scope.focusToggleElement = function() { + if (self.toggleElement) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function(isOpen, wasOpen) { + if (appendTo && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true), + css, + rightalign, + scrollbarPadding, + scrollbarWidth = 0; + + css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; + + rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + scrollbarPadding = $position.scrollbarPadding(appendTo); + + if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + scrollbarWidth = scrollbarPadding.scrollbarWidth; + } + + css.right = window.innerWidth - scrollbarWidth - + (pos.left + $element.prop('offsetWidth')) + 'px'; + } + + // Need to adjust our positioning to be relative to the appendTo container + // if it's not the body element + if (!appendToBody) { + var appendOffset = $position.offset(appendTo); + + css.top = pos.top - appendOffset.top + 'px'; + + if (!rightalign) { + css.left = pos.left - appendOffset.left + 'px'; + } else { + css.right = window.innerWidth - + (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px'; + } + } + + self.dropdownMenu.css(css); + } + + var openContainer = appendTo ? appendTo : $element; + var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass); + + if (hasOpenClass === !isOpen) { + $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + } + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + }); + }); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope, $element); + } else { + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element(''); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + uibDropdownService.close(scope, $element); + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); + } + }); +}]) + +.directive('uibDropdown', function() { + return { + controller: 'UibDropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init(); + } + }; +}) + +.directive('uibDropdownMenu', function() { + return { + restrict: 'A', + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { + return; + } + + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; +}) + +.directive('uibDropdownToggle', function() { + return { + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if (!element.hasClass('disabled') && !attrs.disabled) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.collapse', []) + + .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) { + var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; + return { + link: function(scope, element, attrs) { + var expandingExpr = $parse(attrs.expanding), + expandedExpr = $parse(attrs.expanded), + collapsingExpr = $parse(attrs.collapsing), + collapsedExpr = $parse(attrs.collapsed); + + if (!scope.$eval(attrs.uibCollapse)) { + element.addClass('in') + .addClass('collapse') + .attr('aria-expanded', true) + .attr('aria-hidden', false) + .css({height: 'auto'}); + } + + function expand() { + if (element.hasClass('collapse') && element.hasClass('in')) { + return; + } + + $q.resolve(expandingExpr(scope)) + .then(function() { + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); + + if ($animateCss) { + $animateCss(element, { + addClass: 'in', + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start()['finally'](expandDone); + } else { + $animate.addClass(element, 'in', { + to: { height: element[0].scrollHeight + 'px' } + }).then(expandDone); + } + }); + } + + function expandDone() { + element.removeClass('collapsing') + .addClass('collapse') + .css({height: 'auto'}); + expandedExpr(scope); + } + + function collapse() { + if (!element.hasClass('collapse') && !element.hasClass('in')) { + return collapseDone(); + } + + $q.resolve(collapsingExpr(scope)) + .then(function() { + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); + + if ($animateCss) { + $animateCss(element, { + removeClass: 'in', + to: {height: '0'} + }).start()['finally'](collapseDone); + } else { + $animate.removeClass(element, 'in', { + to: {height: '0'} + }).then(collapseDone); + } + }); + } + + function collapseDone() { + element.css({height: '0'}); // Required so that collapse works when animation is disabled + element.removeClass('collapsing') + .addClass('collapse'); + collapsedExpr(scope); + } + + scope.$watch(attrs.uibCollapse, function(shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('uibAccordionConfig', { + closeOthers: true +}) + +.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) { + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? + $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if (closeOthers) { + angular.forEach(this.groups, function(group) { + if (group !== openGroup) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function(event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if (index !== -1) { + this.groups.splice(index, 1); + } + }; +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('uibAccordion', function() { + return { + controller: 'UibAccordionController', + controllerAs: 'accordion', + transclude: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/accordion/accordion.html'; + } + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('uibAccordionGroup', function() { + return { + require: '^uibAccordion', // We need this directive to be inside an accordion + transclude: true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/accordion/accordion-group.html'; + }, + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + panelClass: '@?', // Ditto with panelClass + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.openClass = attrs.openClass || 'panel-open'; + scope.panelClass = attrs.panelClass || 'panel-default'; + scope.$watch('isOpen', function(value) { + element.toggleClass(scope.openClass, !!value); + if (value) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function($event) { + if (!scope.isDisabled) { + if (!$event || $event.which === 32) { + scope.isOpen = !scope.isOpen; + } + } + }; + + var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + scope.headingId = id + '-tab'; + scope.panelId = id + '-panel'; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +.directive('uibAccordionHeading', function() { + return { + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^uibAccordionGroup', + link: function(scope, element, attrs, accordionGroupCtrl, transclude) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +.directive('uibAccordionTransclude', function() { + return { + require: '^uibAccordionGroup', + link: function(scope, element, attrs, controller) { + scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) { + if (heading) { + var elem = angular.element(element[0].querySelector(getHeaderSelectors())); + elem.html(''); + elem.append(heading); + } + }); + } + }; + + function getHeaderSelectors() { + return 'uib-accordion-header,' + + 'data-uib-accordion-header,' + + 'x-uib-accordion-header,' + + 'uib\\:accordion-header,' + + '[uib-accordion-header],' + + '[data-uib-accordion-header],' + + '[x-uib-accordion-header]'; + } +}); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, and selector delegatation. + */ +angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip']) + +.directive('uibPopoverTemplatePopup', function() { + return { + replace: true, + scope: { uibTitle: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'uib/template/popover/popover-template.html' + }; +}) + +.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverTemplate', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', uibTitle: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/popover/popover-html.html' + }; +}) + +.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverHtml', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverPopup', function() { + return { + replace: true, + scope: { uibTitle: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'uib/template/popover/popover.html' + }; +}) + +.directive('uibPopover', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopover', 'popover', 'click'); +}]); + +angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position']) + +.value('$datepickerPopupLiteralWarning', true) + +.constant('uibDatepickerPopupConfig', { + altInputFormats: [], + appendToBody: false, + clearText: 'Clear', + closeOnDateSelection: true, + closeText: 'Done', + currentText: 'Today', + datepickerPopup: 'yyyy-MM-dd', + datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html', + datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html', + html5Types: { + date: 'yyyy-MM-dd', + 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', + 'month': 'yyyy-MM' + }, + onOpenFocus: true, + showButtonBar: true, + placement: 'auto bottom-left' +}) + +.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning', +function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) { + var cache = {}, + isHtml5DateInput = false; + var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, + datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, + ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [], + timezone; + + this.init = function(_ngModel_) { + ngModel = _ngModel_; + ngModelOptions = _ngModel_.$options; + closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? + $scope.$parent.$eval($attrs.closeOnDateSelection) : + datepickerPopupConfig.closeOnDateSelection; + appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? + $scope.$parent.$eval($attrs.datepickerAppendToBody) : + datepickerPopupConfig.appendToBody; + onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? + $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; + datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? + $attrs.datepickerPopupTemplateUrl : + datepickerPopupConfig.datepickerPopupTemplateUrl; + datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? + $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; + altInputFormats = angular.isDefined($attrs.altInputFormats) ? + $scope.$parent.$eval($attrs.altInputFormats) : + datepickerPopupConfig.altInputFormats; + + $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? + $scope.$parent.$eval($attrs.showButtonBar) : + datepickerPopupConfig.showButtonBar; + + if (datepickerPopupConfig.html5Types[$attrs.type]) { + dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; + isHtml5DateInput = true; + } else { + dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; + $attrs.$observe('uibDatepickerPopup', function(value, oldValue) { + var newDateFormat = value || datepickerPopupConfig.datepickerPopup; + // Invalidate the $modelValue to ensure that formatters re-run + // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 + if (newDateFormat !== dateFormat) { + dateFormat = newDateFormat; + ngModel.$modelValue = null; + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + } + }); + } + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + + if (isHtml5DateInput && $attrs.uibDatepickerPopup) { + throw new Error('HTML5 date input types do not support custom formats.'); + } + + // popup element used to display calendar + popupEl = angular.element('
'); + if (ngModelOptions) { + timezone = ngModelOptions.timezone; + $scope.ngModelOptions = angular.copy(ngModelOptions); + $scope.ngModelOptions.timezone = null; + if ($scope.ngModelOptions.updateOnDefault === true) { + $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ? + $scope.ngModelOptions.updateOn + ' default' : 'default'; + } + + popupEl.attr('ng-model-options', 'ngModelOptions'); + } else { + timezone = null; + } + + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection(date)', + 'template-url': datepickerPopupTemplateUrl + }); + + // datepicker element + datepickerEl = angular.element(popupEl.children()[0]); + datepickerEl.attr('template-url', datepickerTemplateUrl); + + if (!$scope.datepickerOptions) { + $scope.datepickerOptions = {}; + } + + if (isHtml5DateInput) { + if ($attrs.type === 'month') { + $scope.datepickerOptions.datepickerMode = 'month'; + $scope.datepickerOptions.minMode = 'month'; + } + } + + datepickerEl.attr('datepicker-options', 'datepickerOptions'); + + if (!isHtml5DateInput) { + // Internal API to maintain the correct ng-invalid-[key] class + ngModel.$$parserName = 'date'; + ngModel.$validators.date = validator; + ngModel.$parsers.unshift(parseDate); + ngModel.$formatters.push(function(value) { + if (ngModel.$isEmpty(value)) { + $scope.date = value; + return value; + } + + if (angular.isNumber(value)) { + value = new Date(value); + } + + $scope.date = dateParser.fromTimezone(value, timezone); + + return dateParser.filter($scope.date, dateFormat); + }); + } else { + ngModel.$formatters.push(function(value) { + $scope.date = dateParser.fromTimezone(value, timezone); + return value; + }); + } + + // Detect changes in the view from the text box + ngModel.$viewChangeListeners.push(function() { + $scope.date = parseDateString(ngModel.$viewValue); + }); + + $element.on('keydown', inputKeydownBind); + + $popup = $compile(popupEl)($scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if (appendToBody) { + $document.find('body').append($popup); + } else { + $element.after($popup); + } + + $scope.$on('$destroy', function() { + if ($scope.isOpen === true) { + if (!$rootScope.$$phase) { + $scope.$apply(function() { + $scope.isOpen = false; + }); + } + } + + $popup.remove(); + $element.off('keydown', inputKeydownBind); + $document.off('click', documentClickBind); + if (scrollParentEl) { + scrollParentEl.off('scroll', positionPopup); + } + angular.element($window).off('resize', positionPopup); + + //Clear all watch listeners on destroy + while (watchListeners.length) { + watchListeners.shift()(); + } + }); + }; + + $scope.getText = function(key) { + return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + $scope.isDisabled = function(date) { + if (date === 'today') { + date = dateParser.fromTimezone(new Date(), timezone); + } + + var dates = {}; + angular.forEach(['minDate', 'maxDate'], function(key) { + if (!$scope.datepickerOptions[key]) { + dates[key] = null; + } else if (angular.isDate($scope.datepickerOptions[key])) { + dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone); + } else { + if ($datepickerPopupLiteralWarning) { + $log.warn('Literal date support has been deprecated, please switch to date object usage'); + } + + dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium')); + } + }); + + return $scope.datepickerOptions && + dates.minDate && $scope.compare(date, dates.minDate) < 0 || + dates.maxDate && $scope.compare(date, dates.maxDate) > 0; + }; + + $scope.compare = function(date1, date2) { + return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); + }; + + // Inner change + $scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + $scope.date = dt; + } + var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function + $element.val(date); + ngModel.$setViewValue(date); + + if (closeOnDateSelection) { + $scope.isOpen = false; + $element[0].focus(); + } + }; + + $scope.keydown = function(evt) { + if (evt.which === 27) { + evt.stopPropagation(); + $scope.isOpen = false; + $element[0].focus(); + } + }; + + $scope.select = function(date, evt) { + evt.stopPropagation(); + + if (date === 'today') { + var today = new Date(); + if (angular.isDate($scope.date)) { + date = new Date($scope.date); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + $scope.dateSelection(date); + }; + + $scope.close = function(evt) { + evt.stopPropagation(); + + $scope.isOpen = false; + $element[0].focus(); + }; + + $scope.disabled = angular.isDefined($attrs.disabled) || false; + if ($attrs.ngDisabled) { + watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { + $scope.disabled = disabled; + })); + } + + $scope.$watch('isOpen', function(value) { + if (value) { + if (!$scope.disabled) { + $timeout(function() { + positionPopup(); + + if (onOpenFocus) { + $scope.$broadcast('uib:datepicker.focus'); + } + + $document.on('click', documentClickBind); + + var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; + if (appendToBody || $position.parsePlacement(placement)[2]) { + scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); + if (scrollParentEl) { + scrollParentEl.on('scroll', positionPopup); + } + } else { + scrollParentEl = null; + } + + angular.element($window).on('resize', positionPopup); + }, 0, false); + } else { + $scope.isOpen = false; + } + } else { + $document.off('click', documentClickBind); + if (scrollParentEl) { + scrollParentEl.off('scroll', positionPopup); + } + angular.element($window).off('resize', positionPopup); + } + }); + + function cameltoDash(string) { + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + function parseDateString(viewValue) { + var date = dateParser.parse(viewValue, dateFormat, $scope.date); + if (isNaN(date)) { + for (var i = 0; i < altInputFormats.length; i++) { + date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); + if (!isNaN(date)) { + return date; + } + } + } + return date; + } + + function parseDate(viewValue) { + if (angular.isNumber(viewValue)) { + // presumably timestamp to date object + viewValue = new Date(viewValue); + } + + if (!viewValue) { + return null; + } + + if (angular.isDate(viewValue) && !isNaN(viewValue)) { + return viewValue; + } + + if (angular.isString(viewValue)) { + var date = parseDateString(viewValue); + if (!isNaN(date)) { + return dateParser.toTimezone(date, timezone); + } + } + + return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined; + } + + function validator(modelValue, viewValue) { + var value = modelValue || viewValue; + + if (!$attrs.ngRequired && !value) { + return true; + } + + if (angular.isNumber(value)) { + value = new Date(value); + } + + if (!value) { + return true; + } + + if (angular.isDate(value) && !isNaN(value)) { + return true; + } + + if (angular.isString(value)) { + return !isNaN(parseDateString(viewValue)); + } + + return false; + } + + function documentClickBind(event) { + if (!$scope.isOpen && $scope.disabled) { + return; + } + + var popup = $popup[0]; + var dpContainsTarget = $element[0].contains(event.target); + // The popup node may not be an element node + // In some browsers (IE) only element nodes have the 'contains' function + var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); + if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { + $scope.$apply(function() { + $scope.isOpen = false; + }); + } + } + + function inputKeydownBind(evt) { + if (evt.which === 27 && $scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.$apply(function() { + $scope.isOpen = false; + }); + $element[0].focus(); + } else if (evt.which === 40 && !$scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.$apply(function() { + $scope.isOpen = true; + }); + } + } + + function positionPopup() { + if ($scope.isOpen) { + var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); + var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; + var position = $position.positionElements($element, dpElement, placement, appendToBody); + dpElement.css({top: position.top + 'px', left: position.left + 'px'}); + if (dpElement.hasClass('uib-position-measure')) { + dpElement.removeClass('uib-position-measure'); + } + } + } + + $scope.$on('uib:datepicker.mode', function() { + $timeout(positionPopup, 0, false); + }); +}]) + +.directive('uibDatepickerPopup', function() { + return { + require: ['ngModel', 'uibDatepickerPopup'], + controller: 'UibDatepickerPopupController', + scope: { + datepickerOptions: '=?', + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@' + }, + link: function(scope, element, attrs, ctrls) { + var ngModel = ctrls[0], + ctrl = ctrls[1]; + + ctrl.init(ngModel); + } + }; +}) + +.directive('uibDatepickerPopupWrap', function() { + return { + replace: true, + transclude: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html'; + } + }; +}); + +angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/modal/backdrop.html", + "
\n" + + ""); +}]); + +angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/modal/window.html", + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/tabs/tab.html", + "
  • \n" + + " {{heading}}\n" + + "
  • \n" + + ""); +}]); + +angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/tabs/tabset.html", + "
    \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/tooltip/tooltip-html-popup.html", + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/tooltip/tooltip-popup.html", + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/tooltip/tooltip-template-popup.html", + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/timepicker/timepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
        
      \n" + + " \n" + + " :\n" + + " \n" + + " :\n" + + " \n" + + "
        
      \n" + + ""); +}]); + +angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/datepicker/datepicker.html", + "
      \n" + + " \n" + + " \n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/datepicker/day.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      {{::label.abbr}}
      {{ weekNumbers[$index] }}\n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/datepicker/month.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      \n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/datepicker/popup.html", + "
        \n" + + "
      • \n" + + "
      • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      • \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/datepicker/year.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      \n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/accordion/accordion-group.html", + "
      \n" + + "
      \n" + + "

      \n" + + " {{heading}}\n" + + "

      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/accordion/accordion.html", + "
      "); +}]); + +angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/popover/popover-html.html", + "
      \n" + + "
      \n" + + "\n" + + "
      \n" + + "

      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/popover/popover-template.html", + "
      \n" + + "
      \n" + + "\n" + + "
      \n" + + "

      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/popover/popover.html", + "
      \n" + + "
      \n" + + "\n" + + "
      \n" + + "

      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("uib/template/datepickerPopup/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("uib/template/datepickerPopup/popup.html", + "
      \n" + + "
        \n" + + "
      • \n" + + "
      • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      • \n" + + "
      \n" + + "
      \n" + + ""); +}]); +angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend(''); angular.$$uibPositionCss = true; }); +angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend(''); angular.$$uibTooltipCss = true; }); +angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibTimepickerCss = true; }); +angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibDatepickerCss = true; }); +angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend(''); angular.$$uibDatepickerpopupCss = true; }); \ No newline at end of file diff --git a/dist/ui-bootstrap-custom-tpls-1.3.3.min.js b/dist/ui-bootstrap-custom-tpls-1.3.3.min.js new file mode 100644 index 0000000..9ecc8c9 --- /dev/null +++ b/dist/ui-bootstrap-custom-tpls-1.3.3.min.js @@ -0,0 +1,9 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 1.3.3 - 2017-07-07 + * License: MIT + */angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.stackedMap","ui.bootstrap.position","ui.bootstrap.modal","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.timepicker","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.popover","ui.bootstrap.datepickerPopup"]),angular.module("ui.bootstrap.tpls",["uib/template/modal/backdrop.html","uib/template/modal/window.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/timepicker/timepicker.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/datepickerPopup/popup.html"]),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c');a.find("body").append(g),c=g[0].offsetWidth-g[0].clientWidth,c=isFinite(c)?c:0,g.remove()}return c},scrollbarPadding:function(a){a=this.getRawNode(a);var c=b.getComputedStyle(a),d=this.parseStyle(c.paddingRight),e=this.parseStyle(c.paddingBottom),f=this.scrollParent(a,!1,!0),h=this.scrollbarWidth(f,g.test(f.tagName));return{scrollbarWidth:h,widthOverflow:f.scrollWidth>f.clientWidth,right:d+h,originalRight:d,heightOverflow:f.scrollHeight>f.clientHeight,bottom:e+h,originalBottom:e}},isScrollable:function(a,c){a=this.getRawNode(a);var d=c?e.hidden:e.normal,f=b.getComputedStyle(a);return d.test(f.overflow+f.overflowY+f.overflowX)},scrollParent:function(c,d,f){c=this.getRawNode(c);var g=d?e.hidden:e.normal,h=a[0].documentElement,i=b.getComputedStyle(c);if(f&&g.test(i.overflow+i.overflowY+i.overflowX))return c;var j="absolute"===i.position,k=c.parentElement||h;if(k===h||"fixed"===i.position)return h;for(;k.parentElement&&k!==h;){var l=b.getComputedStyle(k);if(j&&"static"!==l.position&&(j=!1),!j&&g.test(l.overflow+l.overflowY+l.overflowX))break;k=k.parentElement}return k},position:function(c,d){c=this.getRawNode(c);var e=this.offset(c);if(d){var f=b.getComputedStyle(c);e.top-=this.parseStyle(f.marginTop),e.left-=this.parseStyle(f.marginLeft)}var g=this.offsetParent(c),h={top:0,left:0};return g!==a[0].documentElement&&(h=this.offset(g),h.top+=g.clientTop-g.scrollTop,h.left+=g.clientLeft-g.scrollLeft),{width:Math.round(angular.isNumber(e.width)?e.width:c.offsetWidth),height:Math.round(angular.isNumber(e.height)?e.height:c.offsetHeight),top:Math.round(e.top-h.top),left:Math.round(e.left-h.left)}},offset:function(c){c=this.getRawNode(c);var d=c.getBoundingClientRect();return{width:Math.round(angular.isNumber(d.width)?d.width:c.offsetWidth),height:Math.round(angular.isNumber(d.height)?d.height:c.offsetHeight),top:Math.round(d.top+(b.pageYOffset||a[0].documentElement.scrollTop)),left:Math.round(d.left+(b.pageXOffset||a[0].documentElement.scrollLeft))}},viewportOffset:function(c,d,e){c=this.getRawNode(c),e=e!==!1;var f=c.getBoundingClientRect(),g={top:0,left:0,bottom:0,right:0},h=d?a[0].documentElement:this.scrollParent(c),i=h.getBoundingClientRect();if(g.top=i.top+h.clientTop,g.left=i.left+h.clientLeft,h===a[0].documentElement&&(g.top+=b.pageYOffset,g.left+=b.pageXOffset),g.bottom=g.top+h.clientHeight,g.right=g.left+h.clientWidth,e){var j=b.getComputedStyle(h);g.top+=this.parseStyle(j.paddingTop),g.bottom-=this.parseStyle(j.paddingBottom),g.left+=this.parseStyle(j.paddingLeft),g.right-=this.parseStyle(j.paddingRight)}return{top:Math.round(f.top-g.top),bottom:Math.round(g.bottom-f.bottom),left:Math.round(f.left-g.left),right:Math.round(g.right-f.right)}},parsePlacement:function(a){var b=f.auto.test(a);return b&&(a=a.replace(f.auto,"")),a=a.split("-"),a[0]=a[0]||"top",f.primary.test(a[0])||(a[0]="top"),a[1]=a[1]||"center",f.secondary.test(a[1])||(a[1]="center"),b?a[2]=!0:a[2]=!1,a},positionElements:function(a,c,d,e){a=this.getRawNode(a),c=this.getRawNode(c);var g=angular.isDefined(c.offsetWidth)?c.offsetWidth:c.prop("offsetWidth"),h=angular.isDefined(c.offsetHeight)?c.offsetHeight:c.prop("offsetHeight");d=this.parsePlacement(d);var i=e?this.offset(a):this.position(a),j={top:0,left:0,placement:""};if(d[2]){var k=this.viewportOffset(a,e),l=b.getComputedStyle(c),m={width:g+Math.round(Math.abs(this.parseStyle(l.marginLeft)+this.parseStyle(l.marginRight))),height:h+Math.round(Math.abs(this.parseStyle(l.marginTop)+this.parseStyle(l.marginBottom)))};if(d[0]="top"===d[0]&&m.height>k.top&&m.height<=k.bottom?"bottom":"bottom"===d[0]&&m.height>k.bottom&&m.height<=k.top?"top":"left"===d[0]&&m.width>k.left&&m.width<=k.right?"right":"right"===d[0]&&m.width>k.right&&m.width<=k.left?"left":d[0],d[1]="top"===d[1]&&m.height-i.height>k.bottom&&m.height-i.height<=k.top?"bottom":"bottom"===d[1]&&m.height-i.height>k.top&&m.height-i.height<=k.bottom?"top":"left"===d[1]&&m.width-i.width>k.right&&m.width-i.width<=k.left?"right":"right"===d[1]&&m.width-i.width>k.left&&m.width-i.width<=k.right?"left":d[1],"center"===d[1])if(f.vertical.test(d[0])){var n=i.width/2-g/2;k.left+n<0&&m.width-i.width<=k.right?d[1]="left":k.right+n<0&&m.width-i.width<=k.left&&(d[1]="right")}else{var o=i.height/2-m.height/2;k.top+o<0&&m.height-i.height<=k.bottom?d[1]="top":k.bottom+o<0&&m.height-i.height<=k.top&&(d[1]="bottom")}}switch(d[0]){case"top":j.top=i.top-h;break;case"bottom":j.top=i.top+i.height;break;case"left":j.left=i.left-g;break;case"right":j.left=i.left+i.width}switch(d[1]){case"top":j.top=i.top;break;case"bottom":j.top=i.top+i.height-h;break;case"left":j.left=i.left;break;case"right":j.left=i.left+i.width-g;break;case"center":f.vertical.test(d[0])?j.left=i.left+i.width/2-g/2:j.top=i.top+i.height/2-h/2}return j.top=Math.round(j.top),j.left=Math.round(j.left),j.placement="center"===d[1]?d[0]:d[0]+"-"+d[1],j},positionArrow:function(a,c){a=this.getRawNode(a);var d=a.querySelector(".tooltip-inner, .popover-inner");if(d){var e=angular.element(d).hasClass("tooltip-inner"),g=e?a.querySelector(".tooltip-arrow"):a.querySelector(".arrow");if(g){var h={top:"",bottom:"",left:"",right:""};if(c=this.parsePlacement(c),"center"===c[1])return void angular.element(g).css(h);var i="border-"+c[0]+"-width",j=b.getComputedStyle(g)[i],k="border-";k+=f.vertical.test(c[0])?c[0]+"-"+c[1]:c[1]+"-"+c[0],k+="-radius";var l=b.getComputedStyle(e?d:a)[k];switch(c[0]){case"top":h.bottom=e?"0":"-"+j;break;case"bottom":h.top=e?"0":"-"+j;break;case"left":h.right=e?"0":"-"+j;break;case"right":h.left=e?"0":"-"+j}h[c[1]]=l,angular.element(g).css(h)}}}}}]),angular.module("ui.bootstrap.modal",["ui.bootstrap.stackedMap","ui.bootstrap.position"]).factory("$$multiMap",function(){return{createNew:function(){var a={};return{entries:function(){return Object.keys(a).map(function(b){return{key:b,value:a[b]}})},get:function(b){return a[b]},hasKey:function(b){return!!a[b]},keys:function(){return Object.keys(a)},put:function(b,c){a[b]||(a[b]=[]),a[b].push(c)},remove:function(b,c){var d=a[b];if(d){var e=d.indexOf(c);e!==-1&&d.splice(e,1),d.length||delete a[b]}}}}}}).provider("$uibResolve",function(){var a=this;this.resolver=null,this.setResolver=function(a){this.resolver=a},this.$get=["$injector","$q",function(b,c){var d=a.resolver?b.get(a.resolver):null;return{resolve:function(a,e,f,g){if(d)return d.resolve(a,e,f,g);var h=[];return angular.forEach(a,function(a){angular.isFunction(a)||angular.isArray(a)?h.push(c.resolve(b.invoke(a))):angular.isString(a)?h.push(c.resolve(b.get(a))):h.push(c.resolve(a))}),c.all(h).then(function(b){var c={},d=0;return angular.forEach(a,function(a,e){c[e]=b[d++]}),c})}}}]}).directive("uibModalBackdrop",["$animate","$injector","$uibModalStack",function(a,b,c){function d(b,d,e){e.modalInClass&&(a.addClass(d,e.modalInClass),b.$on(c.NOW_CLOSING_EVENT,function(c,f){var g=f();b.modalOptions.animation?a.removeClass(d,e.modalInClass).then(g):g()}))}return{replace:!0,templateUrl:"uib/template/modal/backdrop.html",compile:function(a,b){return a.addClass(b.backdropClass),d}}}]).directive("uibModalWindow",["$uibModalStack","$q","$animateCss","$document",function(a,b,c,d){return{scope:{index:"@"},replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/modal/window.html"},link:function(e,f,g){f.addClass(g.windowClass||""),f.addClass(g.windowTopClass||""),e.size=g.size,e.close=function(b){var c=a.getTop();c&&c.value.backdrop&&"static"!==c.value.backdrop&&b.target===b.currentTarget&&(b.preventDefault(),b.stopPropagation(),a.dismiss(c.key,"backdrop click"))},f.on("click",e.close),e.$isRendered=!0;var h=b.defer();g.$observe("modalRender",function(a){"true"===a&&h.resolve()}),h.promise.then(function(){var h=null;g.modalInClass&&(h=c(f,{addClass:g.modalInClass}).start(),e.$on(a.NOW_CLOSING_EVENT,function(a,b){var d=b();c(f,{removeClass:g.modalInClass}).start().then(d)})),b.when(h).then(function(){var b=a.getTop();if(b&&a.modalRendered(b.key),!d[0].activeElement||!f[0].contains(d[0].activeElement)){var c=f[0].querySelector("[autofocus]");c?c.focus():f[0].focus()}})})}}}]).directive("uibModalAnimationClass",function(){return{compile:function(a,b){b.modalAnimation&&a.addClass(b.uibModalAnimationClass)}}}).directive("uibModalTransclude",function(){return{link:function(a,b,c,d,e){e(a.$parent,function(a){b.empty(),b.append(a)})}}}).factory("$uibModalStack",["$animate","$animateCss","$document","$compile","$rootScope","$q","$$multiMap","$$stackedMap","$uibPosition",function(a,b,c,d,e,f,g,h,i){function j(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)}function k(){for(var a=-1,b=v.keys(),c=0;c-1&&a0&&(b=v.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function n(){if(r&&k()===-1){var a=s;o(r,s,function(){a=null}),r=void 0,s=void 0}}function o(b,c,d,e){function g(){g.done||(g.done=!0,a.leave(b).then(function(){b.remove(),e&&e.resolve()}),c.$destroy(),d&&d())}var h,i=null,j=function(){return h||(h=f.defer(),i=h.promise),function(){h.resolve()}};return c.$broadcast(x.NOW_CLOSING_EVENT,j),f.when(i).then(g)}function p(a){if(a.isDefaultPrevented())return a;var b=v.top();if(b)switch(a.which){case 27:b.value.keyboard&&(a.preventDefault(),e.$apply(function(){x.dismiss(b.key,"escape key press")}));break;case 9:var c=x.loadFocusElementList(b),d=!1;a.shiftKey?(x.isFocusInFirstItem(a,c)||x.isModalFocused(a,b))&&(d=x.focusLastFocusableElement(c)):x.isFocusInLastItem(a,c)&&(d=x.focusFirstFocusableElement(c)),d&&(a.preventDefault(),a.stopPropagation())}}function q(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}var r,s,t,u="modal-open",v=h.createNew(),w=g.createNew(),x={NOW_CLOSING_EVENT:"modal.stack.now-closing"},y=0,z=null,A="a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable=true]";return e.$watch(k,function(a){s&&(s.index=a)}),c.on("keydown",p),e.$on("$destroy",function(){c.off("keydown",p)}),x.open=function(b,f){var g=c[0].activeElement,h=f.openedClass||u;m(!1),z=v.top(),v.add(b,{deferred:f.deferred,renderDeferred:f.renderDeferred,closedDeferred:f.closedDeferred,modalScope:f.scope,backdrop:f.backdrop,keyboard:f.keyboard,openedClass:f.openedClass,windowTopClass:f.windowTopClass,animation:f.animation,appendTo:f.appendTo}),w.put(h,b);var j=f.appendTo,l=k();if(!j.length)throw new Error("appendTo element not found. Make sure that the element passed is in DOM.");l>=0&&!r&&(s=e.$new(!0),s.modalOptions=f,s.index=l,r=angular.element('
      '),r.attr("backdrop-class",f.backdropClass),f.animation&&r.attr("modal-animation","true"),d(r)(s),a.enter(r,j),t=i.scrollbarPadding(j),t.heightOverflow&&t.scrollbarWidth&&j.css({paddingRight:t.right+"px"})),y=z?parseInt(z.value.modalDomEl.attr("index"),10)+1:0;var n=angular.element('
      ');n.attr({"template-url":f.windowTemplateUrl,"window-class":f.windowClass,"window-top-class":f.windowTopClass,size:f.size,index:y,animate:"animate"}).html(f.content),f.animation&&n.attr("modal-animation","true"),j.addClass(h),a.enter(d(n)(f.scope),j),v.top().value.modalDomEl=n,v.top().value.modalOpener=g},x.close=function(a,b){var c=v.get(a);return c&&q(c,b,!0)?(a.callCloseFn(b),c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),l(a,c.value.modalOpener),!0):!c},x.dismiss=function(a,b){var c=v.get(a);return c&&q(c,b,!1)?(a.callDismissFn(b),c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),l(a,c.value.modalOpener),!0):!c},x.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},x.getTop=function(){return v.top()},x.modalRendered=function(a){var b=v.get(a);b&&b.value.renderDeferred.resolve()},x.focusFirstFocusableElement=function(a){return a.length>0&&(a[0].focus(),!0)},x.focusLastFocusableElement=function(a){return a.length>0&&(a[a.length-1].focus(),!0)},x.isModalFocused=function(a,b){if(a&&b){var c=b.value.modalDomEl;if(c&&c.length)return(a.target||a.srcElement)===c[0]}return!1},x.isFocusInFirstItem=function(a,b){return b.length>0&&(a.target||a.srcElement)===b[0]},x.isFocusInLastItem=function(a,b){return b.length>0&&(a.target||a.srcElement)===b[b.length-1]},x.loadFocusElementList=function(a){if(a){var b=a.value.modalDomEl;if(b&&b.length){var c=b[0].querySelectorAll(A);return c?Array.prototype.filter.call(c,function(a){return j(a)}):c}}},x.length=function(){return v.length},x}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0,modalOpenFn:function(){},modalCloseFn:function(){},modalDismissFn:function(){}},$get:["$rootScope","$q","$document","$templateRequest","$controller","$uibResolve","$uibModalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?c.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}var j={},k=null;return j.getPromiseChain=function(){return k},j.setCallbacks=function(b,c,d){angular.extend(a.options,{modalOpenFn:b,modalCloseFn:c,modalDismissFn:d})},j.open=function(e){function j(){return r}var l=c.defer(),m=c.defer(),n=c.defer(),o=c.defer(),p={result:l.promise,opened:m.promise,closed:n.promise,rendered:o.promise,close:function(a){return h.close(p,a)},dismiss:function(a){return h.dismiss(p,a)},callDismissFn:function(a){return e.modalDismissFn(e,a),!0},callCloseFn:function(a){return e.modalCloseFn(e,a),!0}};if(e=angular.extend({},a.options,e),e.resolve=e.resolve||{},e.appendTo=e.appendTo||d.find("body").eq(0),!e.template&&!e.templateUrl)throw new Error("One of template or templateUrl options is required.");var q,r=c.all([i(e),g.resolve(e.resolve,{},null,null)]);return q=k=c.all([k]).then(j,j).then(function(a){var c=e.scope||b,d=c.$new();d.$close=p.close,d.$dismiss=p.dismiss,d.$on("$destroy",function(){d.$$uibDestructionScheduled||d.$dismiss("$uibUnscheduledDestruction")});var g,i,j={};e.controller&&(j.$scope=d,j.$scope.$resolve={},j.$uibModalInstance=p,angular.forEach(a[1],function(a,b){j[b]=a,j.$scope.$resolve[b]=a}),i=f(e.controller,j,!0,e.controllerAs),e.controllerAs&&e.bindToController&&(g=i.instance,g.$close=d.$close,g.$dismiss=d.$dismiss,angular.extend(g,{$resolve:j.$scope.$resolve},c)),g=i(),angular.isFunction(g.$onInit)&&g.$onInit()),h.open(p,{scope:d,deferred:l,renderDeferred:o,closedDeferred:n,content:a[0],animation:e.animation,backdrop:e.backdrop,keyboard:e.keyboard,backdropClass:e.backdropClass,windowTopClass:e.windowTopClass,windowClass:e.windowClass,windowTemplateUrl:e.windowTemplateUrl,size:e.size,openedClass:e.openedClass,appendTo:e.appendTo}),e.modalOpenFn(e),m.resolve(!0)},function(a){m.reject(a),l.reject(a)})["finally"](function(){k===q&&(k=null)}),p},j}]};return a}),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){function b(a){for(var b=0;bb.index?1:a.index';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){N.isOpen?q():m()}function m(){M&&!a.$eval(d[k+"Enable"])||(u(),x(),N.popupDelay?G||(G=g(r,N.popupDelay,!1)):r())}function q(){s(),N.popupCloseDelay?H||(H=g(t,N.popupCloseDelay,!1)):t()}function r(){return s(),u(),N.content?(v(),void N.$evalAsync(function(){N.isOpen=!0,y(!0),S()})):angular.noop}function s(){G&&(g.cancel(G),G=null),I&&(g.cancel(I),I=null)}function t(){N&&N.$evalAsync(function(){N&&(N.isOpen=!1,y(!1),N.animation?F||(F=g(w,150,!1)):w())})}function u(){H&&(g.cancel(H),H=null),F&&(g.cancel(F),F=null)}function v(){D||(E=N.$new(),D=c(E,function(a){K?h.find("body").append(a):b.after(a)}),z())}function w(){s(),u(),A(),D&&(D.remove(),D=null),E&&(E.$destroy(),E=null)}function x(){N.title=d[k+"Title"],Q?N.content=Q(a):N.content=d[e],N.popupClass=d[k+"Class"],N.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:n.placement;var b=i.parsePlacement(N.placement);J=b[1]?b[0]+"-"+b[1]:b[0];var c=parseInt(d[k+"PopupDelay"],10),f=parseInt(d[k+"PopupCloseDelay"],10);N.popupDelay=isNaN(c)?n.popupDelay:c,N.popupCloseDelay=isNaN(f)?n.popupCloseDelay:f}function y(b){P&&angular.isFunction(P.assign)&&P.assign(a,b)}function z(){R.length=0,Q?(R.push(a.$watch(Q,function(a){N.content=a,!a&&N.isOpen&&t()})),R.push(E.$watch(function(){O||(O=!0,E.$$postDigest(function(){O=!1,N&&N.isOpen&&S()}))}))):R.push(d.$observe(e,function(a){N.content=a,!a&&N.isOpen?t():S()})),R.push(d.$observe(k+"Title",function(a){N.title=a,N.isOpen&&S()})),R.push(d.$observe(k+"Placement",function(a){N.placement=a?a:n.placement,N.isOpen&&S()}))}function A(){R.length&&(angular.forEach(R,function(a){a()}),R.length=0)}function B(a){N&&N.isOpen&&D&&(b[0].contains(a.target)||D[0].contains(a.target)||q())}function C(){var a=d[k+"Trigger"];T(),L=p(a),"none"!==L.show&&L.show.forEach(function(a,c){"outsideClick"===a?(b.on("click",j),h.on("click",B)):a===L.hide[c]?b.on(a,j):a&&(b.on(a,m),b.on(L.hide[c],q)),b.on("keypress",function(a){27===a.which&&q()})})}var D,E,F,G,H,I,J,K=!!angular.isDefined(n.appendToBody)&&n.appendToBody,L=p(void 0),M=angular.isDefined(d[k+"Enable"]),N=a.$new(!0),O=!1,P=!!angular.isDefined(d[k+"IsOpen"])&&l(d[k+"IsOpen"]),Q=!!n.useContentExp&&l(d[e]),R=[],S=function(){D&&D.html()&&(I||(I=g(function(){var a=i.positionElements(b,D,N.placement,K);D.css({top:a.top+"px",left:a.left+"px"}),D.hasClass(a.placement.split("-")[0])||(D.removeClass(J.split("-")[0]),D.addClass(a.placement.split("-")[0])),D.hasClass(n.placementClassPrefix+a.placement)||(D.removeClass(n.placementClassPrefix+J),D.addClass(n.placementClassPrefix+a.placement)),D.hasClass("uib-position-measure")?(i.positionArrow(D,a.placement),D.removeClass("uib-position-measure")):J!==a.placement&&i.positionArrow(D,a.placement),J=a.placement,I=null},0,!1)))};N.origScope=a,N.isOpen=!1,o.add(N,{close:t}),N.contentExp=function(){return N.content},d.$observe("disabled",function(a){a&&s(),a&&N.isOpen&&t()}),P&&a.$watch(P,function(a){N&&!a===N.isOpen&&j()});var T=function(){L.show.forEach(function(a){"outsideClick"===a?b.off("click",j):(b.off(a,m),b.off(a,j))}),L.hide.forEach(function(a){"outsideClick"===a?h.off("click",B):b.off(a,q)})};C();var U=a.$eval(d[k+"Animation"]);N.animation=angular.isDefined(U)?!!U:n.animation;var V,W=k+"AppendToBody";V=W in d&&void 0===d[W]||a.$eval(d[W]),K=angular.isDefined(V)?V:K,a.$on("$destroy",function(){T(),w(),o.remove(N),N=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",["$uibPosition",function(a){return{restrict:"A",link:function(b,c,d){if(b.placement){var e=a.parsePlacement(b.placement);c.addClass(e[0])}b.popupClass&&c.addClass(b.popupClass),b.animation()&&c.addClass(d.tooltipAnimationClass)}}}]).directive("uibTooltipPopup",function(){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/tooltip/tooltip-popup.html"}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"uib/template/tooltip/tooltip-template-popup.html"}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/tooltip/tooltip-html-popup.html"}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.timepicker",[]).constant("uibTimepickerConfig",{hourStep:1,minuteStep:1,secondStep:1,showMeridian:!0,showSeconds:!1,meridians:null,readonlyInput:!1,mousewheel:!0,arrowkeys:!0,showSpinners:!0,templateUrl:"uib/template/timepicker/timepicker.html"}).controller("UibTimepickerController",["$scope","$element","$attrs","$parse","$log","$locale","uibTimepickerConfig",function(a,b,c,d,e,f,g){function h(){var b=+a.hours,c=a.showMeridian?b>0&&b<13:b>=0&&b<24;if(c&&""!==a.hours)return a.showMeridian&&(12===b&&(b=0),a.meridian===v[1]&&(b+=12)),b}function i(){var b=+a.minutes,c=b>=0&&b<60;if(c&&""!==a.minutes)return b}function j(){var b=+a.seconds;return b>=0&&b<60?b:void 0}function k(a,b){return null===a?"":angular.isDefined(a)&&a.toString().length<2&&!b?"0"+a:a.toString()}function l(a){m(),u.$setViewValue(new Date(s)),n(a)}function m(){u.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1,a.invalidSeconds=!1}function n(b){if(u.$modelValue){var c=s.getHours(),d=s.getMinutes(),e=s.getSeconds();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:k(c,!w),"m"!==b&&(a.minutes=k(d)),a.meridian=s.getHours()<12?v[0]:v[1],"s"!==b&&(a.seconds=k(e)),a.meridian=s.getHours()<12?v[0]:v[1]}else a.hours=null,a.minutes=null,a.seconds=null,a.meridian=v[0]}function o(a){s=q(s,a),l()}function p(a,b){return q(a,60*b)}function q(a,b){var c=new Date(a.getTime()+1e3*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes(),c.getSeconds()),d}function r(){return(null===a.hours||""===a.hours)&&(null===a.minutes||""===a.minutes)&&(!a.showSeconds||a.showSeconds&&(null===a.seconds||""===a.seconds))}var s=new Date,t=[],u={$setViewValue:angular.noop},v=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS,w=!angular.isDefined(c.padHours)||a.$parent.$eval(c.padHours);a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){u=b,u.$render=this.render,u.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=d.eq(2),i=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;i&&this.setupMousewheelEvents(e,f,h);var j=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;j&&this.setupArrowkeyEvents(e,f,h),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f,h)};var x=g.hourStep;c.hourStep&&t.push(a.$parent.$watch(d(c.hourStep),function(a){x=+a}));var y=g.minuteStep;c.minuteStep&&t.push(a.$parent.$watch(d(c.minuteStep),function(a){y=+a}));var z;t.push(a.$parent.$watch(d(c.min),function(a){var b=new Date(a);z=isNaN(b)?void 0:b}));var A;t.push(a.$parent.$watch(d(c.max),function(a){var b=new Date(a);A=isNaN(b)?void 0:b}));var B=!1;c.ngDisabled&&t.push(a.$parent.$watch(d(c.ngDisabled),function(a){B=a})),a.noIncrementHours=function(){var a=p(s,60*x);return B||a>A||as&&a>A},a.noIncrementMinutes=function(){var a=p(s,y);return B||a>A||as&&a>A},a.noIncrementSeconds=function(){var a=q(s,C);return B||a>A||as&&a>A},a.noToggleMeridian=function(){return s.getHours()<12?B||p(s,720)>A:B||p(s,-720)0};b.bind("mousewheel wheel",function(b){B||a.$apply(e(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){B||a.$apply(e(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()}),d.bind("mousewheel wheel",function(b){B||a.$apply(e(b)?a.incrementSeconds():a.decrementSeconds()), +b.preventDefault()})},this.setupArrowkeyEvents=function(b,c,d){b.bind("keydown",function(b){B||(38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply()))}),c.bind("keydown",function(b){B||(38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply()))}),d.bind("keydown",function(b){B||(38===b.which?(b.preventDefault(),a.incrementSeconds(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementSeconds(),a.$apply()))})},this.setupInputEvents=function(b,c,d){if(a.readonlyInput)return a.updateHours=angular.noop,a.updateMinutes=angular.noop,void(a.updateSeconds=angular.noop);var e=function(b,c,d){u.$setViewValue(null),u.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c),angular.isDefined(d)&&(a.invalidSeconds=d)};a.updateHours=function(){var a=h(),b=i();u.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(s.setHours(a),s.setMinutes(b),sA?e(!0):l("h")):e(!0)},b.bind("blur",function(b){u.$setTouched(),r()?m():null===a.hours||""===a.hours?e(!0):!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=k(a.hours,!w)})}),a.updateMinutes=function(){var a=i(),b=h();u.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(s.setHours(b),s.setMinutes(a),sA?e(void 0,!0):l("m")):e(void 0,!0)},c.bind("blur",function(b){u.$setTouched(),r()?m():null===a.minutes?e(void 0,!0):!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=k(a.minutes)})}),a.updateSeconds=function(){var a=j();u.$setDirty(),angular.isDefined(a)?(s.setSeconds(a),l("s")):e(void 0,void 0,!0)},d.bind("blur",function(b){r()?m():!a.invalidSeconds&&a.seconds<10&&a.$apply(function(){a.seconds=k(a.seconds)})})},this.render=function(){var b=u.$viewValue;isNaN(b)?(u.$setValidity("time",!1),e.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.')):(b&&(s=b),sA?(u.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):m(),n())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*x*60)},a.decrementHours=function(){a.noDecrementHours()||o(60*-x*60)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(60*y)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(60*-y)},a.incrementSeconds=function(){a.noIncrementSeconds()||o(C)},a.decrementSeconds=function(){a.noDecrementSeconds()||o(-C)},a.toggleMeridian=function(){var b=i(),c=h();a.noToggleMeridian()||(angular.isDefined(b)&&angular.isDefined(c)?o(720*(s.getHours()<12?60:-60)):a.meridian=a.meridian===v[0]?v[1]:v[0])},a.blur=function(){u.$setTouched()},a.$on("$destroy",function(){for(;t.length;)t.shift()()})}]).directive("uibTimepicker",["uibTimepickerConfig",function(a){return{require:["uibTimepicker","?^ngModel"],controller:"UibTimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(b,c){return c.templateUrl||a.templateUrl},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}]),angular.module("ui.bootstrap.dateparser",[]).service("uibDateParser",["$log","$locale","dateFilter","orderByFilter",function(a,b,c,d){function e(a,b){var c=[],e=a.split(""),f=a.indexOf("'");if(f>-1){var g=!1;a=a.split("");for(var h=f;h-1){a=a.split(""),e[f]="("+d.regex+")",a[f]="$";for(var g=f+1,h=f+d.key.length;g28?29===c&&(a%4===0&&a%100!==0||a%400===0):3!==b&&5!==b&&8!==b&&10!==b||c<31)}function g(a){return parseInt(a,10)}function h(a,b){return a&&b?l(a,b):a}function i(a,b){return a&&b?l(a,b,!0):a}function j(a,b){a=a.replace(/:/g,"");var c=Date.parse("Jan 01, 1970 00:00:00 "+a)/6e4;return isNaN(c)?b:c}function k(a,b){return a=new Date(a.getTime()),a.setMinutes(a.getMinutes()+b),a}function l(a,b,c){c=c?-1:1;var d=a.getTimezoneOffset(),e=j(b,d);return k(a,c*(e-d))}var m,n,o=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){m=b.id,this.parsers={},this.formatters={},n=[{key:"yyyy",regex:"\\d{4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yyyy")}},{key:"yy",regex:"\\d{2}",apply:function(a){a=+a,this.year=a<69?a+2e3:a+1900},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yy")}},{key:"y",regex:"\\d{1,4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"y")}},{key:"M!",regex:"0?[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){var b=a.getMonth();return/^[0-9]$/.test(b)?c(a,"MM"):c(a,"M")}},{key:"MMMM",regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)},formatter:function(a){return c(a,"MMMM")}},{key:"MMM",regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)},formatter:function(a){return c(a,"MMM")}},{key:"MM",regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"MM")}},{key:"M",regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"M")}},{key:"d!",regex:"[0-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){var b=a.getDate();return/^[1-9]$/.test(b)?c(a,"dd"):c(a,"d")}},{key:"dd",regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"dd")}},{key:"d",regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"d")}},{key:"EEEE",regex:b.DATETIME_FORMATS.DAY.join("|"),formatter:function(a){return c(a,"EEEE")}},{key:"EEE",regex:b.DATETIME_FORMATS.SHORTDAY.join("|"),formatter:function(a){return c(a,"EEE")}},{key:"HH",regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"HH")}},{key:"hh",regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"hh")}},{key:"H",regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"H")}},{key:"h",regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"h")}},{key:"mm",regex:"[0-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"mm")}},{key:"m",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"m")}},{key:"sss",regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a},formatter:function(a){return c(a,"sss")}},{key:"ss",regex:"[0-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"ss")}},{key:"s",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"s")}},{key:"a",regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)},formatter:function(a){return c(a,"a")}},{key:"Z",regex:"[+-]\\d{4}",apply:function(a){var b=a.match(/([+-])(\d{2})(\d{2})/),c=b[1],d=b[2],e=b[3];this.hours+=g(c+d),this.minutes+=g(c+e)},formatter:function(a){return c(a,"Z")}},{key:"ww",regex:"[0-4][0-9]|5[0-3]",formatter:function(a){return c(a,"ww")}},{key:"w",regex:"[0-9]|[1-4][0-9]|5[0-3]",formatter:function(a){return c(a,"w")}},{key:"GGGG",regex:b.DATETIME_FORMATS.ERANAMES.join("|").replace(/\s/g,"\\s"),formatter:function(a){return c(a,"GGGG")}},{key:"GGG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GGG")}},{key:"GG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GG")}},{key:"G",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"G")}}]},this.init(),this.filter=function(a,c){if(!angular.isDate(a)||isNaN(a)||!c)return"";c=b.DATETIME_FORMATS[c]||c,b.id!==m&&this.init(),this.formatters[c]||(this.formatters[c]=e(c,"formatter"));var d=this.formatters[c],f=d.map,g=c;return f.reduce(function(b,c,d){var e=g.match(new RegExp("(.*)"+c.key));e&&angular.isString(e[1])&&(b+=e[1],g=g.replace(e[1]+c.key,""));var h=d===f.length-1?g:"";return c.apply?b+c.apply.call(null,a)+h:b+h},"")},this.parse=function(c,d,g){if(!angular.isString(c)||!d)return c;d=b.DATETIME_FORMATS[d]||d,d=d.replace(o,"\\$&"),b.id!==m&&this.init(),this.parsers[d]||(this.parsers[d]=e(d,"apply"));var h=this.parsers[d],i=h.regex,j=h.map,k=c.match(i),l=!1;if(k&&k.length){var n,p;angular.isDate(g)&&!isNaN(g.getTime())?n={year:g.getFullYear(),month:g.getMonth(),date:g.getDate(),hours:g.getHours(),minutes:g.getMinutes(),seconds:g.getSeconds(),milliseconds:g.getMilliseconds()}:(g&&a.warn("dateparser:","baseDate is not a valid date"),n={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var q=1,r=k.length;qm.modes.indexOf(m[b]))&&(a.datepickerMode=m[b],a.datepickerOptions.datepickerMode=m[b])}):m[b]=a[b]=h[b]||null}}),a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),a.disabled=angular.isDefined(b.disabled)||!1,angular.isDefined(b.ngDisabled)&&p.push(a.$parent.$watch(b.ngDisabled,function(b){a.disabled=b,m.refreshView()})),a.isActive=function(b){return 0===m.compare(b.date,m.activeDate)&&(a.activeDateId=b.uid,!0)},this.init=function(b){n=b,o=b.$options||h.ngModelOptions,a.datepickerOptions.initDate?(m.activeDate=k.fromTimezone(a.datepickerOptions.initDate,o.timezone)||new Date,a.$watch("datepickerOptions.initDate",function(a){a&&(n.$isEmpty(n.$modelValue)||n.$invalid)&&(m.activeDate=k.fromTimezone(a,o.timezone),m.refreshView())})):m.activeDate=new Date;var c=n.$modelValue?new Date(n.$modelValue):new Date;this.activeDate=isNaN(c)?k.fromTimezone(new Date,o.timezone):k.fromTimezone(c,o.timezone),n.$render=function(){m.render()}},this.render=function(){if(n.$viewValue){var a=new Date(n.$viewValue),b=!isNaN(a);b?this.activeDate=k.fromTimezone(a,o.timezone):j||f.error('Datepicker directive: "ng-model" value must be a Date object')}this.refreshView()},this.refreshView=function(){if(this.element){a.selectedDt=null,this._refreshView(),a.activeDt&&(a.activeDateId=a.activeDt.uid);var b=n.$viewValue?new Date(n.$viewValue):null;b=k.fromTimezone(b,o.timezone),n.$setValidity("dateDisabled",!b||this.element&&!this.isDisabled(b))}},this.createDateObject=function(b,c){var d=n.$viewValue?new Date(n.$viewValue):null;d=k.fromTimezone(d,o.timezone);var e=new Date;e=k.fromTimezone(e,o.timezone);var f=this.compare(b,e),g={date:b,label:k.filter(b,c),selected:d&&0===this.compare(b,d),disabled:this.isDisabled(b),past:f<0,current:0===f,future:f>0,customClass:this.customClass(b)||null};return d&&0===this.compare(b,d)&&(a.selectedDt=g),m.activeDate&&0===this.compare(g.date,m.activeDate)&&(a.activeDt=g),g},this.isDisabled=function(b){return a.disabled||this.minDate&&this.compare(b,this.minDate)<0||this.maxDate&&this.compare(b,this.maxDate)>0||a.dateDisabled&&a.dateDisabled({date:b,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===m.minMode){var c=n.$viewValue?k.fromTimezone(new Date(n.$viewValue),o.timezone):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),c=k.toTimezone(c,o.timezone),n.$setViewValue(c),n.$render()}else m.activeDate=b,l(m.modes[m.modes.indexOf(a.datepickerMode)-1]),a.$emit("uib:datepicker.mode");a.$broadcast("uib:datepicker.focus")},a.move=function(a){var b=m.activeDate.getFullYear()+a*(m.step.years||0),c=m.activeDate.getMonth()+a*(m.step.months||0);m.activeDate.setFullYear(b,c,1),m.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===m.maxMode&&1===b||a.datepickerMode===m.minMode&&b===-1||(l(m.modes[m.modes.indexOf(a.datepickerMode)+b]),a.$emit("uib:datepicker.mode"))},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var q=function(){m.element[0].focus()};a.$on("uib:datepicker.focus",q),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey&&!a.disabled)if(b.preventDefault(),m.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(m.isDisabled(m.activeDate))return;a.select(m.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(m.handleKeyDown(c,b),m.refreshView()):a.toggleMode("up"===c?1:-1)},a.$on("$destroy",function(){for(;p.length;)p.shift()()})}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;f0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;k<42;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;l<7;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;o');n.dropdownMenu.replaceWith(A),n.dropdownMenu=A}f.close(o,b),n.selectedOption=null}angular.isFunction(r)&&r(a,c)})}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$q","$parse","$injector",function(a,b,c,d){var e=d.has("$animateCss")?d.get("$animateCss"):null;return{link:function(d,f,g){function h(){f.hasClass("collapse")&&f.hasClass("in")||b.resolve(l(d)).then(function(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{addClass:"in",easing:"ease",to:{height:f[0].scrollHeight+"px"}}).start()["finally"](i):a.addClass(f,"in",{to:{height:f[0].scrollHeight+"px"}}).then(i)})}function i(){f.removeClass("collapsing").addClass("collapse").css({height:"auto"}),m(d)}function j(){return f.hasClass("collapse")||f.hasClass("in")?void b.resolve(n(d)).then(function(){f.css({height:f[0].scrollHeight+"px"}).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),e?e(f,{removeClass:"in",to:{height:"0"}}).start()["finally"](k):a.removeClass(f,"in",{to:{height:"0"}}).then(k)}):k()}function k(){f.css({height:"0"}),f.removeClass("collapsing").addClass("collapse"),o(d)}var l=c(g.expanding),m=c(g.expanded),n=c(g.collapsing),o=c(g.collapsed);d.$eval(g.uibCollapse)||f.addClass("in").addClass("collapse").attr("aria-expanded",!0).attr("aria-hidden",!1).css({height:"auto"}),d.$watch(g.uibCollapse,function(a){a?j():h()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);b!==-1&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion-group.html"},scope:{heading:"@",panelClass:"@?",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass||"panel-default",a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)};var e="accordiongroup-"+a.$id+"-"+Math.floor(1e4*Math.random());a.headingId=e+"-tab",a.panelId=e+"-panel"}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){function a(){return"uib-accordion-header,data-uib-accordion-header,x-uib-accordion-header,uib\\:accordion-header,[uib-accordion-header],[data-uib-accordion-header],[x-uib-accordion-header]"}return{require:"^uibAccordionGroup",link:function(b,c,d,e){b.$watch(function(){return e[d.uibAccordionTransclude]},function(b){if(b){var d=angular.element(c[0].querySelector(a()));d.html(""),d.append(b)}})}}}),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{replace:!0,scope:{uibTitle:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"uib/template/popover/popover-template.html"}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",uibTitle:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/popover/popover-html.html"}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{replace:!0,scope:{uibTitle:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/popover/popover.html"}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.datepickerPopup",["ui.bootstrap.datepicker","ui.bootstrap.position"]).value("$datepickerPopupLiteralWarning",!0).constant("uibDatepickerPopupConfig",{altInputFormats:[],appendToBody:!1,clearText:"Clear",closeOnDateSelection:!0,closeText:"Done",currentText:"Today",datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"uib/template/datepickerPopup/popup.html",datepickerTemplateUrl:"uib/template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss", +month:"yyyy-MM"},onOpenFocus:!0,showButtonBar:!0,placement:"auto bottom-left"}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$log","$parse","$window","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout","uibDatepickerConfig","$datepickerPopupLiteralWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){function q(b){var c=l.parse(b,w,a.date);if(isNaN(c))for(var d=0;d
      "),G?(J=G.timezone,a.ngModelOptions=angular.copy(G),a.ngModelOptions.timezone=null,a.ngModelOptions.updateOnDefault===!0&&(a.ngModelOptions.updateOn=a.ngModelOptions.updateOn?a.ngModelOptions.updateOn+" default":"default"),C.attr("ng-model-options","ngModelOptions")):J=null,C.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":A}),D=angular.element(C.children()[0]),D.attr("template-url",B),a.datepickerOptions||(a.datepickerOptions={}),K&&"month"===c.type&&(a.datepickerOptions.datepickerMode="month",a.datepickerOptions.minMode="month"),D.attr("datepicker-options","datepickerOptions"),K?F.$formatters.push(function(b){return a.date=l.fromTimezone(b,J),b}):(F.$$parserName="date",F.$validators.date=s,F.$parsers.unshift(r),F.$formatters.push(function(b){return F.$isEmpty(b)?(a.date=b,b):(angular.isNumber(b)&&(b=new Date(b)),a.date=l.fromTimezone(b,J),l.filter(a.date,w))})),F.$viewChangeListeners.push(function(){a.date=q(F.$viewValue)}),b.on("keydown",u),H=d(C)(a),C.remove(),y?h.find("body").append(H):b.after(H),a.$on("$destroy",function(){for(a.isOpen===!0&&(i.$$phase||a.$apply(function(){a.isOpen=!1})),H.remove(),b.off("keydown",u),h.off("click",t),E&&E.off("scroll",v),angular.element(g).off("resize",v);L.length;)L.shift()()})},a.getText=function(b){return a[b+"Text"]||m[b+"Text"]},a.isDisabled=function(b){"today"===b&&(b=l.fromTimezone(new Date,J));var c={};return angular.forEach(["minDate","maxDate"],function(b){a.datepickerOptions[b]?angular.isDate(a.datepickerOptions[b])?c[b]=l.fromTimezone(new Date(a.datepickerOptions[b]),J):(p&&e.warn("Literal date support has been deprecated, please switch to date object usage"),c[b]=new Date(k(a.datepickerOptions[b],"medium"))):c[b]=null}),a.datepickerOptions&&c.minDate&&a.compare(b,c.minDate)<0||c.maxDate&&a.compare(b,c.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){angular.isDefined(c)&&(a.date=c);var d=a.date?l.filter(a.date,w):null;b.val(d),F.$setViewValue(d),x&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(c.stopPropagation(),a.isOpen=!1,b[0].focus())},a.select=function(b,c){if(c.stopPropagation(),"today"===b){var d=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(d.getFullYear(),d.getMonth(),d.getDate())):b=new Date(d.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(c){c.stopPropagation(),a.isOpen=!1,b[0].focus()},a.disabled=angular.isDefined(c.disabled)||!1,c.ngDisabled&&L.push(a.$parent.$watch(f(c.ngDisabled),function(b){a.disabled=b})),a.$watch("isOpen",function(d){d?a.disabled?a.isOpen=!1:n(function(){v(),z&&a.$broadcast("uib:datepicker.focus"),h.on("click",t);var d=c.popupPlacement?c.popupPlacement:m.placement;y||j.parsePlacement(d)[2]?(E=E||angular.element(j.scrollParent(b)),E&&E.on("scroll",v)):E=null,angular.element(g).on("resize",v)},0,!1):(h.off("click",t),E&&E.off("scroll",v),angular.element(g).off("resize",v))}),a.$on("uib:datepicker.mode",function(){n(v,0,!1)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{datepickerOptions:"=?",isOpen:"=?",currentText:"@",clearText:"@",closeText:"@"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepickerPopup/popup.html"}}}),angular.module("uib/template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("uib/template/modal/backdrop.html",'\n')}]),angular.module("uib/template/modal/window.html",[]).run(["$templateCache",function(a){a.put("uib/template/modal/window.html",'\n')}]),angular.module("uib/template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tab.html",'\n')}]),angular.module("uib/template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tabset.html",'
      \n \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("uib/template/tooltip/tooltip-html-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-html-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("uib/template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("uib/template/tooltip/tooltip-template-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-template-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("uib/template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
        
      \n \n :\n \n :\n \n
        
      \n')}]),angular.module("uib/template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/datepicker.html",'
      \n \n \n \n
      \n')}]),angular.module("uib/template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
      {{::label.abbr}}
      {{ weekNumbers[$index] }}\n \n
      \n')}]),angular.module("uib/template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("uib/template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/popup.html",'\n')}]),angular.module("uib/template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("uib/template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion-group.html",'
      \n \n
      \n
      \n
      \n
      \n')}]),angular.module("uib/template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion.html",'
      ')}]),angular.module("uib/template/popover/popover-html.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-html.html",'
      \n
      \n\n
      \n

      \n
      \n
      \n
      \n')}]),angular.module("uib/template/popover/popover-template.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-template.html",'
      \n
      \n\n
      \n

      \n
      \n
      \n
      \n')}]),angular.module("uib/template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover.html",'
      \n
      \n\n
      \n

      \n
      \n
      \n
      \n')}]),angular.module("uib/template/datepickerPopup/popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepickerPopup/popup.html",'
      \n \n
      \n')}]),angular.module("ui.bootstrap.position").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibPositionCss&&angular.element(document).find("head").prepend(''),angular.$$uibPositionCss=!0}),angular.module("ui.bootstrap.tooltip").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTooltipCss&&angular.element(document).find("head").prepend(''),angular.$$uibTooltipCss=!0}),angular.module("ui.bootstrap.timepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTimepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibTimepickerCss=!0}),angular.module("ui.bootstrap.datepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerCss=!0}),angular.module("ui.bootstrap.datepickerPopup").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerpopupCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerpopupCss=!0}); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..722c734 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +require('./dist/ui-bootstrap-tpls'); + +module.exports = 'ui.bootstrap'; diff --git a/karma.conf.js b/karma.conf.js index b53f7d3..8642933 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -27,6 +27,8 @@ module.exports = function(config) { // list of files to exclude exclude: [ + 'src/**/index.js', + 'src/**/index-nocss.js', 'src/**/docs/*' ], diff --git a/misc/changelog.tpl.md b/misc/changelog.tpl.md index 712c490..846284e 100644 --- a/misc/changelog.tpl.md +++ b/misc/changelog.tpl.md @@ -3,12 +3,12 @@ ## Features <% _(changelog.feat).keys().sort().forEach(function(scope) { %> - **<%= scope%>:** <% changelog.feat[scope].forEach(function(change) { %> - - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) <% }); %><% }); %> <% } %> + - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)<% }); %><% }); %> <% } %> <% if (_(changelog.fix).size() > 0) { %> ## Bug Fixes <% _(changelog.fix).keys().sort().forEach(function(scope) { %> - **<%= scope%>:** <% changelog.fix[scope].forEach(function(change) { %> - - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) <% }); %><% }); %> <% } %> + - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)<% }); %><% }); %> <% } %> <% if (_(changelog.breaking).size() > 0) { %> ## Breaking Changes <% _(changelog.breaking).keys().sort().forEach(function(scope) { %> diff --git a/misc/demo/assets/app.js b/misc/demo/assets/app.js index e531d6b..4f4cd41 100644 --- a/misc/demo/assets/app.js +++ b/misc/demo/assets/app.js @@ -1,6 +1,8 @@ /* global FastClick, smoothScroll */ angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAnimate', 'ngSanitize'], function($httpProvider){ - FastClick.attach(document.body); + if (!!window.FastClick) { + FastClick.attach(document.body); + } delete $httpProvider.defaults.headers.common['X-Requested-With']; }).run(['$location', function($location){ //Allows us to navigate to the correct element on initialization diff --git a/misc/demo/assets/rainbow.js b/misc/demo/assets/rainbow.js index ed8894d..e27d9d9 100644 --- a/misc/demo/assets/rainbow.js +++ b/misc/demo/assets/rainbow.js @@ -311,9 +311,9 @@ window['Rainbow'] = (function() { _processPattern(regex, pattern, code, callback); }; - // every 100 items we process let's call set timeout + // every 50 items we process let's call set timeout // to let the ui breathe a little - return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0); + return match_counter % 50 > 0 ? nextCall() : setTimeout(nextCall, 0); }; // if this is not a child match and it falls inside of another diff --git a/misc/demo/index.html b/misc/demo/index.html index 5c0f319..e6f1252 100644 --- a/misc/demo/index.html +++ b/misc/demo/index.html @@ -154,9 +154,11 @@

      Dependencies

      JavaScript is required. The only required dependencies are:

        -
      • AngularJS (requires AngularJS 1.4.x, tested with <%= ngversion %>). +
      • AngularJS (requires AngularJS 1.4.x or higher, tested with <%= ngversion %>). 0.14.3 is the last version of this library that supports AngularJS 1.3.x and 0.12.0 is the last version that supports AngularJS 1.2.x.
      • +
      • Angular-animate (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.
      • +
      • Angular-touch (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.
      • Bootstrap CSS (tested with version <%= bsversion %>). This version of the library (<%= pkg.version%>) works only with Bootstrap CSS in version 3.x. 0.8.0 is the last version of this library that supports Bootstrap CSS in version 2.3.x. @@ -178,6 +180,7 @@

        Installation

        a dependency on the ui.bootstrap module:
        angular.module('myModule', ['ui.bootstrap']);

        +

        If you are using UI Bootstrap in the CSP mode, e.g. in an extension, make sure you link to the ui-bootstrap-csp.css in your HTML manually.

        You can fork one of the plunkers from this page to see a working example of what is described here.

        Migration to prefixes

        Since version 0.14.0 we started to prefix all our components. If you are upgrading from ui-bootstrap 0.13.4 or earlier, @@ -193,15 +196,21 @@

        Reading the documentation

        Each of the components provided in ui-bootstrap have documentation and interactive Plunker examples.

        - For the directives, we list the different attributes with their default values, and whether they are readonly. In addition to this, some settings - have an eye icon next to them like this . The eye means that the setting has an Angular - $watch listener applied to it. + For the directives, we list the different attributes with their default values. In addition to this, some settings have a badge on it: + +

          +
        • - This setting has an angular $watch listener applied to it.
        • +
        • B - This setting is a boolean. It doesn't need a parameter.
        • +
        • C - This setting can be configured globally in a constant service*.
        • +
        • $ - This setting expects an angular expression instead of a literal string. If the expression support a boolean / integer, you can pass it directly.
        • +
        • readonly - This setting is readonly.
        • +

        For the services (you will recognize them with the $ prefix), we list all the possible parameters you can pass to them and their default values if any.

        - Also, some components have default values that you can override globally within a .config function for example. + * Some directives have a config service that follows the next pattern: uibDirectiveConfig. The service's settings use camel case. The services can be configured in a .config function for example.

        <% demoModules.forEach(function(module) { %> @@ -225,13 +234,13 @@

        <%= module.displayName %>
        - - + +
        <%- module.docs.html %>
        - +
        <%- module.docs.js %>
        @@ -248,7 +257,7 @@

        <%= module.displayName %>
        -

        Designed and built by Angular-UI team and contributors.

        +

        Designed and built by all ui-bootstrap contributors.

        Code licensed under <%= pkg.license %> License.

        Issues

        diff --git a/package.json b/package.json index 42edca1..5cc0d4d 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,20 @@ { "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", "name": "angular-ui-bootstrap", - "version": "1.0.0-SNAPSHOT", + "version": "1.3.3", "homepage": "http://angular-ui.github.io/bootstrap/", "dependencies": {}, "directories": { "lib": "src/" }, "files": [ - "dist/", "src/", "template/" + "index.js", + "dist/", + "src/", + "template/" ], - "scripts":{ + "main": "index.js", + "scripts": { "test": "grunt" }, "repository": { @@ -18,29 +22,30 @@ "url": "https://github.com/angular-ui/bootstrap.git" }, "devDependencies": { - "angular": "^1.4.4", - "angular-mocks": "^1.4.4", - "angular-sanitize": "^1.4.4", + "angular": "1.5.3", + "angular-mocks": "1.5.3", + "angular-sanitize": "1.5.3", "grunt": "^0.4.5", - "grunt-contrib-concat": "^0.5.1", - "grunt-contrib-copy": "^0.8.0", - "grunt-contrib-uglify": "^0.11.0", - "grunt-contrib-watch": "^0.6.1", - "grunt-conventional-changelog": "^5.0.0", + "grunt-contrib-concat": "^1.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-uglify": "^1.0.1", + "grunt-contrib-watch": "^1.0.0", + "grunt-conventional-changelog": "^6.1.0", "grunt-ddescribe-iit": "0.0.6", "grunt-eslint": "^17.3.1", "grunt-html2js": "^0.3.0", "grunt-karma": "^0.12.0", "jasmine-core": "^2.2.0", - "karma": "^0.13.3", + "karma": "0.13.22", "karma-chrome-launcher": "^0.2.0", "karma-coverage": "^0.5.0", "karma-firefox-launcher": "^0.1.4", "karma-jasmine": "^0.3.5", "load-grunt-tasks": "^3.3.0", - "node-markdown": "0.1.1", + "lodash": "^4.1.0", + "marked": "^0.3.5", "semver": "^5.0.1", - "shelljs": "^0.5.1" + "shelljs": "^0.6.0" }, "license": "MIT" } diff --git a/src/accordion/accordion.js b/src/accordion/accordion.js index e44cb2d..586314c 100644 --- a/src/accordion/accordion.js +++ b/src/accordion/accordion.js @@ -64,6 +64,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) }, scope: { heading: '@', // Interpolate the heading attribute onto this scope + panelClass: '@?', // Ditto with panelClass isOpen: '=?', isDisabled: '=?' }, @@ -91,6 +92,10 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) } } }; + + var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + scope.headingId = id + '-tab'; + scope.panelId = id + '-panel'; } }; }) @@ -119,10 +124,21 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) link: function(scope, element, attrs, controller) { scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) { if (heading) { - element.find('span').html(''); - element.find('span').append(heading); + var elem = angular.element(element[0].querySelector(getHeaderSelectors())); + elem.html(''); + elem.append(heading); } }); } }; + + function getHeaderSelectors() { + return 'uib-accordion-header,' + + 'data-uib-accordion-header,' + + 'x-uib-accordion-header,' + + 'uib\\:accordion-header,' + + '[uib-accordion-header],' + + '[data-uib-accordion-header],' + + '[x-uib-accordion-header]'; + } }); diff --git a/src/accordion/docs/demo.html b/src/accordion/docs/demo.html index ffc5aab..83f643f 100644 --- a/src/accordion/docs/demo.html +++ b/src/accordion/docs/demo.html @@ -1,16 +1,13 @@ - -
        diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index 5cebb9f..d4cb4be 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -1,63 +1,93 @@ A lightweight, extensible directive for fancy tooltip creation. The tooltip directive supports multiple placements, optional transition animation, and more. +__Note to mobile developers__: Please note that while tooltips may work correctly on mobile devices (including tablets), + we have made the decision to not officially support such a use-case because it does not make sense from a UX perspective. + There are three versions of the tooltip: `uib-tooltip`, `uib-tooltip-template`, and -`uib-tooltip-html-unsafe`: - -- `uib-tooltip` takes text only and will escape any HTML provided. -- `uib-tooltip-template` takes text that specifies the location of a template to - use for the tooltip. Note that this needs to be wrapped in a tag. -- `uib-tooltip-html` takes - whatever HTML is provided and displays it in a tooltip; *The user is responsible for ensuring the - content is safe to put into the DOM!* - -The tooltip directives provide several optional attributes to control how they -will display: - -- `tooltip-placement`: Where to place it? Defaults to "top". Passing in 'auto' seperated 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: - - - "top" - tooltip on top, horizontally centered on host element. - - "top-left" - tooltip on top, left edge aligned with host element left edge. - - "top-right" - tooltip on top, right edge aligned with host element right edge. - - "bottom" - tooltip on bottom, horizontally centered on host element. - - "bottom-left" - tooltip on bottom, left edge aligned with host element left edge. - - "bottom-right" - tooltip on bottom, right edge aligned with host element right edge. - - "left" - tooltip on left, vertically centered on host element. - - "left-top" - tooltip on left, top edge aligned with host element top edge. - - "left-bottom" - tooltip on left, bottom edge aligned with host element bottom edge. - - "right" - tooltip on right, vertically centered on host element. - - "right-top" - tooltip on right, top edge aligned with host element top edge. - - "right-bottom" - tooltip on right, bottom edge aligned with host element bottom edge. -- `tooltip-animation`: Should it fade in and out? Defaults to "true". -- `tooltip-popup-delay`: For how long should the user have to have the mouse - over the element before the tooltip shows (in milliseconds)? Defaults to 0. -- `tooltip-close-popup-delay`: For how long should the tooltip remain open - after the close trigger event? Defaults to 0. -- `tooltip-trigger`: What should trigger a show of the tooltip? Supports a space separated list of event names. - Note: this attribute is no longer observable. See `tooltip-enable`. -- `tooltip-enable`: Is it enabled? It will enable or disable the configured - `tooltip-trigger`. -- `tooltip-append-to-body`_(Default: false)_: Should the tooltip be appended to `$body` instead of - the parent element? Note that the presence of this attribute without a value implies `true`. -- `tooltip-class`: Custom class to be applied to the tooltip. -- `tooltip-is-open` - _(Default: false)_: +`uib-tooltip-html`: + +* `uib-tooltip` - + Takes text only and will escape any HTML provided. +* `uib-tooltip-html` + $ - + 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!* +* `uib-tooltip-template` + $ - + Takes text that specifies the location of a template to use for the tooltip. Note that this needs to be wrapped in a tag. + +### uib-tooltip-* settings + +All these settings are available for the three types of tooltips. + +* `tooltip-animation` + $ + C + _(Default: `true`, Config: `animation`)_ - + Should it fade in and out? + +* `tooltip-append-to-body` + $ + C + _(Default: `false`, Config: `appendToBody`)_ - + Should the tooltip be appended to '$body' instead of the parent element? + +* `tooltip-class` - + Custom class to be applied to the tooltip. + +* `tooltip-enable` + $ + _(Default: `true`)_ - + Is it enabled? It will enable or disable the configured tooltip-trigger. + +* `tooltip-is-open` + + _(Default: `false`)_ - Whether to show the tooltip. -The tooltip directives require the `$position` service. +* `tooltip-placement` + C + _(Default: `top`, Config: `placement`)_ - + 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: + + * `top` - tooltip on top, horizontally centered on host element. + * `top-left` - tooltip on top, left edge aligned with host element left edge. + * `top-right` - tooltip on top, right edge aligned with host element right edge. + * `bottom` - tooltip on bottom, horizontally centered on host element. + * `bottom-left` - tooltip on bottom, left edge aligned with host element left edge. + * `bottom-right` - tooltip on bottom, right edge aligned with host element right edge. + * `left` - tooltip on left, vertically centered on host element. + * `left-top` - tooltip on left, top edge aligned with host element top edge. + * `left-bottom` - tooltip on left, bottom edge aligned with host element bottom edge. + * `right` - tooltip on right, vertically centered on host element. + * `right-top` - tooltip on right, top edge aligned with host element top edge. + * `right-bottom` - tooltip on right, bottom edge aligned with host element bottom edge. + +* `tooltip-popup-close-delay` + C + _(Default: `0`, Config: `popupCloseDelay`)_ - + For how long should the tooltip remain open after the close trigger event? + +* `tooltip-popup-delay` + C + _(Default: `0`, Config: `popupDelay`)_ - + Popup delay in milliseconds until it opens. -**Triggers** +* `tooltip-trigger` + _(Default: `mouseenter`)_ - + What should trigger a show of the tooltip? Supports a space separated list of event names (see below). -The following show triggers are supported out of the box, along with their -provided hide triggers: +**Note:** To configure the tooltips, you need to do it on `$uibTooltipProvider` (also see below). + +### Triggers + +The following show triggers are supported out of the box, along with their provided hide triggers: - `mouseenter`: `mouseleave` - `click`: `click` - `outsideClick`: `outsideClick` - `focus`: `blur` -- `none`: `` +- `none` The `outsideClick` trigger will cause the tooltip to toggle on click, and hide when anything else is clicked. @@ -65,28 +95,20 @@ For any non-supported value, the trigger will be used to both show and hide the tooltip. Using the 'none' trigger will disable the internal trigger(s), one can then use the `tooltip-is-open` attribute exclusively to show and hide the tooltip. -**$uibTooltipProvider** +### $uibTooltipProvider Through the `$uibTooltipProvider`, you can change the way tooltips and popovers behave by default; the attributes above always take precedence. The following methods are available: -- `setTriggers(obj)`: Extends the default trigger mappings mentioned above - with mappings of your own. E.g. `{ 'openTrigger': 'closeTrigger' }`. -- `options(obj)`: Provide a set of defaults for certain tooltip and popover - attributes. Currently supports 'placement', 'placementClassPrefix', 'animation', 'popupDelay', and - `appendToBody`. Here are the defaults: - -
        -  placement: 'top',
        -  placementClassPrefix: '',
        -  animation: true,
        -  popupDelay: 0,
        -  popupCloseDelay: 500,
        -  appendToBody: false
        -  
        - -**Known issues** +* `setTriggers(obj)` + _(Example: `{ 'openTrigger': 'closeTrigger' }`)_ - + Extends the default trigger mappings mentioned above with mappings of your own. + +* `options(obj)` - + Provide a set of defaults for certain tooltip and popover attributes. Currently supports the ones with the C badge. + +### Known issues For 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: diff --git a/src/tooltip/index-nocss.js b/src/tooltip/index-nocss.js new file mode 100644 index 0000000..627ad2e --- /dev/null +++ b/src/tooltip/index-nocss.js @@ -0,0 +1,12 @@ +require('../position/index-nocss.js'); +require('../stackedMap'); +require('../../template/tooltip/tooltip-popup.html.js'); +require('../../template/tooltip/tooltip-html-popup.html.js'); +require('../../template/tooltip/tooltip-template-popup.html.js'); +require('./tooltip'); + +var MODULE_NAME = 'ui.bootstrap.module.tooltip'; + +angular.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']); + +module.exports = MODULE_NAME; diff --git a/src/tooltip/index.js b/src/tooltip/index.js new file mode 100644 index 0000000..21d5633 --- /dev/null +++ b/src/tooltip/index.js @@ -0,0 +1,3 @@ +require('../position/position.css'); +require('./tooltip.css'); +module.exports = require('./index-nocss.js'); diff --git a/src/tooltip/test/tooltip-template.spec.js b/src/tooltip/test/tooltip-template.spec.js index d0cde43..cec7d4b 100644 --- a/src/tooltip/test/tooltip-template.spec.js +++ b/src/tooltip/test/tooltip-template.spec.js @@ -37,9 +37,7 @@ describe('tooltip template', function() { }); function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 65842ed..870c67d 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -31,9 +31,7 @@ describe('tooltip', function() { }); function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } @@ -750,9 +748,7 @@ describe('tooltipWithDifferentSymbols', function() { })); function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } @@ -798,9 +794,7 @@ describe('tooltip positioning', function() { })); function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } @@ -856,9 +850,7 @@ describe('tooltipHtml', function() { })); function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } @@ -897,9 +889,7 @@ describe('$uibTooltipProvider', function() { tooltipScope; function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } @@ -966,9 +956,10 @@ describe('$uibTooltipProvider', function() { expect($body.children().length).toEqual(bodyLength + 1); })); - it('should close on location change', inject(function($rootScope, $compile) { + it('should append to the body when only attribute present', inject(function($rootScope, $compile, $document) { + $body = $document.find('body'); elmBody = angular.element( - '
        Selector Text
        ' + '
        Selector Text
        ' ); scope = $rootScope; @@ -978,17 +969,39 @@ describe('$uibTooltipProvider', function() { elmScope = elm.scope(); tooltipScope = elmScope.$$childTail; + var bodyLength = $body.children().length; trigger(elm, 'mouseenter'); + expect(tooltipScope.isOpen).toBe(true); + expect(elmBody.children().length).toBe(1); + expect($body.children().length).toEqual(bodyLength + 1); + })); - scope.$broadcast('$locationChangeSuccess'); + it('should not append to the body when attribute value is false', inject(function($rootScope, $compile, $document) { + $body = $document.find('body'); + elmBody = angular.element( + '
        Selector Text
        ' + ); + + scope = $rootScope; + $compile(elmBody)(scope); scope.$digest(); - expect(tooltipScope.isOpen).toBe(false); + elm = elmBody.find('span'); + elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; + + var bodyLength = $body.children().length; + trigger(elm, 'mouseenter'); + + expect(tooltipScope.isOpen).toBe(true); + expect(elmBody.children().length).toBe(2); + expect($body.children().length).toEqual(bodyLength); })); + }); describe('triggers', function() { - describe('triggers with a mapped value', function() { + describe('with a mapped value', function() { beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) { $uibTooltipProvider.options({trigger: 'focus'}); })); @@ -1035,7 +1048,7 @@ describe('$uibTooltipProvider', function() { })); }); - describe('triggers with a custom mapped value', function() { + describe('with a custom mapped value', function() { beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) { $uibTooltipProvider.setTriggers({ customOpenTrigger: 'foo bar' }); $uibTooltipProvider.options({trigger: 'customOpenTrigger'}); @@ -1096,4 +1109,35 @@ describe('$uibTooltipProvider', function() { })); }); }); + + describe('placementClassPrefix', function() { + beforeEach(module('ui.bootstrap.tooltip', function($uibTooltipProvider) { + $uibTooltipProvider.options({placementClassPrefix: 'uib-'}); + })); + + // load the template + beforeEach(module('uib/template/tooltip/tooltip-popup.html')); + + it('should add the classes', inject(function($rootScope, $compile, $timeout) { + elmBody = angular.element( + '
        ' + ); + + scope = $rootScope; + $compile(elmBody)(scope); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; + + expect(elmBody.children().length).toBe(1); + + trigger(elm, 'mouseenter'); + $timeout.flush(); + + var tooltipElm = elmBody.find('.tooltip'); + expect(tooltipElm.hasClass('top')).toBe(true); + expect(tooltipElm.hasClass('uib-top-right')).toBe(true); + })); + }); }); diff --git a/src/tooltip/test/tooltip2.spec.js b/src/tooltip/test/tooltip2.spec.js index 00573eb..e8df646 100644 --- a/src/tooltip/test/tooltip2.spec.js +++ b/src/tooltip/test/tooltip2.spec.js @@ -56,9 +56,7 @@ describe('tooltip directive', function() { } function trigger(element, evt) { - evt = new Event(evt); - - element[0].dispatchEvent(evt); + element.trigger(evt); element.scope().$$childTail.$digest(); } diff --git a/src/tooltip/tooltip.css b/src/tooltip/tooltip.css new file mode 100644 index 0000000..04e5c9f --- /dev/null +++ b/src/tooltip/tooltip.css @@ -0,0 +1,60 @@ +[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow, +[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow, +[uib-popover-popup].popover.top-left > .arrow, +[uib-popover-popup].popover.top-right > .arrow, +[uib-popover-popup].popover.bottom-left > .arrow, +[uib-popover-popup].popover.bottom-right > .arrow, +[uib-popover-popup].popover.left-top > .arrow, +[uib-popover-popup].popover.left-bottom > .arrow, +[uib-popover-popup].popover.right-top > .arrow, +[uib-popover-popup].popover.right-bottom > .arrow, +[uib-popover-html-popup].popover.top-left > .arrow, +[uib-popover-html-popup].popover.top-right > .arrow, +[uib-popover-html-popup].popover.bottom-left > .arrow, +[uib-popover-html-popup].popover.bottom-right > .arrow, +[uib-popover-html-popup].popover.left-top > .arrow, +[uib-popover-html-popup].popover.left-bottom > .arrow, +[uib-popover-html-popup].popover.right-top > .arrow, +[uib-popover-html-popup].popover.right-bottom > .arrow, +[uib-popover-template-popup].popover.top-left > .arrow, +[uib-popover-template-popup].popover.top-right > .arrow, +[uib-popover-template-popup].popover.bottom-left > .arrow, +[uib-popover-template-popup].popover.bottom-right > .arrow, +[uib-popover-template-popup].popover.left-top > .arrow, +[uib-popover-template-popup].popover.left-bottom > .arrow, +[uib-popover-template-popup].popover.right-top > .arrow, +[uib-popover-template-popup].popover.right-bottom > .arrow { + top: auto; + bottom: auto; + left: auto; + right: auto; + margin: 0; +} + +[uib-popover-popup].popover, +[uib-popover-html-popup].popover, +[uib-popover-template-popup].popover { + display: block !important; +} diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 5bfe0f8..bdee804 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -48,14 +48,14 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s /** * This allows you to extend the set of trigger mappings available. E.g.: * - * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } ); */ this.setTriggers = function setTriggers(triggers) { angular.extend(triggerMap, triggers); }; /** - * This is a helper function for translating camel-case to snake-case. + * This is a helper function for translating camel-case to snake_case. */ function snake_case(name) { var regexp = /[A-Z]/g; @@ -121,17 +121,17 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s var startSym = $interpolate.startSymbol(); var endSym = $interpolate.endSymbol(); var template = - '
        ' + '
        '; @@ -154,6 +154,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; var observers = []; + var lastPlacement; var positionTooltip = function() { // check if tooltip exists and is not empty @@ -161,36 +162,29 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s if (!positionTimeout) { positionTimeout = $timeout(function() { - // Reset the positioning. - tooltip.css({ top: 0, left: 0 }); - - // Now set the calculated positioning. var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); - tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' }); + tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' }); + + if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) { + tooltip.removeClass(lastPlacement.split('-')[0]); + tooltip.addClass(ttPosition.placement.split('-')[0]); + } - // If the placement class is prefixed, still need - // to remove the TWBS standard class. - if (options.placementClassPrefix) { - tooltip.removeClass('top bottom left right'); + if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) { + tooltip.removeClass(options.placementClassPrefix + lastPlacement); + tooltip.addClass(options.placementClassPrefix + ttPosition.placement); } - tooltip.removeClass( - options.placementClassPrefix + 'top ' + - options.placementClassPrefix + 'top-left ' + - options.placementClassPrefix + 'top-right ' + - options.placementClassPrefix + 'bottom ' + - options.placementClassPrefix + 'bottom-left ' + - options.placementClassPrefix + 'bottom-right ' + - options.placementClassPrefix + 'left ' + - options.placementClassPrefix + 'left-top ' + - options.placementClassPrefix + 'left-bottom ' + - options.placementClassPrefix + 'right ' + - options.placementClassPrefix + 'right-top ' + - options.placementClassPrefix + 'right-bottom'); - - var placement = ttPosition.placement.split('-'); - tooltip.addClass(placement[0], options.placementClassPrefix + ttPosition.placement); - $position.positionArrow(tooltip, ttPosition.placement); + // first time through tt element will have the + // uib-position-measure class or if the placement + // has changed we need to position the arrow. + if (tooltip.hasClass('uib-position-measure')) { + $position.positionArrow(tooltip, ttPosition.placement); + tooltip.removeClass('uib-position-measure'); + } else if (lastPlacement !== ttPosition.placement) { + $position.positionArrow(tooltip, ttPosition.placement); + } + lastPlacement = ttPosition.placement; positionTimeout = null; }, 0, false); @@ -287,18 +281,20 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s // First things first: we don't show it anymore. ttScope.$evalAsync(function() { - ttScope.isOpen = false; - assignIsOpen(false); - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - // The fade transition in TWBS is 150ms. - if (ttScope.animation) { - if (!transitionTimeout) { - transitionTimeout = $timeout(removeTooltip, 150, false); + if (ttScope) { + ttScope.isOpen = false; + assignIsOpen(false); + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + // The fade transition in TWBS is 150ms. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 150, false); + } + } else { + removeTooltip(); } - } else { - removeTooltip(); } }); } @@ -308,6 +304,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s $timeout.cancel(hideTimeout); hideTimeout = null; } + if (transitionTimeout) { $timeout.cancel(transitionTimeout); transitionTimeout = null; @@ -348,9 +345,9 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s } /** - * Set the inital scope values. Once + * Set the initial scope values. Once * the tooltip is created, the observers - * will be added to keep things in synch. + * will be added to keep things in sync. */ function prepareTooltip() { ttScope.title = attrs[prefix + 'Title']; @@ -362,6 +359,8 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s ttScope.popupClass = attrs[prefix + 'Class']; ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; + var placement = $position.parsePlacement(ttScope.placement); + lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); @@ -481,20 +480,18 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s var unregisterTriggers = function() { triggers.show.forEach(function(trigger) { if (trigger === 'outsideClick') { - element[0].removeEventListener('click', toggleTooltipBind); + element.off('click', toggleTooltipBind); } else { - element[0].removeEventListener(trigger, showTooltipBind); - element[0].removeEventListener(trigger, toggleTooltipBind); + element.off(trigger, showTooltipBind); + element.off(trigger, toggleTooltipBind); } }); triggers.hide.forEach(function(trigger) { - trigger.split(' ').forEach(function(hideTrigger) { - if (trigger === 'outsideClick') { - $document[0].removeEventListener('click', bodyHideTooltipBind); - } else { - element[0].removeEventListener(hideTrigger, hideTooltipBind); - } - }); + if (trigger === 'outsideClick') { + $document.off('click', bodyHideTooltipBind); + } else { + element.off(trigger, hideTooltipBind); + } }); }; @@ -506,17 +503,14 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s if (triggers.show !== 'none') { triggers.show.forEach(function(trigger, idx) { - // Using raw addEventListener due to jqLite/jQuery bug - #4060 if (trigger === 'outsideClick') { - element[0].addEventListener('click', toggleTooltipBind); - $document[0].addEventListener('click', bodyHideTooltipBind); + element.on('click', toggleTooltipBind); + $document.on('click', bodyHideTooltipBind); } else if (trigger === triggers.hide[idx]) { - element[0].addEventListener(trigger, toggleTooltipBind); + element.on(trigger, toggleTooltipBind); } else if (trigger) { - element[0].addEventListener(trigger, showTooltipBind); - triggers.hide[idx].split(' ').forEach(function(trigger) { - element[0].addEventListener(trigger, hideTooltipBind); - }); + element.on(trigger, showTooltipBind); + element.on(triggers.hide[idx], hideTooltipBind); } element.on('keypress', function(e) { @@ -533,20 +527,16 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s var animation = scope.$eval(attrs[prefix + 'Animation']); ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; - var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']); - appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; - - // if a tooltip is attached to we need to remove it on - // location change as its parent scope will probably not be destroyed - // by the change. - if (appendToBody) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() { - if (ttScope.isOpen) { - hide(); - } - }); + var appendToBodyVal; + var appendKey = prefix + 'AppendToBody'; + if (appendKey in attrs && attrs[appendKey] === undefined) { + appendToBodyVal = true; + } else { + appendToBodyVal = scope.$eval(attrs[appendKey]); } + appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; + // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTooltip() { unregisterTriggers(); @@ -648,8 +638,6 @@ function ($animate, $sce, $compile, $templateRequest) { // // in TWBS, so we need the primary position. var position = $uibPosition.parsePlacement(scope.placement); element.addClass(position[0]); - } else { - element.addClass('top'); } if (scope.popupClass) { diff --git a/src/typeahead/docs/demo.html b/src/typeahead/docs/demo.html index c712959..aee4805 100644 --- a/src/typeahead/docs/demo.html +++ b/src/typeahead/docs/demo.html @@ -70,7 +70,7 @@

        ngModelOptions support

        Custom templates for results

        Model: {{customSelected | json}}
        - +

        Custom popup templates for typeahead's dropdown

        Model: {{customPopupSelected | json}}
        diff --git a/src/typeahead/docs/readme.md b/src/typeahead/docs/readme.md index 540f17d..5ea5c36 100644 --- a/src/typeahead/docs/readme.md +++ b/src/typeahead/docs/readme.md @@ -11,82 +11,112 @@ The `sourceArray` expression can use a special `$viewValue` variable that corres This directive works with promises, meaning you can retrieve matches using the `$http` service with minimal effort. -The typeahead directives provide several attributes: +### uib-typeahead settings -* `ng-model` - : - Assignable angular expression to data-bind to +* `ng-model` + $ + - + Assignable angular expression to data-bind to. * `ng-model-options` - : - Options for ng-model (see [ng-model-options directive](https://docs.angularjs.org/api/ng/directive/ngModelOptions)). Currently supports the `debounce` and `getterSetter` options - -* `uib-typeahead` - : - Comprehension Angular expression (see [select directive](http://docs.angularjs.org/api/ng.directive:select)) - -* `typeahead-append-to-body` - _(Defaults: false)_ : Should the typeahead popup be appended to $body instead of the parent element? + $ - + Options for ng-model (see [ng-model-options directive](https://docs.angularjs.org/api/ng/directive/ngModelOptions)). Currently supports the `debounce` and `getterSetter` options. * `typeahead-append-to` - _(Defaults: null)_ : Should the typeahead popup be appended to an element instead of the parent element? - -* `typeahead-editable` - _(Defaults: true)_ : - Should it restrict model values to the ones selected from the popup only ? + $ + _(Default: `null`)_ - + Should the typeahead popup be appended to an element instead of the parent element? + +* `typeahead-append-to-body` + $ + + _(Default: `false`)_ - + Should the typeahead popup be appended to $body instead of the parent element? + +* `typeahead-editable` + $ + + _(Default: `true`)_ - + Should it restrict model values to the ones selected from the popup only? * `typeahead-focus-first` - _(Defaults: true)_ : - Should the first match automatically be focused as you type? - -* `typeahead-input-formatter` - _(Defaults: undefined)_ : - Format the ng-model result after selection - -* `typeahead-loading` - _(Defaults: angular.noop)_ : - Binding to a variable that indicates if matches are being retrieved asynchronously - -* `typeahead-min-length` - _(Defaults: 1)_ : - Minimal no of characters that needs to be entered before typeahead kicks-in. Must be greater than or equal to 0. - -* `typeahead-no-results` - _(Defaults: angular.noop)_ : - Binding to a variable that indicates if no matching results were found - -* `typeahead-on-select($item, $model, $label)` - _(Defaults: null)_ : - A callback executed when a match is selected + $ + _(Default: `true`)_ - + Should the first match automatically be focused as you type? -* `typeahead-select-on-exact` - _(Defaults: false)_ : - Should it automatically select an item when there is one option that exactly matches the user input? - -* `typeahead-template-url` - _(Defaults: `uib/template/typeahead/typeahead-match.html`)_ : - Set custom item template +* `typeahead-focus-on-select` + _(Default: `true`)_ - + On selection, focus the input element the typeahead directive is associated with. + +* `typeahead-input-formatter` + + _(Default: `undefined`)_ - + Format the ng-model result after selection. + +* `typeahead-is-open` + $ + + _(Default: `angular.noop`)_ - + Binding to a variable that indicates if the dropdown is open. + +* `typeahead-loading` + $ + + _(Default: `angular.noop`)_ - + Binding to a variable that indicates if matches are being retrieved asynchronously. + +* `typeahead-min-length` + $ + + _(Default: `1`)_ - + Minimal no of characters that needs to be entered before typeahead kicks-in. Must be greater than or equal to 0. + +* `typeahead-no-results` + $ + + _(Default: `angular.noop`)_ - + Binding to a variable that indicates if no matching results were found. + +* `typeahead-should-select($event)` + $ + _(Default: `null`)_ - + A callback executed when a `keyup` event that might trigger a selection occurs. Selection will only occur if this function returns true. + +* `typeahead-on-select($item, $model, $label, $event)` + $ + _(Default: `null`)_ - + A callback executed when a match is selected. $event can be undefined if selection not triggered from a user event. * `typeahead-popup-template-url` - _(Defaults: `uib/template/typeahead/typeahead-popup.html`)_ : - Set custom popup template - -* `typeahead-wait-ms` - _(Defaults: 0)_ : - Minimal wait time after last character typed before typeahead kicks-in + _(Default: `uib/template/typeahead/typeahead-popup.html`)_ - + Set custom popup template. * `typeahead-select-on-blur` - _(Defaults: false)_ : - On blur, select the currently highlighted match + $ + _(Default: `false`)_ - + On blur, select the currently highlighted match. -* `typeahead-focus-on-select` - _(Defaults: true) : - On selection, focus the input element the typeahead directive is associated with - -* `typeahead-is-open` - _(Defaults: angular.noop)_ : - Binding to a variable that indicates if dropdown is open +* `typeahead-select-on-exact` + $ + _(Default: `false`)_ - + Automatically select the item when it is the only one that exactly matches the user input. * `typeahead-show-hint` - _(Defaults: false)_ : - Should input show hint that matches the first option? + $ + _(Default: `false`)_ - + Show hint when the first option matches. + +* `typeahead-template-url` + _(Default: `uib/template/typeahead/typeahead-match.html`)_ - + Set custom item template. + +* `typeahead-wait-ms` + $ + + _(Default: `0`)_ - + Minimal wait time after last character typed before typeahead kicks-in. + +* `uib-typeahead` + $ + - + Comprehension Angular expression (see [select directive](http://docs.angularjs.org/api/ng.directive:select)). diff --git a/src/typeahead/index-nocss.js b/src/typeahead/index-nocss.js new file mode 100644 index 0000000..45f5d20 --- /dev/null +++ b/src/typeahead/index-nocss.js @@ -0,0 +1,11 @@ +require('../debounce'); +require('../position/index-nocss.js'); +require('../../template/typeahead/typeahead-match.html.js'); +require('../../template/typeahead/typeahead-popup.html.js'); +require('./typeahead'); + +var MODULE_NAME = 'ui.bootstrap.module.typeahead'; + +angular.module(MODULE_NAME, ['ui.bootstrap.typeahead', 'uib/template/typeahead/typeahead-match.html', 'uib/template/typeahead/typeahead-popup.html']); + +module.exports = MODULE_NAME; diff --git a/src/typeahead/index.js b/src/typeahead/index.js new file mode 100644 index 0000000..4400459 --- /dev/null +++ b/src/typeahead/index.js @@ -0,0 +1,3 @@ +require('../position/position.css'); +require('./typeahead.css'); +module.exports = require('./index-nocss.js'); diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index ea250f4..a06de20 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -64,10 +64,14 @@ describe('typeahead tests', function() { return findDropDown(element).find('li'); }; - var triggerKeyDown = function(element, keyCode) { + var triggerKeyDown = function(element, keyCode, options) { + options = options || {}; var inputEl = findInput(element); var e = $.Event('keydown'); e.which = keyCode; + if (options.shiftKey) { + e.shiftKey = true; + } inputEl.trigger(e); }; @@ -196,6 +200,30 @@ describe('typeahead tests', function() { expect(element).toBeClosed(); }); + + it('should support changing min-length', function() { + $scope.typeAheadMinLength = 2; + var element = prepareInputEl('
        '); + + changeInputValueTo(element, 'b'); + + expect(element).toBeClosed(); + + $scope.typeAheadMinLength = 0; + $scope.$digest(); + changeInputValueTo(element, ''); + + expect(element).toBeOpenWithActive(3, 0); + + $scope.typeAheadMinLength = 2; + $scope.$digest(); + changeInputValueTo(element, 'b'); + + expect(element).toBeClosed(); + }); + + + it('should support custom model selecting function', function() { $scope.updaterFn = function(selectedItem) { return 'prefix' + selectedItem; @@ -271,6 +299,37 @@ describe('typeahead tests', function() { expect(inputEl.val()).toEqual(''); }); + it('should clear errors after blur for typeahead-editable="false"', function () { + var element = prepareInputEl( + '
        ' + + '' + + '
        '); + var inputEl = findInput(element); + + changeInputValueTo(element, 'not in matches'); + expect($scope.result).toEqual(undefined); + expect(inputEl.val()).toEqual('not in matches'); + inputEl.blur(); + + expect($scope.form.input.$error.editable).toBeFalsy(); + expect($scope.form.input.$error.parse).toBeFalsy(); + }); + + it('should go through other validators after blur for typeahead-editable="false"', function () { + var element = prepareInputEl( + '
        ' + + '' + + '
        '); + var inputEl = findInput(element); + + changeInputValueTo(element, 'not in matches'); + expect($scope.result).toEqual(undefined); + expect(inputEl.val()).toEqual('not in matches'); + inputEl.blur(); // input loses focus + expect($scope.result).toEqual(undefined); + expect($scope.form.input.$error.required).toBeTruthy(); + }); + it('should clear view value when no value selected for typeahead-editable="false" typeahead-select-on-blur="false"', function () { var element = prepareInputEl('
        '); var inputEl = findInput(element); @@ -413,6 +472,48 @@ describe('typeahead tests', function() { }); }); + describe('shouldSelect', function() { + it('should select a match when function returns true', function() { + $scope.shouldSelectFn = function() { + return true; + }; + var element = prepareInputEl('
        '); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + triggerKeyDown(element, 13); + + expect($scope.result).toEqual('bar'); + expect(inputEl.val()).toEqual('bar'); + expect(element).toBeClosed(); + }); + it('should not select a match when function returns false', function() { + $scope.shouldSelectFn = function() { + return false; + }; + var element = prepareInputEl('
        '); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + triggerKeyDown(element, 13); + + // no change + expect($scope.result).toEqual('b'); + expect(inputEl.val()).toEqual('b'); + }); + it('should pass key event into select trigger function', function() { + $scope.shouldSelectFn = jasmine.createSpy('shouldSelectFn');//.and.returnValue(true); + var element = prepareInputEl('
        '); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + triggerKeyDown(element, 13); + + expect($scope.shouldSelectFn.calls.count()).toEqual(1); + expect($scope.shouldSelectFn.calls.argsFor(0)[0].which).toEqual(13); + }); + }); + describe('selecting a match', function() { it('should select a match on enter', function() { var element = prepareInputEl('
        '); @@ -478,12 +579,13 @@ describe('typeahead tests', function() { }); it('should invoke select callback on select', function() { - $scope.onSelect = function($item, $model, $label) { + $scope.onSelect = function($item, $model, $label, $event) { $scope.$item = $item; $scope.$model = $model; $scope.$label = $label; + $scope.$event = $event; }; - var element = prepareInputEl('
        '); + var element = prepareInputEl('
        '); changeInputValueTo(element, 'Alas'); triggerKeyDown(element, 13); @@ -492,6 +594,7 @@ describe('typeahead tests', function() { expect($scope.$item).toEqual($scope.states[0]); expect($scope.$model).toEqual('AL'); expect($scope.$label).toEqual('Alaska'); + expect($scope.$event.type).toEqual("keydown"); }); it('should correctly update inputs value on mapping where label is not derived from the model', function() { @@ -687,23 +790,36 @@ describe('typeahead tests', function() { it('should activate prev/next matches on up/down keys', function() { changeInputValueTo(element, 'b'); - expect(element).toBeOpenWithActive(2, 0); + var parentNode = element.find('ul').eq(0)[0]; + var liIndex; + + liIndex = 0; + expect(element).toBeOpenWithActive(2, liIndex); + expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop); // Down arrow key triggerKeyDown(element, 40); - expect(element).toBeOpenWithActive(2, 1); + liIndex = 1; + expect(element).toBeOpenWithActive(2, liIndex); + expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop); // Down arrow key goes back to first element triggerKeyDown(element, 40); - expect(element).toBeOpenWithActive(2, 0); + liIndex = 0; + expect(element).toBeOpenWithActive(2, liIndex); + expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop); // Up arrow key goes back to last element triggerKeyDown(element, 38); - expect(element).toBeOpenWithActive(2, 1); + liIndex = 1; + expect(element).toBeOpenWithActive(2, liIndex); + expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop); // Up arrow key goes back to first element triggerKeyDown(element, 38); - expect(element).toBeOpenWithActive(2, 0); + liIndex = 0; + expect(parentNode.scrollTop).toEqual(element.find('li').eq(liIndex)[0].offsetTop); + expect(element).toBeOpenWithActive(2, liIndex); }); it('should close popup on escape key', function() { @@ -1172,6 +1288,11 @@ describe('typeahead tests', function() { triggerKeyDown(element, 27); expect(hintEl.val()).toEqual(''); }); + + it("should set tab index on hint input element", function(){ + var hintEl = findInput(element); + expect(hintEl.attr('tabindex')).toEqual('-1'); + }); }); describe('append to', function() { @@ -1307,6 +1428,23 @@ describe('typeahead tests', function() { expect(element).toBeClosed(); }); + it("should not capture tab when shift key is pressed", function(){ + $scope.select_count = 0; + $scope.onSelect = function($item, $model, $label) { + $scope.select_count = $scope.select_count + 1; + }; + var element = prepareInputEl('
        '); + changeInputValueTo(element, 'b'); + + // down key should be captured and focus first element + triggerKeyDown(element, 40); + + triggerKeyDown(element, 9, {shiftKey: true}); + expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy(); + expect($scope.select_count).toEqual(0); + expect(element).toBeClosed(); + }); + it('should capture enter or tab when an item is focused', function() { $scope.select_count = 0; $scope.onSelect = function($item, $model, $label) { @@ -1337,6 +1475,7 @@ describe('typeahead tests', function() { var element = prepareInputEl('
        '); var inputEl = findInput(element); inputEl.focus(); + $timeout.flush(); $scope.$digest(); expect(element).toBeOpenWithActive(3, 0); }); diff --git a/src/typeahead/typeahead.css b/src/typeahead/typeahead.css new file mode 100644 index 0000000..8e2463f --- /dev/null +++ b/src/typeahead/typeahead.css @@ -0,0 +1,3 @@ +[uib-typeahead-popup].dropdown-menu { + display: block; +} diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index e52e1bd..5a43f9e 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -39,6 +39,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap minLength = 1; } + originalScope.$watch(attrs.typeaheadMinLength, function (newVal) { + minLength = !newVal && newVal !== 0 ? 1 : newVal; + }); + //minimal wait time after last character typed before typeahead kicks-in var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; @@ -46,11 +50,17 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; originalScope.$watch(attrs.typeaheadEditable, function (newVal) { isEditable = newVal !== false; - }); + }); //binding to a variable that indicates if matches are being retrieved asynchronously var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + //a function to determine if an event should cause selection + var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) { + var evt = vals.$event; + return evt.which === 13 || evt.which === 9; + }; + //a callback executed when a match is selected var onSelectCallback = $parse(attrs.typeaheadOnSelect); @@ -125,6 +135,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap element.after(inputsContainer); hintInputElem = element.clone(); hintInputElem.attr('placeholder', ''); + hintInputElem.attr('tabindex', '-1'); hintInputElem.val(''); hintInputElem.css({ 'position': 'absolute', @@ -151,7 +162,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap id: popupId, matches: 'matches', active: 'activeIdx', - select: 'select(activeIdx)', + select: 'select(activeIdx, evt)', 'move-in-progress': 'moveInProgress', query: 'query', position: 'position', @@ -202,7 +213,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap return false; }; - var getMatchesAsync = function(inputValue) { + var getMatchesAsync = function(inputValue, evt) { var locals = {$viewValue: inputValue}; isLoadingSetter(originalScope, true); isNoResultsSetter(originalScope, false); @@ -238,19 +249,20 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { $$debounce(function() { - scope.select(0); + scope.select(0, evt); }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); } else { - scope.select(0); + scope.select(0, evt); } } if (showHint) { var firstLabel = scope.matches[0].label; - if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { + if (angular.isString(inputValue) && + inputValue.length > 0 && + firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { hintInputElem.val(inputValue + firstLabel.slice(inputValue.length)); - } - else { + } else { hintInputElem.val(''); } } @@ -329,7 +341,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap isOpenSetter(originalScope, isOpen); }; - scope.select = function(activeIdx) { + scope.select = function(activeIdx, evt) { //called from within the $digest() cycle var locals = {}; var model, item; @@ -344,7 +356,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap onSelectCallback(originalScope, { $item: item, $model: model, - $label: parserResult.viewMapper(originalScope, locals) + $label: parserResult.viewMapper(originalScope, locals), + $event: evt }); resetMatches(); @@ -363,69 +376,83 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap return; } - // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results - if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { + var shouldSelect = isSelectEvent(originalScope, {$event: evt}); + + /** + * if there's nothing selected (i.e. focusFirst) and enter or tab is hit + * or + * shift + tab is pressed to bring focus to the previous element + * then clear the results + */ + if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) { resetMatches(); scope.$digest(); return; } evt.preventDefault(); - + var target; switch (evt.which) { - case 9: - case 13: - scope.$apply(function () { - if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { - $$debounce(function() { - scope.select(scope.activeIdx); - }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); - } else { - scope.select(scope.activeIdx); - } - }); - break; - case 27: + case 27: // escape evt.stopPropagation(); resetMatches(); - scope.$digest(); + originalScope.$digest(); break; - case 38: + case 38: // up arrow scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; scope.$digest(); - popUpEl.children()[scope.activeIdx].scrollIntoView(false); + target = popUpEl.find('li')[scope.activeIdx]; + target.parentNode.scrollTop = target.offsetTop; break; - case 40: + case 40: // down arrow scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; scope.$digest(); - popUpEl.children()[scope.activeIdx].scrollIntoView(false); + target = popUpEl.find('li')[scope.activeIdx]; + target.parentNode.scrollTop = target.offsetTop; break; + default: + if (shouldSelect) { + scope.$apply(function() { + if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { + $$debounce(function() { + scope.select(scope.activeIdx, evt); + }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); + } else { + scope.select(scope.activeIdx, evt); + } + }); + } } }); - element.bind('focus', function () { + element.bind('focus', function (evt) { hasFocus = true; if (minLength === 0 && !modelCtrl.$viewValue) { - getMatchesAsync(modelCtrl.$viewValue); + $timeout(function() { + getMatchesAsync(modelCtrl.$viewValue, evt); + }, 0); } }); - element.bind('blur', function() { + element.bind('blur', function(evt) { if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { selected = true; scope.$apply(function() { if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) { $$debounce(function() { - scope.select(scope.activeIdx); + scope.select(scope.activeIdx, evt); }, scope.debounceUpdate.blur); } else { - scope.select(scope.activeIdx); + scope.select(scope.activeIdx, evt); } }); } if (!isEditable && modelCtrl.$error.editable) { - modelCtrl.$viewValue = ''; + modelCtrl.$setViewValue(); + // Reset validity as we are clearing + modelCtrl.$setValidity('editable', true); + modelCtrl.$setValidity('parse', true); element.val(''); } hasFocus = false; @@ -439,7 +466,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { resetMatches(); if (!$rootScope.$$phase) { - scope.$digest(); + originalScope.$digest(); } } }; @@ -583,14 +610,14 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap scope.active = matchIdx; }; - scope.selectMatch = function(activeIdx) { + scope.selectMatch = function(activeIdx, evt) { var debounce = scope.debounce(); if (angular.isNumber(debounce) || angular.isObject(debounce)) { $$debounce(function() { - scope.select({activeIdx: activeIdx}); + scope.select({activeIdx: activeIdx, evt: evt}); }, angular.isNumber(debounce) ? debounce : debounce['default']); } else { - scope.select({activeIdx: activeIdx}); + scope.select({activeIdx: activeIdx, evt: evt}); } }; } diff --git a/template/accordion/accordion-group.html b/template/accordion/accordion-group.html index c9edcbb..728c674 100644 --- a/template/accordion/accordion-group.html +++ b/template/accordion/accordion-group.html @@ -1,10 +1,10 @@
        -
        + -
        -
        +
        +
        diff --git a/template/accordion/accordion.html b/template/accordion/accordion.html index ba428f3..2a55b9b 100644 --- a/template/accordion/accordion.html +++ b/template/accordion/accordion.html @@ -1 +1 @@ -
        \ No newline at end of file +
        \ No newline at end of file diff --git a/template/carousel/carousel.html b/template/carousel/carousel.html index 372c547..dc98a5a 100644 --- a/template/carousel/carousel.html +++ b/template/carousel/carousel.html @@ -1,10 +1,10 @@

      • - \ No newline at end of file + diff --git a/template/datepicker/datepicker.html b/template/datepicker/datepicker.html index f315d1f..6099c28 100644 --- a/template/datepicker/datepicker.html +++ b/template/datepicker/datepicker.html @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/template/datepicker/day.html b/template/datepicker/day.html index 1af8b4a..69e7007 100644 --- a/template/datepicker/day.html +++ b/template/datepicker/day.html @@ -2,7 +2,7 @@ - + @@ -13,8 +13,17 @@ {{ weekNumbers[$index] }} - - + + diff --git a/template/datepicker/month.html b/template/datepicker/month.html index c0a89ae..1aff9c8 100644 --- a/template/datepicker/month.html +++ b/template/datepicker/month.html @@ -2,14 +2,23 @@ - + - - + + diff --git a/template/datepicker/popup.html b/template/datepicker/popup.html deleted file mode 100644 index d9b6c4b..0000000 --- a/template/datepicker/popup.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/template/datepicker/year.html b/template/datepicker/year.html index e204067..05a0077 100644 --- a/template/datepicker/year.html +++ b/template/datepicker/year.html @@ -2,14 +2,23 @@ - + - - + + diff --git a/template/datepickerPopup/popup.html b/template/datepickerPopup/popup.html new file mode 100644 index 0000000..0a9b73d --- /dev/null +++ b/template/datepickerPopup/popup.html @@ -0,0 +1,12 @@ +
        + +
        diff --git a/template/modal/window.html b/template/modal/window.html index ae17e40..e28a8e8 100644 --- a/template/modal/window.html +++ b/template/modal/window.html @@ -2,5 +2,5 @@ uib-modal-animation-class="fade" modal-in-class="in" ng-style="{'z-index': 1050 + index*10, display: 'block'}"> - + diff --git a/template/popover/popover-html.html b/template/popover/popover-html.html index 9d33c26..e127a0f 100644 --- a/template/popover/popover-html.html +++ b/template/popover/popover-html.html @@ -5,7 +5,7 @@
        -

        +

        diff --git a/template/popover/popover-template.html b/template/popover/popover-template.html index 3d8a8ec..52d2c2f 100644 --- a/template/popover/popover-template.html +++ b/template/popover/popover-template.html @@ -5,7 +5,7 @@
        -

        +

        diff --git a/template/popover/popover.html b/template/popover/popover.html index b31a5b4..a5cbdb5 100644 --- a/template/popover/popover.html +++ b/template/popover/popover.html @@ -5,7 +5,7 @@
        -

        +

        diff --git a/template/progressbar/bar.html b/template/progressbar/bar.html index b5b61e0..f839074 100644 --- a/template/progressbar/bar.html +++ b/template/progressbar/bar.html @@ -1 +1 @@ -
        +
        diff --git a/template/progressbar/progressbar.html b/template/progressbar/progressbar.html index dbfa5aa..fb8eff0 100644 --- a/template/progressbar/progressbar.html +++ b/template/progressbar/progressbar.html @@ -1,3 +1,3 @@
        -
        +
        diff --git a/template/rating/rating.html b/template/rating/rating.html index 5543b03..bd74ffb 100644 --- a/template/rating/rating.html +++ b/template/rating/rating.html @@ -1,4 +1,4 @@ - + ({{ $index < value ? '*' : ' ' }}) - + diff --git a/template/tabs/tab.html b/template/tabs/tab.html index b7b83e5..a368f86 100644 --- a/template/tabs/tab.html +++ b/template/tabs/tab.html @@ -1,3 +1,3 @@ -
      • -
        {{heading}}
        +
      • diff --git a/template/tabs/tabset.html b/template/tabs/tabset.html index 294c86a..e5d17f8 100644 --- a/template/tabs/tabset.html +++ b/template/tabs/tabset.html @@ -1,9 +1,9 @@
        - +
        -
        diff --git a/template/timepicker/timepicker.html b/template/timepicker/timepicker.html index 1013c04..4acf647 100644 --- a/template/timepicker/timepicker.html +++ b/template/timepicker/timepicker.html @@ -10,15 +10,15 @@ - + : - + : - + diff --git a/template/typeahead/typeahead-match.html b/template/typeahead/typeahead-match.html index c64462b..ea8a54b 100644 --- a/template/typeahead/typeahead-match.html +++ b/template/typeahead/typeahead-match.html @@ -1 +1,4 @@ - + diff --git a/template/typeahead/typeahead-popup.html b/template/typeahead/typeahead-popup.html index 20a362b..71232be 100644 --- a/template/typeahead/typeahead-popup.html +++ b/template/typeahead/typeahead-popup.html @@ -1,5 +1,5 @@ -