diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 9e92540ee5..6b74877384 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,15 +1,5 @@
-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.
+# PLEASE READ
-### Bug description:
+As per the [README](https://github.com/angular-ui/bootstrap/blob/master/README.md), this project is no longer being maintained. Any issues entered will remain uninvestigated and unresolved.
-### Link to minimally-working plunker that reproduces the issue:
-
-### Version of Angular, UIBS, and Bootstrap
-
-Angular:
-
-UIBS:
-
-Bootstrap:
+We thank you for your contributions over the years. This library would not have been successful without them.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..676cdb40f9
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,5 @@
+# PLEASE READ
+
+As per the [README](https://github.com/angular-ui/bootstrap/blob/master/README.md), this project is no longer being maintained. Any PRs entered will not be reviewed or merged and will remain open.
+
+We thank you for your contributions over the years. This library would not have been successful without them.
diff --git a/.travis.yml b/.travis.yml
index fba6738247..e69e4c76f0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@ node_js:
- "5.9"
env:
- CXX=g++-4.8
+dist: trusty
addons:
apt:
sources:
@@ -13,7 +14,7 @@ addons:
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- - npm install --quiet -g grunt-cli karma
+ - npm install --quiet -g karma
script: grunt
sudo: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4376216c31..56e5e8e6ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,246 @@
+
+# [2.5.0](https://github.com/angular-ui/bootstrap/compare/2.4.0...v2.5.0) (2017-01-28)
+
+
+### Bug Fixes
+
+* **angular:** add compatibility with Angular 1.6([0d79005](https://github.com/angular-ui/bootstrap/commit/0d79005)), closes [#6427](https://github.com/angular-ui/bootstrap/issues/6427) [#6360](https://github.com/angular-ui/bootstrap/issues/6360)
+* **carousel:** remove transition buffering([86ee770](https://github.com/angular-ui/bootstrap/commit/86ee770)), closes [#6367](https://github.com/angular-ui/bootstrap/issues/6367) [#5967](https://github.com/angular-ui/bootstrap/issues/5967)
+* **dropdown:** do nothing if not open when clicking([761db7b](https://github.com/angular-ui/bootstrap/commit/761db7b)), closes [#6414](https://github.com/angular-ui/bootstrap/issues/6414)
+* **tooltip:** unbind keypress listener on hide([f5b357f](https://github.com/angular-ui/bootstrap/commit/f5b357f)), closes [#6423](https://github.com/angular-ui/bootstrap/issues/6423) [#6405](https://github.com/angular-ui/bootstrap/issues/6405)
+
+
+### Features
+
+* **dropdown:** make dropdown-append-to-body configurable ([#6356](https://github.com/angular-ui/bootstrap/issues/6356))([7d3a750](https://github.com/angular-ui/bootstrap/commit/7d3a750))
+* **pagination:** Added menu and menuitem roles (closes [#6383](https://github.com/angular-ui/bootstrap/issues/6383)) ([#6386](https://github.com/angular-ui/bootstrap/issues/6386))([71dc691](https://github.com/angular-ui/bootstrap/commit/71dc691)), closes [#6383](https://github.com/angular-ui/bootstrap/issues/6383) [(#6386](https://github.com/(/issues/6386)
+
+
+
+
+# [2.4.0](https://github.com/angular-ui/bootstrap/compare/2.3.2...v2.4.0) (2016-12-30)
+
+
+### Features
+
+* **dateparser:** allow overriding of parsers([5a3e44a](https://github.com/angular-ui/bootstrap/commit/5a3e44a)), closes [#6370](https://github.com/angular-ui/bootstrap/issues/6370) [#6373](https://github.com/angular-ui/bootstrap/issues/6373)
+
+
+
+
+## [2.3.2](https://github.com/angular-ui/bootstrap/compare/2.3.1...v2.3.2) (2016-12-27)
+
+
+### Bug Fixes
+
+* **dropdown:** re-add close([955848c](https://github.com/angular-ui/bootstrap/commit/955848c)), closes [#6382](https://github.com/angular-ui/bootstrap/issues/6382) [#6321](https://github.com/angular-ui/bootstrap/issues/6321) [#6357](https://github.com/angular-ui/bootstrap/issues/6357) [#6364](https://github.com/angular-ui/bootstrap/issues/6364)
+
+
+
+
+## [2.3.1](https://github.com/angular-ui/bootstrap/compare/2.3.0...v2.3.1) (2016-12-10)
+
+
+### Bug Fixes
+
+* **dateparser:** add new date format for angular 1.5+ only([f2722b5](https://github.com/angular-ui/bootstrap/commit/f2722b5)), closes [#6349](https://github.com/angular-ui/bootstrap/issues/6349)
+
+* **datepickerPopup:** change to toTimezone only([1962485](https://github.com/angular-ui/bootstrap/commit/1962485)), fixes [#6235](https://github.com/angular-ui/bootstrap/issues/6235)
+
+* **modal:** revert focus behavior on open([8a4f625](https://github.com/angular-ui/bootstrap/commit/8a4f625)), closes [#6295](https://github.com/angular-ui/bootstrap/issues/6295)
+
+
+# [2.3.0](https://github.com/angular-ui/bootstrap/compare/2.2.0...2.3.0) (2016-11-26)
+
+
+### Features
+
+* **dateparser:** add LLLL support([25ff206](https://github.com/angular-ui/bootstrap/commit/25ff206)), closes [#6281](https://github.com/angular-ui/bootstrap/issues/6281)
+
+
+
+
+# [2.2.0](https://github.com/angular-ui/bootstrap/compare/2.1.4...2.2.0) (2016-10-10)
+
+
+### Bug Fixes
+
+* **dropdown:** exit keybind is not open ([14384fc](https://github.com/angular-ui/bootstrap/commit/14384fc)), closes [#6278](https://github.com/angular-ui/bootstrap/issues/6278) [#6208](https://github.com/angular-ui/bootstrap/issues/6208)
+* **modal:** improve ARIA support. ([f9f7e02](https://github.com/angular-ui/bootstrap/commit/f9f7e02)), closes [#6203](https://github.com/angular-ui/bootstrap/issues/6203)
+* **position:** correct scrollbar width calculation ([58f1813](https://github.com/angular-ui/bootstrap/commit/58f1813)), closes [#6273](https://github.com/angular-ui/bootstrap/issues/6273)
+* **tooltip:** cancel timeout when hidden ([7855976](https://github.com/angular-ui/bootstrap/commit/7855976)), closes [#6226](https://github.com/angular-ui/bootstrap/issues/6226) [#6221](https://github.com/angular-ui/bootstrap/issues/6221)
+
+### Features
+
+* **timepicker:** add validation information ([9666c64](https://github.com/angular-ui/bootstrap/commit/9666c64)), closes [#6230](https://github.com/angular-ui/bootstrap/issues/6230) [#6259](https://github.com/angular-ui/bootstrap/issues/6259)
+
+
+
+
+## [2.1.4](https://github.com/angular-ui/bootstrap/compare/2.1.3...2.1.4) (2016-09-24)
+
+
+### Bug Fixes
+
+* **datepicker:** improve accessibility ([3f70d76](https://github.com/angular-ui/bootstrap/commit/3f70d76)), closes [#6247](https://github.com/angular-ui/bootstrap/issues/6247)
+* **dropdown:** prevent premature scope removal ([08ee30a](https://github.com/angular-ui/bootstrap/commit/08ee30a)), closes [#6238](https://github.com/angular-ui/bootstrap/issues/6238) [#6225](https://github.com/angular-ui/bootstrap/issues/6225)
+
+
+
+
+## [2.1.3](https://github.com/angular-ui/bootstrap/compare/2.1.2...2.1.3) (2016-08-25)
+
+
+### Bug Fixes
+
+* **modal:** compile only once with component ([969eb9c](https://github.com/angular-ui/bootstrap/commit/969eb9c)), closes [#6202](https://github.com/angular-ui/bootstrap/issues/6202) [#6201](https://github.com/angular-ui/bootstrap/issues/6201)
+
+
+
+
+## [2.1.2](https://github.com/angular-ui/bootstrap/compare/2.1.1...2.1.2) (2016-08-22)
+
+
+### Bug Fixes
+
+* **collapse:** revert change to transition css ([515bcf2](https://github.com/angular-ui/bootstrap/commit/515bcf2)), closes [#6196](https://github.com/angular-ui/bootstrap/issues/6196) [#6194](https://github.com/angular-ui/bootstrap/issues/6194)
+* **datepicker:** fix accidental global ([ddcacb7](https://github.com/angular-ui/bootstrap/commit/ddcacb7)), closes [#6188](https://github.com/angular-ui/bootstrap/issues/6188)
+* **modal:** close and dismiss bindings on component ([3e8ecff](https://github.com/angular-ui/bootstrap/commit/3e8ecff)), closes [#6192](https://github.com/angular-ui/bootstrap/issues/6192) [#6191](https://github.com/angular-ui/bootstrap/issues/6191)
+
+
+
+
+## [2.1.1](https://github.com/angular-ui/bootstrap/compare/2.1.0...2.1.1) (2016-08-21)
+
+
+### Bug Fixes
+
+* **collapse:** default to css([aef24cd](https://github.com/angular-ui/bootstrap/commit/aef24cd)), closes [#6182](https://github.com/angular-ui/bootstrap/issues/6182) [#6045](https://github.com/angular-ui/bootstrap/issues/6045)
+* **modal:** switch to .append([fb5fabf](https://github.com/angular-ui/bootstrap/commit/fb5fabf)), closes [#6187](https://github.com/angular-ui/bootstrap/issues/6187) [#6186](https://github.com/angular-ui/bootstrap/issues/6186)
+
+
+
+
+# [2.1.0](https://github.com/angular-ui/bootstrap/compare/2.0.2...2.1.0) (2016-08-19)
+
+
+### Bug Fixes
+
+* **collapse:** remove unnecessary inherit ([ca20be4](https://github.com/angular-ui/bootstrap/commit/ca20be4)), closes [#6164](https://github.com/angular-ui/bootstrap/issues/6164) [#6163](https://github.com/angular-ui/bootstrap/issues/6163)
+* **collapse:** set overflow to hidden on transition ([84cc2cf](https://github.com/angular-ui/bootstrap/commit/84cc2cf)), closes [#6180](https://github.com/angular-ui/bootstrap/issues/6180) [#5474](https://github.com/angular-ui/bootstrap/issues/5474)
+* **datepickerPopup:** apply timezone conversion ([f147d22](https://github.com/angular-ui/bootstrap/commit/f147d22)), closes [#6173](https://github.com/angular-ui/bootstrap/issues/6173) [#6147](https://github.com/angular-ui/bootstrap/issues/6147)
+* **modal:** improve ARIA support ([4a5e6a7](https://github.com/angular-ui/bootstrap/commit/4a5e6a7)), closes [#4772](https://github.com/angular-ui/bootstrap/issues/4772)
+* **tooltip:** close tooltip on esc ([f5ff12c](https://github.com/angular-ui/bootstrap/commit/f5ff12c)), closes [#6177](https://github.com/angular-ui/bootstrap/issues/6177) [#6108](https://github.com/angular-ui/bootstrap/issues/6108)
+
+### Features
+
+* **modal:** add component support ([2ade054](https://github.com/angular-ui/bootstrap/commit/2ade054)), closes [#5683](https://github.com/angular-ui/bootstrap/issues/5683) [#6179](https://github.com/angular-ui/bootstrap/issues/6179)
+
+
+
+
+## [2.0.2](https://github.com/angular-ui/bootstrap/compare/2.0.1...2.0.2) (2016-08-15)
+
+
+### Bug Fixes
+
+* **datepickerPopup:** correctly format to timezone ([fbd0845](https://github.com/angular-ui/bootstrap/commit/fbd0845)), closes [#6159](https://github.com/angular-ui/bootstrap/issues/6159) [#6105](https://github.com/angular-ui/bootstrap/issues/6105) [#6146](https://github.com/angular-ui/bootstrap/issues/6146) [#6147](https://github.com/angular-ui/bootstrap/issues/6147)
+* **dropdown:** fix keyboard-nav ([6bad759](https://github.com/angular-ui/bootstrap/commit/6bad759)), closes [#6102](https://github.com/angular-ui/bootstrap/issues/6102) [#6154](https://github.com/angular-ui/bootstrap/issues/6154)
+
+
+
+
+## [2.0.1](https://github.com/angular-ui/bootstrap/compare/2.0.0...2.0.1) (2016-08-02)
+
+
+### Bug Fixes
+
+* **modal:** restore broken stacked modals([c61d16a](https://github.com/angular-ui/bootstrap/commit/c61d16a)), closes [#6103](https://github.com/angular-ui/bootstrap/issues/6103) [#6104](https://github.com/angular-ui/bootstrap/issues/6104)
+
+
+
+
+# [2.0.0](https://github.com/angular-ui/bootstrap/compare/1.3.3...2.0.0) (2016-07-20)
+
+
+### Bug Fixes
+
+* **dateparser:** correctly format with literals([d846e2d](https://github.com/angular-ui/bootstrap/commit/d846e2d)), closes [#6055](https://github.com/angular-ui/bootstrap/issues/6055) [#5620](https://github.com/angular-ui/bootstrap/issues/5620) [#5802](https://github.com/angular-ui/bootstrap/issues/5802)
+* **datepickerPopup:** clear date when button is clicked([b0466d9](https://github.com/angular-ui/bootstrap/commit/b0466d9)), closes [#5945](https://github.com/angular-ui/bootstrap/issues/5945) [#5906](https://github.com/angular-ui/bootstrap/issues/5906)
+* **datepickerPopup:** specify dependency on datepicker([4fef037](https://github.com/angular-ui/bootstrap/commit/4fef037)), closes [#5954](https://github.com/angular-ui/bootstrap/issues/5954)
+* **datepickerPopup:** use value instead of viewValue([7e320e0](https://github.com/angular-ui/bootstrap/commit/7e320e0)), closes [#6007](https://github.com/angular-ui/bootstrap/issues/6007)
+* **dropdown:** align position correctly with scrollbar([2d831bc](https://github.com/angular-ui/bootstrap/commit/2d831bc)), closes [#6008](https://github.com/angular-ui/bootstrap/issues/6008) [#5942](https://github.com/angular-ui/bootstrap/issues/5942)
+* **dropdown:** bind event listener on dropdown menu([6038403](https://github.com/angular-ui/bootstrap/commit/6038403)), closes [#5982](https://github.com/angular-ui/bootstrap/issues/5982)
+* **modal:** check for overflow hidden([433e536](https://github.com/angular-ui/bootstrap/commit/433e536)), closes [#6037](https://github.com/angular-ui/bootstrap/issues/6037) [#6041](https://github.com/angular-ui/bootstrap/issues/6041)
+* **modal:** filter out non-tabbable elements([35ced04](https://github.com/angular-ui/bootstrap/commit/35ced04)), closes [#5963](https://github.com/angular-ui/bootstrap/issues/5963) [#5955](https://github.com/angular-ui/bootstrap/issues/5955)
+* **modal:** remove unused template from modal([1de58a3](https://github.com/angular-ui/bootstrap/commit/1de58a3))
+* **modal:** remove window class after animation([409b7aa](https://github.com/angular-ui/bootstrap/commit/409b7aa)), closes [#6056](https://github.com/angular-ui/bootstrap/issues/6056) [#6051](https://github.com/angular-ui/bootstrap/issues/6051)
+* **tooltip:** missed dependency for cjs([164811a](https://github.com/angular-ui/bootstrap/commit/164811a)), closes [#5999](https://github.com/angular-ui/bootstrap/issues/5999)
+* **tooltip:** reposition for placement top([34a1443](https://github.com/angular-ui/bootstrap/commit/34a1443)), closes [#5959](https://github.com/angular-ui/bootstrap/issues/5959)
+* **typeahead:** change to select class([13c14af](https://github.com/angular-ui/bootstrap/commit/13c14af)), closes [#5922](https://github.com/angular-ui/bootstrap/issues/5922) [#5848](https://github.com/angular-ui/bootstrap/issues/5848)
+* **typeahead:** clear validity in $digest([ed3400b](https://github.com/angular-ui/bootstrap/commit/ed3400b)), closes [#6033](https://github.com/angular-ui/bootstrap/issues/6033) [#6032](https://github.com/angular-ui/bootstrap/issues/6032)
+* **typeahead:** remove duplicate id attribute([6d5b84a](https://github.com/angular-ui/bootstrap/commit/6d5b84a)), closes [#5936](https://github.com/angular-ui/bootstrap/issues/5936) [#5926](https://github.com/angular-ui/bootstrap/issues/5926)
+
+
+### Features
+
+* **accordion:** add appropriate tabindex on disabled([5f4eedd](https://github.com/angular-ui/bootstrap/commit/5f4eedd)), closes [#4067](https://github.com/angular-ui/bootstrap/issues/4067) [#5990](https://github.com/angular-ui/bootstrap/issues/5990)
+* **accordion:** remove replace: true usage([3819bbe](https://github.com/angular-ui/bootstrap/commit/3819bbe)), closes [#5985](https://github.com/angular-ui/bootstrap/issues/5985)
+* **alert:** remove replace: true usage([2458c28](https://github.com/angular-ui/bootstrap/commit/2458c28)), closes [#5986](https://github.com/angular-ui/bootstrap/issues/5986)
+* **carousel:** remove replace: true usage([5046bd4](https://github.com/angular-ui/bootstrap/commit/5046bd4)), closes [#5987](https://github.com/angular-ui/bootstrap/issues/5987)
+* **collapse:** add horizontal support([1ec0997](https://github.com/angular-ui/bootstrap/commit/1ec0997)), closes [#3593](https://github.com/angular-ui/bootstrap/issues/3593) [#6010](https://github.com/angular-ui/bootstrap/issues/6010)
+* **datepicker:** add monthColumns([39d9b98](https://github.com/angular-ui/bootstrap/commit/39d9b98)), closes [#5515](https://github.com/angular-ui/bootstrap/issues/5515) [#5935](https://github.com/angular-ui/bootstrap/issues/5935)
+* **datepicker:** add ngModelOptions support([23b91d9](https://github.com/angular-ui/bootstrap/commit/23b91d9)), closes [#5933](https://github.com/angular-ui/bootstrap/issues/5933) [#5825](https://github.com/angular-ui/bootstrap/issues/5825)
+* **datepicker:** remove replace: true usage([e92fb7f](https://github.com/angular-ui/bootstrap/commit/e92fb7f)), closes [#5988](https://github.com/angular-ui/bootstrap/issues/5988)
+* **datepickerPopup:** remove replace usage([a47bced](https://github.com/angular-ui/bootstrap/commit/a47bced)), closes [#5993](https://github.com/angular-ui/bootstrap/issues/5993)
+* **dropdown:** focus toggle on close from click([cce0097](https://github.com/angular-ui/bootstrap/commit/cce0097)), closes [#5934](https://github.com/angular-ui/bootstrap/issues/5934) [#5782](https://github.com/angular-ui/bootstrap/issues/5782)
+* **dropdown:** use .value() for uibDropdownConfig([7457fb0](https://github.com/angular-ui/bootstrap/commit/7457fb0)), closes [#6004](https://github.com/angular-ui/bootstrap/issues/6004) [#6006](https://github.com/angular-ui/bootstrap/issues/6006)
+* **modal:** append using $animate([1cbd73d](https://github.com/angular-ui/bootstrap/commit/1cbd73d)), closes [#6023](https://github.com/angular-ui/bootstrap/issues/6023) [#6029](https://github.com/angular-ui/bootstrap/issues/6029)
+* **modal:** remove replace usage([96d52ce](https://github.com/angular-ui/bootstrap/commit/96d52ce)), closes [#5989](https://github.com/angular-ui/bootstrap/issues/5989)
+* **pager:** remove replace usage([9b24e1d](https://github.com/angular-ui/bootstrap/commit/9b24e1d)), closes [#5991](https://github.com/angular-ui/bootstrap/issues/5991)
+* **pager:** toggle tabindex when disabled([0d8cec6](https://github.com/angular-ui/bootstrap/commit/0d8cec6)), closes [#5996](https://github.com/angular-ui/bootstrap/issues/5996)
+* **pagination:** disable tabbing when disabled([1a870a3](https://github.com/angular-ui/bootstrap/commit/1a870a3)), closes [#5984](https://github.com/angular-ui/bootstrap/issues/5984)
+* **pagination:** remove replace usage([387c6e7](https://github.com/angular-ui/bootstrap/commit/387c6e7)), closes [#5992](https://github.com/angular-ui/bootstrap/issues/5992)
+* **rating:** remove replace usage([d6fe9c7](https://github.com/angular-ui/bootstrap/commit/d6fe9c7)), closes [#5998](https://github.com/angular-ui/bootstrap/issues/5998)
+* **stackedMap:** improve perf of removeTop([a075824](https://github.com/angular-ui/bootstrap/commit/a075824)), closes [#5925](https://github.com/angular-ui/bootstrap/issues/5925) [#5932](https://github.com/angular-ui/bootstrap/issues/5932)
+* **timepicker:** avoid allowing to tab to controls([4e68778](https://github.com/angular-ui/bootstrap/commit/4e68778)), closes [#5930](https://github.com/angular-ui/bootstrap/issues/5930)
+* **timepicker:** remove replace usage([7518821](https://github.com/angular-ui/bootstrap/commit/7518821)), closes [#5997](https://github.com/angular-ui/bootstrap/issues/5997)
+* **tooltip:** add expression support([4b91222](https://github.com/angular-ui/bootstrap/commit/4b91222)), closes [#5221](https://github.com/angular-ui/bootstrap/issues/5221) [#5938](https://github.com/angular-ui/bootstrap/issues/5938)
+* **tooltip:** remove replace usage([1616e97](https://github.com/angular-ui/bootstrap/commit/1616e97)), closes [#5994](https://github.com/angular-ui/bootstrap/issues/5994)
+
+
+### Reverts
+
+* **dropdown:** change back to .constant()([4e0e34f](https://github.com/angular-ui/bootstrap/commit/4e0e34f))
+
+
+### BREAKING CHANGES
+
+* tooltip: The template structure changed slightly due to the removal of `replace: true` - see documentation examples in action to see differences in practice.
+* typeahead: This changes the selector used so that it doesn't select for the `li` tag specifically, but the elements with the class `uib-typeahead-match` - if using a custom template, this class needs to be added to the `li` element in the typeahead popup template.
+* rating: Due to the removal of `replace: true`, this results in a slight change to the HTML structure - see the documentation examples to see it in action.
+* timepicker: This removes `replace: true`, which changes the HTML structure slightly - see the documentation examples to see it in action.
+* datepickerPopup: Due to the nature of `replace: true`, this has a slight structural HTML change in the popup as a result - see documentation examples for the change in action.
+* pagination: Due to the removal of `replace: true`, this affects the HTML structure slightly - see documentation examples to see new usage.
+* pager: This removes `replace: true` usage from the pager, which causes a slight usage change - see documentation examples for new usage.
+* modal: This removes `replace: true` usage, causing some structural changes to the HTML - the major part here is that there is no more backdrop template, and the top level elements for the window/backdrop elements lose a little flexibility. See documentation examples for new structure.
+* modal: This introduces a minor behavior change in when the class is removed
+* carousel: Due to the removal of `replace: true`, this causes a slight HTML structure change to the carousel and the slide elements - see documentation demos to see how it changes. This also caused removal of the ngTouch built in support - if one is using ng-touch, one needs to add the `ng-swipe-left` and `ng-swipe-right` directives to the carousel element with relevant logic.
+* alert: This removes the `replace: true` usage, so this has an effect on how one uses the directive in the template - see documentation for reference
+* accordion: This removes usage of `replace: true` in the accordion group, which results in a template change where the template no longer needs to contain the panel itself, but its contents. The accordion group will add the `panel` class by default, so the user just needs to add the appropriate classes to the accordion group element. This allows the user to use ng-class as well to fully control the panel related classes, so `panel-class` now is unnecessary
+* tooltip: This removes support for plain strings and interpolations in tooltip-trigger and popover-trigger - please change these appropriately. See test changes in this commit for reference
+* typeahead: This change removes the `id` attribute on the first ``
+element placed into the DOM when the `typeahead-show-hint` attribute is used
+and there is an `id` attribute present on the original `uib-typeahead` element.
+This could affect selectors if they are being used.
+* dropdown: This changes the focus behavior of the dropdown slightly, and potentially may break code built around current usage
+* datepicker: This modifies the current behavior around the datepicker & popup's ngModelOptions, which may affect custom components that are built around both
+* datepicker: As a result of removal of `replace: true`, there is the potential that this may break some CSS layout due to the slightly different HTML. Refer to the documentation examples to see the new structure.
+
+
+
-## [1.3.3](https://github.com/angular-ui/bootstrap/compare/1.3.2...v1.3.3) (2016-05-23)
+## [1.3.3](https://github.com/angular-ui/bootstrap/compare/1.3.2...1.3.3) (2016-05-23)
### Bug Fixes
@@ -25,7 +266,7 @@
-## [1.3.2](https://github.com/angular-ui/bootstrap/compare/1.3.1...v1.3.2) (2016-04-14)
+## [1.3.2](https://github.com/angular-ui/bootstrap/compare/1.3.1...1.3.2) (2016-04-14)
### Bug Fixes
@@ -51,7 +292,7 @@
-## [1.3.1](https://github.com/angular-ui/bootstrap/compare/1.3.0...v1.3.1) (2016-04-05)
+## [1.3.1](https://github.com/angular-ui/bootstrap/compare/1.3.0...1.3.1) (2016-04-05)
### Bug Fixes
@@ -62,7 +303,7 @@
-# [1.3.0](https://github.com/angular-ui/bootstrap/compare/1.2.5...v1.3.0) (2016-04-05)
+# [1.3.0](https://github.com/angular-ui/bootstrap/compare/1.2.5...1.3.0) (2016-04-05)
### Bug Fixes
@@ -105,7 +346,7 @@ 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)
+## [1.2.5](https://github.com/angular-ui/bootstrap/compare/1.2.4...1.2.5) (2016-03-20)
### Bug Fixes
@@ -128,13 +369,13 @@ attribute pass-throughs in the popup
-## [1.2.4](https://github.com/angular-ui/bootstrap/compare/1.2.3...v1.2.4) (2016-03-06)
+## [1.2.4](https://github.com/angular-ui/bootstrap/compare/1.2.3...1.2.4) (2016-03-06)
-## [1.2.3](https://github.com/angular-ui/bootstrap/compare/1.2.2...v1.2.3) (2016-03-06)
+## [1.2.3](https://github.com/angular-ui/bootstrap/compare/1.2.2...1.2.3) (2016-03-06)
### Bug Fixes
@@ -153,7 +394,7 @@ attribute pass-throughs in the popup
-## [1.2.2](https://github.com/angular-ui/bootstrap/compare/1.2.1...v1.2.2) (2016-03-03)
+## [1.2.2](https://github.com/angular-ui/bootstrap/compare/1.2.1...1.2.2) (2016-03-03)
### Bug Fixes
@@ -170,7 +411,7 @@ attribute pass-throughs in the popup
-## [1.2.1](https://github.com/angular-ui/bootstrap/compare/1.2.0...v1.2.1) (2016-02-27)
+## [1.2.1](https://github.com/angular-ui/bootstrap/compare/1.2.0...1.2.1) (2016-02-27)
### Bug Fixes
@@ -180,7 +421,7 @@ attribute pass-throughs in the popup
-# [1.2.0](https://github.com/angular-ui/bootstrap/compare/1.1.2...v1.2.0) (2016-02-26)
+# [1.2.0](https://github.com/angular-ui/bootstrap/compare/1.1.2...1.2.0) (2016-02-26)
### Bug Fixes
@@ -240,7 +481,7 @@ template
-## [1.1.2](https://github.com/angular-ui/bootstrap/compare/1.1.1...v1.1.2) (2016-02-01)
+## [1.1.2](https://github.com/angular-ui/bootstrap/compare/1.1.1...1.1.2) (2016-02-01)
### Bug Fixes
@@ -258,7 +499,7 @@ template
-## [1.1.1](https://github.com/angular-ui/bootstrap/compare/v1.1.0...v1.1.1) (2016-01-25)
+## [1.1.1](https://github.com/angular-ui/bootstrap/compare/1.1.0...1.1.1) (2016-01-25)
### Bug Fixes
@@ -308,7 +549,7 @@ 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)
+# [1.0.3](https://github.com/angular-ui/bootstrap/compare/1.0.2...1.0.3) (2016-01-12)
## Bug Fixes
@@ -318,7 +559,7 @@ template in one's app and provide the necessary CSS
-# [1.0.2](https://github.com/angular-ui/bootstrap/compare/v1.0.1...v1.0.2) (2016-01-12)
+# [1.0.2](https://github.com/angular-ui/bootstrap/compare/1.0.1...1.0.2) (2016-01-12)
## Bug Fixes
@@ -328,7 +569,7 @@ template in one's app and provide the necessary CSS
-# [1.0.1](https://github.com/angular-ui/bootstrap/compare/1.0.0...v1.0.1) (2016-01-12)
+# [1.0.1](https://github.com/angular-ui/bootstrap/compare/1.0.0...1.0.1) (2016-01-12)
## Bug Fixes
@@ -349,7 +590,7 @@ template in one's app and provide the necessary CSS
-# [1.0.0](https://github.com/angular-ui/bootstrap/compare/0.14.3...v1.0.0) (2016-01-08)
+# [1.0.0](https://github.com/angular-ui/bootstrap/compare/0.14.3...1.0.0) (2016-01-08)
## Bug Fixes
@@ -494,7 +735,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
-# [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...v0.14.3) (2015-10-23)
+# [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...0.14.3) (2015-10-23)
## Bug Fixes
@@ -519,7 +760,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
-# [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...0.14.2) (2015-10-14)
## Bug Fixes
@@ -531,7 +772,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
-# [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...0.14.1) (2015-10-11)
## Bug Fixes
@@ -714,7 +955,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
-# 0.13.3 (2015-08-09)
+# [0.13.3](https://github.com/angular-ui/bootstrap/compare/0.13.2...0.13.3) (2015-08-09)
## Bug Fixes
@@ -801,7 +1042,7 @@ Closes #4080
-# 0.13.2 (2015-08-02)
+# [0.13.2](https://github.com/angular-ui/bootstrap/compare/0.13.1...0.13.2) (2015-08-02)
## Bug Fixes
@@ -847,7 +1088,7 @@ Closes #4080
-# 0.13.1 (2015-07-23)
+# [0.13.1](https://github.com/angular-ui/bootstrap/compare/0.13.0...0.13.1) (2015-07-23)
## Bug Fixes
@@ -908,7 +1149,7 @@ Closes #4080
-# 0.13.0 (2015-05-02)
+# [0.13.0](https://github.com/angular-ui/bootstrap/compare/0.12.1...0.13.0) (2015-05-02)
## Bug Fixes
@@ -1007,7 +1248,8 @@ Closes #4080
* **transition:** deprecate transition module ([8a552443](https://github.com/angular-ui/bootstrap/commit/8a552443741d1e5b4b29d9da9c7e9990fa37886c), closes [#3497](https://github.com/angular-ui/bootstrap/issues/3497))
-# 0.12.1 (2015-02-20)
+
+# [0.12.1](https://github.com/angular-ui/bootstrap/compare/0.12.0...0.12.1) (2015-02-20)
## Bug Fixes
@@ -1015,7 +1257,7 @@ Closes #4080
- incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef))
-# 0.12.0 (2014-11-16)
+# [0.12.0](https://github.com/angular-ui/bootstrap/compare/0.11.2...0.12.0) (2014-11-16)
## Bug Fixes
@@ -1068,11 +1310,13 @@ once* and can no longer be changed after initialization.
```
-# 0.11.2 (2014-09-26)
+
+# [0.11.2](https://github.com/angular-ui/bootstrap/compare/0.11.1...0.11.2) (2014-09-26)
Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/bootstrap/commit/1a998c4))
-# 0.11.1 (2014-09-26)
+
+# [0.11.1](https://github.com/angular-ui/bootstrap/compare/0.11.0...0.11.1) (2014-09-26)
## Features
@@ -1106,7 +1350,8 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b
- 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)
+
+# [0.11.0](https://github.com/angular-ui/bootstrap/compare/0.10.0...0.11.0) (2014-05-01)
## Features
@@ -1284,7 +1529,8 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b
```
-# 0.10.0 (2014-01-13)
+
+# [0.10.0](https://github.com/angular-ui/bootstrap/compare/0.9.0...0.10.0) (2014-01-13)
_This release adds AngularJS 1.2 support_
@@ -1305,7 +1551,8 @@ _This release adds AngularJS 1.2 support_
- **tooltip:**
- performance and scope fixes ([c0df3201](http://github.com/angular-ui/bootstrap/commit/c0df3201))
-# 0.9.0 (2013-12-28)
+
+# [0.9.0](https://github.com/angular-ui/bootstrap/compare/0.8.0...0.9.0) (2013-12-28)
_This release adds Bootstrap3 support_
@@ -1348,7 +1595,8 @@ _This release adds Bootstrap3 support_
- **tooltip:**
- re-position tooltip after draw ([a99b3608](http://github.com/angular-ui/bootstrap/commit/a99b3608))
-# 0.8.0 (2013-12-28)
+
+# [0.8.0](https://github.com/angular-ui/bootstrap/compare/0.7.0...0.8.0) (2013-12-28)
## Features
@@ -1415,7 +1663,8 @@ _This release adds Bootstrap3 support_
```
-# 0.7.0 (2013-11-22)
+
+# [0.7.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-11-22)
## Features
@@ -1463,7 +1712,8 @@ _This release adds Bootstrap3 support_
- 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)
+
+# [0.6.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-09-08)
## Features
@@ -1542,7 +1792,8 @@ Check the documentation for the `$modal` service to migrate from `$dialog`
The placment='mouse' is gone with no equivalent
-# 0.5.0 (2013-08-04)
+
+# [0.5.0](https://github.com/angular-ui/bootstrap/compare/0.4.0...0.5.0) (2013-08-04)
## Features
@@ -1625,7 +1876,8 @@ The placment='mouse' is gone with no equivalent
```
-# 0.4.0 (2013-06-24)
+
+# [0.4.0](https://github.com/angular-ui/bootstrap/compare/0.3.0...0.4.0) (2013-06-24)
## Features
@@ -1743,8 +1995,8 @@ The placment='mouse' is gone with no equivalent
```
-
-# 0.3.0 (2013-04-30)
+
+# [0.3.0](https://github.com/angular-ui/bootstrap/compare/0.2.0...0.3.0) (2013-04-30)
## Features
@@ -1785,7 +2037,8 @@ The placment='mouse' is gone with no equivalent
- correctly higlight matches if query contains regexp-special chars ([467afcd6](https://github.com/angular-ui/bootstrap/commit/467afcd6))
- fix matches pop-up positioning issues ([74beecdb](https://github.com/angular-ui/bootstrap/commit/74beecdb))
-# 0.2.0 (2013-03-03)
+
+# [0.2.0](https://github.com/angular-ui/bootstrap/compare/0.1.0...0.2.0) (2013-03-03)
## Features
@@ -1819,6 +2072,7 @@ The placment='mouse' is gone with no equivalent
- **typeahead:**
- update inputs value on mapping where label is not derived from the model ([a5f64de](https://github.com/angular-ui/bootstrap/commit/a5f64de))
+
# 0.1.0 (2013-02-02)
_Very first, initial release_.
diff --git a/Gruntfile.js b/Gruntfile.js
index 72baea5040..ed0ec0576a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -9,8 +9,8 @@ module.exports = function(grunt) {
grunt.util.linefeed = '\n';
grunt.initConfig({
- ngversion: '1.5.5',
- bsversion: '3.3.6',
+ ngversion: '1.6.1',
+ bsversion: '3.3.7',
modules: [],//to be filled in by build task
pkg: grunt.file.readJSON('package.json'),
dist: 'dist',
diff --git a/LICENSE b/LICENSE
index 64050c0cbc..cf7b84b3fb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License
-Copyright (c) 2012-2016 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112
+Copyright (c) 2012-2017 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 a2e08beb10..8964146889 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,16 @@
+# Project Status (please read)
+Due to [Angular](https://angular.io)'s continued adoption, our creation of [the Angular version of this library](https://ng-bootstrap.github.io), and the the project maintainers' moving on to other things, this project is considered feature-complete and is no longer being maintained.
+
+We thank you for all your contributions over the years and hope you've enjoyed using this library as much as we've had developing and maintaining it. It would not have been successful without them.
+
+---
+
### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com)
[](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](http://travis-ci.org/angular-ui/bootstrap)
[](https://david-dm.org/angular-ui/bootstrap#info=devDependencies)
+[](https://cdnjs.com/libraries/angular-ui-bootstrap/)
### Quick links
- [Demo](#demo)
@@ -27,7 +35,7 @@
# Demo
-Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/!
+Do you want to see directives in action? Visit https://angular-ui.github.io/bootstrap/!
# Angular 2
@@ -73,7 +81,7 @@ PM> Install-Package Angular.UI.Bootstrap
#### Custom build
-Head over to http://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it.
+Head over to https://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it.
#### Manual download
@@ -118,6 +126,7 @@ If you would prefer not to load your css through your JavaScript file loader/bun
* datepicker
* datepickerPopup
* dropdown
+* modal
* popover
* position
* timepicker
@@ -130,9 +139,13 @@ The other modules, such as `accordion` in the example below, do not have CSS res
import accordion from 'angular-ui-bootstrap/src/accordion';
import typeahead from 'angular-ui-bootstrap/src/typeahead/index-nocss.js';
-angular.module('myModule', [accordion, datepicker]);
+angular.module('myModule', [accordion, typeahead]);
```
+# Versioning
+
+Pre-2.0.0 does not follow a particular versioning system. 2.0.0 and onwards follows [semantic versioning](http://semver.org/). All release changes can be viewed on our [changelog](CHANGELOG.md).
+
# Support
## FAQ
diff --git a/misc/demo/assets/demo.css b/misc/demo/assets/demo.css
index d1cadcddd3..994d63b1dd 100644
--- a/misc/demo/assets/demo.css
+++ b/misc/demo/assets/demo.css
@@ -73,7 +73,7 @@ section {
}
-.navbar .collapse {
+.navbar-fixed-top .collapse {
border-top: 1px solid #e7e7e7;
margin-left: -15px;
margin-right: -15px;
diff --git a/misc/demo/assets/favicon.ico b/misc/demo/assets/favicon.ico
index 3d3f0002f1..147f4de37e 100644
Binary files a/misc/demo/assets/favicon.ico and b/misc/demo/assets/favicon.ico differ
diff --git a/misc/demo/assets/plunker.js b/misc/demo/assets/plunker.js
index 1e586108ab..f1bd5487b8 100644
--- a/misc/demo/assets/plunker.js
+++ b/misc/demo/assets/plunker.js
@@ -4,7 +4,7 @@ angular.module('plunker', [])
return function (ngVersion, bsVersion, version, module, content) {
- var form = angular.element('
+ For Angular 2 support, check out
+
+ ng-bootstrap
+ , created by the UI Bootstrap team.
+
Dependencies
This repository contains a set of native AngularJS directives based on
@@ -173,8 +180,9 @@
Files to download
Alternativelly, if you are only interested in a subset of directives, you can
create your own build.
-
Whichever method you choose the good news that the overall size of a download is very small:
- <76kB for all directives (~20kB with gzip compression!)
+
Whichever method you choose the good news that the overall size of a download is fairly small:
+ 122K minified for all directives with templates and 98K without (~31kB with gzip
+ compression, with templates, and 28K gzipped without)
Installation
As soon as you've got all the files downloaded and included in your page you just need to declare
a dependency on the ui.bootstrapmodule:
diff --git a/package.json b/package.json
index 559928ab92..ca5cfe407d 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,15 @@
{
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
"name": "angular-ui-bootstrap",
- "version": "2.0.0-SNAPSHOT",
+ "version": "2.5.4",
+ "description": "Native AngularJS (Angular) directives for Bootstrap",
"homepage": "http://angular-ui.github.io/bootstrap/",
+ "keywords": [
+ "angularjs",
+ "angular",
+ "bootstrap",
+ "ui"
+ ],
"dependencies": {},
"directories": {
"lib": "src/"
@@ -15,6 +22,7 @@
],
"main": "index.js",
"scripts": {
+ "demo": "grunt after-test && static dist -a 0.0.0.0 -H '{\"Cache-Control\": \"no-cache, must-revalidate\"}'",
"test": "grunt"
},
"repository": {
@@ -22,10 +30,11 @@
"url": "https://github.com/angular-ui/bootstrap.git"
},
"devDependencies": {
- "angular": "1.5.5",
- "angular-mocks": "1.5.5",
- "angular-sanitize": "1.5.5",
+ "angular": "1.6.1",
+ "angular-mocks": "1.6.1",
+ "angular-sanitize": "1.6.1",
"grunt": "^0.4.5",
+ "grunt-cli": "^1.2.0",
"grunt-contrib-concat": "^1.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-uglify": "^1.0.1",
@@ -44,6 +53,7 @@
"load-grunt-tasks": "^3.3.0",
"lodash": "^4.1.0",
"marked": "^0.3.5",
+ "node-static": "^0.7.8",
"semver": "^5.0.1",
"shelljs": "^0.6.0"
},
diff --git a/src/accordion/accordion.js b/src/accordion/accordion.js
index 586314c9c6..9b07a96131 100644
--- a/src/accordion/accordion.js
+++ b/src/accordion/accordion.js
@@ -1,4 +1,4 @@
-angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
+angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])
.constant('uibAccordionConfig', {
closeOthers: true
@@ -58,7 +58,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
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
+ restrict: 'A',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
},
@@ -74,6 +74,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
};
},
link: function(scope, element, attrs, accordionCtrl) {
+ element.addClass('panel');
accordionCtrl.addGroup(scope);
scope.openClass = attrs.openClass || 'panel-open';
diff --git a/src/accordion/docs/demo.html b/src/accordion/docs/demo.html
index 83f643fff7..2b119b3e5e 100644
--- a/src/accordion/docs/demo.html
+++ b/src/accordion/docs/demo.html
@@ -1,18 +1,16 @@
@@ -28,35 +26,35 @@
-
+
This content is straight in the template.
-
-
+
+
{{group.content}}
-
-
+
+
The body of the uib-accordion group grows to fit the contents
{{item}}
-
-
+
+
Hello
-
-
+
+
Custom template with custom header template
World
-
-
+
+
Please, to delete your account, click the button below
-
-
+
+
I can have markup, too!
This is just some content to illustrate fancy headings.
-
+
diff --git a/src/accordion/docs/readme.md b/src/accordion/docs/readme.md
index a20a951d6e..25f790c3fa 100644
--- a/src/accordion/docs/readme.md
+++ b/src/accordion/docs/readme.md
@@ -32,11 +32,6 @@ The body of each accordion group is transcluded into the body of the collapsible
_(Default: `false`)_ -
Whether accordion group is open or closed.
-* `panel-class`
-
- _(Default: `panel-default`)_ -
- Add ability to use Bootstrap's contextual panel classes (panel-primary, panel-success, panel-info, etc...) or your own. This must be a string.
-
* `template-url`
_(Default: `uib/template/accordion/accordion-group.html`)_ -
Add the ability to override the template used on the component.
diff --git a/src/accordion/index.js b/src/accordion/index.js
index 0209794844..491b4f518f 100644
--- a/src/accordion/index.js
+++ b/src/accordion/index.js
@@ -1,4 +1,5 @@
require('../collapse');
+require('../tabindex');
require('../../template/accordion/accordion-group.html.js');
require('../../template/accordion/accordion.html.js');
require('./accordion');
diff --git a/src/accordion/test/accordion.spec.js b/src/accordion/test/accordion.spec.js
index 2cae4bd4da..bb64a4b2c6 100644
--- a/src/accordion/test/accordion.spec.js
+++ b/src/accordion/test/accordion.spec.js
@@ -176,12 +176,12 @@ describe('uib-accordion', function() {
var tpl =
'' +
- '' +
+ '' +
'';
element = $compile(tpl)(scope);
scope.$digest();
- expect(element.find('[template-url]').html()).toBe('baz');
+ expect(element.find('[template-url]').html()).toBe('
' +
'';
element = $compile(tpl)(scope);
scope.$digest();
@@ -551,7 +551,7 @@ describe('uib-accordion', function() {
});
it('attaches the same scope to the transcluded heading and body', function() {
- expect(findGroupLink(0).find('span.ng-scope').scope().$id).toBe(findGroupBody(0).find('span').scope().$id);
+ expect(findGroupLink(0).scope().$id).toBe(findGroupBody(0).scope().$id);
});
it('should wrap the transcluded content in a span', function() {
@@ -565,10 +565,10 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'' +
- '' +
+ '
' +
'
Heading Element {{x}}
' +
'Body' +
- '' +
+ '
' +
'';
element = $compile(tpl)(scope);
scope.$digest();
@@ -580,7 +580,7 @@ describe('uib-accordion', function() {
});
it('attaches the same scope to the transcluded heading and body', function() {
- expect(findGroupLink(0).find('span.ng-scope').scope().$id).toBe(findGroupBody(0).find('span').scope().$id);
+ expect(findGroupLink(0).scope().$id).toBe(findGroupBody(0).scope().$id);
});
it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
@@ -588,7 +588,7 @@ describe('uib-accordion', function() {
describe('uib-accordion-heading, with repeating uib-accordion-groups', function() {
it('should clone the uib-accordion-heading for each group', function() {
- element = $compile('{{x}}')(scope);
+ element = $compile('
{{x}}
')(scope);
scope.$digest();
groups = element.find('.panel');
expect(groups.length).toBe(3);
@@ -600,7 +600,7 @@ describe('uib-accordion', function() {
describe('uib-accordion-heading attribute, with repeating uib-accordion-groups', function() {
it('should clone the uib-accordion-heading for each group', function() {
- element = $compile('
{{x}}
')(scope);
+ element = $compile('
{{x}}
')(scope);
scope.$digest();
groups = element.find('.panel');
expect(groups.length).toBe(3);
@@ -614,46 +614,11 @@ describe('uib-accordion', function() {
it('should transclude heading to a template using data-uib-accordion-header', inject(function($templateCache) {
$templateCache.put('foo/bar.html', '
');
- element = $compile('baz')(scope);
+ element = $compile('
baz
')(scope);
scope.$digest();
groups = element.find('.panel');
expect(findGroupLink(0).text()).toBe('baz');
}));
});
-
- describe('uib-accordion group panel class', function() {
- it('should use the default value when panel class is falsy - #3968', function() {
- element = $compile('Content')(scope);
- scope.$digest();
- groups = element.find('.panel');
- expect(groups.eq(0)).toHaveClass('panel-default');
-
- element = $compile('Content')(scope);
- scope.$digest();
- groups = element.find('.panel');
- expect(groups.eq(0)).toHaveClass('panel-default');
- });
-
- it('should use the specified value when not falsy - #3968', function() {
- element = $compile('Content')(scope);
- scope.$digest();
- groups = element.find('.panel');
- expect(groups.eq(0)).toHaveClass('custom-class');
- expect(groups.eq(0)).not.toHaveClass('panel-default');
- });
-
- it('should change class if panel-class is changed', function() {
- element = $compile('Content')(scope);
- scope.panelClass = 'custom-class';
- scope.$digest();
- groups = element.find('.panel');
- expect(groups.eq(0)).toHaveClass('custom-class');
-
- scope.panelClass = 'different-class';
- scope.$digest();
- expect(groups.eq(0)).toHaveClass('different-class');
- expect(groups.eq(0)).not.toHaveClass('custom-class');
- });
- });
});
});
diff --git a/src/alert/alert.js b/src/alert/alert.js
index 0ad230be7f..73387e55a5 100644
--- a/src/alert/alert.js
+++ b/src/alert/alert.js
@@ -1,7 +1,12 @@
angular.module('ui.bootstrap.alert', [])
-.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
+.controller('UibAlertController', ['$scope', '$element', '$attrs', '$interpolate', '$timeout', function($scope, $element, $attrs, $interpolate, $timeout) {
$scope.closeable = !!$attrs.close;
+ $element.addClass('alert');
+ $attrs.$set('role', 'alert');
+ if ($scope.closeable) {
+ $element.addClass('alert-dismissible');
+ }
var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
$interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
@@ -17,13 +22,12 @@ angular.module('ui.bootstrap.alert', [])
return {
controller: 'UibAlertController',
controllerAs: 'alert',
+ restrict: 'A',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/alert/alert.html';
},
transclude: true,
- replace: true,
scope: {
- type: '@',
close: '&'
}
};
diff --git a/src/alert/docs/demo.html b/src/alert/docs/demo.html
index e2ce4b3870..b599f48091 100644
--- a/src/alert/docs/demo.html
+++ b/src/alert/docs/demo.html
@@ -1,11 +1,9 @@
- {{alert.msg}}
- A happy alert!
+
{{alert.msg}}
+
A happy alert!
diff --git a/src/alert/docs/readme.md b/src/alert/docs/readme.md
index 3361f6418e..cf0bcad1ab 100644
--- a/src/alert/docs/readme.md
+++ b/src/alert/docs/readme.md
@@ -5,15 +5,11 @@ This directive can be used both to generate alerts from static and dynamic model
* `close()`
$ -
A callback function that gets fired when an `alert` is closed. If the attribute exists, a close button is displayed as well.
-
+
* `dismiss-on-timeout`
_(Default: `none`)_ -
Takes the number of milliseconds that specify the timeout duration, after which the alert will be closed. This attribute requires the presence of the `close` attribute.
-
+
* `template-url`
_(Default: `uib/template/alert/alert.html`)_ -
Add the ability to override the template used in the component.
-
-* `type`
- _(Default: `warning`)_ -
- Defines the type of the alert. Go to [bootstrap page](http://getbootstrap.com/components/#alerts) to see the type of alerts available.
diff --git a/src/alert/test/alert.spec.js b/src/alert/test/alert.spec.js
index 752127ae51..bc87c7f7ad 100644
--- a/src/alert/test/alert.spec.js
+++ b/src/alert/test/alert.spec.js
@@ -12,9 +12,10 @@ describe('uib-alert', function() {
element = angular.element(
'
' +
- '{{alert.msg}}' +
- '' +
+ '
' +
'');
scope.alerts = [
@@ -35,13 +36,13 @@ describe('uib-alert', function() {
}
function findContent(index) {
- return element.find('div[ng-transclude] span').eq(index);
+ return element.find('div[ng-transclude]').eq(index);
}
it('should expose the controller to the view', function() {
$templateCache.put('uib/template/alert/alert.html', '
{{alert.text}}
');
- element = $compile('')(scope);
+ element = $compile('')(scope);
scope.$digest();
var ctrl = element.controller('uib-alert');
@@ -50,16 +51,16 @@ describe('uib-alert', function() {
ctrl.text = 'foo';
scope.$digest();
- expect(element.html()).toBe('foo');
+ expect(element.html()).toBe('
foo
');
});
it('should support custom templates', function() {
$templateCache.put('foo/bar.html', '
baz
');
- element = $compile('')(scope);
+ element = $compile('')(scope);
scope.$digest();
- expect(element.html()).toBe('baz');
+ expect(element.html()).toBe('
baz
');
});
it('should generate alerts using ng-repeat', function() {
@@ -67,23 +68,6 @@ describe('uib-alert', function() {
expect(alerts.length).toEqual(3);
});
- it('should use correct classes for different alert types', function() {
- var alerts = createAlerts();
- expect(alerts.eq(0)).toHaveClass('alert-success');
- expect(alerts.eq(1)).toHaveClass('alert-error');
- expect(alerts.eq(2)).toHaveClass('alert-warning');
- });
-
- it('should respect alert type binding', function() {
- var alerts = createAlerts();
- expect(alerts.eq(0)).toHaveClass('alert-success');
-
- scope.alerts[0].type = 'error';
- scope.$digest();
-
- expect(alerts.eq(0)).toHaveClass('alert-error');
- });
-
it('should show the alert content', function() {
var alerts = createAlerts();
@@ -115,22 +99,15 @@ describe('uib-alert', function() {
});
it('should not show close button and have the dismissible class if no close callback specified', function() {
- element = $compile('No close')(scope);
+ element = $compile('
No close
')(scope);
scope.$digest();
expect(findCloseButton(0)).toBeHidden();
expect(element).not.toHaveClass('alert-dismissible');
});
- it('should be possible to add additional classes for alert', function() {
- var element = $compile('Default alert!')(scope);
- scope.$digest();
- expect(element).toHaveClass('alert-block');
- expect(element).toHaveClass('alert-info');
- });
-
it('should close automatically if dismiss-on-timeout is defined on the element', function() {
scope.removeAlert = jasmine.createSpy();
- $compile('Default alert!')(scope);
+ $compile('
Default alert!
')(scope);
scope.$digest();
$timeout.flush();
@@ -140,7 +117,7 @@ describe('uib-alert', function() {
it('should not close immediately with a dynamic dismiss-on-timeout', function() {
scope.removeAlert = jasmine.createSpy();
scope.dismissTime = 500;
- $compile('Default alert!')(scope);
+ $compile('
'
)(scope);
$rootScope.$digest();
diff --git a/src/collapse/collapse.js b/src/collapse/collapse.js
index cafd93a3ea..7e605b641d 100644
--- a/src/collapse/collapse.js
+++ b/src/collapse/collapse.js
@@ -5,16 +5,42 @@ angular.module('ui.bootstrap.collapse', [])
return {
link: function(scope, element, attrs) {
var expandingExpr = $parse(attrs.expanding),
- expandedExpr = $parse(attrs.expanded),
- collapsingExpr = $parse(attrs.collapsing),
- collapsedExpr = $parse(attrs.collapsed);
+ expandedExpr = $parse(attrs.expanded),
+ collapsingExpr = $parse(attrs.collapsing),
+ collapsedExpr = $parse(attrs.collapsed),
+ horizontal = false,
+ css = {},
+ cssTo = {};
- if (!scope.$eval(attrs.uibCollapse)) {
- element.addClass('in')
- .addClass('collapse')
- .attr('aria-expanded', true)
- .attr('aria-hidden', false)
- .css({height: 'auto'});
+ init();
+
+ function init() {
+ horizontal = !!('horizontal' in attrs);
+ if (horizontal) {
+ css = {
+ width: ''
+ };
+ cssTo = {width: '0'};
+ } else {
+ css = {
+ height: ''
+ };
+ cssTo = {height: '0'};
+ }
+ if (!scope.$eval(attrs.uibCollapse)) {
+ element.addClass('in')
+ .addClass('collapse')
+ .attr('aria-expanded', true)
+ .attr('aria-hidden', false)
+ .css(css);
+ }
+ }
+
+ function getScrollFromElement(element) {
+ if (horizontal) {
+ return {width: element.scrollWidth + 'px'};
+ }
+ return {height: element.scrollHeight + 'px'};
}
function expand() {
@@ -33,20 +59,26 @@ angular.module('ui.bootstrap.collapse', [])
$animateCss(element, {
addClass: 'in',
easing: 'ease',
- to: { height: element[0].scrollHeight + 'px' }
+ css: {
+ overflow: 'hidden'
+ },
+ to: getScrollFromElement(element[0])
}).start()['finally'](expandDone);
} else {
$animate.addClass(element, 'in', {
- to: { height: element[0].scrollHeight + 'px' }
+ css: {
+ overflow: 'hidden'
+ },
+ to: getScrollFromElement(element[0])
}).then(expandDone);
}
- });
+ }, angular.noop);
}
function expandDone() {
element.removeClass('collapsing')
.addClass('collapse')
- .css({height: 'auto'});
+ .css(css);
expandedExpr(scope);
}
@@ -58,10 +90,10 @@ angular.module('ui.bootstrap.collapse', [])
$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'})
+ // IMPORTANT: The width must be set before adding "collapsing" class.
+ // Otherwise, the browser attempts to animate from width 0 (in
+ // collapsing class) to the given width here.
+ .css(getScrollFromElement(element[0]))
// initially all panel collapse have the collapse class, this removal
// prevents the animation from jumping to collapsed state
.removeClass('collapse')
@@ -72,18 +104,18 @@ angular.module('ui.bootstrap.collapse', [])
if ($animateCss) {
$animateCss(element, {
removeClass: 'in',
- to: {height: '0'}
+ to: cssTo
}).start()['finally'](collapseDone);
} else {
$animate.removeClass(element, 'in', {
- to: {height: '0'}
+ to: cssTo
}).then(collapseDone);
}
- });
+ }, angular.noop);
}
function collapseDone() {
- element.css({height: '0'}); // Required so that collapse works when animation is disabled
+ element.css(cssTo); // Required so that collapse works when animation is disabled
element.removeClass('collapsing')
.addClass('collapse');
collapsedExpr(scope);
diff --git a/src/collapse/docs/demo.html b/src/collapse/docs/demo.html
index 462bda3ba0..f5e06e7fbc 100644
--- a/src/collapse/docs/demo.html
+++ b/src/collapse/docs/demo.html
@@ -1,7 +1,40 @@
+
-
+
Resize window to less than 768 pixels to display mobile menu toggle button.
+
+
+
Some content
+
+
+
+
+
Some content
+
diff --git a/src/collapse/docs/demo.js b/src/collapse/docs/demo.js
index 897eecaf58..f685290001 100644
--- a/src/collapse/docs/demo.js
+++ b/src/collapse/docs/demo.js
@@ -1,3 +1,5 @@
angular.module('ui.bootstrap.demo').controller('CollapseDemoCtrl', function ($scope) {
+ $scope.isNavCollapsed = true;
$scope.isCollapsed = false;
+ $scope.isCollapsedHorizontal = false;
});
diff --git a/src/collapse/docs/readme.md b/src/collapse/docs/readme.md
index 60237b9952..5cdacfd3d0 100644
--- a/src/collapse/docs/readme.md
+++ b/src/collapse/docs/readme.md
@@ -28,3 +28,10 @@
_(Default: `false`)_ -
Whether the element should be collapsed or not.
+* `horizontal`
+ $ -
+ An optional attribute that permit to collapse horizontally.
+
+### Known Issues
+
+When using the `horizontal` attribute with this directive, CSS can reflow as the collapse element goes from `0px` to its desired end width, which can result in height changes. This can cause animations to not appear to run. The best way around this is to set a fixed height via CSS on the horizontal collapse element so that this situation does not occur, and so the animation can run as expected.
diff --git a/src/collapse/test/collapseHorizontally.spec.js b/src/collapse/test/collapseHorizontally.spec.js
new file mode 100644
index 0000000000..e5b22bdb56
--- /dev/null
+++ b/src/collapse/test/collapseHorizontally.spec.js
@@ -0,0 +1,255 @@
+describe('collapse directive', function() {
+ var elementH, compileFnH, scope, $compile, $animate, $q;
+
+ beforeEach(module('ui.bootstrap.collapse'));
+ beforeEach(module('ngAnimateMock'));
+ beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {
+ scope = _$rootScope_;
+ $compile = _$compile_;
+ $animate = _$animate_;
+ $q = _$q_;
+ }));
+
+ beforeEach(function() {
+ elementH = angular.element(
+ '
')($rootScope);
}
beforeEach(function() {
@@ -69,9 +69,10 @@ describe('uib-dropdown', function() {
});
it('should close on escape key & focus toggle element', function() {
+ var dropdownMenu = element.find('[uib-dropdown-menu]');
$document.find('body').append(element);
clickDropdownToggle();
- var event = triggerKeyDown(element, 27);
+ var event = triggerKeyDown(dropdownMenu, 27);
expect(element).not.toHaveClass(dropdownConfig.openClass);
expect(element.find('a')).toHaveFocus();
expect(event.stopPropagation).toHaveBeenCalled();
@@ -98,17 +99,17 @@ describe('uib-dropdown', function() {
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
expect(elm2).not.toHaveClass(dropdownConfig.openClass);
- clickDropdownToggle( elm1 );
+ clickDropdownToggle(elm1);
expect(elm1).toHaveClass(dropdownConfig.openClass);
expect(elm2).not.toHaveClass(dropdownConfig.openClass);
- clickDropdownToggle( elm2 );
+ clickDropdownToggle(elm2);
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
expect(elm2).toHaveClass(dropdownConfig.openClass);
});
it('should not toggle if the element has `disabled` class', function() {
- var elm = $compile('
Hello
')($rootScope);
+ var elm = $compile('
Hello
')($rootScope);
clickDropdownToggle( elm );
expect(elm).not.toHaveClass(dropdownConfig.openClass);
});
@@ -121,7 +122,7 @@ describe('uib-dropdown', function() {
it('should not toggle if the element has `ng-disabled` as true', function() {
$rootScope.isdisabled = true;
- var elm = $compile('
Hello
')($rootScope);
+ var elm = $compile('
Hello
')($rootScope);
$rootScope.$digest();
elm.find('div').click();
expect(elm).not.toHaveClass(dropdownConfig.openClass);
@@ -134,7 +135,7 @@ describe('uib-dropdown', function() {
it('should unbind events on scope destroy', function() {
var $scope = $rootScope.$new();
- var elm = $compile('
')($rootScope);
}
beforeEach(function() {
+ $document.find('body').append(angular.element(''));
+
+ $rootScope.appendTo = container = $document.find('#dropdown-container');
+
element = dropdown();
$document.find('body').append(element);
});
afterEach(function() {
- element.remove();
+ // Cleanup the extra elements we appended
+ $document.find('#dropdown-container').remove();
});
- it('adds the menu to the body', function() {
- expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
+ it('does not add the menu to the container', function() {
+ expect($document.find('#dropdown-menu').parent()[0]).not.toBe(container[0]);
+ });
+ it('does not add open class on container', function() {
+ expect(container).not.toHaveClass('uib-dropdown-open');
});
- it('focuses the dropdown element on close', function() {
- var toggle = element.find('[uib-dropdown-toggle]');
- var menu = $document.find('#dropdown-menu a');
- toggle.trigger('click');
- menu.focus();
-
- menu.trigger('click');
+ describe('when toggled open', function() {
+ var toggle;
+ beforeEach(function() {
+ toggle = element.find('[uib-dropdown-toggle]');
+ toggle.trigger('click');
+ });
+ it('adds the menu to the container', function() {
+ expect($document.find('#dropdown-menu').parent()[0]).toBe(container[0]);
+ });
+ it('adds open class on container', function() {
+ expect(container).toHaveClass('uib-dropdown-open');
+ });
- expect(document.activeElement).toBe(toggle[0]);
- });
+ describe('when toggled closed', function() {
+ beforeEach(function() {
+ toggle.trigger('click');
+ });
+ it('removes the menu from the container', function() {
+ expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
+ });
+ it('removes open class from container', function() {
+ expect(container).not.toHaveClass('uib-dropdown-open');
+ });
+ });
- it('removes the menu when the dropdown is removed', function() {
- element.remove();
- $rootScope.$digest();
- expect($document.find('#dropdown-menu').length).toEqual(0);
+ describe('when closed by clicking on menu', function() {
+ var menu;
+ beforeEach(function() {
+ menu = $document.find('#dropdown-menu a');
+ menu.focus();
+ menu.trigger('click');
+ });
+ it('focuses the dropdown element on close', function() {
+ expect(document.activeElement).toBe(toggle[0]);
+ });
+ it('removes the menu from the container', function() {
+ expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
+ });
+ it('removes open class from container', function() {
+ expect(container).not.toHaveClass('uib-dropdown-open');
+ });
+ });
+ describe('when the dropdown is removed', function() {
+ beforeEach(function() {
+ element.remove();
+ $rootScope.$digest();
+ });
+ it('removes the menu from the container', function() {
+ expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
+ });
+ });
});
});
- describe('using dropdown-append-to', function() {
- var initialPage;
-
+ describe('using dropdown-append-to with two dropdowns', function() {
function dropdown() {
- return $compile('
')($rootScope);
+ }
+
+ beforeEach(function() {
+ element = dropdown();
+ $document.find('body').append(element);
+
+ var menu = $document.find('#dropdown-menu');
+ menu.css('position', 'absolute');
+ });
+
+ afterEach(function() {
+ element.remove();
+ });
+
+ it('should align the menu correctly when the body has no vertical scrollbar', function() {
+ var toggle = element.find('[uib-dropdown-toggle]');
+ var menu = $document.find('#dropdown-menu');
+ toggle.trigger('click');
+
+ // Get the offsets of the rightmost position of both the toggle and the menu (offset from the left of the window)
+ var toggleRight = Math.round(toggle.offset().left + toggle.outerWidth());
+ var menuRight = Math.round(menu.offset().left + menu.outerWidth());
+ expect(menuRight).toBe(toggleRight);
+ });
+ });
});
diff --git a/src/modal/docs/demo.html b/src/modal/docs/demo.html
index ac96e0780b..616b1a0c03 100644
--- a/src/modal/docs/demo.html
+++ b/src/modal/docs/demo.html
@@ -1,25 +1,44 @@
-
+
+
-
-
-
-
-
Selection from a modal: {{ selected }}
-
\ No newline at end of file
+
+
+
+
+
+
+
+
Selection from a modal: {{ $ctrl.selected }}
+
+
+
diff --git a/src/modal/docs/demo.js b/src/modal/docs/demo.js
index 1055775653..53b335dad1 100644
--- a/src/modal/docs/demo.js
+++ b/src/modal/docs/demo.js
@@ -1,51 +1,126 @@
-angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $uibModal, $log) {
+angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($uibModal, $log, $document) {
+ var $ctrl = this;
+ $ctrl.items = ['item1', 'item2', 'item3'];
- $scope.items = ['item1', 'item2', 'item3'];
-
- $scope.animationsEnabled = true;
-
- $scope.open = function (size) {
+ $ctrl.animationsEnabled = true;
+ $ctrl.open = function (size, parentSelector) {
+ var parentElem = parentSelector ?
+ angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
var modalInstance = $uibModal.open({
- animation: $scope.animationsEnabled,
+ animation: $ctrl.animationsEnabled,
+ ariaLabelledBy: 'modal-title',
+ ariaDescribedBy: 'modal-body',
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
+ controllerAs: '$ctrl',
size: size,
+ appendTo: parentElem,
resolve: {
items: function () {
- return $scope.items;
+ return $ctrl.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
- $scope.selected = selectedItem;
+ $ctrl.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
- $scope.toggleAnimation = function () {
- $scope.animationsEnabled = !$scope.animationsEnabled;
+ $ctrl.openComponentModal = function () {
+ var modalInstance = $uibModal.open({
+ animation: $ctrl.animationsEnabled,
+ component: 'modalComponent',
+ resolve: {
+ items: function () {
+ return $ctrl.items;
+ }
+ }
+ });
+
+ modalInstance.result.then(function (selectedItem) {
+ $ctrl.selected = selectedItem;
+ }, function () {
+ $log.info('modal-component dismissed at: ' + new Date());
+ });
};
+ $ctrl.openMultipleModals = function () {
+ $uibModal.open({
+ animation: $ctrl.animationsEnabled,
+ ariaLabelledBy: 'modal-title-bottom',
+ ariaDescribedBy: 'modal-body-bottom',
+ templateUrl: 'stackedModal.html',
+ size: 'sm',
+ controller: function($scope) {
+ $scope.name = 'bottom';
+ }
+ });
+
+ $uibModal.open({
+ animation: $ctrl.animationsEnabled,
+ ariaLabelledBy: 'modal-title-top',
+ ariaDescribedBy: 'modal-body-top',
+ templateUrl: 'stackedModal.html',
+ size: 'sm',
+ controller: function($scope) {
+ $scope.name = 'top';
+ }
+ });
+ };
+
+ $ctrl.toggleAnimation = function () {
+ $ctrl.animationsEnabled = !$ctrl.animationsEnabled;
+ };
});
// Please note that $uibModalInstance represents a modal window (instance) dependency.
// It is not the same as the $uibModal service used above.
-angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, items) {
-
- $scope.items = items;
- $scope.selected = {
- item: $scope.items[0]
+angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($uibModalInstance, items) {
+ var $ctrl = this;
+ $ctrl.items = items;
+ $ctrl.selected = {
+ item: $ctrl.items[0]
};
- $scope.ok = function () {
- $uibModalInstance.close($scope.selected.item);
+ $ctrl.ok = function () {
+ $uibModalInstance.close($ctrl.selected.item);
};
- $scope.cancel = function () {
+ $ctrl.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
});
+
+// Please note that the close and dismiss bindings are from $uibModalInstance.
+
+angular.module('ui.bootstrap.demo').component('modalComponent', {
+ templateUrl: 'myModalContent.html',
+ bindings: {
+ resolve: '<',
+ close: '&',
+ dismiss: '&'
+ },
+ controller: function () {
+ var $ctrl = this;
+
+ $ctrl.$onInit = function () {
+ $ctrl.items = $ctrl.resolve.items;
+ $ctrl.selected = {
+ item: $ctrl.items[0]
+ };
+ };
+
+ $ctrl.ok = function () {
+ $ctrl.close({$value: $ctrl.selected.item});
+ };
+
+ $ctrl.cancel = function () {
+ $ctrl.dismiss({$value: 'cancel'});
+ };
+ }
+});
diff --git a/src/modal/docs/readme.md b/src/modal/docs/readme.md
index 50f3fc9105..66b6f36803 100644
--- a/src/modal/docs/readme.md
+++ b/src/modal/docs/readme.md
@@ -1,5 +1,5 @@
`$uibModal` is a service to create modal windows.
-Creating modals is straightforward: create a template, a controller and reference them when using `$uibModal`.
+Creating modals is straightforward: create a template and controller, and reference them when using `$uibModal`.
The `$uibModal` service has only one method: `open(options)`.
@@ -10,15 +10,23 @@ The `$uibModal` service has only one method: `open(options)`.
* `animation`
_(Type: `boolean`, Default: `true`)_ -
Set to false to disable animations on new modal/backdrop. Does not toggle animations for modals/backdrops that are already displayed.
-
-* `appendTo`
+
+* `appendTo`
_(Type: `angular.element`, Default: `body`: Example: `$document.find('aside').eq(0)`)_ -
Appends the modal to a specific element.
-
+
+* `ariaDescribedBy`
+ _(Type: `string`, `my-modal-description`)_ -
+ Sets the [`aria-describedby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-describedby) property on the modal. The value should be an id (without the leading `#`) pointing to the element that describes your modal. Typically, this will be the text on your modal, but does not include something the user would interact with, like buttons or a form. Omitting this option will not impact sighted users but will weaken your accessibility support.
+
+* `ariaLabelledBy`
+ _(Type: `string`, `my-modal-title`)_ -
+ Sets the [`aria-labelledby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-labelledby) property on the modal. The value should be an id (without the leading `#`) pointing to the element that labels your modal. Typically, this will be a header element. Omitting this option will not impact sighted users but will weaken your accessibility support.
+
* `backdrop`
_(Type: `boolean|string`, Default: `true`)_ -
Controls presence of a backdrop. Allowed values: `true` (default), `false` (no backdrop), `'static'` (disables modal closing by click on the backdrop).
-
+
* `backdropClass`
_(Type: `string`)_ -
Additional CSS class(es) to be added to a modal backdrop template.
@@ -27,15 +35,29 @@ The `$uibModal` service has only one method: `open(options)`.
_(Type: `boolean`, Default: `false`)_ -
When used with `controllerAs` & set to `true`, it will bind the $scope properties onto the controller.
+* `component`
+ _(Type: `string`, Example: `myComponent`)_ -
+ A string reference to the component to be rendered that is registered with Angular's compiler. If using a directive, the directive must have `restrict: 'E'` and a template or templateUrl set.
+
+ It supports these bindings:
+
+ * `close` - A method that can be used to close a modal, passing a result. The result must be passed in this format: `{$value: myResult}`
+
+ * `dismiss` - A method that can be used to dismiss a modal, passing a result. The result must be passed in this format: `{$value: myRejectedResult}`
+
+ * `modalInstance` - The modal instance. This is the same `$uibModalInstance` injectable found when using `controller`.
+
+ * `resolve` - An object of the modal resolve values. See [UI Router resolves](#ui-router-resolves) for details.
+
* `controller`
_(Type: `function|string|array`, Example: `MyModalController`)_ -
A controller for the modal instance, either a controller name as a string, or an inline controller function, optionally wrapped in array notation for dependency injection. Allows the controller-as syntax. Has a special `$uibModalInstance` injectable to access the modal instance.
* `controllerAs`
- _(Type: `string`, Example: `ctrl`)_ -
+ _(Type: `string`, Example: `ctrl`)_ -
An alternative to the controller-as syntax. Requires the `controller` option to be provided as well.
-* `keyboard` -
+* `keyboard` -
_(Type: `boolean`, Default: `true`)_ -
Indicates whether the dialog should be closable by hitting the ESC key.
@@ -76,7 +98,7 @@ The `$uibModal` service has only one method: `open(options)`.
CSS class(es) to be added to the top modal window.
Global defaults may be set for `$uibModal` via `$uibModalProvider.options`.
-
+
#### return
The `open` method returns a modal instance, an object with the following properties:
@@ -103,8 +125,8 @@ The `open` method returns a modal instance, an object with the following propert
* `rendered`
_(Type: `promise`)_ -
- Is resolved when a modal is rendered.
-
+ Is resolved when a modal is rendered.
+
---
The scope associated with modal's content is augmented with:
@@ -125,9 +147,9 @@ Also, when using `bindToController`, you can define an `$onInit` method in the c
Events fired:
-* `$uibUnscheduledDestruction` -
+* `$uibUnscheduledDestruction` -
This event is fired if the $scope is destroyed via unexpected mechanism, such as it being passed in the modal options and a $route/$state transition occurs. The modal will also be dismissed.
-
+
* `modal.closing` -
This event is broadcast to the modal scope before the modal closes. If the listener calls preventDefault() on the event, then the modal will remain open.
Also, the `$close` and `$dismiss` methods returns true if the event was executed. This event also includes a parameter for the result/reason and a boolean that indicates whether the modal is being closed (true) or dismissed.
@@ -136,4 +158,4 @@ Events fired:
If one wants to have the modal resolve using [UI Router's](https://github.com/angular-ui/ui-router) pre-1.0 resolve mechanism, one can call `$uibResolve.setResolver('$resolve')` in the configuration phase of the application. One can also provide a custom resolver as well, as long as the signature conforms to UI Router's [$resolve](http://angular-ui.github.io/ui-router/site/#/api/ui.router.util.$resolve).
-When the modal is opened with a controller, a `$resolve` object is exposed on the template with the resolved values from the resolve object.
+When the modal is opened with a controller, a `$resolve` object is exposed on the template with the resolved values from the resolve object. If using the component option, see details on how to access this object in component section of the modal documentation.
diff --git a/src/modal/index-nocss.js b/src/modal/index-nocss.js
index 10473fae44..d3f8a94b7d 100644
--- a/src/modal/index-nocss.js
+++ b/src/modal/index-nocss.js
@@ -1,11 +1,11 @@
+require('../multiMap');
require('../position/index-nocss.js');
require('../stackedMap');
-require('../../template/modal/backdrop.html.js');
require('../../template/modal/window.html.js');
require('./modal');
var MODULE_NAME = 'ui.bootstrap.module.modal';
-angular.module(MODULE_NAME, ['ui.bootstrap.modal', 'uib/template/modal/backdrop.html', 'uib/template/modal/window.html']);
+angular.module(MODULE_NAME, ['ui.bootstrap.modal', 'uib/template/modal/window.html']);
module.exports = MODULE_NAME;
diff --git a/src/modal/modal.js b/src/modal/modal.js
index 356299da8b..679ff188e8 100644
--- a/src/modal/modal.js
+++ b/src/modal/modal.js
@@ -1,59 +1,4 @@
-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];
- }
- }
- };
- }
- };
- })
-
+angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
/**
* Pluggable resolve mechanism for the modal resolve resolution
* Supports UI Router's $resolve service
@@ -106,8 +51,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
.directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
function($animate, $injector, $modalStack) {
return {
- replace: true,
- templateUrl: 'uib/template/modal/backdrop.html',
+ restrict: 'A',
compile: function(tElement, tAttrs) {
tElement.addClass(tAttrs.backdropClass);
return linkFn;
@@ -136,13 +80,12 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
scope: {
index: '@'
},
- replace: true,
+ restrict: 'A',
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;
@@ -165,14 +108,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
// {@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.
+ // Deferred object that will be resolved when this modal is rendered.
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();
- }
+ // Resolve render promise post-digest
+ scope.$$postDigest(function() {
+ modalRenderDeferObj.resolve();
});
modalRenderDeferObj.promise.then(function() {
@@ -201,7 +141,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
/**
* 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.
+ * directive that causes focus) then there's no need to try to focus anything.
*/
if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
var inputWithAutofocus = element[0].querySelector('[autofocus]');
@@ -235,16 +175,16 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
};
})
- .directive('uibModalTransclude', function() {
+ .directive('uibModalTransclude', ['$animate', function($animate) {
return {
link: function(scope, element, attrs, controller, transclude) {
transclude(scope.$parent, function(clone) {
element.empty();
- element.append(clone);
+ $animate.enter(clone, element);
});
}
};
- })
+ }])
.factory('$uibModalStack', ['$animate', '$animateCss', '$document',
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
@@ -259,12 +199,22 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
};
var topModalIndex = 0;
var previousTopOpenedModal = null;
+ var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count';
//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 tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' +
+ 'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' +
+ 'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]';
var scrollbarPadding;
+ var SNAKE_CASE_REGEXP = /[A-Z]/g;
+
+ // TODO: extract into common dependency with tooltip
+ function snake_case(name) {
+ var separator = '-';
+ return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+ }
function isVisible(element) {
return !!(element.offsetWidth ||
@@ -380,6 +330,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
afterAnimating.done = true;
$animate.leave(domEl).then(function() {
+ if (done) {
+ done();
+ }
+
domEl.remove();
if (closedDeferred) {
closedDeferred.resolve();
@@ -387,9 +341,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
});
scope.$destroy();
- if (done) {
- done();
- }
}
}
@@ -468,66 +419,152 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
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);
+ backdropDomEl.attr({
+ 'class': 'modal-backdrop',
+ 'ng-style': '{\'z-index\': 1040 + (index && 1 || 0) + index*10}',
+ 'uib-modal-animation-class': 'fade',
+ 'modal-in-class': 'in'
+ });
+ if (modal.backdropClass) {
+ backdropDomEl.addClass(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'});
+ if ($uibPosition.isScrollable(appendToElement)) {
+ scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
+ if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
+ appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
+ }
}
}
+ var content;
+ if (modal.component) {
+ content = document.createElement(snake_case(modal.component.name));
+ content = angular.element(content);
+ content.attr({
+ resolve: '$resolve',
+ 'modal-instance': '$uibModalInstance',
+ close: '$close($value)',
+ dismiss: '$dismiss($value)'
+ });
+ } else {
+ content = modal.content;
+ }
+
// 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({
+ 'class': 'modal',
'template-url': modal.windowTemplateUrl,
- 'window-class': modal.windowClass,
'window-top-class': modal.windowTopClass,
+ 'role': 'dialog',
+ 'aria-labelledby': modal.ariaLabelledBy,
+ 'aria-describedby': modal.ariaDescribedBy,
'size': modal.size,
'index': topModalIndex,
- 'animate': 'animate'
- }).html(modal.content);
+ 'animate': 'animate',
+ 'ng-style': '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}',
+ 'tabindex': -1,
+ 'uib-modal-animation-class': 'fade',
+ 'modal-in-class': 'in'
+ }).append(content);
+ if (modal.windowClass) {
+ angularDomEl.addClass(modal.windowClass);
+ }
+
if (modal.animation) {
angularDomEl.attr('modal-animation', 'true');
}
appendToElement.addClass(modalBodyClass);
+ if (modal.scope) {
+ // we need to explicitly add the modal index to the modal scope
+ // because it is needed by ngStyle to compute the zIndex property.
+ modal.scope.$$topModalIndex = topModalIndex;
+ }
$animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
openedWindows.top().value.modalDomEl = angularDomEl;
openedWindows.top().value.modalOpener = modalOpener;
+
+ applyAriaHidden(angularDomEl);
+
+ function applyAriaHidden(el) {
+ if (!el || el[0].tagName === 'BODY') {
+ return;
+ }
+
+ getSiblings(el).forEach(function(sibling) {
+ var elemIsAlreadyHidden = sibling.getAttribute('aria-hidden') === 'true',
+ ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);
+
+ if (!ariaHiddenCount) {
+ ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
+ }
+
+ sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);
+ sibling.setAttribute('aria-hidden', 'true');
+ });
+
+ return applyAriaHidden(el.parent());
+
+ function getSiblings(el) {
+ var children = el.parent() ? el.parent().children() : [];
+
+ return Array.prototype.filter.call(children, function(child) {
+ return child !== el[0];
+ });
+ }
+ }
};
function broadcastClosing(modalWindow, resultOrReason, closing) {
return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
}
+ function unhideBackgroundElements() {
+ Array.prototype.forEach.call(
+ document.querySelectorAll('[' + ARIA_HIDDEN_ATTRIBUTE_NAME + ']'),
+ function(hiddenEl) {
+ var ariaHiddenCount = parseInt(hiddenEl.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10),
+ newHiddenCount = ariaHiddenCount - 1;
+ hiddenEl.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, newHiddenCount);
+
+ if (!newHiddenCount) {
+ hiddenEl.removeAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME);
+ hiddenEl.removeAttribute('aria-hidden');
+ }
+ }
+ );
+ }
+
$modalStack.close = function(modalInstance, result) {
var modalWindow = openedWindows.get(modalInstance);
+ unhideBackgroundElements();
if (modalWindow && broadcastClosing(modalWindow, result, true)) {
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);
+ unhideBackgroundElements();
if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
modalWindow.value.deferred.reject(reason);
@@ -599,7 +636,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
- var elements = modalDomE1[0].querySelectorAll(tabableSelector);
+ var elements = modalDomE1[0].querySelectorAll(tabbableSelector);
return elements ?
Array.prototype.filter.call(elements, function(element) {
return isVisible(element);
@@ -658,13 +695,22 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
modalOptions.resolve = modalOptions.resolve || {};
modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
+ if (!modalOptions.appendTo.length) {
+ throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
+ }
+
//verify options
- if (!modalOptions.template && !modalOptions.templateUrl) {
- throw new Error('One of template or templateUrl options is required.');
+ if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
+ throw new Error('One of component or template or templateUrl options is required.');
}
- var templateAndResolvePromise =
- $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
+ var templateAndResolvePromise;
+ if (modalOptions.component) {
+ templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null));
+ } else {
+ templateAndResolvePromise =
+ $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
+ }
function resolveWithTemplate() {
return templateAndResolvePromise;
@@ -690,17 +736,34 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
}
});
+ var modal = {
+ scope: modalScope,
+ deferred: modalResultDeferred,
+ renderDeferred: modalRenderDeferred,
+ closedDeferred: modalClosedDeferred,
+ animation: modalOptions.animation,
+ backdrop: modalOptions.backdrop,
+ keyboard: modalOptions.keyboard,
+ backdropClass: modalOptions.backdropClass,
+ windowTopClass: modalOptions.windowTopClass,
+ windowClass: modalOptions.windowClass,
+ windowTemplateUrl: modalOptions.windowTemplateUrl,
+ ariaLabelledBy: modalOptions.ariaLabelledBy,
+ ariaDescribedBy: modalOptions.ariaDescribedBy,
+ size: modalOptions.size,
+ openedClass: modalOptions.openedClass,
+ appendTo: modalOptions.appendTo
+ };
+
+ var component = {};
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;
- });
+ if (modalOptions.component) {
+ constructLocals(component, false, true, false);
+ component.name = modalOptions.component;
+ modal.component = component;
+ } else if (modalOptions.controller) {
+ constructLocals(ctrlLocals, true, false, true);
// 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
@@ -721,25 +784,31 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
}
}
- $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
- });
+ if (!modalOptions.component) {
+ modal.content = tplAndVars[0];
+ }
+
+ $modalStack.open(modalInstance, modal);
modalOpenedDeferred.resolve(true);
+ function constructLocals(obj, template, instanceOnScope, injectable) {
+ obj.$scope = modalScope;
+ obj.$scope.$resolve = {};
+ if (instanceOnScope) {
+ obj.$scope.$uibModalInstance = modalInstance;
+ } else {
+ obj.$uibModalInstance = modalInstance;
+ }
+
+ var resolves = template ? tplAndVars[1] : tplAndVars;
+ angular.forEach(resolves, function(value, key) {
+ if (injectable) {
+ obj[key] = value;
+ }
+
+ obj.$scope.$resolve[key] = value;
+ });
+ }
}, function resolveError(reason) {
modalOpenedDeferred.reject(reason);
modalResultDeferred.reject(reason);
diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js
index 1e43b037d2..4bf85243da 100644
--- a/src/modal/test/modal.spec.js
+++ b/src/modal/test/modal.spec.js
@@ -46,13 +46,68 @@ describe('$uibResolve', function() {
});
});
+describe('uibModalTransclude', function() {
+ var uibModalTranscludeDDO,
+ $animate;
+
+ beforeEach(module('ui.bootstrap.modal'));
+ beforeEach(module(function($provide) {
+ $animate = jasmine.createSpyObj('$animate', ['enter']);
+ $provide.value('$animate', $animate);
+ }));
+
+ beforeEach(inject(function(uibModalTranscludeDirective) {
+ uibModalTranscludeDDO = uibModalTranscludeDirective[0];
+ }));
+
+ describe('when initialised', function() {
+ var scope,
+ element,
+ transcludeSpy,
+ transcludeFn;
+
+ beforeEach(function() {
+ scope = {
+ $parent: 'parentScope'
+ };
+
+ element = jasmine.createSpyObj('containerElement', ['empty']);
+ transcludeSpy = jasmine.createSpy('transcludeSpy').and.callFake(function(scope, fn) {
+ transcludeFn = fn;
+ });
+
+ uibModalTranscludeDDO.link(scope, element, {}, {}, transcludeSpy);
+ });
+
+ it('should call the transclusion function', function() {
+ expect(transcludeSpy).toHaveBeenCalledWith(scope.$parent, jasmine.any(Function));
+ });
+
+ describe('transclusion callback', function() {
+ var transcludedContent;
+
+ beforeEach(function() {
+ transcludedContent = 'my transcluded content';
+ transcludeFn(transcludedContent);
+ });
+
+ it('should empty the element', function() {
+ expect(element.empty).toHaveBeenCalledWith();
+ });
+
+ it('should append the transcluded content', function() {
+ expect($animate.enter).toHaveBeenCalledWith(transcludedContent, element);
+ });
+ });
+ });
+});
+
describe('$uibModal', function() {
var $animate, $controllerProvider, $rootScope, $document, $compile, $templateCache, $timeout, $q;
var $uibModal, $uibModalStack, $uibModalProvider;
beforeEach(module('ngAnimateMock'));
beforeEach(module('ui.bootstrap.modal'));
- beforeEach(module('uib/template/modal/backdrop.html'));
beforeEach(module('uib/template/modal/window.html'));
beforeEach(module(function(_$controllerProvider_, _$uibModalProvider_, $compileProvider) {
$controllerProvider = _$controllerProvider_;
@@ -76,6 +131,16 @@ describe('$uibModal', function() {
elem.focus();
}
};
+ }).component('fooBar', {
+ bindings: {
+ resolve: '<',
+ modalInstance: '<',
+ close: '&',
+ dismiss: '&'
+ },
+ controller: angular.noop,
+ controllerAs: 'foobar',
+ template: '
Foo Bar
'
});
}));
@@ -96,6 +161,7 @@ describe('$uibModal', function() {
toBeResolvedWith: function(util, customEqualityTesters) {
return {
compare: function(promise, expected) {
+ var called = false;
promise.then(function(result) {
expect(result).toEqual(expected);
@@ -104,10 +170,18 @@ describe('$uibModal', function() {
} else {
result.message = 'Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".';
}
+ }, function(result) {
+ fail('Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".');
+ })['finally'](function() {
+ called = true;
});
$rootScope.$digest();
+ if (!called) {
+ fail('Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".');
+ }
+
return {pass: true};
}
};
@@ -116,9 +190,10 @@ describe('$uibModal', function() {
return {
compare: function(promise, expected) {
var result = {};
+ var called = false;
- promise.then(function() {
-
+ promise.then(function(result) {
+ fail('Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".');
}, function(result) {
expect(result).toEqual(expected);
@@ -127,10 +202,16 @@ describe('$uibModal', function() {
} else {
result.message = 'Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".';
}
+ })['finally'](function() {
+ called = true;
});
$rootScope.$digest();
+ if (!called) {
+ fail('Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".');
+ }
+
return {pass: true};
}
};
@@ -215,6 +296,8 @@ describe('$uibModal', function() {
function open(modalOptions, noFlush, noDigest) {
var modal = $uibModal.open(modalOptions);
+ modal.opened['catch'](angular.noop);
+ modal.result['catch'](angular.noop);
if (!noDigest) {
$rootScope.$digest();
@@ -445,7 +528,7 @@ describe('$uibModal', function() {
var modal = open({template: '
Content
'});
$rootScope.$digest();
- expect(document.activeElement.tagName).toBe('DIV');
+ expect(document.activeElement.className.split(' ')).toContain('modal');
expect($document).toHaveModalsOpen(1);
triggerKeyDown($document, 27);
@@ -575,7 +658,7 @@ describe('$uibModal', function() {
it('should not focus on the element that has autofocus attribute when the modal is opened and something in the modal already has focus and the animations have finished', function() {
function openAndCloseModalWithAutofocusElement() {
- var modal = open({template: ''});
+ var modal = open({template: ''});
$rootScope.$digest();
expect(angular.element('#auto-focus-element')).not.toHaveFocus();
expect(angular.element('#pre-focus-element')).toHaveFocus();
@@ -617,7 +700,7 @@ describe('$uibModal', function() {
$rootScope.$digest();
$animate.flush();
- expect(document.activeElement.tagName).toBe('DIV');
+ expect(document.activeElement.className.split(' ')).toContain('modal');
close(modal, 'closed ok');
@@ -807,6 +890,52 @@ describe('$uibModal', function() {
initialPage.remove();
});
+
+ it('should change focus to next tabbable element when tab is pressed', function() {
+ var initialPage = angular.element('Outland link');
+ angular.element(document.body).append(initialPage);
+ initialPage.focus();
+
+ open({
+ template:'a' +
+ 'bc' +
+ '',
+ keyboard: false
+ });
+ $rootScope.$digest();
+ expect($document).toHaveModalsOpen(1);
+
+ $('#tab-focus-link3').focus();
+ expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
+
+ triggerKeyDown(angular.element(document.activeElement), 9, false);
+ expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
+
+ initialPage.remove();
+ });
+
+ it('should change focus to previous tabbable element when shift+tab is pressed', function() {
+ var initialPage = angular.element('Outland link');
+ angular.element(document.body).append(initialPage);
+ initialPage.focus();
+
+ open({
+ template:'a' +
+ 'bc' +
+ '',
+ keyboard: false
+ });
+ $rootScope.$digest();
+ expect($document).toHaveModalsOpen(1);
+
+ $('#tab-focus-link1').focus();
+ expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
+
+ triggerKeyDown(angular.element(document.activeElement), 9, true);
+ expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
+
+ initialPage.remove();
+ });
});
describe('default options can be changed in a provider', function() {
@@ -829,16 +958,89 @@ describe('$uibModal', function() {
});
});
- describe('option by option', function () {
- describe('template and templateUrl', function () {
- it('should throw an error if none of template and templateUrl are provided', function() {
+ describe('option by option', function() {
+ describe('component', function() {
+ function getModalComponent($document) {
+ return $document.find('body > div.modal > div.modal-dialog > div.modal-content foo-bar');
+ }
+
+ it('should use as modal content', function() {
+ open({
+ component: 'fooBar'
+ });
+
+ var component = getModalComponent($document);
+ expect(component.html()).toBe('
Foo Bar
');
+ });
+
+ it('should bind expected values', function() {
+ var modal = open({
+ component: 'fooBar',
+ resolve: {
+ foo: function() {
+ return 'bar';
+ }
+ }
+ });
+
+ var component = getModalComponent($document);
+ var componentScope = component.isolateScope();
+
+ expect(componentScope.foobar.resolve.foo).toBe('bar');
+ expect(componentScope.foobar.modalInstance).toBe(modal);
+ expect(componentScope.foobar.close).toEqual(jasmine.any(Function));
+ expect(componentScope.foobar.dismiss).toEqual(jasmine.any(Function));
+ });
+
+ it('should close the modal', function() {
+ var modal = open({
+ component: 'fooBar',
+ resolve: {
+ foo: function() {
+ return 'bar';
+ }
+ }
+ });
+
+ var component = getModalComponent($document);
+ var componentScope = component.isolateScope();
+
+ componentScope.foobar.close({
+ $value: 'baz'
+ });
+
+ expect(modal.result).toBeResolvedWith('baz');
+ });
+
+ it('should dismiss the modal', function() {
+ var modal = open({
+ component: 'fooBar',
+ resolve: {
+ foo: function() {
+ return 'bar';
+ }
+ }
+ });
+
+ var component = getModalComponent($document);
+ var componentScope = component.isolateScope();
+
+ componentScope.foobar.dismiss({
+ $value: 'baz'
+ });
+
+ expect(modal.result).toBeRejectedWith('baz');
+ });
+ });
+
+ describe('template and templateUrl', function() {
+ it('should throw an error if none of component, template and templateUrl are provided', function() {
expect(function(){
var modal = open({});
- }).toThrow(new Error('One of template or templateUrl options is required.'));
+ }).toThrow(new Error('One of component or template or templateUrl options is required.'));
});
it('should not fail if a templateUrl contains leading / trailing white spaces', function() {
-
$templateCache.put('whitespace.html', '
Whitespaces
');
open({templateUrl: 'whitespace.html'});
expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
@@ -1289,7 +1491,7 @@ describe('$uibModal', function() {
expect(body).not.toHaveClass('modal-open');
});
- it('should remove the custom class on closing of modal', function() {
+ it('should remove the custom class on closing of modal after animations have completed', function() {
var modal = open({
template: '
');
});
it('disables the "previous" link if current page is 1', function() {
@@ -102,7 +102,7 @@ describe('pager directive', function() {
it('executes the `ng-change` expression when an element is clicked', function() {
$rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
expect(getPaginationEl(0).text()).toBe('FI');
@@ -838,7 +847,7 @@ describe('pagination directive', function() {
it('contains number of pages + 2 li elements', function() {
paginationConfig.itemsPerPage = 5;
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
expect(getPaginationBarSize()).toBe(12);
@@ -846,7 +855,7 @@ describe('pagination directive', function() {
it('should take maxSize defaults into account', function() {
paginationConfig.maxSize = 2;
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
expect(getPaginationBarSize()).toBe(4);
@@ -854,7 +863,7 @@ describe('pagination directive', function() {
it('should take forceEllipses defaults into account', function () {
paginationConfig.forceEllipses = true;
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
// Should contain 2 nav buttons, 2 pages, and 2 ellipsis since the currentPage defaults to 3, which is in the middle
@@ -865,7 +874,7 @@ describe('pagination directive', function() {
paginationConfig.boundaryLinkNumbers = true;
$rootScope.total = 88; // 9 pages
$rootScope.currentPage = 5;
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
// Should contain 2 nav buttons, 2 pages, 2 ellipsis, and 2 extra end numbers since the currentPage is in the middle
@@ -879,7 +888,7 @@ describe('pagination directive', function() {
$rootScope.pageLabel = function(id) {
return 'test_'+ id;
};
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.$digest();
});
@@ -904,7 +913,7 @@ describe('pagination directive', function() {
describe('disabled with ngDisable', function() {
beforeEach(function() {
- element = $compile('')($rootScope);
+ element = $compile('
')($rootScope);
$rootScope.currentPage = 3;
$rootScope.$digest();
});
@@ -941,7 +950,7 @@ describe('pagination directive', function() {
it('should retain the model value when total-items starts as undefined', function() {
$rootScope.currentPage = 5;
$rootScope.total = undefined;
- element = $compile('')($rootScope);
+ element = $compile('