Compare commits

...

246 Commits

Author SHA1 Message Date
Adam Gordon a2dee1bdeb docs: add steps to reproduce section to template (#6443)
* add new steps to reproduce section to github issue template to ask
issue filers to provide explicit steps to reproduce the issue so people
aren't left to guess or parse poorly written ones.
2017-01-30 13:13:38 -08:00
Wesley Cho 409eec4019 chore(release): starting 2.5.1 2017-01-28 05:34:16 -08:00
Wesley Cho b90485b2a4 chore(release): 2.5.0 2017-01-28 05:30:11 -08:00
Daniel Smith 0d79005f8d fix(angular): add compatibility with Angular 1.6
- Change how ngModelOptions is used to conform to Angular 1.6
- Catch rejections as per change to $q

Closes #6427
Fixes #6360
2017-01-28 05:26:34 -08:00
mattcollier e538d2f71f docs(collapse): fix typo
Closes #6398
Closes #6421
2017-01-23 11:31:37 -08:00
Matthias Dailey f5b357fd11 fix(tooltip): unbind keypress listener on hide
- Unbind listener on hide to fix memory leak

Closes #6423
Fixes #6405
2017-01-23 11:30:23 -08:00
Alex (Jinghao) Yan 761db7b4b7 fix(dropdown): do nothing if not open when clicking
- When clicking, do nothing if dropdown is closed on document listener

Closes #6414
2017-01-23 03:07:20 -08:00
tariqporter 86ee770834 fix(carousel): remove transition buffering
- Remove transition buffering

Closes #6367
Fixes #5967
2017-01-23 02:27:05 -08:00
david.humphrey@senecacollege.ca 47c4544e23 chore(npm): add fields to package.json
Closes #6418
2017-01-21 07:07:47 -08:00
Wesley Cho e8479514cd chore(release): bump to 2.5.0 2017-01-21 07:06:41 -08:00
Sherry Chang cf3f870735 Add CDNJS version badge in README.md (#6297)
This will add the badge to show its version on CDNJS and also link to its page on CDNJS!
2017-01-20 13:16:27 -08:00
Umer Farooq 7d3a7502fa feat(dropdown): make dropdown-append-to-body configurable (#6356)
* feat(dropdown): make dropdown-append-to-body configurable

Make dropdown-append-to-body accept a value which will be evaluated to
determine if the menu should be appended to body. If no value is
specified, or a non-false value is specified then the menu will be
appended to body. If the value is `false` then the menu will not be
appended to body.

* feat(dropdown): append and remove menu when menu is opened or closed

Only append and remove append-to and append-to-body menus when the
menu is opened or closed. This allows for the values of append-to and
append-to-body to be evaluated when the menu is toggled open, and also
prevents littering of the DOM.

* fix(dropdown): don't remove the dropdown-menu on close

Instead of removing the dropdown-menu on close, append it back to the
original element.
2017-01-20 13:14:29 -08:00
Jeff Carey 71dc691323 feat(pagination): Added menu and menuitem roles (closes #6383) (#6386)
feat(pagination): Added test
2017-01-20 13:13:54 -08:00
Scott Kao 42f3cc847a chore(license): update year to 2017 2017-01-10 05:23:17 -08:00
Wesley Cho b755559ec9 chore(release): starting 2.4.1 2016-12-29 23:19:51 -08:00
Wesley Cho 9e6d2a0a14 chore(changelog): update for 2.4.0 2016-12-29 23:17:21 -08:00
Wesley Cho 2596b9805f chore(release): 2.4.0 2016-12-29 23:15:22 -08:00
Douglas Ludlow 5a3e44a146 feat(dateparser): allow overriding of parsers
- Add ability to override parser

Closes #6370
Closes #6373
2016-12-29 23:12:17 -08:00
Wesley Cho 4872c05a32 docs(accordion): remove unnecessary div
- Remove unnecessary div creating a doubled border

Closes #6378
2016-12-27 03:01:04 -08:00
Wesley Cho fc686bb7a3 chore(release): starting 2.3.3 2016-12-27 01:59:02 -08:00
Wesley Cho 7f664f9f45 chore(release): 2.3.2 2016-12-27 01:54:51 -08:00
khlevon98 955848c3b1 fix(dropdown): re-add close
- Re-add closing via service

Closes #6382
Fixes #6321
Fixes #6357
Fixes #6364
2016-12-27 01:47:15 -08:00
Wesley Cho 7a1d54c8c3 chore(release): starting 2.3.2 2016-12-10 03:18:18 -08:00
Wesley Cho 0ed1a59aef chore(release): 2.3.1 2016-12-10 03:14:22 -08:00
Wesley Cho f2722b59a5 fix(dateparser): add new date format for angular 1.5+ only
- Only add parsing ability for new date format for angular 1.5+

Fixes #6349
2016-12-10 03:06:50 -08:00
béla 57ed7e4f7f chore(readme): fix variable used
Closes #6344
2016-12-02 06:23:16 -08:00
Steven Yeh 90848144e8 chore(changelog): fix generated links
- Fix links to use proper tags

Closes #6340
2016-12-01 22:35:18 -08:00
Wesley Cho a4d7076c8e chore(dropdown): fix accidental deletion 2016-11-29 23:25:21 -08:00
Wesley Cho c824731ae8 revert(dropdown): undo addition of unbind 2016-11-29 23:19:43 -08:00
Wesley Cho 1653afa210 refactor(dropdown): use container for class toggle detection 2016-11-29 01:47:23 -08:00
Wesley Cho 7e2f2c1bad chore(datepickerPopup): change to on/off 2016-11-29 00:19:19 -08:00
Wesley Cho 61f365abfd chore(timepicker): change to on/off 2016-11-29 00:18:36 -08:00
Wesley Cho ec2d9ad605 chore(typeahead): change to on/off 2016-11-29 00:17:13 -08:00
Wesley Cho 08b50ccb1c chore(dropdown): change to on/off 2016-11-29 00:14:33 -08:00
patricksmms 44ab0a8106 fix(dropDown): remove dropdown on close
- Unbind keybind on close

Closes #6326
Fixes #6314
2016-11-29 00:09:08 -08:00
Wesley Cho 1962485504 fix(datepickerPopup): change to toTimezone
- Change conversion to use `toTimezone`, as the date is going from UI date with timezone applied to timezone set by `ngModelOptions`

Fixes #6235
2016-11-27 02:26:52 -08:00
Jonathan Perez 8a4f625ef6 fix(modal): revert focus behavior on open
- On open, do not focus the first focusable element

Closes #6295
2016-11-27 02:18:17 -08:00
Wesley Cho 7be665399f chore(release): starting 2.3.1 2016-11-26 07:43:50 -08:00
Wesley Cho 2edb5d38cb chore(release): update changelog 2016-11-26 07:41:20 -08:00
Wesley Cho 4e06553f7c chore(release): 2.3.0 2016-11-26 07:38:21 -08:00
Janne 25ff206767 feat(dateparser): add LLLL support
- Add LLLL support. This requires Angular 1.5+ to take advantage of this.

Closes #6281
2016-11-26 07:35:37 -08:00
Wesley Cho 997813f0eb chore(release): starting 2.2.1 2016-10-10 08:30:03 -07:00
Wesley Cho 25db68e903 chore(release): 2.2.0 2016-10-10 08:26:52 -07:00
Mark Wheeldon 78559761dc fix(tooltip): cancel timeout when hidden
- Cancel timeout when hidden so that positioning does not incorrectly occur when not visible

Closes #6226
Fixes #6221
2016-10-10 07:47:56 -07:00
nmccready 14384fc40f fix(dropdown): exit keybind is not open
- Exit keybindFilter if the $scope is not present

Closes #6278
Fixes #6208
2016-10-10 07:44:34 -07:00
JuliaUsanova 58f1813aca fix(position): correct scrollbar width calculation
Closes #6273
2016-10-10 07:42:36 -07:00
Eric Schneider 9666c64f4a feat(timepicker): add validation information
- Add validation information for specific violations, allowing users to style classes generated by Angular

Closes #6230
Closes #6259
2016-09-24 21:14:55 -04:00
Nicholas Heiner f9f7e02d15 fix(modal): improve ARIA support.
- Add accessibility with multiple modals

Closes #6203
2016-09-23 23:21:25 -05:00
Wesley Cho a4bea6f229 chore(release): starting 2.2.0 2016-09-23 23:19:16 -05:00
Wesley Cho c00851a18b chore(release): 2.1.4 2016-09-23 23:15:28 -05:00
Sergey Lysenko c687acd489 docs: create retina-ready favicon
- Update favicon with retina-friendly icon

Closes #6255
2016-09-23 23:09:24 -05:00
Eric Schneider 3f70d76327 fix(datepicker): improve accessibility
- Improve accessibility with forward and previous buttons with screen readers

Closes #6247
2016-09-23 23:06:17 -05:00
Nathan Herzing 08ee30a91e fix(dropdown): prevent premature scope removal
- Toggle false after focus executes

Closes #6238
Fixes #6225
2016-09-23 23:03:16 -05:00
ravi-123 7671bd6ed4 docs(popover): add popover-class 2016-09-23 23:01:31 -05:00
Evan Theurer 1a132dd757 docs(collapse): add navigation collapse example
- Add an example for using collapse with the bootstrap navbar

Closes #3481
Closes #6217
2016-09-01 20:18:59 -07:00
Wesley Cho 9881a27397 chore(release): starting 2.1.4 2016-08-25 08:38:42 -07:00
Wesley Cho 294b151342 chore(release): 2.1.3 2016-08-25 08:35:57 -07:00
Axel Navarro 969eb9c74d fix(modal): compile only once with component
- When using the component option, do not compile content in order to take advantage of later compilation

Closes #6202
Fixes #6201
2016-08-24 20:26:33 -07:00
Wesley Cho 048d85a2ad chore(release): starting 2.1.3 2016-08-22 10:04:12 -07:00
Wesley Cho 876e72a0df chore(release): 2.1.2 2016-08-22 10:01:02 -07:00
Wesley Cho 6734908108 test(modal): fix custom matchers
- Trigger a test failure if resolved on rejected promise and rejected on resolved promise
- Trigger a test failure if neither resolved or rejected when expected resolved/rejected promise

Closes #6195
2016-08-22 09:57:20 -07:00
Wesley Cho 515bcf2933 fix(collapse): revert change to transition css
- Revert change to `getScrollFromElement` function

Closes #6196
Fixes #6194
2016-08-22 09:54:30 -07:00
Wesley Cho 148371fb6d docs(tooltip): add info on Safari bug
- Add information on Safari bug, affecting all versions up to 9 at the least

Closes #5445
2016-08-22 09:53:11 -07:00
Axel Navarro 8047c06258 docs(modal): added a simple component modal demo
Closes #6193
2016-08-22 08:03:06 -07:00
Axel Navarro 3e8ecfffe0 fix(modal): close and dismiss bindings on component
- Correctly bind close and dismiss handlers on component usage

Closes #6192
Fixes #6191
2016-08-21 12:12:44 -07:00
Georgii Dolzhykov ddcacb7a83 fix(datepicker): fix accidental global
- Fix accidental global usage

Closes #6188
2016-08-21 04:42:13 -07:00
Wesley Cho 915eda0540 chore(release): starting 2.1.2 2016-08-20 22:54:15 -07:00
Wesley Cho f9eede7555 chore(release): 2.1.1 2016-08-20 22:49:30 -07:00
Axel Navarro fb5fabf580 fix(modal): switch to .append
- Switch to `append` since `html` is jQuery specific

Closes #6187
Fixes #6186
2016-08-20 22:46:35 -07:00
Wesley Cho aef24cde4b fix(collapse): default to css
- Default to value set from CSS for height/width

Closes #6182
Fixes #6045
2016-08-20 05:58:59 -07:00
Wesley Cho b893a93f82 docs(collapse): fix height on horizontal
- Fix height on horizontal collapse usage

Closes #6050
Closes #6183
2016-08-20 05:58:03 -07:00
Wesley Cho 6352f13e85 chore(release): starting 2.1.1 2016-08-19 14:34:37 -07:00
Wesley Cho 76cd65ef1a chore(release): 2.1.0 2016-08-19 14:31:32 -07:00
Wesley Cho 84cc2cf5cc fix(collapse): set overflow to hidden on transition
- When collapse is transitioning, set `overflow: hidden` to avoid possibility of scrollbars changing size

Closes #6180
Fixes #5474
2016-08-19 10:37:06 -07:00
Wesley Cho 2ade0545a9 feat(modal): add component support
- Add support for `component` option

Closes #5683
Closes #6179
2016-08-19 09:54:05 -07:00
Stuart Forster f5ff12c0a4 fix(tooltip): close tooltip on esc
- On keypress of escape, close tooltip/popover

Closes #6177
Fixes #6108
2016-08-18 14:10:17 -07:00
Wesley Cho f147d22f5c fix(datepickerPopup): apply timezone conversion
- Apply timezone conversion to selecting today

Closes #6173
Fixes #6147
2016-08-18 13:21:15 -07:00
Wesley Cho f798a47d48 chore: bump angular version 2016-08-18 10:12:21 -07:00
Wesley Cho 43a1f75e7c chore(demo): bump versions 2016-08-18 10:11:55 -07:00
Nick Heiner 4a5e6a7418 fix(modal): improve ARIA support
- Add aria-labelledby and aria-described by support

Closes #4772
2016-08-17 22:09:30 -07:00
Wesley Cho af6c2aaca0 chore(release): starting 2.1.0 2016-08-17 22:09:18 -07:00
Kirkland 61415e1968 Adding roles to the datepicker templates to resolve an ADA violation. (#6170)
Closes #6170
2016-08-17 17:45:34 -07:00
Wesley Cho 5123e38a49 docs(tooltip): add ngSanitize to Plunker
- Add ngSanitize to Plunkers for uib-tooltip-html and uib-popover-html Plunkers

Closes #6165
Fixes #6157
2016-08-16 08:36:00 -07:00
Wesley Cho ca20be4667 fix(collapse): remove unnecessary inherit
- Remove unnecessary `inherit` property, which causes issues when custom widths are used, including percentage based widths

Closes #6164
Fixes #6163
2016-08-16 08:35:09 -07:00
Wesley Cho d9dd5803ae chore(release): starting 2.0.3 2016-08-15 13:45:53 -07:00
Wesley Cho 86f528730b chore(release): 2.0.2 2016-08-15 13:42:33 -07:00
Wesley Cho fbd0845c95 fix(datepickerPopup): correctly format to timezone
- Format according to ngModelOptions timezone if specified

Closes #6159
Fixes #6105
Fixes #6146
Fixes #6147
2016-08-15 13:39:17 -07:00
Patrick Johnmeyer 5597e2fa99 docs(carousel): switch to unsplash.it
- Change image hosting services due to expired SSL cert for lorempixel

Closes #6144
2016-08-15 07:50:14 -07:00
Matt Lewis 6bad7592b9 fix(dropdown): fix keyboard-nav
- Fix keyboard-nav for when not using append-to or append-to-body

Fixes #6102
Closes #6154
2016-08-11 04:38:14 -07:00
Wesley Cho 0023d1bd97 chore(typeahead): adjust regex comment 2016-08-08 23:09:09 -07:00
Wesley Cho be0d0564c2 docs(demo): add link to ng-bootstrap
- Add link to ng-bootstrap project on demo site

Closes #6083
Closes #6145
2016-08-08 02:52:06 -07:00
Gerardo 9f3e387473 docs(accordion): add wrapping div
Closes #6139
2016-08-08 02:44:57 -07:00
Martin Staffa 58e0433b96 chore(demo): use https in the generated link
This avoids the popup about an unsecure connection when opening examples in plnkr.

Closes #6132
2016-08-02 08:36:06 +02:00
Wesley Cho 87a373936d chore(release): starting 2.0.2 2016-08-02 08:33:35 +02:00
Wesley Cho e8e4ef1070 chore(release): 2.0.1 2016-08-02 08:28:25 +02:00
Francesco Pipita c61d16a933 fix(modal): restore broken stacked modals
Closes #6103
Closes #6104
2016-07-22 00:25:10 +01:00
Adam Gordon 6bfb4cdbdf chore(docs): fix specified sizes
Closes #6110
Closes #6111
2016-07-21 23:33:51 +01:00
Wesley Cho cac6a45345 chore(changelog): update with changes 2016-07-20 00:10:00 -07:00
Bryan Gillespie 8a53327bd6 docs(tooltip): add inline html example
- Make demo more clear on html tooltip capability

Closes #4738
Closes #6067
2016-07-20 00:06:27 -07:00
Wesley Cho 9f06a8bfdb chore(release): starting 2.0.1 2016-07-20 00:02:45 -07:00
Wesley Cho 5173779f84 chore(release): 2.0.0 2016-07-19 23:59:40 -07:00
Wesley Cho 1616e9720d feat(tooltip): remove replace usage
- Remove `replace: true` usage

BREAKING CHANGE: The template structure changed slightly due to the removal of `replace: true` - see documentation examples in action to see differences in practice.

Closes #5994
2016-07-19 13:40:38 -07:00
Ashton b77f02cdce docs(readme): change to https
- Change from http to https

Closes #6064
2016-06-30 08:45:49 -07:00
craig 39d9b9886d feat(datepicker): add monthColumns
- Add `monthColumns` option

Closes #5515
Closes #5935
2016-06-27 09:45:30 -07:00
Dolev Dori 2d831bcc2a fix(dropdown): align position correctly with scrollbar
- Fix horizontal alignment independent of the presence of a vertical scrollbar

Closes #6008
Fixes #5942
2016-06-27 09:32:13 -07:00
Wesley Cho d846e2d788 fix(dateparser): correctly format with literals
- Fix formatting of dates with literal usage

Closes #6055
Fixes #5620
Fixes #5802
2016-06-27 09:18:25 -07:00
Anthony Cleaver 409b7aa3b3 fix(modal): remove window class after animation
- Wait until animations are complete before removing window class

BREAKING CHANGE: This introduces a minor behavior change in when the class is removed

Closes #6056
Fixes #6051
2016-06-27 08:42:20 -07:00
Manuel Siggen ed3400bb68 fix(typeahead): clear validity in $digest
- Clear within $digest cycle

Closes #6033
Fixes #6032
2016-06-26 17:17:46 -07:00
Anthony Cleaver 1cbd73d227 feat(modal): append using $animate
- Use $animate.enter to append transcluded content

Closes #6023
Closes #6029
2016-06-26 17:07:01 -07:00
robjacobs 433e536e6b fix(modal): check for overflow hidden
Check if appendToElement has scrolling disabled before adding
the scrollbar padding.

Fixes #6037
Closes #6041
2016-06-21 15:35:26 -04:00
squelix 1ec099767f feat(collapse): add horizontal support
- Add support for horizontal collapsing

Closes #3593
Closes #6010
2016-06-16 10:52:31 -07:00
Wesley Cho 4e0e34f47a revert(dropdown): change back to .constant() 2016-06-15 10:12:14 -07:00
Wei Wang 7457fb0e63 feat(dropdown): use .value() for uibDropdownConfig
Closes #6004
Closes #6006
2016-06-15 06:44:55 -07:00
Wei Wang 23ef372a17 docs(README): add modal to index-nocss list
Closes #6005
2016-06-14 14:04:25 -07:00
Andrij Fedyk 164811ab35 fix(tooltip): missed dependency for cjs
Closes #5999
2016-06-14 14:03:19 -07:00
Andrij Fedyk 1de58a3e5d fix(modal): remove unused template from modal 2016-06-14 14:03:17 -07:00
dirk 7e320e0e92 fix(datepickerPopup): use value instead of viewValue
Closes #6007
2016-06-14 14:02:22 -07:00
Wesley Cho d6fe9c7e7a feat(rating): remove replace usage
- Remove `replace: true` usage

BREAKING CHANGE: 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.

Closes #5998
2016-06-13 20:17:38 -07:00
Wesley Cho 75188211aa feat(timepicker): remove replace usage
- Remove `replace: true` usage

BREAKING CHANGE: This removes `replace: true`, which changes the HTML structure slightly - see the documentation examples to see it in action.

Closes #5997
2016-06-13 20:17:03 -07:00
johanneskrtek 4e68778561 feat(timepicker): avoid allowing to tab to controls
- Prevent the ability to tab to timepicker controls, leaving incrementing/decrementing to keyboard actions on the individual inputs

Closes #5930
2016-06-13 20:15:34 -07:00
Wesley Cho a68cc20dd4 chore(dropdown): add guard when closing 2016-06-13 12:54:15 -07:00
Wesley Cho ba113a050e chore(pager): add tabindex module dep 2016-06-13 10:35:53 -07:00
Wesley Cho 0d8cec6e4f feat(pager): toggle tabindex when disabled
- Add `tabindex="-1"` when disabled and remove it when not for accessibility

Closes #5996
2016-06-13 10:34:52 -07:00
Wesley Cho a47bcedad4 feat(datepickerPopup): remove replace usage
- Remove `replace: true` usage

BREAKING CHANGE: 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.

Closes #5993
2016-06-13 10:18:37 -07:00
Wesley Cho 387c6e7bbc feat(pagination): remove replace usage
- Remove `replace: true` usage

BREAKING CHANGE: Due to the removal of `replace: true`, this affects the HTML structure slightly - see documentation examples to see new usage.

Closes #5992
2016-06-13 10:17:53 -07:00
Wesley Cho 9b24e1dfed feat(pager): remove replace usage
- Remove `replace: true usage

BREAKING CHANGE: This removes `replace: true` usage from the pager, which causes a slight usage change - see documentation examples for new usage.

Closes #5991
2016-06-13 10:08:01 -07:00
Wesley Cho 5f4eedd0e7 feat(accordion): add appropriate tabindex on disabled
- Add `tabindex="-1"` to accordion group header when disabled and remove when not to assist with accessibility

Closes #4067
Closes #5990
2016-06-13 10:07:24 -07:00
Wesley Cho 96d52ce150 feat(modal): remove replace usage
- Remove `replace: true` usage

BREAKING CHANGE: 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.

Closes #5989
2016-06-13 10:06:48 -07:00
renhao 55cf26bf28 chore(datepicker): remove dead code
Closes #5995
2016-06-13 10:05:31 -07:00
Wesley Cho 5fdcbd6b90 chore(dropdown): add guard 2016-06-13 00:21:39 -07:00
Wesley Cho e92fb7fa7c feat(datepicker): remove replace: true usage
- Remove `replace: true` usage

BREAKING CHANGE: 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.

Closes #5988
2016-06-12 23:53:11 -07:00
Wesley Cho 1a870a3bb1 feat(pagination): disable tabbing when disabled
- Add directive for toggling `tabindex="-1"` when ngDisabled is true, and removing tabindex when false

Closes #5984
2016-06-12 23:52:20 -07:00
Wesley Cho 6038403179 fix(dropdown): bind event listener on dropdown menu
- Bind keypress listener to dropdown menu for handling keyboard navigation

Closes #5982
2016-06-12 23:51:25 -07:00
Wesley Cho fb38d0db2e chore(position): add missing , 2016-06-12 15:18:32 -07:00
Wesley Cho 5046bd4e56 feat(carousel): remove replace: true usage
- Remove `replace: true` usage from the carousel and the slide

BREAKING CHANGE: 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.

Closes #5987
2016-06-12 15:13:26 -07:00
Wesley Cho 2458c2863d feat(alert): remove replace: true usage
- Remove `replace: true` usage

BREAKING CHANGE: This removes the `replace: true` usage, so this has an effect on how one uses the directive in the template - see documentation for reference

Closes #5986
2016-06-12 15:12:25 -07:00
Wesley Cho 3819bbe8fa feat(accordion): remove replace: true usage
- Remove usage of `replace: true`
- Remove unnecessary `panel-class` feature

BREAKING CHANGE: 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

Closes #5985
2016-06-12 15:11:10 -07:00
Wesley Cho e2016fd2eb refactor(position): abstract out common logic
- Abstracting out conditional to position service adjustTop method to increase readability and to prepare it for use in dropdown/typeahead

Closes #5983
2016-06-12 15:10:15 -07:00
Wesley Cho 34a1443a85 fix(tooltip): reposition for placement top
- Reposition the tooltip when the placement option contains top in order to account for resizing of tooltip from right side

Closes #5959
2016-06-11 15:47:04 -07:00
atrinh 35ced04445 fix(modal): filter out non-tabbable elements
- Ensure non-tabbable elements with taxindex of -1 are not tabbed to

Closes #5963
Fixes #5955
2016-06-09 14:25:22 -07:00
Wesley Cho 90fa2f682a docs(readme): add note about versioning policy
- Add information on versioning policy

Closes #5800
Closes #5958
2016-06-03 21:40:23 -07:00
cherry-geqi 4fef0377bb fix(datepickerPopup): specify dependency on datepicker
- Specify dependency on datepicker module

Closes #5954
2016-06-02 23:27:38 -07:00
rcholic b0466d9e6e fix(datepickerPopup): clear date when button is clicked
- Fix clearing the date when the clear button is clicked

Closes #5945
Fixes #5906
2016-05-31 08:50:12 -07:00
Wesley Cho 4b912228be feat(tooltip): add expression support
- Add expression support for tooltip-trigger
- Add support for object event bindings for showing/hiding tooltips

BREAKING CHANGE: 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

Closes #5221
Closes #5938
2016-05-28 22:14:05 -07:00
Wesley Cho 9a606dca7c chore(typeahead): add matcher class 2016-05-28 18:21:36 -07:00
Adam Gordon 6d5b84a0ba fix(typeahead): remove duplicate id attribute
* remove any duplicate `id` attribute on the typeahead hint element if used on
  the original input element.

BREAKING CHANGE: This change removes the `id` attribute on the first `<input>`
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.

Closes #5936
Fixes #5926
2016-05-28 17:27:20 -07:00
Wesley Cho cce00970f1 feat(dropdown): focus toggle on close from click
- Focus the toggle element when the click causes the dropdown menu to close

BREAKING CHANGE: This changes the focus behavior of the dropdown slightly, and potentially may break code built around current usage

Closes #5934
Fixes #5782
2016-05-28 17:18:39 -07:00
Wesley Cho 23b91d951e feat(datepicker): add ngModelOptions support
- Adds support for ngModelOptions in the `datepicker-options` object

BREAKING CHANGE: This modifies the current behavior around the datepicker & popup's ngModelOptions, which may affect custom components that are built around both

Closes #5933
Fixes #5825
2016-05-28 09:09:18 -07:00
Wesley Cho 053035234c chore: bump angular to 1.5.5
Closes #5920
2016-05-28 07:27:45 -07:00
deplay a075824087 feat(stackedMap): improve perf of removeTop
- Change `removeTop` from `slice` to `pop`

Closes #5925
Closes #5932
2016-05-28 07:24:25 -07:00
Wesley Cho 13c14af9a2 fix(typeahead): change to select class
- Change to select `.uib-typeahead-match` class instead of the `li` tag

BREAKING CHANGE: 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.

Closes #5922
Fixes #5848
2016-05-24 11:31:34 -07:00
Wesley Cho 280ddaf47e chore(release): starting 2.0.0 2016-05-22 20:09:55 -07:00
Wesley Cho dc85f67b2d chore(release): 1.3.3 2016-05-22 20:05:01 -07:00
Christopher Rued 2ffb86f201 feat(typeahead): Add support for should-select
By default behaves as before. If specified, allows a custom function
to be defined to determine whether the keydown event should trigger
selection based on the `typeahead-should-select` attribute.

Closes #5671
Closes #5794
2016-05-21 22:33:00 -07:00
Adam Gordon aec5304ef5 chore(button): add known issue for tooltip/popover
* add known issue to button docs indicating a CSS issue when using a tooltip or
  a popover.

Closes #5890
2016-05-16 08:47:26 -07:00
robjacobs eb02778297 chore(tab): code cleanup
Removing unnecessary code added under PR #5489

Closes #5891
Fixes #5888
2016-05-10 14:09:39 -04:00
robjacobs 26d399557e feat(tab): allow strings for index
- Allows the use of strings for the tab index and active value
on the tabset.

Closes #5713
Closes #5827
2016-05-09 16:09:47 -04:00
Adam Gordon 9436b9e7bd docs: add note for datepicker/datepicker-popup
* update the datepicker and datepicker popup docs to let user know
  about using the `uibDateParser` service to convert a date string
  into a `Date` object.

Closes #5880
2016-05-08 07:57:28 -07:00
kwiateusz b2984b0416 docs(tabs): mention that index needs to be number
Closes #5876
2016-05-07 07:53:22 -06:00
Guillaume Royer 5188463f69 fix(datepickerPopup): convert numbers to date before parsing
Closes #5873
Fixes #5871
2016-05-04 09:04:55 -06:00
Wesley Cho a417ec2ab9 fix(datepicker): add intermediary valid date check
- Add intermediary check for whether the date is valid due to an IE quirk of turning a valid date into an invalid date when using the date constructor

Closes #5872
Fixes #5865
2016-05-03 13:15:34 -07:00
Wesley Cho d923df9100 chore(dateparser): port logic from Angular
- Port logic from timezone converter from Angular

Closes #5870
2016-05-03 09:47:07 -07:00
Wesley Cho dd09148893 feat(modal): add resolve values to template
- Expose resolve in template as $resolve for those modals opened with a controller

BREAKING CHANGE: Since this adds support for $resolve being exposed on $scope, it could potentially overwrite any pre-existing usage of it - this is an unlikely scenario, but marked as a breaking change in case this key is being used

Closes #5808
Closes #5857
2016-04-29 12:02:34 -07:00
ruiann 5741be9d5b chore(modal): use $controller to apply controllerAs
- Change to use `$controller` to apply the `controllerAs` feature

Closes #5861
2016-04-29 08:58:49 -07:00
Scotty Waggoner 1611248daa docs(progressbar): fix danger reference
Closes #5854
2016-04-28 08:35:25 -07:00
Bohdan L e51373d84b chore(readme): fix link
Closes #5852
2016-04-28 07:40:43 -07:00
Perry Hoffman 4c40d9db7f feat(accordion): add alternative attribute support
- Add support for alternative attributes

Closes #5834
Closes #5839
2016-04-27 21:24:56 -07:00
crhistian26 f88067a04f docs(accordion): fix typo
Closes #5838
2016-04-24 21:32:27 -07:00
Wesley Cho 0137179ee6 docs(accordion): add example with custom header
- Add an example with custom header template

Closes #5831
Fixes #5829
2016-04-21 09:53:53 -07:00
Dolev Dori 2b48259a35 fix(dropdown): align position with vertical scrollbar
- Correctly aligns the dropdown when a vertical scrollbar is present

Closes #5830
Fixes #4317
2016-04-20 07:38:04 -07:00
Karl Sieburg 241fea8355 feat(tabs): pass the selected index to onDeselect
-  Add the index of the tab the user attempted to open to the onDeselect call
Closes #5820
Closes #5821
2016-04-19 13:24:01 -07:00
bryanbak f0e661c7c0 docs(tabs): get rid of 'active' option on uib-tab directive
Update demo to remove deprecated active attribute

Closes #5809
2016-04-14 13:31:27 -07:00
Wesley Cho 78db2cefe7 chore(release): starting 1.3.3 2016-04-14 08:32:07 -07:00
Wesley Cho 5eab8ffc49 chore(release): 1.3.2 2016-04-14 08:27:49 -07:00
Daniel Gornstein 68200bbccb fix(dropdown): stop esc keydown event
Fixes #5778
Closes #5787

BREAKING CHANGE: Stops propagation of keydown event when escape key is pressed. Removes keydown event from the document and moves it to the dropdown element.
2016-04-13 10:11:54 -07:00
Jean-Francois Larouche 68ed7ab477 docs(datepicker): fix customClass and dateDisabled
- Fix documentation for customClass and dateDisabled functions

Closes #5797
Fixes #5757
2016-04-13 06:37:22 -07:00
robjacobs d96d53ec32 fix(tooltip): arrow position
- Ensure the arrow position gets recalculated if the tooltip
position changes while open.

Closes #5785
Fixes #5779
2016-04-12 12:43:47 -07:00
robjacobs aef08d5555 fix(modal): scroll padding only added once
- Ensure the scroll padding meant to offset the modal-open class
only gets added once when mulitple modal windows are open.

Closes #5790
Fixes #5789
2016-04-12 12:41:42 -07:00
Wesley Cho a27a4e2204 feat(modal): add no css import script
- Add index-nocss.js for modals

Closes #5788
2016-04-12 09:09:15 -07:00
Mike Chen a887d2b4c9 fix(modal): missing require in modal
- Fix incorrect require of css instead of position module

Closes #5786
2016-04-12 08:21:33 -07:00
Paul des Garets bba3f27e3d fix(typeahead): use $setViewValue on blur
Use $setViewValue instead of setting directly $viewValue to '', to make new value go through validators.

Closes #5769
Fixes #5694
2016-04-07 15:26:13 -07:00
Wesley Cho 3d7191cc06 chore(isClass): clean up code style 2016-04-06 15:19:38 -07:00
Wesley Cho 32f78b8718 chore(isClass): clean up code style 2016-04-06 15:19:27 -07:00
Wesley Cho 4044f08980 chore(isClass): clean up code style 2016-04-06 15:19:16 -07:00
Wesley Cho 33cbd0f4a2 fix(datepicker): ensure datepicker is focused
- When date is selected, ensure datepicker is selected in Chrome

Closes #5761
Fixes #5754
2016-04-06 13:32:41 -07:00
Wesley Cho 3b8f18e373 chore(datepicker): change to single quotes 2016-04-06 10:34:08 -07:00
Wesley Cho 0f3a5214f4 chore(site): add guard to accessing global 2016-04-06 09:04:48 -07:00
Chris Thielen 37f9cd26e5 fix(site): allow FastClick to be blocked
Some users' adblock and virus protection is accidentally blocking third party `fastclick.js`.

Put a guard around the line that invokes the FastClick api

Closes #5756
2016-04-06 08:41:18 -07:00
Abdellatif Ait boudad 169c50403c docs(datepicker): remove popup example.
Closes #5755
2016-04-06 06:24:11 -07:00
Wesley Cho 38a334c7d8 chore(timepicker): remove inline style
- Remove inline style due to style being present in CSS

Closes #5751
2016-04-06 06:22:49 -07:00
Wesley Cho c05aefe96d chore(release): start 1.3.2 for real 2016-04-05 10:05:44 -07:00
Daniel Gornstein 98eb8f1a77 fix(datepickerPopup): do not show incorrect warning
This stops the datepicker popup from incorrectly showing a warning when not supplying any datepicker options.

Fixes #5743
Closes #5747
2016-04-05 09:46:25 -07:00
Wesley Cho 1c6c7b913c chore(release): starting 1.3.2 2016-04-05 07:39:19 -07:00
Wesley Cho 1519c4b46d chore(release): 1.3.1 2016-04-05 07:36:01 -07:00
Abdellatif Ait boudad 86ee9c3af1 fix(datepickerPopup): register popup module
Closes #5745
2016-04-05 07:34:25 -07:00
Pem Taira a08ad700f6 fix(modal): ensure correct index is set
- Fixes index set to avoid potential concurrent z-index values for multiple modals

Closes #5733
Fixes #5670
2016-04-04 22:20:00 -07:00
Wesley Cho 4ec35e4dcb chore(release): starting 1.3.1 2016-04-04 21:19:11 -07:00
Wesley Cho daf5c820e3 chore(release): 1.3.0 2016-04-04 21:16:08 -07:00
Adam Gordon d767afbd2a fix(tab): fix event handler call
* switch event handler call to look at `event.isDefaultPrevented()` instead of
  `event.defaultPrevented` as angular has implemented that method on jqlite
  events.

Fixes #5720
Closes #5738
2016-04-04 09:48:43 -06:00
robjacobs 701e0cbded feat(dateparser): use 68 as the yy format pivot year
- Use the same pivot year (68) for 2 digit date years as moment.js.
68 becomes 2068, 69 becomes 1969.

Closes #5735
2016-04-04 08:16:58 -07:00
Wesley Cho fc02fd1e30 feat(datepicker): deprecate literal usage
- Deprecate support for non-date objects with the datepicker

Closes #5658
Closes #5732
2016-04-04 08:15:09 -07:00
Türker Teke 4fe5d9936d docs(datepicker): fix creation of afterTomorrow
Closes #5714
2016-03-31 14:57:12 -07:00
Adam Gordon e0fcc004c0 fix(tab): add support for tab deselect prevention
* add ability for user to prevent currently selected tab's deselection by
calling `$event.preventDefault()` in tab's `deselect` callback.

Fixes #5720
Addresses #2715, #4836, and #5716.
Closes #5723
2016-03-31 14:15:08 -06:00
robjacobs cca1460eb0 fix(popover): rename title attribute
- Using an attribute of title on the tooltip template was
causing the browser tooltip to display.

Fixes #5719
Closes #5721
2016-03-31 13:47:12 -04:00
robjacobs c83d0a8daf fix(modal): body content shift
- Implements TWBS body padding fix to keep content in an element
with a container class from shifting when the body overflow is
set to hidden with the modal-open class.

Fixes #2631
Closes #5711
2016-03-30 18:01:49 -07:00
plondon a3964d4d81 feat(carousel): disable prev/next arrows if at bounds
- When at bounds and noWrap is enabled, disable the prev and next arrows when appropriate

BREAKING CHANGE: This adds disabled CSS - there is a possibility this may break existing UI slightly for those adding custom CSS/making use of custom templates. Modify the template appropriately if necessary

Closes #5704
Closes #5708
2016-03-30 08:29:30 -07:00
Charlie Rudolph 434c1c595b fix(carousel): use proper sort comparison
Fixes #5638
Closes #5702
2016-03-28 12:49:42 -07:00
Foxandxss 48f62a6dad chore(timepicker): remove css require on noncss 2016-03-27 19:05:25 +02:00
Foxandxss 1fa358fa6b chore: update devDependencies 2016-03-27 13:55:22 +02:00
Foxandxss 393d00e415 chore: update to Angular 1.5.3 2016-03-27 13:43:48 +02:00
Wei Wang 80748994a3 fix: modify CSS loaded for modules with dependencies
- Modify loading of CSS to be purely in the index.js files for modules that depend on other modules with CSS files

Closes #5698
2016-03-26 22:56:47 -07:00
Wesley Cho e50f602488 chore: add another exclusion 2016-03-26 15:16:27 -07:00
Wesley Cho f57302707c chore: fix improper build 2016-03-26 15:14:58 -07:00
Wei Wang de504fb512 feat: add support for loading without CSS
- Add support for loading components without bringing along the CSS,
  which is necessary for SystemJS/JSPM users

Closes #5696
2016-03-26 14:57:10 -07:00
Wesley Cho daf3b2e7f7 chore: remove patch version 2016-03-26 14:56:31 -07:00
Wesley Cho 41e670c34a chore: update node on Travis 2016-03-26 14:52:12 -07:00
Wesley Cho d1c49fe342 docs(timepicker): add note about DST
- Add note about DST, and declare explicit intent to not support
  concurrent usage of the date object with the timepicker and datepicker

Closes #5693
2016-03-25 22:21:32 -07:00
Wesley Cho f6735dc2bb test(dateparser): add literal tests
- Add broken tests for dateparser literals

Closes #5692
2016-03-25 22:20:35 -07:00
Tom Yau 03e60479d4 fix(tab): correctly identify index on removal
- Fix tab remove when tabs are destroyed in the middle

Closes #5689
2016-03-24 12:26:21 -07:00
Alexander Surinov 449c0d11d6 feat(timepicker): remove automatic padding when empty
- Removes automatic padding when input is empty to allow ability to manipulate inputs on user's side

Closes #5293
Closes #5299

BREAKING CHANGE: This removes automatic padding done by the timepicker
when the input is empty - if that behavior is desired, write a custom
directive implementing it
2016-03-24 06:54:10 -07:00
Ryan McKeel c534cb4a94 feat(dropdown): remove $locationChangeSuccess listener
- Remove listener for $locationChangeSuccess event

BREAKING CHANGE: The dropdown no longer will remain open on $locationChangeSuccess with autoclose set to disabled. Implement this logic inside app along with usage of isOpen two-way binding if this functionality is desired.

Closes #5648
Closes #5680
2016-03-22 12:19:32 -07:00
Wesley Cho 45165ba58b feat(datepicker): watch for changes when falsy
- When initial value is falsy, continue to watch for changes to update

Closes #5672
Closes #5677
2016-03-22 11:36:37 -07:00
Wesley Cho 8590ea7240 chore(dropdown): avoid IE11/Edge rendering bug
- Add `dropdown-menu` class to avoid rendering bug in IE11/Edge

Closes #5675
Fixes #5595
2016-03-22 11:35:59 -07:00
Wesley Cho 44fb19bc83 fix(carousel): fix animation from programmatic trigger
- Fix animations of slides when programmatically changing active slide
  index

Closes #5674
Fixes #5601
2016-03-22 11:35:19 -07:00
Wesley Cho fbc873cfb2 feat(datepickerPopup): split off popup from datepicker
- Split off datepicker popup into new datepickerPopup module

BREAKING CHANGE: The datepicker popup is no longer a part of the
datepicker module, but now a part of the datepickerPopup module. Please
change code accordingly if including specific modules

Closes #5676
2016-03-22 07:46:19 -07:00
Wesley Cho ad91c82164 feat(datepicker): remove deprecated code
- Remove all deprecated attribute support

BREAKING CHANGE: This removes inline datepicker attribute support and
attribute pass-throughs in the popup

Closes #5660
2016-03-21 11:11:13 -07:00
Adam Misiorny 46ff62dd75 docs(tooltip): fix example
Closes #5666
2016-03-21 11:06:31 -07:00
Adam Misiorny 34adfbce01 chore(tooltip): add missing space
Closes #5664
2016-03-21 09:03:54 -07:00
Foxandxss 307d8992a8 chore: update to Angular 1.5.2
Closes #5663
2016-03-21 09:02:20 -07:00
Wesley Cho fa46e16eab chore(release): starting 1.3.0 2016-03-20 09:04:42 -07:00
Wesley Cho 6955cf0079 chore(release): 1.2.5 2016-03-20 08:59:14 -07:00
Wesley Cho c7be087ca0 feat(timepicker): add pad-hours support
- Add support for optionally padding hours

Closes #4288
Closes #5633
2016-03-18 11:29:48 -07:00
Wesley Cho 1b888d4b0e fix(datepicker): dereference date initialization
- Dereference the date set to active date to prevent model automatically
  updating from equal reference

Closes #5643
Fixes #5441
2016-03-18 11:27:25 -07:00
Wesley Cho 5ef56e2de8 feat(modal): exclude hidden elements from query
- Modify element query to exclude hidden elements

Closes #5512
Closes #5644
2016-03-18 09:00:12 -07:00
mbisioli bc7c55aec8 fix(typeahead): reset errors when clearing
- Reset validation errors when clearing

Closes #5641
2016-03-16 19:31:42 -07:00
Wesley Cho 316e96c9b0 fix(datepicker): fix today button disabled condition
- Fix conditional for today button in popup

Closes #5637
Fixes #5622
2016-03-16 10:46:09 -07:00
Kaiyu Hsu 8747b585f4 feat(rating): ability to disable rating reset
Closes #5532
Closes #5631
2016-03-15 09:18:01 -07:00
Wesley Cho e15a22a8d4 fix(modal): dynamically fetch elements
- Dynamically fetch elements for determination of which is the first/last to tab/shift+tab to

Closes #5630
Fixes #5050
Fixes #5421
2016-03-15 08:58:58 -07:00
Wesley Cho bb36e40582 feat(tab): add select expressions
- Add support for event being available in select/deselect
  callbacks

Closes #5438
2016-03-14 12:33:39 -07:00
Thorsten Möller 7f93bfc16f docs(popover): add popover-enable
Closes #5615
Closes #5626
2016-03-14 12:19:57 -07:00
Spencer 1714a388b4 docs(popover): add popoverHtml usage
Closes #5617
2016-03-10 16:48:53 -08:00
robjacobs 66c416cf77 fix(tooltip): css to support arrow positioning
- Added css selectors to support arrow positioning on
popover/tooltip html and template directives.

Fixes #5610
Closes #5611
2016-03-09 09:40:26 -08:00
Ivan Voznyakovsky f2004a9186 docs(tab): remove old incorrect attribute blurb 2016-03-09 07:01:02 -08:00
philjones 54ac06af80 fix(popover): add popover-html css
- Add base CSS for popover-html

Closes #5603
Fixes #5602
2016-03-08 09:17:34 -08:00
Daniel Blanco ffb5529771 feat(accordion): add bound panel-class support
- Add `@` binding support for `panel-class`

Closes #5368
Closes #5596
2016-03-07 14:14:11 -08:00
Wesley Cho d6b9ee17e5 chore(release): starting 1.2.5 2016-03-06 07:08:57 -08:00
150 changed files with 6929 additions and 5712 deletions
+2
View File
@@ -6,6 +6,8 @@ existing issues (both open and closed) prior to opening any new issue and ensure
### Link to minimally-working plunker that reproduces the issue:
### Steps to reproduce the issue:
### Version of Angular, UIBS, and Bootstrap
Angular:
+2 -2
View File
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "5.7"
- "5.9"
env:
- CXX=g++-4.8
addons:
@@ -13,7 +13,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
+416 -33
View File
@@ -1,11 +1,381 @@
<a name="2.5.0"></a>
# [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)
<a name="2.4.0"></a>
# [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)
<a name="2.3.2"></a>
## [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)
<a name="2.3.1"></a>
## [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)
<a name="2.3.0"></a>
# [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)
<a name="2.2.0"></a>
# [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)
<a name="2.1.4"></a>
## [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)
<a name="2.1.3"></a>
## [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)
<a name="2.1.2"></a>
## [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)
<a name="2.1.1"></a>
## [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)
<a name="2.1.0"></a>
# [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)
<a name="2.0.2"></a>
## [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)
<a name="2.0.1"></a>
## [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)
<a name="2.0.0"></a>
# [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 `<input>`
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.
<a name="1.3.3"></a>
## [1.3.3](https://github.com/angular-ui/bootstrap/compare/1.3.2...1.3.3) (2016-05-23)
### Bug Fixes
* **datepicker:** add intermediary valid date check([a417ec2](https://github.com/angular-ui/bootstrap/commit/a417ec2)), closes [#5872](https://github.com/angular-ui/bootstrap/issues/5872) [#5865](https://github.com/angular-ui/bootstrap/issues/5865)
* **datepickerPopup:** convert numbers to date before parsing([5188463](https://github.com/angular-ui/bootstrap/commit/5188463)), closes [#5873](https://github.com/angular-ui/bootstrap/issues/5873) [#5871](https://github.com/angular-ui/bootstrap/issues/5871)
* **dropdown:** align position with vertical scrollbar([2b48259](https://github.com/angular-ui/bootstrap/commit/2b48259)), closes [#5830](https://github.com/angular-ui/bootstrap/issues/5830) [#4317](https://github.com/angular-ui/bootstrap/issues/4317)
### Features
* **accordion:** add alternative attribute support([4c40d9d](https://github.com/angular-ui/bootstrap/commit/4c40d9d)), closes [#5834](https://github.com/angular-ui/bootstrap/issues/5834) [#5839](https://github.com/angular-ui/bootstrap/issues/5839)
* **modal:** add resolve values to template([dd09148](https://github.com/angular-ui/bootstrap/commit/dd09148)), closes [#5808](https://github.com/angular-ui/bootstrap/issues/5808) [#5857](https://github.com/angular-ui/bootstrap/issues/5857)
* **tab:** allow strings for index([26d3995](https://github.com/angular-ui/bootstrap/commit/26d3995)), closes [#5713](https://github.com/angular-ui/bootstrap/issues/5713) [#5827](https://github.com/angular-ui/bootstrap/issues/5827)
* **tabs:** pass the selected index to onDeselect([241fea8](https://github.com/angular-ui/bootstrap/commit/241fea8)), closes [#5820](https://github.com/angular-ui/bootstrap/issues/5820) [#5821](https://github.com/angular-ui/bootstrap/issues/5821)
* **typeahead:** Add support for `should-select`([2ffb86f](https://github.com/angular-ui/bootstrap/commit/2ffb86f)), closes [#5671](https://github.com/angular-ui/bootstrap/issues/5671) [#5794](https://github.com/angular-ui/bootstrap/issues/5794)
### BREAKING CHANGES
* modal: Since this adds support for $resolve being exposed on $scope, it could potentially overwrite any pre-existing usage of it - this is an unlikely scenario, but marked as a breaking change in case this key is being used
<a name="1.3.2"></a>
## [1.3.2](https://github.com/angular-ui/bootstrap/compare/1.3.1...1.3.2) (2016-04-14)
### Bug Fixes
* **datepicker:** ensure datepicker is focused ([33cbd0f](https://github.com/angular-ui/bootstrap/commit/33cbd0f)), closes [#5761](https://github.com/angular-ui/bootstrap/issues/5761) [#5754](https://github.com/angular-ui/bootstrap/issues/5754)
* **datepickerPopup:** do not show incorrect warning ([98eb8f1](https://github.com/angular-ui/bootstrap/commit/98eb8f1)), closes [#5743](https://github.com/angular-ui/bootstrap/issues/5743) [#5747](https://github.com/angular-ui/bootstrap/issues/5747)
* **dropdown:** stop esc keydown event ([68200bb](https://github.com/angular-ui/bootstrap/commit/68200bb)), closes [#5778](https://github.com/angular-ui/bootstrap/issues/5778) [#5787](https://github.com/angular-ui/bootstrap/issues/5787)
* **modal:** missing require in modal ([a887d2b](https://github.com/angular-ui/bootstrap/commit/a887d2b)), closes [#5786](https://github.com/angular-ui/bootstrap/issues/5786)
* **modal:** scroll padding only added once ([aef08d5](https://github.com/angular-ui/bootstrap/commit/aef08d5)), closes [#5790](https://github.com/angular-ui/bootstrap/issues/5790) [#5789](https://github.com/angular-ui/bootstrap/issues/5789)
* **site:** allow FastClick to be blocked ([37f9cd2](https://github.com/angular-ui/bootstrap/commit/37f9cd2)), closes [#5756](https://github.com/angular-ui/bootstrap/issues/5756)
* **tooltip:** arrow position ([d96d53e](https://github.com/angular-ui/bootstrap/commit/d96d53e)), closes [#5785](https://github.com/angular-ui/bootstrap/issues/5785) [#5779](https://github.com/angular-ui/bootstrap/issues/5779)
* **typeahead:** use $setViewValue on blur ([bba3f27](https://github.com/angular-ui/bootstrap/commit/bba3f27)), closes [#5769](https://github.com/angular-ui/bootstrap/issues/5769) [#5694](https://github.com/angular-ui/bootstrap/issues/5694)
### Features
* **modal:** add no css import script ([a27a4e2](https://github.com/angular-ui/bootstrap/commit/a27a4e2)), closes [#5788](https://github.com/angular-ui/bootstrap/issues/5788)
### BREAKING CHANGES
* dropdown: Stops propagation of keydown event when escape key is pressed. Removes keydown event from the document and moves it to the dropdown element.
<a name="1.3.1"></a>
## [1.3.1](https://github.com/angular-ui/bootstrap/compare/1.3.0...1.3.1) (2016-04-05)
### Bug Fixes
* **datepickerPopup:** register popup module ([86ee9c3](https://github.com/angular-ui/bootstrap/commit/86ee9c3)), closes [#5745](https://github.com/angular-ui/bootstrap/issues/5745)
* **modal:** ensure correct index is set ([a08ad70](https://github.com/angular-ui/bootstrap/commit/a08ad70)), closes [#5733](https://github.com/angular-ui/bootstrap/issues/5733) [#5670](https://github.com/angular-ui/bootstrap/issues/5670)
<a name="1.3.0"></a>
# [1.3.0](https://github.com/angular-ui/bootstrap/compare/1.2.5...1.3.0) (2016-04-05)
### Bug Fixes
* **carousel:** fix animation from programmatic trigger ([44fb19b](https://github.com/angular-ui/bootstrap/commit/44fb19b)), closes [#5674](https://github.com/angular-ui/bootstrap/issues/5674) [#5601](https://github.com/angular-ui/bootstrap/issues/5601)
* modify CSS loaded for modules with dependencies ([8074899](https://github.com/angular-ui/bootstrap/commit/8074899)), closes [#5698](https://github.com/angular-ui/bootstrap/issues/5698)
* **carousel:** use proper sort comparison ([434c1c5](https://github.com/angular-ui/bootstrap/commit/434c1c5)), closes [#5638](https://github.com/angular-ui/bootstrap/issues/5638) [#5702](https://github.com/angular-ui/bootstrap/issues/5702)
* **modal:** body content shift ([c83d0a8](https://github.com/angular-ui/bootstrap/commit/c83d0a8)), closes [#2631](https://github.com/angular-ui/bootstrap/issues/2631) [#5711](https://github.com/angular-ui/bootstrap/issues/5711)
* **popover:** rename title attribute ([cca1460](https://github.com/angular-ui/bootstrap/commit/cca1460)), closes [#5719](https://github.com/angular-ui/bootstrap/issues/5719) [#5721](https://github.com/angular-ui/bootstrap/issues/5721)
* **tab:** add support for tab deselect prevention ([e0fcc00](https://github.com/angular-ui/bootstrap/commit/e0fcc00)), closes [#5720](https://github.com/angular-ui/bootstrap/issues/5720) [#5723](https://github.com/angular-ui/bootstrap/issues/5723)
* **tab:** correctly identify index on removal ([03e6047](https://github.com/angular-ui/bootstrap/commit/03e6047)), closes [#5689](https://github.com/angular-ui/bootstrap/issues/5689)
* **tab:** fix event handler call ([d767afb](https://github.com/angular-ui/bootstrap/commit/d767afb)), closes [#5720](https://github.com/angular-ui/bootstrap/issues/5720) [#5738](https://github.com/angular-ui/bootstrap/issues/5738)
### Features
* add support for loading without CSS ([de504fb](https://github.com/angular-ui/bootstrap/commit/de504fb)), closes [#5696](https://github.com/angular-ui/bootstrap/issues/5696)
* **carousel:** disable prev/next arrows if at bounds ([a3964d4](https://github.com/angular-ui/bootstrap/commit/a3964d4)), closes [#5704](https://github.com/angular-ui/bootstrap/issues/5704) [#5708](https://github.com/angular-ui/bootstrap/issues/5708)
* **dateparser:** use 68 as the yy format pivot year ([701e0cb](https://github.com/angular-ui/bootstrap/commit/701e0cb)), closes [#5735](https://github.com/angular-ui/bootstrap/issues/5735)
* **datepicker:** deprecate literal usage ([fc02fd1](https://github.com/angular-ui/bootstrap/commit/fc02fd1)), closes [#5658](https://github.com/angular-ui/bootstrap/issues/5658) [#5732](https://github.com/angular-ui/bootstrap/issues/5732)
* **datepicker:** remove deprecated code ([ad91c82](https://github.com/angular-ui/bootstrap/commit/ad91c82)), closes [#5660](https://github.com/angular-ui/bootstrap/issues/5660)
* **datepicker:** watch for changes when falsy ([45165ba](https://github.com/angular-ui/bootstrap/commit/45165ba)), closes [#5672](https://github.com/angular-ui/bootstrap/issues/5672) [#5677](https://github.com/angular-ui/bootstrap/issues/5677)
* **datepickerPopup:** split off popup from datepicker ([fbc873c](https://github.com/angular-ui/bootstrap/commit/fbc873c)), closes [#5676](https://github.com/angular-ui/bootstrap/issues/5676)
* **dropdown:** remove $locationChangeSuccess listener ([c534cb4](https://github.com/angular-ui/bootstrap/commit/c534cb4)), closes [#5648](https://github.com/angular-ui/bootstrap/issues/5648) [#5680](https://github.com/angular-ui/bootstrap/issues/5680)
* **timepicker:** remove automatic padding when empty ([449c0d1](https://github.com/angular-ui/bootstrap/commit/449c0d1)), closes [#5293](https://github.com/angular-ui/bootstrap/issues/5293) [#5299](https://github.com/angular-ui/bootstrap/issues/5299)
### BREAKING CHANGES
* carousel: This adds disabled CSS - there is a possibility this may break existing UI slightly for those adding custom CSS/making use of custom templates. Modify the template appropriately if necessary
* timepicker: This removes automatic padding done by the timepicker
when the input is empty - if that behavior is desired, write a custom
directive implementing it
* dropdown: The dropdown no longer will remain open on $locationChangeSuccess with autoclose set to disabled. Implement this logic inside app along with usage of isOpen two-way binding if this functionality is desired.
* datepickerPopup: The datepicker popup is no longer a part of the
datepicker module, but now a part of the datepickerPopup module. Please
change code accordingly if including specific modules
* datepicker: This removes inline datepicker attribute support and
attribute pass-throughs in the popup
<a name="1.2.5"></a>
## [1.2.5](https://github.com/angular-ui/bootstrap/compare/1.2.4...1.2.5) (2016-03-20)
### Bug Fixes
* **datepicker:** dereference date initialization ([1b888d4](https://github.com/angular-ui/bootstrap/commit/1b888d4)), closes [#5643](https://github.com/angular-ui/bootstrap/issues/5643) [#5441](https://github.com/angular-ui/bootstrap/issues/5441)
* **datepicker:** fix today button disabled condition ([316e96c](https://github.com/angular-ui/bootstrap/commit/316e96c)), closes [#5637](https://github.com/angular-ui/bootstrap/issues/5637) [#5622](https://github.com/angular-ui/bootstrap/issues/5622)
* **modal:** dynamically fetch elements ([e15a22a](https://github.com/angular-ui/bootstrap/commit/e15a22a)), closes [#5630](https://github.com/angular-ui/bootstrap/issues/5630) [#5050](https://github.com/angular-ui/bootstrap/issues/5050) [#5421](https://github.com/angular-ui/bootstrap/issues/5421)
* **popover:** add popover-html css ([54ac06a](https://github.com/angular-ui/bootstrap/commit/54ac06a)), closes [#5603](https://github.com/angular-ui/bootstrap/issues/5603) [#5602](https://github.com/angular-ui/bootstrap/issues/5602)
* **tooltip:** css to support arrow positioning ([66c416c](https://github.com/angular-ui/bootstrap/commit/66c416c)), closes [#5610](https://github.com/angular-ui/bootstrap/issues/5610) [#5611](https://github.com/angular-ui/bootstrap/issues/5611)
* **typeahead:** reset errors when clearing ([bc7c55a](https://github.com/angular-ui/bootstrap/commit/bc7c55a)), closes [#5641](https://github.com/angular-ui/bootstrap/issues/5641)
### Features
* **accordion:** add bound panel-class support ([ffb5529](https://github.com/angular-ui/bootstrap/commit/ffb5529)), closes [#5368](https://github.com/angular-ui/bootstrap/issues/5368) [#5596](https://github.com/angular-ui/bootstrap/issues/5596)
* **modal:** exclude hidden elements from query ([5ef56e2](https://github.com/angular-ui/bootstrap/commit/5ef56e2)), closes [#5512](https://github.com/angular-ui/bootstrap/issues/5512) [#5644](https://github.com/angular-ui/bootstrap/issues/5644)
* **rating:** ability to disable rating reset ([8747b58](https://github.com/angular-ui/bootstrap/commit/8747b58)), closes [#5532](https://github.com/angular-ui/bootstrap/issues/5532) [#5631](https://github.com/angular-ui/bootstrap/issues/5631)
* **tab:** add select expressions ([bb36e40](https://github.com/angular-ui/bootstrap/commit/bb36e40)), closes [#5438](https://github.com/angular-ui/bootstrap/issues/5438)
* **timepicker:** add pad-hours support ([c7be087](https://github.com/angular-ui/bootstrap/commit/c7be087)), closes [#4288](https://github.com/angular-ui/bootstrap/issues/4288) [#5633](https://github.com/angular-ui/bootstrap/issues/5633)
<a name="1.2.4"></a>
## [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)
<a name="1.2.3"></a>
## [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
@@ -24,7 +394,7 @@
<a name="1.2.2"></a>
## [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
@@ -41,7 +411,7 @@
<a name="1.2.1"></a>
## [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
@@ -51,7 +421,7 @@
<a name="1.2.0"></a>
# [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
@@ -111,7 +481,7 @@ template
<a name="1.1.2"></a>
## [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
@@ -129,7 +499,7 @@ template
<a name="1.1.1"></a>
## [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
@@ -179,7 +549,7 @@ template in one's app and provide the necessary CSS
<a name="1.0.3"></a>
# [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
@@ -189,7 +559,7 @@ template in one's app and provide the necessary CSS
<a name="1.0.2"></a>
# [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
@@ -199,7 +569,7 @@ template in one's app and provide the necessary CSS
<a name="1.0.1"></a>
# [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
@@ -220,7 +590,7 @@ template in one's app and provide the necessary CSS
<a name="1.0.0"></a>
# [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
@@ -365,7 +735,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
<a name="0.14.3"></a>
# [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
@@ -390,7 +760,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
<a name="0.14.2"></a>
# [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
@@ -402,7 +772,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
<a name="0.14.1"></a>
# [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
@@ -585,7 +955,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
<a name"0.13.3"></a>
# 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
@@ -672,7 +1042,7 @@ Closes #4080
<a name"0.13.2"></a>
# 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
@@ -718,7 +1088,7 @@ Closes #4080
<a name"0.13.1"></a>
# 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
@@ -779,7 +1149,7 @@ Closes #4080
<a name="0.13.0"></a>
# 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
@@ -878,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)
<a name="0.12.1"></a>
# [0.12.1](https://github.com/angular-ui/bootstrap/compare/0.12.0...0.12.1) (2015-02-20)
## Bug Fixes
@@ -886,7 +1257,7 @@ Closes #4080
- incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef))
<a name="0.12.0"></a>
# 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
@@ -939,11 +1310,13 @@ once* and can no longer be changed after initialization.
```
# 0.11.2 (2014-09-26)
<a name="0.11.2"></a>
# [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)
<a name="0.11.1"></a>
# [0.11.1](https://github.com/angular-ui/bootstrap/compare/0.11.0...0.11.1) (2014-09-26)
## Features
@@ -977,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)
<a name="0.11.0"></a>
# [0.11.0](https://github.com/angular-ui/bootstrap/compare/0.10.0...0.11.0) (2014-05-01)
## Features
@@ -1155,7 +1529,8 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b
<tabset type="{{navtype}}" ...></tabset>
```
# 0.10.0 (2014-01-13)
<a name="0.10.0"></a>
# [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_
@@ -1176,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)
<a name="0.9.0"></a>
# [0.9.0](https://github.com/angular-ui/bootstrap/compare/0.8.0...0.9.0) (2013-12-28)
_This release adds Bootstrap3 support_
@@ -1219,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)
<a name="0.8.0"></a>
# [0.8.0](https://github.com/angular-ui/bootstrap/compare/0.7.0...0.8.0) (2013-12-28)
## Features
@@ -1286,7 +1663,8 @@ _This release adds Bootstrap3 support_
<progress><bar ng-repeat="obj in objs" value="obj.var" type="{{obj.type}}"></bar></progress>
```
# 0.7.0 (2013-11-22)
<a name="0.7.0"></a>
# [0.7.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-11-22)
## Features
@@ -1334,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)
<a name="0.6.0"></a>
# [0.6.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-09-08)
## Features
@@ -1413,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)
<a name="0.5.0"></a>
# [0.5.0](https://github.com/angular-ui/bootstrap/compare/0.4.0...0.5.0) (2013-08-04)
## Features
@@ -1496,7 +1876,8 @@ The placment='mouse' is gone with no equivalent
<pagination first-text="{{var1}}" ...></pagination>
```
# 0.4.0 (2013-06-24)
<a name="0.4.0"></a>
# [0.4.0](https://github.com/angular-ui/bootstrap/compare/0.3.0...0.4.0) (2013-06-24)
## Features
@@ -1614,8 +1995,8 @@ The placment='mouse' is gone with no equivalent
</tabset>
```
# 0.3.0 (2013-04-30)
<a name="0.3.0"></a>
# [0.3.0](https://github.com/angular-ui/bootstrap/compare/0.2.0...0.3.0) (2013-04-30)
## Features
@@ -1656,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)
<a name="0.2.0"></a>
# [0.2.0](https://github.com/angular-ui/bootstrap/compare/0.1.0...0.2.0) (2013-03-03)
## Features
@@ -1690,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))
<a name="0.1.0"></a>
# 0.1.0 (2013-02-02)
_Very first, initial release_.
+4 -4
View File
@@ -9,8 +9,8 @@ module.exports = function(grunt) {
grunt.util.linefeed = '\n';
grunt.initConfig({
ngversion: '1.5.0',
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',
@@ -232,7 +232,7 @@ module.exports = function(grunt) {
name: name,
moduleName: enquote(`ui.bootstrap.${name}`),
displayName: ucwords(breakup(name, ' ')),
srcFiles: grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`]),
srcFiles: grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`]),
cssFiles: grunt.file.expand(`src/${name}/*.css`),
tplFiles: grunt.file.expand(`template/${name}/*.html`),
tpljsFiles: grunt.file.expand(`template/${name}/*.html.js`),
@@ -264,7 +264,7 @@ module.exports = function(grunt) {
function dependenciesForModule(name) {
var deps = [];
grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`])
grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`])
.map(grunt.file.read)
.forEach(function(contents) {
//Strategy: find where module is declared,
+1 -1
View File
@@ -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
+34 -7
View File
@@ -3,6 +3,7 @@
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.svg)](http://travis-ci.org/angular-ui/bootstrap)
[![devDependency Status](https://david-dm.org/angular-ui/bootstrap/dev-status.svg?branch=master)](https://david-dm.org/angular-ui/bootstrap#info=devDependencies)
[![CDNJS](https://img.shields.io/cdnjs/v/angular-ui-bootstrap.svg)](https://cdnjs.com/libraries/angular-ui-bootstrap/)
### Quick links
- [Demo](#demo)
@@ -13,7 +14,7 @@
- [NuGet](#install-with-nuget)
- [Custom](#custom-build)
- [Manual](#manual-download)
- [Webpack](#webpack)
- [Webpack / JSPM](#webpack--jspm)
- [Support](#support)
- [FAQ](#faq)
- [Code of Conduct](#code-of-conduct)
@@ -27,16 +28,16 @@
# 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
Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core).
Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core).
# Installation
Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required.
*Notes:*
*Notes:*
* Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation.
* UI Bootstrap depends on [ngTouch](https://docs.angularjs.org/api/ngTouch) for swipe actions. Include `ngTouch` in the module dependencies for your app in order to enable swiping.
@@ -73,7 +74,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
@@ -89,9 +90,10 @@ When you are done downloading all the dependencies and project files the only re
angular.module('myModule', ['ui.bootstrap']);
```
# Webpack
# Webpack / JSPM
To use this project with webpack, follow the [NPM](#install-with-npm) instructions. Now, if you want to use only the accordion, you can do:
To use this project with webpack, follow the [NPM](#install-with-npm) instructions.
Now, if you want to use only the accordion, you can do:
```js
import accordion from 'angular-ui-bootstrap/src/accordion';
@@ -112,6 +114,31 @@ This will load all the dependencies (if any) and also the templates (if any).
Be sure to have a loader able to process `css` files like `css-loader`.
If you would prefer not to load your css through your JavaScript file loader/bundler, you can choose to import the `index-nocss.js` file instead, which is available for the modules:
* carousel
* datepicker
* datepickerPopup
* dropdown
* modal
* popover
* position
* timepicker
* tooltip
* typeahead
The other modules, such as `accordion` in the example below, do not have CSS resources to load, so you should continue to import them as normal:
```js
import accordion from 'angular-ui-bootstrap/src/accordion';
import typeahead from 'angular-ui-bootstrap/src/typeahead/index-nocss.js';
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
+1
View File
@@ -28,6 +28,7 @@ module.exports = function(config) {
// list of files to exclude
exclude: [
'src/**/index.js',
'src/**/index-nocss.js',
'src/**/docs/*'
],
+3 -1
View File
@@ -1,6 +1,8 @@
/* global FastClick, smoothScroll */
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAnimate', 'ngSanitize'], function($httpProvider){
FastClick.attach(document.body);
if (!!window.FastClick) {
FastClick.attach(document.body);
}
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}).run(['$location', function($location){
//Allows us to navigate to the correct element on initialization
+1 -1
View File
@@ -73,7 +73,7 @@ section {
}
.navbar .collapse {
.navbar-fixed-top .collapse {
border-top: 1px solid #e7e7e7;
margin-left: -15px;
margin-right: -15px;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

+3 -2
View File
@@ -4,7 +4,7 @@ angular.module('plunker', [])
return function (ngVersion, bsVersion, version, module, content) {
var form = angular.element('<form style="display: none;" method="post" action="http://plnkr.co/edit/?p=preview" target="_blank"></form>');
var form = angular.element('<form style="display: none;" method="post" action="https://plnkr.co/edit/?p=preview" target="_blank"></form>');
var addField = function (name, value) {
var input = angular.element('<input type="hidden" name="' + name + '">');
input.attr('value', value);
@@ -17,6 +17,7 @@ angular.module('plunker', [])
' <head>\n' +
' <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular.js"></script>\n' +
' <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-animate.js"></script>\n' +
' <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-sanitize.js"></script>\n' +
' <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-'+version+'.js"></script>\n' +
' <script src="example.js"></script>\n' +
' <link href="//netdna.bootstrapcdn.com/bootstrap/'+bsVersion+'/css/bootstrap.min.css" rel="stylesheet">\n' +
@@ -28,7 +29,7 @@ angular.module('plunker', [])
};
var scriptContent = function(content) {
return "angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap']);" + "\n" + content;
return "angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);" + "\n" + content;
};
addField('description', 'http://angular-ui.github.io/bootstrap/');
+10 -2
View File
@@ -147,6 +147,13 @@
<div class="page-header">
<h1>Getting started</h1>
</div>
<h3>Angular 2</h3>
<p>
For Angular 2 support, check out
<a href="https://ng-bootstrap.github.io" target="_blank">
ng-bootstrap
</a>, created by the UI Bootstrap team.
</p>
<h3>Dependencies</h3>
<p>
This repository contains a set of <strong>native AngularJS directives</strong> based on
@@ -173,8 +180,9 @@
<p>Alternativelly, if you are only interested in a subset of directives, you can
<a ng-click="showBuildModal()">create your own build</a>.
</p>
<p>Whichever method you choose the good news that the overall size of a download is very small:
&lt;76kB for all directives (~20kB with gzip compression!)</p>
<p>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)</p>
<h3>Installation</h3>
<p>As soon as you've got all the files downloaded and included in your page you just need to declare
a dependency on the <code>ui.bootstrap</code> <a href="http://docs.angularjs.org/guide/module">module</a>:<br>
+21 -11
View File
@@ -1,8 +1,15 @@
{
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
"name": "angular-ui-bootstrap",
"version": "1.2.4",
"version": "2.5.1-SNAPSHOT",
"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,21 +30,22 @@
"url": "https://github.com/angular-ui/bootstrap.git"
},
"devDependencies": {
"angular": "1.5.0",
"angular-mocks": "1.5.0",
"angular-sanitize": "1.5.0",
"angular": "1.6.1",
"angular-mocks": "1.6.1",
"angular-sanitize": "1.6.1",
"grunt": "^0.4.5",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-uglify": "^0.11.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-conventional-changelog": "^5.0.0",
"grunt-cli": "^1.2.0",
"grunt-contrib-concat": "^1.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-uglify": "^1.0.1",
"grunt-contrib-watch": "^1.0.0",
"grunt-conventional-changelog": "^6.1.0",
"grunt-ddescribe-iit": "0.0.6",
"grunt-eslint": "^17.3.1",
"grunt-html2js": "^0.3.0",
"grunt-karma": "^0.12.0",
"jasmine-core": "^2.2.0",
"karma": "0.13.5",
"karma": "0.13.22",
"karma-chrome-launcher": "^0.2.0",
"karma-coverage": "^0.5.0",
"karma-firefox-launcher": "^0.1.4",
@@ -44,8 +53,9 @@
"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.5.1"
"shelljs": "^0.6.0"
},
"license": "MIT"
}
+15 -3
View File
@@ -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,12 +58,13 @@ 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';
},
scope: {
heading: '@', // Interpolate the heading attribute onto this scope
panelClass: '@?', // Ditto with panelClass
isOpen: '=?',
isDisabled: '=?'
},
@@ -73,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';
@@ -123,11 +125,21 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
link: function(scope, element, attrs, controller) {
scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
if (heading) {
var elem = angular.element(element[0].querySelector('[uib-accordion-header]'));
var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
elem.html('');
elem.append(heading);
}
});
}
};
function getHeaderSelectors() {
return 'uib-accordion-header,' +
'data-uib-accordion-header,' +
'x-uib-accordion-header,' +
'uib\\:accordion-header,' +
'[uib-accordion-header],' +
'[data-uib-accordion-header],' +
'[x-uib-accordion-header]';
}
});
+29 -22
View File
@@ -1,15 +1,16 @@
<div ng-controller="AccordionDemoCtrl">
<script type="text/ng-template" id="group-template.html">
<div class="panel {{panelClass || 'panel-default'}}">
<div class="panel-heading">
<h4 class="panel-title" style="color:#fa39c3">
<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span
ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
</h4>
</div>
<div class="panel-collapse collapse" uib-collapse="!isOpen">
<div class="panel-body" style="text-align: right" ng-transclude></div>
</div>
<div class="panel-heading">
<h4 class="panel-title" style="color:#fa39c3">
<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading">
<span uib-accordion-header ng-class="{'text-muted': isDisabled}">
{{heading}}
</span>
</a>
</h4>
</div>
<div class="panel-collapse collapse" uib-collapse="!isOpen">
<div class="panel-body" style="text-align: right" ng-transclude></div>
</div>
</script>
@@ -25,29 +26,35 @@
</label>
</div>
<uib-accordion close-others="oneAtATime">
<uib-accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
<div uib-accordion-group class="panel-default" heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
This content is straight in the template.
</uib-accordion-group>
<uib-accordion-group heading="{{group.title}}" ng-repeat="group in groups">
</div>
<div uib-accordion-group class="panel-default" heading="{{group.title}}" ng-repeat="group in groups">
{{group.content}}
</uib-accordion-group>
<uib-accordion-group heading="Dynamic Body Content">
</div>
<div uib-accordion-group class="panel-default" heading="Dynamic Body Content">
<p>The body of the uib-accordion group grows to fit the contents</p>
<button type="button" class="btn btn-default btn-sm" ng-click="addItem()">Add Item</button>
<div ng-repeat="item in items">{{item}}</div>
</uib-accordion-group>
<uib-accordion-group heading="Custom template" template-url="group-template.html">
</div>
<div uib-accordion-group class="panel-default" heading="Custom template" template-url="group-template.html">
Hello
</uib-accordion-group>
<uib-accordion-group heading="Delete account" panel-class="panel-danger">
</div>
<div uib-accordion-group class="panel-default" is-open="status.isCustomHeaderOpen" template-url="group-template.html">
<uib-accordion-heading>
Custom template with custom header template <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.isCustomHeaderOpen, 'glyphicon-chevron-right': !status.isCustomHeaderOpen}"></i>
</uib-accordion-heading>
World
</div>
<div uib-accordion-group class="panel-danger" heading="Delete account">
<p>Please, to delete your account, click the button below</p>
<button class="btn btn-danger">Delete</button>
</uib-accordion-group>
<uib-accordion-group is-open="status.open">
</div>
<div uib-accordion-group class="panel-default" is-open="status.open">
<uib-accordion-heading>
I can have markup, too! <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
</uib-accordion-heading>
This is just some content to illustrate fancy headings.
</uib-accordion-group>
</div>
</uib-accordion>
</div>
+1
View File
@@ -20,6 +20,7 @@ angular.module('ui.bootstrap.demo').controller('AccordionDemoCtrl', function ($s
};
$scope.status = {
isCustomHeaderOpen: false,
isFirstOpen: true,
isFirstDisabled: false
};
+1 -5
View File
@@ -32,10 +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.
@@ -48,6 +44,6 @@ If you're using a custom template for the `uib-accordion-group`, you'll need to
### Known issues
To use clickable elements within the accordion, you have override the accordion-group template to use div elements instead of anchor elements, and add `cursor: pointer` in your CSS. This is due to browsers interpreting anchor elements as the target of any click event, which triggers routing when certain elements such as buttons are nested inside the anchor element.
To use clickable elements within the accordion, you have to override the accordion-group template to use div elements instead of anchor elements, and add `cursor: pointer` in your CSS. This is due to browsers interpreting anchor elements as the target of any click event, which triggers routing when certain elements such as buttons are nested inside the anchor element.
If custom classes on the accordion-group element are desired, one needs to either modify the template to remove the `ng-class` usage in the accordion-group template and use ng-class on the accordion-group element (not recommended), or use an interpolated expression in the class attribute, i.e. `<uib-accordion-group class="{{customClass()}}"></uib-accordion-group>`.
+1
View File
@@ -1,4 +1,5 @@
require('../collapse');
require('../tabindex');
require('../../template/accordion/accordion-group.html.js');
require('../../template/accordion/accordion.html.js');
require('./accordion');
+32 -43
View File
@@ -176,12 +176,12 @@ describe('uib-accordion', function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group heading="title 1" template-url="foo/bar.html"></uib-accordion-group>' +
'<div uib-accordion-group heading="title 1" template-url="foo/bar.html"></div>' +
'</uib-accordion>';
element = $compile(tpl)(scope);
scope.$digest();
expect(element.find('[template-url]').html()).toBe('baz');
expect(element.find('[template-url]').html()).toBe('<div>baz</div>');
}));
describe('with static panels', function() {
@@ -189,8 +189,8 @@ describe('uib-accordion', function() {
spyOn(Math, 'random').and.returnValue(0.1);
var tpl =
'<uib-accordion>' +
'<uib-accordion-group heading="title 1">Content 1</uib-accordion-group>' +
'<uib-accordion-group heading="title 2">Content 2</uib-accordion-group>' +
'<div uib-accordion-group heading="title 1">Content 1</div>' +
'<div uib-accordion-group heading="title 2">Content 2</div>' +
'</uib-accordion>';
element = angular.element(tpl);
$compile(element)(scope);
@@ -288,8 +288,8 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group heading="title 1" open-class="custom-open-class">Content 1</uib-accordion-group>' +
'<uib-accordion-group heading="title 2" open-class="custom-open-class">Content 2</uib-accordion-group>' +
'<div uib-accordion-group heading="title 1" open-class="custom-open-class">Content 1</div>' +
'<div uib-accordion-group heading="title 2" open-class="custom-open-class">Content 2</div>' +
'</uib-accordion>';
element = angular.element(tpl);
$compile(element)(scope);
@@ -318,7 +318,7 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</uib-accordion-group>' +
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</div>' +
'</uib-accordion>';
element = angular.element(tpl);
model = [
@@ -363,8 +363,8 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group heading="title 1" is-open="open.first">Content 1</uib-accordion-group>' +
'<uib-accordion-group heading="title 2" is-open="open.second">Content 2</uib-accordion-group>' +
'<div uib-accordion-group heading="title 1" is-open="open.first">Content 1</div>' +
'<div uib-accordion-group heading="title 2" is-open="open.second">Content 2</div>' +
'</uib-accordion>';
element = angular.element(tpl);
scope.open = { first: false, second: true };
@@ -393,8 +393,8 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group heading="title 1" is-open="open1"><div ng-repeat="item in items">{{item}}</div></uib-accordion-group>' +
'<uib-accordion-group heading="title 2" is-open="open2">Static content</uib-accordion-group>' +
'<div uib-accordion-group heading="title 1" is-open="open1"><div ng-repeat="item in items">{{item}}</div></div>' +
'<div uib-accordion-group heading="title 2" is-open="open2">Static content</div>' +
'</uib-accordion>';
element = angular.element(tpl);
scope.items = ['Item 1', 'Item 2', 'Item 3'];
@@ -421,7 +421,7 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open">{{group.content}}</uib-accordion-group>' +
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open">{{group.content}}</div>' +
'</uib-accordion>';
element = angular.element(tpl);
scope.groups = [
@@ -456,7 +456,7 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open" class="testClass">{{group.content}}</uib-accordion-group>' +
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open" class="testClass">{{group.content}}</div>' +
'</uib-accordion>';
element = angular.element(tpl);
scope.groups = [
@@ -480,7 +480,7 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion>' +
'<uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</uib-accordion-group>' +
'<div uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</div>' +
'</uib-accordion>';
element = angular.element(tpl);
scope.disabled = true;
@@ -519,10 +519,10 @@ describe('uib-accordion', function() {
function isDisabledStyleCheck() {
var tpl =
'<uib-accordion ng-init="a = [1,2,3]">' +
'<uib-accordion-group heading="I get overridden" is-disabled="true">' +
'<div uib-accordion-group heading="I get overridden" is-disabled="true">' +
'<uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </uib-accordion-heading>' +
'Body' +
'</uib-accordion-group>' +
'</div>' +
'</uib-accordion>';
scope.disabled = true;
element = $compile(tpl)(scope);
@@ -536,10 +536,10 @@ describe('uib-accordion', function() {
beforeEach(function() {
var tpl =
'<uib-accordion ng-init="a = [1,2,3]">' +
'<uib-accordion-group heading="I get overridden">' +
'<div uib-accordion-group heading="I get overridden">' +
'<uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </uib-accordion-heading>' +
'Body' +
'</uib-accordion-group>' +
'</div>' +
'</uib-accordion>';
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 =
'<uib-accordion ng-init="a = [1,2,3]">' +
'<uib-accordion-group heading="I get overridden">' +
'<div uib-accordion-group heading="I get overridden">' +
'<div uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
'Body' +
'</uib-accordion-group>' +
'</div>' +
'</uib-accordion>';
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('<uib-accordion><uib-accordion-group ng-repeat="x in [1,2,3]"><uib-accordion-heading>{{x}}</uib-accordion-heading></uib-accordion-group></uib-accordion>')(scope);
element = $compile('<uib-accordion><div uib-accordion-group ng-repeat="x in [1,2,3]"><uib-accordion-heading>{{x}}</uib-accordion-heading></div></uib-accordion>')(scope);
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('<uib-accordion><uib-accordion-group ng-repeat="x in [1,2,3]"><div uib-accordion-heading>{{x}}</div></uib-accordion-group></uib-accordion>')(scope);
element = $compile('<uib-accordion><div uib-accordion-group ng-repeat="x in [1,2,3]"><div uib-accordion-heading>{{x}}</div></div></uib-accordion>')(scope);
scope.$digest();
groups = element.find('.panel');
expect(groups.length).toBe(3);
@@ -610,26 +610,15 @@ describe('uib-accordion', function() {
});
});
describe('uib-accordion group panel class - #3968', function() {
it('should use the default value when panel class is falsy', function() {
element = $compile('<uib-accordion><uib-accordion-group heading="Heading">Content</uib-accordion-group></uib-accordion>')(scope);
scope.$digest();
groups = element.find('.panel');
expect(groups.eq(0)).toHaveClass('panel-default');
describe('uib-accordion-heading attribute, with custom template', function() {
it('should transclude heading to a template using data-uib-accordion-header', inject(function($templateCache) {
$templateCache.put('foo/bar.html', '<div class="panel"><a uib-accordion-transclude="heading" class="accordion-toggle"><span data-uib-accordion-header></span></a><div ng-transclude></div></div>');
element = $compile('<uib-accordion><uib-accordion-group heading="Heading" panel-class="">Content</uib-accordion-group></uib-accordion>')(scope);
scope.$digest();
groups = element.find('.panel');
expect(groups.eq(0)).toHaveClass('panel-default');
});
it('should use the specified value when not falsy', function() {
element = $compile('<uib-accordion><uib-accordion-group heading="Heading" panel-class="custom-class">Content</uib-accordion-group></uib-accordion>')(scope);
scope.$digest();
groups = element.find('.panel');
expect(groups.eq(0)).toHaveClass('custom-class');
expect(groups.eq(0)).not.toHaveClass('panel-default');
});
element = $compile('<uib-accordion><div uib-accordion-group template-url="foo/bar.html"><uib-accordion-heading>baz</uib-accordion-heading></div></uib-accordion>')(scope);
scope.$digest();
groups = element.find('.panel');
expect(findGroupLink(0).text()).toBe('baz');
}));
});
});
});
+7 -3
View File
@@ -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: '&'
}
};
+3 -5
View File
@@ -1,11 +1,9 @@
<div ng-controller="AlertDemoCtrl">
<script type="text/ng-template" id="alert.html">
<div class="alert" style="background-color:#fa39c3;color:white" role="alert">
<div ng-transclude></div>
</div>
<div ng-transclude></div>
</script>
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<uib-alert template-url="alert.html">A happy alert!</uib-alert>
<div uib-alert ng-repeat="alert in alerts" ng-class="'alert-' + (alert.type || 'warning')" close="closeAlert($index)">{{alert.msg}}</div>
<div uib-alert template-url="alert.html" style="background-color:#fa39c3;color:white">A happy alert!</div>
<button type="button" class='btn btn-default' ng-click="addAlert()">Add Alert</button>
</div>
+2 -6
View File
@@ -5,15 +5,11 @@ This directive can be used both to generate alerts from static and dynamic model
* `close()`
<small class="badge">$</small> -
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.
+11 -34
View File
@@ -12,9 +12,10 @@ describe('uib-alert', function() {
element = angular.element(
'<div>' +
'<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}"' +
'<div uib-alert ng-repeat="alert in alerts" ' +
'ng-class="\'alert-\' + (alert.type || \'warning\')" ' +
'close="removeAlert($index)">{{alert.msg}}' +
'</uib-alert>' +
'</div>' +
'</div>');
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', '<div>{{alert.text}}</div>');
element = $compile('<uib-alert></uib-alert>')(scope);
element = $compile('<div uib-alert></div>')(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('<div class="ng-binding">foo</div>');
});
it('should support custom templates', function() {
$templateCache.put('foo/bar.html', '<div>baz</div>');
element = $compile('<uib-alert template-url="foo/bar.html"></uib-alert>')(scope);
element = $compile('<div uib-alert template-url="foo/bar.html"></div>')(scope);
scope.$digest();
expect(element.html()).toBe('baz');
expect(element.html()).toBe('<div>baz</div>');
});
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('<uib-alert>No close</uib-alert>')(scope);
element = $compile('<div uib-alert>No close</div>')(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('<uib-alert class="alert-block" type="info">Default alert!</uib-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('<uib-alert close="removeAlert()" dismiss-on-timeout="500">Default alert!</uib-alert>')(scope);
$compile('<div uib-alert close="removeAlert()" dismiss-on-timeout="500">Default alert!</div>')(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('<uib-alert close="removeAlert()" dismiss-on-timeout="{{dismissTime}}">Default alert!</uib-alert>')(scope);
$compile('<div uib-alert close="removeAlert()" dismiss-on-timeout="{{dismissTime}}">Default alert!</div>')(scope);
scope.$digest();
$timeout.flush(100);
+4
View File
@@ -44,3 +44,7 @@ With the buttons directive, we can make a group of buttons behave like a set of
* `toggleEvent`
_(Default: `click`)_ -
Event used to toggle the buttons.
### Known issues
To use tooltips or popovers on elements within a `btn-group`, set the tooltip/popover `appendToBody` option to `true`. This is due to Bootstrap CSS styling. See [here](http://getbootstrap.com/components/#btn-groups) for more information.
+24 -30
View File
@@ -5,9 +5,10 @@ angular.module('ui.bootstrap.carousel', [])
slides = self.slides = $scope.slides = [],
SLIDE_DIRECTION = 'uib-slideDirection',
currentIndex = $scope.active,
currentInterval, isPlaying, bufferedTransitions = [];
currentInterval, isPlaying;
var destroyed = false;
$element.addClass('carousel');
self.addSlide = function(slide, element) {
slides.push({
@@ -15,7 +16,7 @@ angular.module('ui.bootstrap.carousel', [])
element: element
});
slides.sort(function(a, b) {
return +a.slide.index > +b.slide.index;
return +a.slide.index - +b.slide.index;
});
//if this is the first slide or the slide is set to active, select it
if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
@@ -66,11 +67,6 @@ angular.module('ui.bootstrap.carousel', [])
self.removeSlide = function(slide) {
var index = findSlideIndex(slide);
var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
if (bufferedIndex !== -1) {
bufferedTransitions.splice(bufferedIndex, 1);
}
//get the index of the slide inside the carousel
slides.splice(index, 1);
if (slides.length > 0 && currentIndex === index) {
@@ -94,7 +90,6 @@ angular.module('ui.bootstrap.carousel', [])
if (slides.length === 0) {
currentIndex = null;
$scope.active = null;
clearBufferedTransitions();
}
};
@@ -109,8 +104,6 @@ angular.module('ui.bootstrap.carousel', [])
if (nextSlide.slide.index !== currentIndex &&
!$scope.$currentTransition) {
goNext(nextSlide.slide, nextIndex, direction);
} else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
bufferedTransitions.push(slides[nextIndex]);
}
};
@@ -123,6 +116,14 @@ angular.module('ui.bootstrap.carousel', [])
return $scope.active === slide.slide.index;
};
$scope.isPrevDisabled = function() {
return $scope.active === 0 && $scope.noWrap();
};
$scope.isNextDisabled = function() {
return $scope.active === slides.length - 1 && $scope.noWrap();
};
$scope.pause = function() {
if (!$scope.noPause) {
isPlaying = false;
@@ -137,6 +138,9 @@ angular.module('ui.bootstrap.carousel', [])
}
};
$element.on('mouseenter', $scope.pause);
$element.on('mouseleave', $scope.play);
$scope.$on('$destroy', function() {
destroyed = true;
resetTimer();
@@ -161,19 +165,13 @@ angular.module('ui.bootstrap.carousel', [])
var slide = slides[index];
if (slide) {
currentIndex = index;
setActive(index);
self.select(slides[index]);
currentIndex = index;
}
}
});
function clearBufferedTransitions() {
while (bufferedTransitions.length) {
bufferedTransitions.shift();
}
}
function getSlideByIndex(index) {
for (var i = 0, l = slides.length; i < l; ++i) {
if (slides[i].index === index) {
@@ -209,14 +207,6 @@ angular.module('ui.bootstrap.carousel', [])
if (phase === 'close') {
$scope.$currentTransition = null;
$animate.off('addClass', element);
if (bufferedTransitions.length) {
var nextSlide = bufferedTransitions.pop().slide;
var nextIndex = nextSlide.index;
var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
clearBufferedTransitions();
goNext(nextSlide, nextIndex, nextDirection);
}
}
});
}
@@ -247,7 +237,6 @@ angular.module('ui.bootstrap.carousel', [])
function resetTransition(slides) {
if (!slides.length) {
$scope.$currentTransition = null;
clearBufferedTransitions();
}
}
@@ -272,9 +261,9 @@ angular.module('ui.bootstrap.carousel', [])
.directive('uibCarousel', function() {
return {
transclude: true,
replace: true,
controller: 'UibCarouselController',
controllerAs: 'carousel',
restrict: 'A',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/carousel/carousel.html';
},
@@ -288,11 +277,11 @@ angular.module('ui.bootstrap.carousel', [])
};
})
.directive('uibSlide', function() {
.directive('uibSlide', ['$animate', function($animate) {
return {
require: '^uibCarousel',
restrict: 'A',
transclude: true,
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/carousel/slide.html';
},
@@ -301,14 +290,19 @@ angular.module('ui.bootstrap.carousel', [])
index: '=?'
},
link: function (scope, element, attrs, carouselCtrl) {
element.addClass('item');
carouselCtrl.addSlide(scope, element);
//when the scope is destroyed then remove the slide from the current slides array
scope.$on('$destroy', function() {
carouselCtrl.removeSlide(scope);
});
scope.$watch('active', function(active) {
$animate[active ? 'addClass' : 'removeClass'](element, 'active');
});
}
};
})
}])
.animation('.item', ['$animateCss',
function($animateCss) {
+4 -4
View File
@@ -1,14 +1,14 @@
<div ng-controller="CarouselDemoCtrl">
<div style="height: 305px">
<uib-carousel active="active" interval="myInterval" no-wrap="noWrapSlides">
<uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
<div uib-carousel active="active" interval="myInterval" no-wrap="noWrapSlides">
<div uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
<h4>Slide {{slide.id}}</h4>
<p>{{slide.text}}</p>
</div>
</uib-slide>
</uib-carousel>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
+1 -1
View File
@@ -8,7 +8,7 @@ angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($sc
$scope.addSlide = function() {
var newWidth = 600 + slides.length + 1;
slides.push({
image: 'http://lorempixel.com/' + newWidth + '/300',
image: '//unsplash.it/' + newWidth + '/300',
text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4],
id: currIndex++
});
+9
View File
@@ -0,0 +1,9 @@
require('../../template/carousel/carousel.html.js');
require('../../template/carousel/slide.html.js');
require('./carousel');
var MODULE_NAME = 'ui.bootstrap.module.carousel';
angular.module(MODULE_NAME, ['ui.bootstrap.carousel', 'uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html']);
module.exports = MODULE_NAME;
+1 -10
View File
@@ -1,11 +1,2 @@
require('../../template/carousel/carousel.html.js');
require('../../template/carousel/slide.html.js');
require('./carousel');
require('./carousel.css');
var MODULE_NAME = 'ui.bootstrap.module.carousel';
angular.module(MODULE_NAME, ['ui.bootstrap.carousel', 'uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html']);
module.exports = MODULE_NAME;
module.exports = require('./index-nocss.js');
+69 -63
View File
@@ -1,17 +1,5 @@
describe('carousel', function() {
beforeEach(module('ui.bootstrap.carousel', function($compileProvider, $provide) {
angular.forEach(['ngSwipeLeft', 'ngSwipeRight'], makeMock);
function makeMock(name) {
$provide.value(name + 'Directive', []); //remove existing directive if it exists
$compileProvider.directive(name, function() {
return function(scope, element, attr) {
element.on(name, function() {
scope.$apply(attr[name]);
});
};
});
}
}));
beforeEach(module('ui.bootstrap.carousel'));
beforeEach(module('ngAnimateMock'));
beforeEach(module('uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html'));
@@ -36,11 +24,11 @@ describe('carousel', function() {
{content: 'three', index: 2}
];
elm = $compile(
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
'<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
'<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
'{{slide.content}}' +
'</uib-slide>' +
'</uib-carousel>'
'</div>' +
'</div>'
)(scope);
scope.interval = 5000;
scope.nopause = undefined;
@@ -60,19 +48,19 @@ describe('carousel', function() {
it('should allow overriding of the carousel template', function() {
$templateCache.put('foo/bar.html', '<div>foo</div>');
elm = $compile('<uib-carousel template-url="foo/bar.html"></uib-carousel>')(scope);
elm = $compile('<div uib-carousel template-url="foo/bar.html"></div>')(scope);
$rootScope.$digest();
expect(elm.html()).toBe('foo');
expect(elm.html()).toBe('<div>foo</div>');
});
it('should allow overriding of the slide template', function() {
$templateCache.put('foo/bar.html', '<div class="slide">bar</div>');
elm = $compile(
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
'<uib-slide template-url="foo/bar.html"></uib-slide>' +
'</uib-carousel>'
'<div uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
'<div uib-slide template-url="foo/bar.html"></div>' +
'</div>'
)(scope);
$rootScope.$digest();
@@ -101,11 +89,11 @@ describe('carousel', function() {
it('should stop cycling slides forward when noWrap is truthy', function () {
elm = $compile(
'<uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
'<div uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
'<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
'{{slide.content}}' +
'</uib-slide>' +
'</uib-carousel>'
'</div>' +
'</div>'
)(scope);
scope.noWrap = true;
@@ -124,11 +112,11 @@ describe('carousel', function() {
it('should stop cycling slides backward when noWrap is truthy', function () {
elm = $compile(
'<uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
'<div uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
'<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
'{{slide.content}}' +
'</uib-slide>' +
'</uib-carousel>'
'</div>' +
'</div>'
)(scope);
scope.noWrap = true;
@@ -147,11 +135,11 @@ describe('carousel', function() {
scope.slides = [{active:false,content:'one'}];
scope.$apply();
elm = $compile(
'<uib-carousel active="active" interval="interval" no-transition="true">' +
'<uib-slide ng-repeat="slide in slides" index="$index">' +
'<div uib-carousel active="active" interval="interval" no-transition="true">' +
'<div uib-slide ng-repeat="slide in slides" index="$index">' +
'{{slide.content}}' +
'</uib-slide>' +
'</uib-carousel>'
'</div>' +
'</div>'
)(scope);
var indicators = elm.find('ol.carousel-indicators > li');
expect(indicators.length).toBe(0);
@@ -163,6 +151,38 @@ describe('carousel', function() {
expect(navPrev.length).toBe(0);
});
it('should disable prev button when slide index is 0 and noWrap is truthy', function() {
scope.$apply();
var $scope = elm.isolateScope();
$scope.noWrap = function() {return true;};
$scope.isPrevDisabled();
scope.$apply();
var navPrev = elm.find('a.left');
expect(navPrev.hasClass('disabled')).toBe(true);
});
it('should disable next button when last slide is active and noWrap is truthy', function() {
scope.slides = [
{content: 'one', index: 0},
{content: 'two', index: 1}
];
scope.$apply();
var $scope = elm.isolateScope();
$scope.noWrap = function() {return true;};
$scope.next();
$scope.isNextDisabled();
scope.$apply();
var navNext = elm.find('a.right');
expect(navNext.hasClass('disabled')).toBe(true);
});
it('should show navigation when there are 3 slides', function () {
var indicators = elm.find('ol.carousel-indicators > li');
expect(indicators.length).not.toBe(0);
@@ -196,20 +216,6 @@ describe('carousel', function() {
testSlideActive(0);
});
describe('swiping', function() {
it('should go next on swipeLeft', function() {
testSlideActive(0);
elm.triggerHandler('ngSwipeLeft');
testSlideActive(1);
});
it('should go prev on swipeRight', function() {
testSlideActive(0);
elm.triggerHandler('ngSwipeRight');
testSlideActive(2);
});
});
it('should select a slide when clicking on slide indicators', function () {
var indicators = elm.find('ol.carousel-indicators > li');
indicators.eq(1).click();
@@ -237,7 +243,7 @@ describe('carousel', function() {
});
it('should bind the content to slides', function() {
var contents = elm.find('div.item');
var contents = elm.find('div.item [ng-transclude]');
expect(contents.length).toBe(3);
expect(contents.eq(0).text()).toBe('one');
@@ -311,7 +317,7 @@ describe('carousel', function() {
{content:'new3', index: 6}
];
scope.$apply();
var contents = elm.find('div.item');
var contents = elm.find('div.item [ng-transclude]');
expect(contents.length).toBe(3);
expect(contents.eq(0).text()).toBe('new1');
expect(contents.eq(1).text()).toBe('new2');
@@ -409,11 +415,11 @@ describe('carousel', function() {
{content: 'three', id: 2}
];
elm = $compile(
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
'<uib-slide ng-repeat="slide in slides | orderBy: \'id\' track by slide.id" index="slide.id">' +
'<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
'<div uib-slide ng-repeat="slide in slides | orderBy: \'id\' track by slide.id" index="slide.id">' +
'{{slide.content}}' +
'</uib-slide>' +
'</uib-carousel>'
'</div>' +
'</div>'
)(scope);
scope.$apply();
});
@@ -433,7 +439,7 @@ describe('carousel', function() {
scope.slides[1].id = 2;
scope.slides[2].id = 1;
scope.$apply();
var contents = elm.find('div.item');
var contents = elm.find('div.item [ng-transclude]');
expect(contents.length).toBe(3);
expect(contents.eq(0).text()).toBe('three');
expect(contents.eq(1).text()).toBe('two');
@@ -459,7 +465,7 @@ describe('carousel', function() {
scope.slides[2].id = 4;
scope.slides.push({content:'four', id: 5});
scope.$apply();
var contents = elm.find('div.item');
var contents = elm.find('div.item [ng-transclude]');
expect(contents.length).toBe(4);
expect(contents.eq(0).text()).toBe('two');
expect(contents.eq(1).text()).toBe('one');
@@ -471,7 +477,7 @@ describe('carousel', function() {
testSlideActive(1);
scope.slides.splice(1, 1);
scope.$apply();
var contents = elm.find('div.item');
var contents = elm.find('div.item [ng-transclude]');
expect(contents.length).toBe(2);
expect(contents.eq(0).text()).toBe('three');
expect(contents.eq(1).text()).toBe('one');
@@ -551,7 +557,7 @@ describe('carousel', function() {
$templateCache.put('uib/template/carousel/carousel.html', '<div>{{carousel.text}}</div>');
var scope = $rootScope.$new();
var elm = $compile('<uib-carousel interval="bar" no-transition="false" no-pause="true"></uib-carousel>')(scope);
var elm = $compile('<div uib-carousel interval="bar" no-transition="false" no-pause="true"></div>')(scope);
$rootScope.$digest();
var ctrl = elm.controller('uibCarousel');
@@ -561,7 +567,7 @@ describe('carousel', function() {
ctrl.text = 'foo';
$rootScope.$digest();
expect(elm.html()).toBe('foo');
expect(elm.html()).toBe('<div class="ng-binding">foo</div>');
}));
});
@@ -573,11 +579,11 @@ describe('carousel', function() {
{active:false,content:'three'}
];
var elm = $compile(
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
'<uib-slide ng-repeat="slide in slides" index="$index" actual="slide">' +
'<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
'<div uib-slide ng-repeat="slide in slides" index="$index" actual="slide">' +
'{{slide.content}}' +
'</uib-slide>' +
'</uib-carousel>'
'</div>' +
'</div>'
)(scope);
$rootScope.$digest();
+53 -21
View File
@@ -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);
+34 -1
View File
@@ -1,7 +1,40 @@
<style>
.horizontal-collapse {
height: 70px;
}
.navbar-collapse.in {
overflow-y: hidden;
}
</style>
<div ng-controller="CollapseDemoCtrl">
<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse</button>
<p>Resize window to less than 768 pixels to display mobile menu toggle button.</p>
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" ng-click="isNavCollapsed = !isNavCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">A menu</a>
</div>
<div class="collapse navbar-collapse" uib-collapse="isNavCollapsed">
<ul class="nav navbar-nav">
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
</ul>
</div>
</nav>
<hr>
<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse Vertically</button>
<hr>
<div uib-collapse="isCollapsed">
<div class="well well-lg">Some content</div>
</div>
<button type="button" class="btn btn-default" ng-click="isCollapsedHorizontal = !isCollapsedHorizontal">Toggle collapse Horizontally</button>
<hr>
<div class="horizontal-collapse" uib-collapse="isCollapsedHorizontal" horizontal>
<div class="well well-lg">Some content</div>
</div>
</div>
+2
View File
@@ -1,3 +1,5 @@
angular.module('ui.bootstrap.demo').controller('CollapseDemoCtrl', function ($scope) {
$scope.isNavCollapsed = true;
$scope.isCollapsed = false;
$scope.isCollapsedHorizontal = false;
});
+7
View File
@@ -28,3 +28,10 @@
_(Default: `false`)_ -
Whether the element should be collapsed or not.
* `horizontal`
<small class="badge">$</small> -
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.
@@ -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(
'<div uib-collapse="isCollapsed" '
+ 'expanding="expanding()" '
+ 'expanded="expanded()" '
+ 'collapsing="collapsing()" '
+ 'collapsed="collapsed()" '
+ 'horizontal>'
+ 'Some Content</div>');
compileFnH = $compile(elementH);
angular.element(document.body).append(elementH);
});
afterEach(function() {
elementH.remove();
});
function initCallbacks() {
scope.collapsing = jasmine.createSpy('scope.collapsing');
scope.collapsed = jasmine.createSpy('scope.collapsed');
scope.expanding = jasmine.createSpy('scope.expanding');
scope.expanded = jasmine.createSpy('scope.expanded');
}
function assertCallbacks(expected) {
['collapsing', 'collapsed', 'expanding', 'expanded'].forEach(function(cbName) {
if (expected[cbName]) {
expect(scope[cbName]).toHaveBeenCalled();
} else {
expect(scope[cbName]).not.toHaveBeenCalled();
}
});
}
it('should be hidden on initialization if isCollapsed = true', function() {
initCallbacks();
scope.isCollapsed = true;
compileFnH(scope);
scope.$digest();
expect(elementH.width()).toBe(0);
assertCallbacks({ collapsed: true });
});
it('should not trigger any animation on initialization if isCollapsed = true', function() {
var wrapperFn = function() {
$animate.flush();
};
scope.isCollapsed = true;
compileFnH(scope);
scope.$digest();
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
});
it('should collapse if isCollapsed = true on subsequent use', function() {
scope.isCollapsed = false;
compileFnH(scope);
scope.$digest();
initCallbacks();
scope.isCollapsed = true;
scope.$digest();
$animate.flush();
expect(elementH.width()).toBe(0);
assertCallbacks({ collapsing: true, collapsed: true });
});
it('should show after toggled from collapsed', function() {
initCallbacks();
scope.isCollapsed = true;
compileFnH(scope);
scope.$digest();
expect(elementH.width()).toBe(0);
assertCallbacks({ collapsed: true });
scope.collapsed.calls.reset();
scope.isCollapsed = false;
scope.$digest();
$animate.flush();
expect(elementH.width()).not.toBe(0);
assertCallbacks({ expanding: true, expanded: true });
});
it('should not trigger any animation on initialization if isCollapsed = false', function() {
var wrapperFn = function() {
$animate.flush();
};
scope.isCollapsed = false;
compileFnH(scope);
scope.$digest();
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
});
it('should expand if isCollapsed = false on subsequent use', function() {
scope.isCollapsed = false;
compileFnH(scope);
scope.$digest();
scope.isCollapsed = true;
scope.$digest();
$animate.flush();
initCallbacks();
scope.isCollapsed = false;
scope.$digest();
$animate.flush();
expect(elementH.width()).not.toBe(0);
assertCallbacks({ expanding: true, expanded: true });
});
it('should collapse if isCollapsed = true on subsequent uses', function() {
scope.isCollapsed = false;
compileFnH(scope);
scope.$digest();
scope.isCollapsed = true;
scope.$digest();
$animate.flush();
scope.isCollapsed = false;
scope.$digest();
$animate.flush();
initCallbacks();
scope.isCollapsed = true;
scope.$digest();
$animate.flush();
expect(elementH.width()).toBe(0);
assertCallbacks({ collapsing: true, collapsed: true });
});
it('should change aria-expanded attribute', function() {
scope.isCollapsed = false;
compileFnH(scope);
scope.$digest();
expect(elementH.attr('aria-expanded')).toBe('true');
scope.isCollapsed = true;
scope.$digest();
$animate.flush();
expect(elementH.attr('aria-expanded')).toBe('false');
});
it('should change aria-hidden attribute', function() {
scope.isCollapsed = false;
compileFnH(scope);
scope.$digest();
expect(elementH.attr('aria-hidden')).toBe('false');
scope.isCollapsed = true;
scope.$digest();
$animate.flush();
expect(elementH.attr('aria-hidden')).toBe('true');
});
describe('expanding callback returning a promise', function() {
var defer, collapsedWidth;
beforeEach(function() {
defer = $q.defer();
scope.isCollapsed = true;
scope.expanding = function() {
return defer.promise;
};
compileFnH(scope);
scope.$digest();
collapsedWidth = elementH.width();
// set flag to expand ...
scope.isCollapsed = false;
scope.$digest();
// ... shouldn't expand yet ...
expect(elementH.attr('aria-expanded')).not.toBe('true');
expect(elementH.width()).toBe(collapsedWidth);
});
it('should wait for it to resolve before animating', function() {
defer.resolve();
// should now expand
scope.$digest();
$animate.flush();
expect(elementH.attr('aria-expanded')).toBe('true');
expect(elementH.width()).toBeGreaterThan(collapsedWidth);
});
it('should not animate if it rejects', function() {
defer.reject();
// should NOT expand
scope.$digest();
expect(elementH.attr('aria-expanded')).not.toBe('true');
expect(elementH.width()).toBe(collapsedWidth);
});
});
describe('collapsing callback returning a promise', function() {
var defer, expandedWidth;
beforeEach(function() {
defer = $q.defer();
scope.isCollapsed = false;
scope.collapsing = function() {
return defer.promise;
};
compileFnH(scope);
scope.$digest();
expandedWidth = elementH.width();
// set flag to collapse ...
scope.isCollapsed = true;
scope.$digest();
// ... but it shouldn't collapse yet ...
expect(elementH.attr('aria-expanded')).not.toBe('false');
expect(elementH.width()).toBe(expandedWidth);
});
it('should wait for it to resolve before animating', function() {
defer.resolve();
// should now collapse
scope.$digest();
$animate.flush();
expect(elementH.attr('aria-expanded')).toBe('false');
expect(elementH.width()).toBeLessThan(expandedWidth);
});
it('should not animate if it rejects', function() {
defer.reject();
// should NOT collapse
scope.$digest();
expect(elementH.attr('aria-expanded')).not.toBe('false');
expect(elementH.width()).toBe(expandedWidth);
});
});
});
+103 -26
View File
@@ -1,6 +1,6 @@
angular.module('ui.bootstrap.dateparser', [])
.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) {
.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) {
// Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
@@ -27,7 +27,7 @@ angular.module('ui.bootstrap.dateparser', [])
{
key: 'yy',
regex: '\\d{2}',
apply: function(value) { this.year = +value + 2000; },
apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
formatter: function(date) {
var _date = new Date();
_date.setFullYear(Math.abs(date.getFullYear()));
@@ -230,11 +230,37 @@ angular.module('ui.bootstrap.dateparser', [])
formatter: function(date) { return dateFilter(date, 'G'); }
}
];
if (angular.version.major >= 1 && angular.version.minor > 4) {
formatCodeToRegex.push({
key: 'LLLL',
regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'),
apply: function(value) { this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value); },
formatter: function(date) { return dateFilter(date, 'LLLL'); }
});
}
};
this.init();
function createParser(format, func) {
function getFormatCodeToRegex(key) {
return filterFilter(formatCodeToRegex, {key: key}, true)[0];
}
this.getParser = function (key) {
var f = getFormatCodeToRegex(key);
return f && f.apply || null;
};
this.overrideParser = function (key, parser) {
var f = getFormatCodeToRegex(key);
if (f && angular.isFunction(parser)) {
this.parsers = {};
f.apply = parser;
}
}.bind(this);
function createParser(format) {
var map = [], regex = format.split('');
// check for literal values
@@ -283,7 +309,7 @@ angular.module('ui.bootstrap.dateparser', [])
map.push({
index: index,
key: data.key,
apply: data[func],
apply: data.apply,
matcher: data.regex
});
}
@@ -295,6 +321,70 @@ angular.module('ui.bootstrap.dateparser', [])
};
}
function createFormatter(format) {
var formatters = [];
var i = 0;
var formatter, literalIdx;
while (i < format.length) {
if (angular.isNumber(literalIdx)) {
if (format.charAt(i) === '\'') {
if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
formatters.push(constructLiteralFormatter(format, literalIdx, i));
literalIdx = null;
}
} else if (i === format.length) {
while (literalIdx < format.length) {
formatter = constructFormatterFromIdx(format, literalIdx);
formatters.push(formatter);
literalIdx = formatter.endIdx;
}
}
i++;
continue;
}
if (format.charAt(i) === '\'') {
literalIdx = i;
i++;
continue;
}
formatter = constructFormatterFromIdx(format, i);
formatters.push(formatter.parser);
i = formatter.endIdx;
}
return formatters;
}
function constructLiteralFormatter(format, literalIdx, endIdx) {
return function() {
return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
};
}
function constructFormatterFromIdx(format, i) {
var currentPosStr = format.substr(i);
for (var j = 0; j < formatCodeToRegex.length; j++) {
if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
var data = formatCodeToRegex[j];
return {
endIdx: i + data.key.length,
parser: data.formatter
};
}
}
return {
endIdx: i + 1,
parser: function() {
return currentPosStr.charAt(0);
}
};
}
this.filter = function(date, format) {
if (!angular.isDate(date) || isNaN(date) || !format) {
return '';
@@ -307,28 +397,13 @@ angular.module('ui.bootstrap.dateparser', [])
}
if (!this.formatters[format]) {
this.formatters[format] = createParser(format, 'formatter');
this.formatters[format] = createFormatter(format);
}
var parser = this.formatters[format],
map = parser.map;
var formatters = this.formatters[format];
var _format = format;
return map.reduce(function(str, mapper, i) {
var match = _format.match(new RegExp('(.*)' + mapper.key));
if (match && angular.isString(match[1])) {
str += match[1];
_format = _format.replace(match[1] + mapper.key, '');
}
var endStr = i === map.length - 1 ? _format : '';
if (mapper.apply) {
return str + mapper.apply.call(null, date) + endStr;
}
return str + endStr;
return formatters.reduce(function(str, formatter) {
return str + formatter(date);
}, '');
};
@@ -442,8 +517,9 @@ angular.module('ui.bootstrap.dateparser', [])
return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
}
//https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
//https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
function timezoneToOffset(timezone, fallback) {
timezone = timezone.replace(/:/g, '');
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}
@@ -456,7 +532,8 @@ angular.module('ui.bootstrap.dateparser', [])
function convertTimezoneToLocal(date, timezone, reverse) {
reverse = reverse ? -1 : 1;
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
var dateTimezoneOffset = date.getTimezoneOffset();
var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
}
}]);
+3
View File
@@ -58,6 +58,9 @@ Certain format codes support i18n. Check this [guide](https://docs.angularjs.org
_(Example: `3` or `03`)_ -
Parses a numeric month, but allowing an optional leading zero
* `LLLL`
_(Example: `February`, i18n support)_ - Stand-alone month in year (January-December). Requires Angular version 1.5.1 or higher.
* `dd`
_(Example: `05`, Leading 0)_ -
Parses a numeric day.
+77 -14
View File
@@ -84,6 +84,16 @@ describe('date parser', function() {
expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');
expectFilter(oldDate, 'yyyy/M!/dd', '0001/03/06');
});
it('should work correctly for `LLLL`', function() {
expectFilter(new Date(2013, 7, 24, 0), 'LLLL/dd/yyyy', 'August/24/2013');
expectFilter(new Date(2004, 10, 7, 0), 'dd.LLLL.yy', '07.November.04');
expectFilter(new Date(2011, 4, 18, 0), 'dd-LLLL-yy', '18-May-11');
expectFilter(new Date(1980, 1, 5, 0), 'LLLL/dd/yyyy', 'February/05/1980');
expectFilter(new Date(1955, 2, 5, 0), 'yyyy/LLLL/dd', '1955/March/05');
expectFilter(new Date(2011, 5, 2, 0), 'dd-LLLL-yy', '02-June-11');
expectFilter(oldDate, 'yyyy/LLLL/dd', '0001/March/06');
});
it('should work correctly for `d`', function() {
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy', '17.November.13');
@@ -282,11 +292,16 @@ describe('date parser', function() {
it('should work correctly for `yy`', function() {
expectParse('17.11.13', 'dd.MM.yy', new Date(2013, 10, 17, 0));
expectParse('02-05-11', 'dd-MM-yy', new Date(2011, 4, 2, 0));
expectParse('02/05/80', 'MM/dd/yy', new Date(2080, 1, 5, 0));
expectParse('02/05/80', 'MM/dd/yy', new Date(1980, 1, 5, 0));
expectParse('55/02/05', 'yy/MM/dd', new Date(2055, 1, 5, 0));
expectParse('11-08-13', 'dd-MM-yy', new Date(2013, 7, 11, 0));
});
it('should use `68` as the pivot year for `yy`', function() {
expectParse('17.11.68', 'dd.MM.yy', new Date(2068, 10, 17, 0));
expectParse('17.11.69', 'dd.MM.yy', new Date(1969, 10, 17, 0));
});
it('should work correctly for `y`', function() {
expectParse('17.11.2013', 'dd.MM.y', new Date(2013, 10, 17, 0));
expectParse('31.12.2013', 'dd.MM.y', new Date(2013, 11, 31, 0));
@@ -560,24 +575,48 @@ describe('date parser', function() {
});
describe('with value literals', function() {
it('should work with multiple literals', function() {
expect(dateParser.parse('29 de January de 2013', 'd \'de\' MMMM \'de\' y')).toEqual(new Date(2013, 0, 29));
describe('filter', function() {
it('should work with multiple literals', function() {
expect(dateParser.filter(new Date(2013, 0, 29), 'd \'de\' MMMM \'de\' y')).toEqual('29 de January de 2013');
});
it('should work with escaped single quote', function() {
expect(dateParser.filter(new Date(2015, 2, 22, 12), 'd.MMMM.yy h \'o\'\'clock\'')).toEqual('22.March.15 12 o\'clock');
});
it('should work with only a single quote', function() {
expect(dateParser.filter(new Date(2015, 2, 22), 'd.MMMM.yy \'\'\'')).toEqual('22.March.15 \'');
});
it('should work with trailing literal', function() {
expect(dateParser.filter(new Date(2013, 0, 1), '\'year\' y')).toEqual('year 2013');
});
it('should work without whitespace', function() {
expect(dateParser.filter(new Date(2013, 0, 1), '\'year:\'y')).toEqual('year:2013');
});
});
it('should work with escaped single quote', function() {
expect(dateParser.parse('22.March.15 12 o\'clock', 'd.MMMM.yy h \'o\'\'clock\'')).toEqual(new Date(2015, 2, 22, 12));
});
describe('parse', function() {
it('should work with multiple literals', function() {
expect(dateParser.parse('29 de January de 2013', 'd \'de\' MMMM \'de\' y')).toEqual(new Date(2013, 0, 29));
});
it('should work with only a single quote', function() {
expect(dateParser.parse('22.March.15 \'', 'd.MMMM.yy \'\'\'')).toEqual(new Date(2015, 2, 22));
});
it('should work with escaped single quote', function() {
expect(dateParser.parse('22.March.15 12 o\'clock', 'd.MMMM.yy h \'o\'\'clock\'')).toEqual(new Date(2015, 2, 22, 12));
});
it('should work with trailing literal', function() {
expect(dateParser.parse('year 2013', '\'year\' y')).toEqual(new Date(2013, 0, 1));
});
it('should work with only a single quote', function() {
expect(dateParser.parse('22.March.15 \'', 'd.MMMM.yy \'\'\'')).toEqual(new Date(2015, 2, 22));
});
it('should work without whitespace', function() {
expect(dateParser.parse('year:2013', '\'year:\'y')).toEqual(new Date(2013, 0, 1));
it('should work with trailing literal', function() {
expect(dateParser.parse('year 2013', '\'year\' y')).toEqual(new Date(2013, 0, 1));
});
it('should work without whitespace', function() {
expect(dateParser.parse('year:2013', '\'year:\'y')).toEqual(new Date(2013, 0, 1));
});
});
});
@@ -752,4 +791,28 @@ describe('date parser', function() {
});
});
});
describe('overrideParser', function() {
var twoDigitYearParser = function (value) {
this.year = +value + (+value > 30 ? 1900 : 2000);
};
it('should get the current parser', function() {
expect(dateParser.getParser('yy')).toBeTruthy();
});
it('should override the parser', function() {
dateParser.overrideParser('yy', twoDigitYearParser);
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);
expect(dateParser.parse('67', 'yy').getFullYear()).toEqual(1967);
expect(dateParser.parse('31', 'yy').getFullYear()).toEqual(1931);
expect(dateParser.parse('30', 'yy').getFullYear()).toEqual(2030);
});
it('should clear cached parsers', function() {
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(2068);
dateParser.overrideParser('yy', twoDigitYearParser);
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);
});
});
});
-10
View File
@@ -6,16 +6,6 @@
min-width: 100%;
}
.uib-datepicker-popup.dropdown-menu {
display: block;
float: none;
margin: 0;
}
.uib-button-bar {
padding: 10px 9px 2px;
}
.uib-left, .uib-right {
width: 100%
}
File diff suppressed because it is too large Load Diff
+1 -27
View File
@@ -15,33 +15,7 @@
<h4>Inline</h4>
<div style="display:inline-block; min-height:290px;">
<uib-datepicker ng-model="dt" class="well well-sm" datepicker-options="inlineOptions"></uib-datepicker>
</div>
<h4>Popup</h4>
<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup ng-model="dt" is-open="popup2.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Format: <span class="muted-text">(manual alternate <em>{{altInputFormats[0]}}</em>)</span></label> <select class="form-control" ng-model="format" ng-options="f for f in formats"><option></option></select>
</div>
<div uib-datepicker ng-model="dt" class="well well-sm" datepicker-options="options"></div>
</div>
<hr />
+3 -32
View File
@@ -8,20 +8,12 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($
$scope.dt = null;
};
$scope.inlineOptions = {
$scope.options = {
customClass: getDayClass,
minDate: new Date(),
showWeeks: true
};
$scope.dateOptions = {
dateDisabled: disabled,
formatYear: 'yy',
maxDate: new Date(2020, 5, 22),
minDate: new Date(),
startingDay: 1
};
// Disable weekend selection
function disabled(data) {
var date = data.date,
@@ -30,39 +22,18 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($
}
$scope.toggleMin = function() {
$scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date();
$scope.dateOptions.minDate = $scope.inlineOptions.minDate;
$scope.options.minDate = $scope.options.minDate ? null : new Date();
};
$scope.toggleMin();
$scope.open1 = function() {
$scope.popup1.opened = true;
};
$scope.open2 = function() {
$scope.popup2.opened = true;
};
$scope.setDate = function(year, month, day) {
$scope.dt = new Date(year, month, day);
};
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
$scope.format = $scope.formats[0];
$scope.altInputFormats = ['M!/d!/yyyy'];
$scope.popup1 = {
opened: false
};
$scope.popup2 = {
opened: false
};
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
var afterTomorrow = new Date();
var afterTomorrow = new Date(tomorrow);
afterTomorrow.setDate(tomorrow.getDate() + 1);
$scope.events = [
{
+37 -128
View File
@@ -2,8 +2,6 @@ Our datepicker is flexible and fully customizable.
You can navigate through days, months and years.
It comes in two formats, an inline `uib-datepicker` and an `uib-datepicker-popup` to be embedded in an input.
The datepicker has 3 modes:
* `day` - In this mode you're presented with a 6-week calendar for a specified month.
@@ -15,7 +13,7 @@ The datepicker has 3 modes:
* `ng-model`
<small class="badge">$</small>
<i class="glyphicon glyphicon-eye-open"></i> -
The date object. Needs to be a Javascript Date object.
The date object. Must be a Javascript `Date` object. You may use the `uibDateParser` service to assist in string-to-object conversion.
* `ng-model-options`
<small class="badge">$</small>
@@ -24,210 +22,123 @@ The datepicker has 3 modes:
Supported [angular ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions):
* allowInvalid
* timezone
* `template-url`
_(Default: `uib/template/datepicker/datepicker.html`)_ -
Add the ability to override the template used on the component.
Apart from the previous settings, to configure the uib-datepicker you need to create an object in Javascript with all the options and use it on the `datepicker-options` attribute:
* `datepicker-options`
<small class="badge">$</small> -
An object to configure the datepicker in one place.
* `customClass (date, mode)` -
An optional expression to add classes based on passing a date and current mode.
* `dateDisabled (date, mode)` -
An optional expression to disable visible options based on passing a date and current mode.
* `customClass ({date: date, mode: mode})` -
An optional expression to add classes based on passing an object with date and current mode properties.
* `dateDisabled ({date: date, mode: mode})` -
An optional expression to disable visible options based on passing an object with date and current mode properties.
* `datepickerMode`
<small class="badge">C</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `day`)_ -
Current mode of the datepicker _(day|month|year)_. Can be used to initialize the datepicker in a specific mode.
* `formatDay`
<small class="badge">C</small>
_(Default: `dd`)_ -
Format of day in month.
* `formatMonth`
<small class="badge">C</small>
_(Default: `MMMM`)_ -
Format of month in year.
* `formatYear`
<small class="badge">C</small>
_(Default: `yyyy`)_ -
Format of year in year range.
* `formatDayHeader`
<small class="badge">C</small>
_(Default: `EEE`)_ -
Format of day in week header.
* `formatDayTitle`
<small class="badge">C</small>
_(Default: `MMMM yyyy`)_ -
Format of title when selecting day.
* `formatMonthTitle`
<small class="badge">C</small>
_(Default: `yyyy`)_ -
Format of title when selecting month.
* `initDate`
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `null`)_ -
The initial date view when no model value is specified.
* `maxDate`
<small class="badge">C</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `null`)_ -
Defines the maximum available date.
Defines the maximum available date. Requires a Javascript Date object.
* `maxMode`
<small class="badge">C</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `year`)_ -
Sets an upper limit for mode.
* `minDate`
<small class="badge">C</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `null`)_ -
Defines the minimum available date.
Defines the minimum available date. Requires a Javascript Date object.
* `minMode`
<small class="badge">C</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `day`)_ -
Sets a lower limit for mode.
* `monthColumns`
<small class="badge">C</small>
_(Default: `3`)_ -
Number of columns displayed in month selection.
* `ngModelOptions`
<small class="badge">C</small>
_(Default: `null`)_ -
Sets `ngModelOptions` for datepicker. This can be overridden through attribute usage
* `shortcutPropagation`
<small class="badge">C</small>
_(Default: `false`)_ -
An option to disable the propagation of the keydown event.
* `showWeeks`
<small class="badge">C</small>
_(Default: `true`)_ -
Whether to display week numbers.
* `startingDay`
<small class="badge">C</small>
*(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)* -
Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday).
* `yearRows`
<small class="badge">C</small>
_(Default: `4`)_ -
Number of rows displayed in year selection.
* `yearColumns`
<small class="badge">C</small>
_(Default: `5`)_ -
Number of columns displayed in year selection.
### uib-datepicker-popup settings
The popup is a wrapper that you can use in an input to toggle a datepicker. To configure the datepicker, use `datepicker-options`.
* `alt-input-formats`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `[]`)_ -
A list of alternate formats acceptable for manual entry.
* `clear-text`
<small class="badge">C</small>
_(Default: `Clear`)_ -
The text to display for the clear button.
* `close-on-date-selection`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `true`)_ -
Whether to close calendar when a date is chosen.
* `close-text`
<small class="badge">C</small>
_(Default: `Done`)_ -
The text to display for the close button.
* `current-text`
<small class="badge">C</small>
_(Default: `Today`)_ -
The text to display for the current day button.
* `datepicker-append-to-body`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `false`, Config: `appendToBody`)_ -
Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`.
* `datepicker-options`
<small class="badge">$</small> -
An object with any combination of the datepicker settings (in camelCase) used to configure the wrapped datepicker.
* `datepicker-popup-template-url`
<small class="badge">C</small>
_(Default: `uib/template/datepicker/popup.html`)_ -
Add the ability to override the template used on the component.
* `datepicker-template-url`
<small class="badge">C</small>
_(Default: `uib/template/datepicker/datepicker.html`)_ -
Add the ability to override the template used on the component (inner uib-datepicker).
* `is-open`
<small class="badge">$</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `false`)_ -
Whether or not to show the datepicker.
* `on-open-focus`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `true`)_ -
Whether or not to focus the datepicker popup upon opening.
* `show-button-bar`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `true`)_ -
Whether or not to display a button bar underneath the uib-datepicker.
* `type`
<small class="badge">C</small>
_(Default: `text`, Config: `html5Types`)_ -
You can override the input type to be _(date|datetime-local|month)_. That will change the date format of the popup.
* `popup-placement`
<small class="badge">C</small>
_(Default: `auto bottom-left`, Config: 'placement')_ -
Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: "auto bottom-left". The popup will attempt to position where it fits in the closest scrollable ancestor. Accepts:
* `top` - popup on top, horizontally centered on input element.
* `top-left` - popup on top, left edge aligned with input element left edge.
* `top-right` - popup on top, right edge aligned with input element right edge.
* `bottom` - popup on bottom, horizontally centered on input element.
* `bottom-left` - popup on bottom, left edge aligned with input element left edge.
* `bottom-right` - popup on bottom, right edge aligned with input element right edge.
* `left` - popup on left, vertically centered on input element.
* `left-top` - popup on left, top edge aligned with input element top edge.
* `left-bottom` - popup on left, bottom edge aligned with input element bottom edge.
* `right` - popup on right, vertically centered on input element.
* `right-top` - popup on right, top edge aligned with input element top edge.
* `right-bottom` - popup on right, bottom edge aligned with input element bottom edge.
* `uib-datepicker-popup`
<small class="badge">C</small>
_(Default: `yyyy-MM-dd`, Config: `datepickerConfig`)_ -
The format for displayed dates. This string can take string literals by surrounding the value with single quotes, i.e. `yyyy-MM-dd h 'o\'clock'`.
### Keyboard support
Depending on datepicker's current mode, the date may refer either to day, month or year. Accordingly, the term view refers either to a month, year or year range.
@@ -248,5 +159,3 @@ Depending on datepicker's current mode, the date may refer either to day, month
**Notes**
If the date a user enters falls outside of the min-/max-date range, a `dateDisabled` validation error will show on the form.
If using this directive on input type date, a native browser datepicker could also appear.
+13
View File
@@ -0,0 +1,13 @@
require('../dateparser');
require('../isClass');
require('../../template/datepicker/datepicker.html.js');
require('../../template/datepicker/day.html.js');
require('../../template/datepicker/month.html.js');
require('../../template/datepicker/year.html.js');
require('./datepicker');
var MODULE_NAME = 'ui.bootstrap.module.datepicker';
angular.module(MODULE_NAME, ['ui.bootstrap.datepicker', 'uib/template/datepicker/datepicker.html', 'uib/template/datepicker/day.html', 'uib/template/datepicker/month.html', 'uib/template/datepicker/year.html']);
module.exports = MODULE_NAME;
+1 -16
View File
@@ -1,17 +1,2 @@
require('../dateparser');
require('../isClass');
require('../position');
require('../../template/datepicker/datepicker.html.js');
require('../../template/datepicker/day.html.js');
require('../../template/datepicker/month.html.js');
require('../../template/datepicker/year.html.js');
require('../../template/datepicker/popup.html.js');
require('./datepicker');
require('./datepicker.css');
var MODULE_NAME = 'ui.bootstrap.module.datepicker';
angular.module(MODULE_NAME, ['ui.bootstrap.datepicker', 'uib/template/datepicker/datepicker.html', 'uib/template/datepicker/day.html', 'uib/template/datepicker/month.html', 'uib/template/datepicker/year.html', 'uib/template/datepicker/popup.html']);
module.exports = MODULE_NAME;
module.exports = require('./index-nocss.js');
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
<style>
.full button span {
background-color: limegreen;
border-radius: 32px;
color: black;
}
.partially button span {
background-color: orange;
border-radius: 32px;
color: black;
}
</style>
<div ng-controller="DatepickerPopupDemoCtrl">
<pre>Selected date is: <em>{{dt | date:'fullDate' }}</em></pre>
<h4>Popup</h4>
<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup ng-model="dt" is-open="popup2.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Format: <span class="muted-text">(manual alternate <em>{{altInputFormats[0]}}</em>)</span></label> <select class="form-control" ng-model="format" ng-options="f for f in formats"><option></option></select>
</div>
</div>
<hr />
<button type="button" class="btn btn-sm btn-info" ng-click="today()">Today</button>
<button type="button" class="btn btn-sm btn-default" ng-click="setDate(2009, 7, 24)">2009-08-24</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="clear()">Clear</button>
<button type="button" class="btn btn-sm btn-default" ng-click="toggleMin()" uib-tooltip="After today restriction">Min date</button>
</div>
+95
View File
@@ -0,0 +1,95 @@
angular.module('ui.bootstrap.demo').controller('DatepickerPopupDemoCtrl', function ($scope) {
$scope.today = function() {
$scope.dt = new Date();
};
$scope.today();
$scope.clear = function() {
$scope.dt = null;
};
$scope.inlineOptions = {
customClass: getDayClass,
minDate: new Date(),
showWeeks: true
};
$scope.dateOptions = {
dateDisabled: disabled,
formatYear: 'yy',
maxDate: new Date(2020, 5, 22),
minDate: new Date(),
startingDay: 1
};
// Disable weekend selection
function disabled(data) {
var date = data.date,
mode = data.mode;
return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6);
}
$scope.toggleMin = function() {
$scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date();
$scope.dateOptions.minDate = $scope.inlineOptions.minDate;
};
$scope.toggleMin();
$scope.open1 = function() {
$scope.popup1.opened = true;
};
$scope.open2 = function() {
$scope.popup2.opened = true;
};
$scope.setDate = function(year, month, day) {
$scope.dt = new Date(year, month, day);
};
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
$scope.format = $scope.formats[0];
$scope.altInputFormats = ['M!/d!/yyyy'];
$scope.popup1 = {
opened: false
};
$scope.popup2 = {
opened: false
};
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
var afterTomorrow = new Date();
afterTomorrow.setDate(tomorrow.getDate() + 1);
$scope.events = [
{
date: tomorrow,
status: 'full'
},
{
date: afterTomorrow,
status: 'partially'
}
];
function getDayClass(data) {
var date = data.date,
mode = data.mode;
if (mode === 'day') {
var dayToCheck = new Date(date).setHours(0,0,0,0);
for (var i = 0; i < $scope.events.length; i++) {
var currentDay = new Date($scope.events[i].date).setHours(0,0,0,0);
if (dayToCheck === currentDay) {
return $scope.events[i].status;
}
}
}
return '';
}
});
+107
View File
@@ -0,0 +1,107 @@
The datepicker popup is meant to be used with an input element. To understand usage of the datepicker, please refer to its documentation [here](https://angular-ui.github.io/bootstrap/#/datepicker).
### uib-datepicker-popup settings
The popup is a wrapper that you can use in an input to toggle a datepicker. To configure the datepicker, use `datepicker-options` as documented in the [inline datepicker](https://angular-ui.github.io/bootstrap/#/datepicker).
* `alt-input-formats`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `[]`)_ -
A list of alternate formats acceptable for manual entry.
* `clear-text`
<small class="badge">C</small>
_(Default: `Clear`)_ -
The text to display for the clear button.
* `close-on-date-selection`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `true`)_ -
Whether to close calendar when a date is chosen.
* `close-text`
<small class="badge">C</small>
_(Default: `Done`)_ -
The text to display for the close button.
* `current-text`
<small class="badge">C</small>
_(Default: `Today`)_ -
The text to display for the current day button.
* `datepicker-append-to-body`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `false`, Config: `appendToBody`)_ -
Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`.
* `datepicker-options`
<small class="badge">$</small> -
An object with any combination of the datepicker settings (in camelCase) used to configure the wrapped datepicker.
* `datepicker-popup-template-url`
<small class="badge">C</small>
_(Default: `uib/template/datepickerPopup/popup.html`)_ -
Add the ability to override the template used on the component.
* `datepicker-template-url`
<small class="badge">C</small>
_(Default: `uib/template/datepicker/datepicker.html`)_ -
Add the ability to override the template used on the component (inner uib-datepicker).
* `is-open`
<small class="badge">$</small>
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `false`)_ -
Whether or not to show the datepicker.
* `ng-model`
<small class="badge">$</small>
<i class="glyphicon glyphicon-eye-open"></i> -
The date object. Must be a Javascript `Date` object. You may use the `uibDateParser` service to assist in string-to-object conversion.
* `on-open-focus`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `true`)_ -
Whether or not to focus the datepicker popup upon opening.
* `show-button-bar`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `true`)_ -
Whether or not to display a button bar underneath the uib-datepicker.
* `type`
<small class="badge">C</small>
_(Default: `text`, Config: `html5Types`)_ -
You can override the input type to be _(date|datetime-local|month)_. That will change the date format of the popup.
* `popup-placement`
<small class="badge">C</small>
_(Default: `auto bottom-left`, Config: 'placement')_ -
Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: "auto bottom-left". The popup will attempt to position where it fits in the closest scrollable ancestor. Accepts:
* `top` - popup on top, horizontally centered on input element.
* `top-left` - popup on top, left edge aligned with input element left edge.
* `top-right` - popup on top, right edge aligned with input element right edge.
* `bottom` - popup on bottom, horizontally centered on input element.
* `bottom-left` - popup on bottom, left edge aligned with input element left edge.
* `bottom-right` - popup on bottom, right edge aligned with input element right edge.
* `left` - popup on left, vertically centered on input element.
* `left-top` - popup on left, top edge aligned with input element top edge.
* `left-bottom` - popup on left, bottom edge aligned with input element bottom edge.
* `right` - popup on right, vertically centered on input element.
* `right-top` - popup on right, top edge aligned with input element top edge.
* `right-bottom` - popup on right, bottom edge aligned with input element bottom edge.
* `uib-datepicker-popup`
<small class="badge">C</small>
_(Default: `yyyy-MM-dd`, Config: `datepickerConfig`)_ -
The format for displayed dates. This string can take string literals by surrounding the value with single quotes, i.e. `yyyy-MM-dd h 'o\'clock'`.
**Notes**
If using this directive on input type date, a native browser datepicker could also appear.
+10
View File
@@ -0,0 +1,10 @@
require('../datepicker/index-nocss.js');
require('../position/index-nocss.js');
require('../../template/datepickerPopup/popup.html.js');
require('./popup.js');
var MODULE_NAME = 'ui.bootstrap.module.datepickerPopup';
angular.module(MODULE_NAME, ['ui.bootstrap.datepickerPopup', 'uib/template/datepickerPopup/popup.html', 'ui.bootstrap.module.datepicker']);
module.exports = MODULE_NAME;
+4
View File
@@ -0,0 +1,4 @@
require('../datepicker/datepicker.css');
require('../position/position.css');
require('./popup.css');
module.exports = require('./index-nocss.js');
+9
View File
@@ -0,0 +1,9 @@
.uib-datepicker-popup.dropdown-menu {
display: block;
float: none;
margin: 0;
}
.uib-button-bar {
padding: 10px 9px 2px;
}
+466
View File
@@ -0,0 +1,466 @@
angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
.value('$datepickerPopupLiteralWarning', true)
.constant('uibDatepickerPopupConfig', {
altInputFormats: [],
appendToBody: false,
clearText: 'Clear',
closeOnDateSelection: true,
closeText: 'Done',
currentText: 'Today',
datepickerPopup: 'yyyy-MM-dd',
datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
html5Types: {
date: 'yyyy-MM-dd',
'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
'month': 'yyyy-MM'
},
onOpenFocus: true,
showButtonBar: true,
placement: 'auto bottom-left'
})
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
var cache = {},
isHtml5DateInput = false;
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];
this.init = function(_ngModel_) {
ngModel = _ngModel_;
ngModelOptions = extractOptions(ngModel);
closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
$scope.$parent.$eval($attrs.closeOnDateSelection) :
datepickerPopupConfig.closeOnDateSelection;
appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
$scope.$parent.$eval($attrs.datepickerAppendToBody) :
datepickerPopupConfig.appendToBody;
onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
$scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
$attrs.datepickerPopupTemplateUrl :
datepickerPopupConfig.datepickerPopupTemplateUrl;
datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
$attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
altInputFormats = angular.isDefined($attrs.altInputFormats) ?
$scope.$parent.$eval($attrs.altInputFormats) :
datepickerPopupConfig.altInputFormats;
$scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
$scope.$parent.$eval($attrs.showButtonBar) :
datepickerPopupConfig.showButtonBar;
if (datepickerPopupConfig.html5Types[$attrs.type]) {
dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
isHtml5DateInput = true;
} else {
dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
$attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
// Invalidate the $modelValue to ensure that formatters re-run
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
if (newDateFormat !== dateFormat) {
dateFormat = newDateFormat;
ngModel.$modelValue = null;
if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
}
});
}
if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
throw new Error('HTML5 date input types do not support custom formats.');
}
// popup element used to display calendar
popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
popupEl.attr({
'ng-model': 'date',
'ng-change': 'dateSelection(date)',
'template-url': datepickerPopupTemplateUrl
});
// datepicker element
datepickerEl = angular.element(popupEl.children()[0]);
datepickerEl.attr('template-url', datepickerTemplateUrl);
if (!$scope.datepickerOptions) {
$scope.datepickerOptions = {};
}
if (isHtml5DateInput) {
if ($attrs.type === 'month') {
$scope.datepickerOptions.datepickerMode = 'month';
$scope.datepickerOptions.minMode = 'month';
}
}
datepickerEl.attr('datepicker-options', 'datepickerOptions');
if (!isHtml5DateInput) {
// Internal API to maintain the correct ng-invalid-[key] class
ngModel.$$parserName = 'date';
ngModel.$validators.date = validator;
ngModel.$parsers.unshift(parseDate);
ngModel.$formatters.push(function(value) {
if (ngModel.$isEmpty(value)) {
$scope.date = value;
return value;
}
if (angular.isNumber(value)) {
value = new Date(value);
}
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
return dateParser.filter($scope.date, dateFormat);
});
} else {
ngModel.$formatters.push(function(value) {
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
return value;
});
}
// Detect changes in the view from the text box
ngModel.$viewChangeListeners.push(function() {
$scope.date = parseDateString(ngModel.$viewValue);
});
$element.on('keydown', inputKeydownBind);
$popup = $compile(popupEl)($scope);
// Prevent jQuery cache memory leak (template is now redundant after linking)
popupEl.remove();
if (appendToBody) {
$document.find('body').append($popup);
} else {
$element.after($popup);
}
$scope.$on('$destroy', function() {
if ($scope.isOpen === true) {
if (!$rootScope.$$phase) {
$scope.$apply(function() {
$scope.isOpen = false;
});
}
}
$popup.remove();
$element.off('keydown', inputKeydownBind);
$document.off('click', documentClickBind);
if (scrollParentEl) {
scrollParentEl.off('scroll', positionPopup);
}
angular.element($window).off('resize', positionPopup);
//Clear all watch listeners on destroy
while (watchListeners.length) {
watchListeners.shift()();
}
});
};
$scope.getText = function(key) {
return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
};
$scope.isDisabled = function(date) {
if (date === 'today') {
date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
}
var dates = {};
angular.forEach(['minDate', 'maxDate'], function(key) {
if (!$scope.datepickerOptions[key]) {
dates[key] = null;
} else if (angular.isDate($scope.datepickerOptions[key])) {
dates[key] = new Date($scope.datepickerOptions[key]);
} else {
if ($datepickerPopupLiteralWarning) {
$log.warn('Literal date support has been deprecated, please switch to date object usage');
}
dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
}
});
return $scope.datepickerOptions &&
dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
};
$scope.compare = function(date1, date2) {
return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
};
// Inner change
$scope.dateSelection = function(dt) {
$scope.date = dt;
var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
$element.val(date);
ngModel.$setViewValue(date);
if (closeOnDateSelection) {
$scope.isOpen = false;
$element[0].focus();
}
};
$scope.keydown = function(evt) {
if (evt.which === 27) {
evt.stopPropagation();
$scope.isOpen = false;
$element[0].focus();
}
};
$scope.select = function(date, evt) {
evt.stopPropagation();
if (date === 'today') {
var today = new Date();
if (angular.isDate($scope.date)) {
date = new Date($scope.date);
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
} else {
date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
date.setHours(0, 0, 0, 0);
}
}
$scope.dateSelection(date);
};
$scope.close = function(evt) {
evt.stopPropagation();
$scope.isOpen = false;
$element[0].focus();
};
$scope.disabled = angular.isDefined($attrs.disabled) || false;
if ($attrs.ngDisabled) {
watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
$scope.disabled = disabled;
}));
}
$scope.$watch('isOpen', function(value) {
if (value) {
if (!$scope.disabled) {
$timeout(function() {
positionPopup();
if (onOpenFocus) {
$scope.$broadcast('uib:datepicker.focus');
}
$document.on('click', documentClickBind);
var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
if (appendToBody || $position.parsePlacement(placement)[2]) {
scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
if (scrollParentEl) {
scrollParentEl.on('scroll', positionPopup);
}
} else {
scrollParentEl = null;
}
angular.element($window).on('resize', positionPopup);
}, 0, false);
} else {
$scope.isOpen = false;
}
} else {
$document.off('click', documentClickBind);
if (scrollParentEl) {
scrollParentEl.off('scroll', positionPopup);
}
angular.element($window).off('resize', positionPopup);
}
});
function cameltoDash(string) {
return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
}
function parseDateString(viewValue) {
var date = dateParser.parse(viewValue, dateFormat, $scope.date);
if (isNaN(date)) {
for (var i = 0; i < altInputFormats.length; i++) {
date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
if (!isNaN(date)) {
return date;
}
}
}
return date;
}
function parseDate(viewValue) {
if (angular.isNumber(viewValue)) {
// presumably timestamp to date object
viewValue = new Date(viewValue);
}
if (!viewValue) {
return null;
}
if (angular.isDate(viewValue) && !isNaN(viewValue)) {
return viewValue;
}
if (angular.isString(viewValue)) {
var date = parseDateString(viewValue);
if (!isNaN(date)) {
return dateParser.toTimezone(date, ngModelOptions.getOption('timezone'));
}
}
return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined;
}
function validator(modelValue, viewValue) {
var value = modelValue || viewValue;
if (!$attrs.ngRequired && !value) {
return true;
}
if (angular.isNumber(value)) {
value = new Date(value);
}
if (!value) {
return true;
}
if (angular.isDate(value) && !isNaN(value)) {
return true;
}
if (angular.isString(value)) {
return !isNaN(parseDateString(value));
}
return false;
}
function documentClickBind(event) {
if (!$scope.isOpen && $scope.disabled) {
return;
}
var popup = $popup[0];
var dpContainsTarget = $element[0].contains(event.target);
// The popup node may not be an element node
// In some browsers (IE) only element nodes have the 'contains' function
var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
$scope.$apply(function() {
$scope.isOpen = false;
});
}
}
function inputKeydownBind(evt) {
if (evt.which === 27 && $scope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
$scope.$apply(function() {
$scope.isOpen = false;
});
$element[0].focus();
} else if (evt.which === 40 && !$scope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
$scope.$apply(function() {
$scope.isOpen = true;
});
}
}
function positionPopup() {
if ($scope.isOpen) {
var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
var position = $position.positionElements($element, dpElement, placement, appendToBody);
dpElement.css({top: position.top + 'px', left: position.left + 'px'});
if (dpElement.hasClass('uib-position-measure')) {
dpElement.removeClass('uib-position-measure');
}
}
}
function extractOptions(ngModelCtrl) {
var ngModelOptions;
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
// guarantee a value
ngModelOptions = angular.isObject(ngModelCtrl.$options) ?
ngModelCtrl.$options :
{
timezone: null
};
// mimic 1.6+ api
ngModelOptions.getOption = function (key) {
return ngModelOptions[key];
};
} else { // in angular >=1.6 $options is always present
ngModelOptions = ngModelCtrl.$options;
}
return ngModelOptions;
}
$scope.$on('uib:datepicker.mode', function() {
$timeout(positionPopup, 0, false);
});
}])
.directive('uibDatepickerPopup', function() {
return {
require: ['ngModel', 'uibDatepickerPopup'],
controller: 'UibDatepickerPopupController',
scope: {
datepickerOptions: '=?',
isOpen: '=?',
currentText: '@',
clearText: '@',
closeText: '@'
},
link: function(scope, element, attrs, ctrls) {
var ngModel = ctrls[0],
ctrl = ctrls[1];
ctrl.init(ngModel);
}
};
})
.directive('uibDatepickerPopupWrap', function() {
return {
restrict: 'A',
transclude: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
}
};
});
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -5,7 +5,7 @@
<a href id="simple-dropdown" uib-dropdown-toggle>
Click me for a dropdown, yo!
</a>
<ul uib-dropdown-menu aria-labelledby="simple-dropdown">
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="simple-dropdown">
<li ng-repeat="choice in items">
<a href>{{choice}}</a>
</li>
@@ -17,7 +17,7 @@
<button id="single-button" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">
Button dropdown <span class="caret"></span>
</button>
<ul uib-dropdown-menu role="menu" aria-labelledby="single-button">
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
<li role="menuitem"><a href="#">Action</a></li>
<li role="menuitem"><a href="#">Another action</a></li>
<li role="menuitem"><a href="#">Something else here</a></li>
@@ -33,7 +33,7 @@
<span class="caret"></span>
<span class="sr-only">Split button!</span>
</button>
<ul uib-dropdown-menu role="menu" aria-labelledby="split-button">
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="split-button">
<li role="menuitem"><a href="#">Action</a></li>
<li role="menuitem"><a href="#">Another action</a></li>
<li role="menuitem"><a href="#">Something else here</a></li>
@@ -47,7 +47,7 @@
<button id="btn-append-to-body" type="button" class="btn btn-primary" uib-dropdown-toggle>
Dropdown on Body <span class="caret"></span>
</button>
<ul uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-body">
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-body">
<li role="menuitem"><a href="#">Action</a></li>
<li role="menuitem"><a href="#">Another action</a></li>
<li role="menuitem"><a href="#">Something else here</a></li>
@@ -61,7 +61,7 @@
<button id="button-template-url" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">
Dropdown using template <span class="caret"></span>
</button>
<ul uib-dropdown-menu template-url="dropdown.html" aria-labelledby="button-template-url">
<ul class="dropdown-menu" uib-dropdown-menu template-url="dropdown.html" aria-labelledby="button-template-url">
</ul>
</div>
@@ -77,7 +77,7 @@
<button id="simple-btn-keyboard-nav" type="button" class="btn btn-primary" uib-dropdown-toggle>
Dropdown with keyboard navigation <span class="caret"></span>
</button>
<ul uib-dropdown-menu role="menu" aria-labelledby="simple-btn-keyboard-nav">
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="simple-btn-keyboard-nav">
<li role="menuitem"><a href="#">Action</a></li>
<li role="menuitem"><a href="#">Another action</a></li>
<li role="menuitem"><a href="#">Something else here</a></li>
+2 -2
View File
@@ -14,7 +14,7 @@ Each of these parts need to be used as attribute directives.
_(Default: `always`)_ -
Controls the behavior of the menu when clicked.
* `always` - Automatically closes the dropdown when any of its elements is clicked.
* `disabled` - Disables the auto close. You can control it manually with `is-open`. It still gets closed if the toggle is clicked, `esc` is pressed or another dropdown is open. It also won't be closed on `$locationchangeSuccess`.
* `disabled` - Disables the auto close. You can control it manually with `is-open`. It still gets closed if the toggle is clicked, `esc` is pressed or another dropdown is open.
* `outsideClick` - Closes the dropdown automatically only when the user clicks any element outside the dropdown.
* `dropdown-append-to`
@@ -25,7 +25,7 @@ Each of these parts need to be used as attribute directives.
* `dropdown-append-to-body`
<small class="badge">B</small>
_(Default: `false`)_ -
Appends the inner dropdown-menu to the body element.
Appends the inner dropdown-menu to the body element if the attribute is present without a value, or with a non `false` value.
* `is-open`
<small class="badge">$</small>
+139 -45
View File
@@ -1,17 +1,35 @@
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
.constant('uibDropdownConfig', {
appendToOpenClass: 'uib-dropdown-open',
openClass: 'open'
})
.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
var openScope = null;
var openedContainers = $$multiMap.createNew();
this.open = function(dropdownScope) {
this.isOnlyOpen = function(dropdownScope, appendTo) {
var openedDropdowns = openedContainers.get(appendTo);
if (openedDropdowns) {
var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {
if (dropdown.scope === dropdownScope) {
return dropdown;
}
return toClose;
}, {});
if (openDropdown) {
return openedDropdowns.length === 1;
}
}
return false;
};
this.open = function(dropdownScope, element, appendTo) {
if (!openScope) {
$document.on('click', closeDropdown);
$document.on('keydown', keybindFilter);
}
if (openScope && openScope !== dropdownScope) {
@@ -19,20 +37,58 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}
openScope = dropdownScope;
if (!appendTo) {
return;
}
var openedDropdowns = openedContainers.get(appendTo);
if (openedDropdowns) {
var openedScopes = openedDropdowns.map(function(dropdown) {
return dropdown.scope;
});
if (openedScopes.indexOf(dropdownScope) === -1) {
openedContainers.put(appendTo, {
scope: dropdownScope
});
}
} else {
openedContainers.put(appendTo, {
scope: dropdownScope
});
}
};
this.close = function(dropdownScope) {
this.close = function(dropdownScope, element, appendTo) {
if (openScope === dropdownScope) {
openScope = null;
$document.off('click', closeDropdown);
$document.off('keydown', keybindFilter);
$document.off('keydown', this.keybindFilter);
openScope = null;
}
if (!appendTo) {
return;
}
var openedDropdowns = openedContainers.get(appendTo);
if (openedDropdowns) {
var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {
if (dropdown.scope === dropdownScope) {
return dropdown;
}
return toClose;
}, {});
if (dropdownToClose) {
openedContainers.remove(appendTo, dropdownToClose);
}
}
};
var closeDropdown = function(evt) {
// This method may still be called during the same mouse event that
// unbound this event handler. So check openScope before proceeding.
if (!openScope) { return; }
if (!openScope || !openScope.isOpen) { return; }
if (evt && openScope.getAutoClose() === 'disabled') { return; }
@@ -49,6 +105,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
return;
}
openScope.focusToggleElement();
openScope.isOpen = false;
if (!$rootScope.$$phase) {
@@ -56,11 +113,21 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}
};
var keybindFilter = function(evt) {
this.keybindFilter = function(evt) {
if (!openScope) {
// see this.close as ESC could have been pressed which kills the scope so we can not proceed
return;
}
var dropdownElement = openScope.getDropdownElement();
var toggleElement = openScope.getToggleElement();
var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
if (evt.which === 27) {
evt.stopPropagation();
openScope.focusToggleElement();
closeDropdown();
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
evt.preventDefault();
evt.stopPropagation();
openScope.focusDropdownEntry(evt.which);
@@ -77,8 +144,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
getIsOpen,
setIsOpen = angular.noop,
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
appendToBody = false,
appendTo = null,
keynavEnabled = false,
selectedOption = null,
body = $document.find('body');
@@ -95,26 +160,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
});
}
if (angular.isDefined($attrs.dropdownAppendTo)) {
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
if (appendToEl) {
appendTo = angular.element(appendToEl);
}
}
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
keynavEnabled = angular.isDefined($attrs.keyboardNav);
if (appendToBody && !appendTo) {
appendTo = body;
}
if (appendTo && self.dropdownMenu) {
appendTo.append(self.dropdownMenu);
$element.on('$destroy', function handleDestroyEvent() {
self.dropdownMenu.remove();
});
}
};
this.toggle = function(open) {
@@ -186,11 +232,48 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}
};
function removeDropdownMenu() {
$element.append(self.dropdownMenu);
}
scope.$watch('isOpen', function(isOpen, wasOpen) {
var appendTo = null,
appendToBody = false;
if (angular.isDefined($attrs.dropdownAppendTo)) {
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
if (appendToEl) {
appendTo = angular.element(appendToEl);
}
}
if (angular.isDefined($attrs.dropdownAppendToBody)) {
var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope);
if (appendToBodyValue !== false) {
appendToBody = true;
}
}
if (appendToBody && !appendTo) {
appendTo = body;
}
if (appendTo && self.dropdownMenu) {
if (isOpen) {
appendTo.append(self.dropdownMenu);
$element.on('$destroy', removeDropdownMenu);
} else {
$element.off('$destroy', removeDropdownMenu);
removeDropdownMenu();
}
}
if (appendTo && self.dropdownMenu) {
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
css,
rightalign;
rightalign,
scrollbarPadding,
scrollbarWidth = 0;
css = {
top: pos.top + 'px',
@@ -203,7 +286,13 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
css.right = 'auto';
} else {
css.left = 'auto';
css.right = window.innerWidth -
scrollbarPadding = $position.scrollbarPadding(appendTo);
if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
scrollbarWidth = scrollbarPadding.scrollbarWidth;
}
css.right = window.innerWidth - scrollbarWidth -
(pos.left + $element.prop('offsetWidth')) + 'px';
}
@@ -226,10 +315,18 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}
var openContainer = appendTo ? appendTo : $element;
var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);
if (hasOpenClass === !isOpen) {
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
var toggleClass;
if (appendTo) {
toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';
} else {
toggleClass = isOpen ? 'addClass' : 'removeClass';
}
$animate[toggleClass](openContainer, dropdownOpenClass).then(function() {
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
toggleInvoker($scope, { open: !!isOpen });
}
@@ -244,13 +341,17 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
var newEl = dropdownElement;
self.dropdownMenu.replaceWith(newEl);
self.dropdownMenu = newEl;
$document.on('keydown', uibDropdownService.keybindFilter);
});
});
} else {
$document.on('keydown', uibDropdownService.keybindFilter);
}
scope.focusToggleElement();
uibDropdownService.open(scope);
uibDropdownService.open(scope, $element, appendTo);
} else {
uibDropdownService.close(scope, $element, appendTo);
if (self.dropdownMenuTemplateUrl) {
if (templateScope) {
templateScope.$destroy();
@@ -260,7 +361,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
self.dropdownMenu = newEl;
}
uibDropdownService.close(scope);
self.selectedOption = null;
}
@@ -268,12 +368,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
setIsOpen($scope, isOpen);
}
});
$scope.$on('$locationChangeSuccess', function() {
if (scope.getAutoClose() !== 'disabled') {
scope.isOpen = false;
}
});
}])
.directive('uibDropdown', function() {
@@ -330,7 +424,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}
};
element.bind('click', toggleDropdown);
element.on('click', toggleDropdown);
// WAI-ARIA
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
@@ -339,7 +433,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
});
scope.$on('$destroy', function() {
element.unbind('click', toggleDropdown);
element.off('click', toggleDropdown);
});
}
};
+9
View File
@@ -0,0 +1,9 @@
require('../multiMap');
require('../position/index-nocss.js');
require('./dropdown');
var MODULE_NAME = 'ui.bootstrap.module.dropdown';
angular.module(MODULE_NAME, ['ui.bootstrap.dropdown']);
module.exports = MODULE_NAME;
+2 -8
View File
@@ -1,8 +1,2 @@
require('../position');
require('./dropdown');
var MODULE_NAME = 'ui.bootstrap.module.dropdown';
angular.module(MODULE_NAME, ['ui.bootstrap.dropdown']);
module.exports = MODULE_NAME;
require('../position/position.css');
module.exports = require('./index-nocss.js');
+339 -100
View File
@@ -26,13 +26,16 @@ describe('uib-dropdown', function() {
var triggerKeyDown = function (element, keyCode) {
var e = $.Event('keydown');
spyOn(e, 'stopPropagation');
e.stopPropagation.and.callThrough();
e.which = keyCode;
element.trigger(e);
return e;
};
describe('basic', function() {
function dropdown() {
return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li></ul></li>')($rootScope);
}
beforeEach(function() {
@@ -66,16 +69,18 @@ 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();
triggerKeyDown($document, 27);
var event = triggerKeyDown(dropdownMenu, 27);
expect(element).not.toHaveClass(dropdownConfig.openClass);
expect(element.find('a')).toHaveFocus();
expect(event.stopPropagation).toHaveBeenCalled();
});
it('should not close on backspace key', function() {
clickDropdownToggle();
triggerKeyDown($document, 8);
triggerKeyDown(element, 8);
expect(element).toHaveClass(dropdownConfig.openClass);
});
@@ -88,31 +93,23 @@ describe('uib-dropdown', function() {
expect(element).toHaveClass(dropdownConfig.openClass);
});
it('should close on $location change', function() {
clickDropdownToggle();
expect(element).toHaveClass(dropdownConfig.openClass);
$rootScope.$broadcast('$locationChangeSuccess');
$rootScope.$apply();
expect(element).not.toHaveClass(dropdownConfig.openClass);
});
it('should only allow one dropdown to be open at once', function() {
var elm1 = dropdown();
var elm2 = dropdown();
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('<li uib-dropdown><a class="disabled" uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
var elm = $compile('<li uib-dropdown><a class="disabled" uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
clickDropdownToggle( elm );
expect(elm).not.toHaveClass(dropdownConfig.openClass);
});
@@ -125,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('<li uib-dropdown><div ng-disabled="isdisabled" uib-dropdown-toggle></div><ul><li>Hello</li></ul></li>')($rootScope);
var elm = $compile('<li uib-dropdown><div ng-disabled="isdisabled" uib-dropdown-toggle></div><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
elm.find('div').click();
expect(elm).not.toHaveClass(dropdownConfig.openClass);
@@ -138,7 +135,7 @@ describe('uib-dropdown', function() {
it('should unbind events on scope destroy', function() {
var $scope = $rootScope.$new();
var elm = $compile('<li uib-dropdown><button ng-disabled="isdisabled" uib-dropdown-toggle></button><ul><li>Hello</li></ul></li>')($scope);
var elm = $compile('<li uib-dropdown><button ng-disabled="isdisabled" uib-dropdown-toggle></button><ul uib-dropdown-menu><li>Hello</li></ul></li>')($scope);
$scope.$digest();
var buttonEl = elm.find('button');
@@ -218,27 +215,198 @@ describe('uib-dropdown', function() {
});
describe('using dropdown-append-to-body', function() {
function dropdown() {
return $compile('<li uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
}
describe('with no value', function() {
function dropdown() {
return $compile('<li uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
}
beforeEach(function() {
element = dropdown();
beforeEach(function() {
element = dropdown();
$document.find('body').append(element);
});
afterEach(function() {
element.remove();
});
it('does not add the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
describe('when toggled open', function() {
var toggle;
beforeEach(function() {
toggle = element.find('[uib-dropdown-toggle]');
toggle.trigger('click');
});
it('adds the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
});
describe('when toggled closed', function() {
beforeEach(function() {
toggle.trigger('click');
});
it('removes the menu from body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[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 body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
describe('when the dropdown is removed', function() {
beforeEach(function() {
element.remove();
$rootScope.$digest();
});
it('removes the menu from body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
});
});
it('adds the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
});
describe('with a value', function() {
function dropdown() {
return $compile('<li uib-dropdown dropdown-append-to-body="appendToBody"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
}
describe('that is not false', function() {
beforeEach(function() {
$rootScope.appendToBody = 'sure';
it('removes the menu when the dropdown is removed', function() {
element.remove();
$rootScope.$digest();
expect($document.find('#dropdown-menu').length).toEqual(0);
element = dropdown();
$document.find('body').append(element);
});
afterEach(function() {
element.remove();
});
it('does not add the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
describe('when toggled open', function() {
var toggle;
beforeEach(function() {
toggle = element.find('[uib-dropdown-toggle]');
toggle.trigger('click');
});
it('adds the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
});
describe('when toggled closed', function() {
beforeEach(function() {
toggle.trigger('click');
});
it('removes the menu from body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[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 body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
describe('when the dropdown is removed', function() {
beforeEach(function() {
element.remove();
$rootScope.$digest();
});
it('removes the menu from body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
});
});
describe('that is false', function() {
beforeEach(function() {
$rootScope.appendToBody = false;
element = dropdown();
$document.find('body').append(element);
});
afterEach(function() {
element.remove();
});
it('does not add the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
describe('when toggled open', function() {
var toggle;
beforeEach(function() {
toggle = element.find('[uib-dropdown-toggle]');
toggle.trigger('click');
});
it('does not add the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
describe('when toggled closed', function() {
beforeEach(function() {
toggle.trigger('click');
});
it('does not remove the menu', function() {
expect($document.find('#dropdown-menu').length).not.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('does not removes the menu from body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
describe('when the dropdown is removed', function() {
beforeEach(function() {
element.remove();
$rootScope.$digest();
});
it('removes the menu from body', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
});
});
});
});
describe('using dropdown-append-to', function() {
var initialPage;
var initialPage, container;
function dropdown() {
return $compile('<li uib-dropdown dropdown-append-to="appendTo"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></li>')($rootScope);
@@ -247,7 +415,7 @@ describe('uib-dropdown', function() {
beforeEach(function() {
$document.find('body').append(angular.element('<div id="dropdown-container"></div>'));
$rootScope.appendTo = $document.find('#dropdown-container');
$rootScope.appendTo = container = $document.find('#dropdown-container');
element = dropdown();
$document.find('body').append(element);
@@ -258,50 +426,95 @@ describe('uib-dropdown', function() {
$document.find('#dropdown-container').remove();
});
it('appends to container', function() {
expect($document.find('#dropdown-menu').parent()[0].id).toBe('dropdown-container');
it('does not add the menu to the container', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe(container[0]);
});
it('toggles open class on container', function() {
var container = $document.find('#dropdown-container');
expect(container).not.toHaveClass('uib-dropdown-open');
element.find('[uib-dropdown-toggle]').click();
expect(container).toHaveClass('uib-dropdown-open');
element.find('[uib-dropdown-toggle]').click();
it('does not add open class on 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 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');
});
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');
});
});
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('integration with $location URL rewriting', function() {
describe('using dropdown-append-to with two dropdowns', function() {
function dropdown() {
// Simulate URL rewriting behavior
$document.on('click', 'a[href="#something"]', function() {
$rootScope.$broadcast('$locationChangeSuccess');
$rootScope.$apply();
});
return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a>' +
'<ul><li><a href="#something">Hello</a></li></ul></li>')($rootScope);
return $compile('<div><div class="dropdown1" uib-dropdown dropdown-append-to="appendTo" on-toggle="log(1, open)"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></div><div class="dropdown2" uib-dropdown dropdown-append-to="appendTo" on-toggle="log(2, open)"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></div></div>')($rootScope);
}
beforeEach(function() {
$document.find('body').append(angular.element('<div id="dropdown-container"></div>'));
$rootScope.appendTo = $document.find('#dropdown-container');
$rootScope.log = jasmine.createSpy('log');
element = dropdown();
$document.find('body').append(element);
});
it('should close without errors on $location change', function() {
$document.find('body').append(element);
clickDropdownToggle();
expect(element).toHaveClass(dropdownConfig.openClass);
var optionEl = element.find('ul > li').eq(0).find('a').eq(0);
optionEl.click();
expect(element).not.toHaveClass(dropdownConfig.openClass);
afterEach(function() {
// Cleanup the extra elements we appended
$document.find('#dropdown-container').remove();
});
it('should keep the class when toggling from one dropdown to another with the same container', function() {
var container = $document.find('#dropdown-container');
expect(container).not.toHaveClass('uib-dropdown-open');
element.find('.dropdown1 [uib-dropdown-toggle]').click();
expect(container).toHaveClass('uib-dropdown-open');
element.find('.dropdown2 [uib-dropdown-toggle]').click();
expect(container).toHaveClass('uib-dropdown-open');
});
});
@@ -309,7 +522,7 @@ describe('uib-dropdown', function() {
describe('with uib-dropdown-toggle', function() {
beforeEach(function() {
$rootScope.isopen = true;
element = $compile('<li uib-dropdown is-open="isopen"><a href uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
element = $compile('<li uib-dropdown is-open="isopen"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
});
@@ -343,7 +556,7 @@ describe('uib-dropdown', function() {
describe('without uib-dropdown-toggle', function() {
beforeEach(function() {
$rootScope.isopen = true;
element = $compile('<li uib-dropdown is-open="isopen"><ul><li>Hello</li></ul></li>')($rootScope);
element = $compile('<li uib-dropdown is-open="isopen"><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
});
@@ -364,7 +577,7 @@ describe('uib-dropdown', function() {
beforeEach(function() {
$rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
$rootScope.isopen = false;
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
});
@@ -391,7 +604,7 @@ describe('uib-dropdown', function() {
beforeEach(function() {
$rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
$rootScope.isopen = true;
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
});
@@ -419,7 +632,7 @@ describe('uib-dropdown', function() {
describe('without is-open', function() {
beforeEach(function() {
$rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
});
@@ -447,7 +660,7 @@ describe('uib-dropdown', function() {
function dropdown(autoClose) {
return $compile('<li uib-dropdown ' +
(autoClose === undefined ? '' : 'auto-close="' + autoClose + '"') +
'><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
'><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li></ul></li>')($rootScope);
}
describe('always', function() {
@@ -479,7 +692,7 @@ describe('uib-dropdown', function() {
it('control with is-open', function() {
$rootScope.isopen = true;
element = $compile('<li uib-dropdown is-open="isopen" auto-close="disabled"><a href uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
element = $compile('<li uib-dropdown is-open="isopen" auto-close="disabled"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
$rootScope.$digest();
expect(element).toHaveClass(dropdownConfig.openClass);
@@ -502,9 +715,10 @@ describe('uib-dropdown', function() {
it('should close anyway if esc is pressed', function() {
element = dropdown('disabled');
var dropdownMenu = element.find('[uib-dropdown-menu]');
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 27);
triggerKeyDown(dropdownMenu, 27);
expect(element).not.toHaveClass(dropdownConfig.openClass);
expect(element.find('a')).toHaveFocus();
});
@@ -521,16 +735,6 @@ describe('uib-dropdown', function() {
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
expect(elm2).toHaveClass(dropdownConfig.openClass);
});
it('should not close on $locationChangeSuccess if auto-close="disabled"', function() {
var elm1 = dropdown('disabled');
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
clickDropdownToggle(elm1);
expect(elm1).toHaveClass(dropdownConfig.openClass);
$rootScope.$broadcast('$locationChangeSuccess');
$rootScope.$digest();
expect(elm1).toHaveClass(dropdownConfig.openClass);
});
});
describe('outsideClick', function() {
@@ -559,7 +763,10 @@ describe('uib-dropdown', function() {
describe('using keyboard-nav', function() {
function dropdown() {
return $compile('<li uib-dropdown keyboard-nav><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
return $compile('<li uib-dropdown keyboard-nav><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
}
function getFocusedElement() {
return angular.element(document.activeElement);
}
beforeEach(function() {
element = dropdown();
@@ -568,7 +775,7 @@ describe('uib-dropdown', function() {
it('should focus first list element when down arrow pressed', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
triggerKeyDown(getFocusedElement(), 40);
expect(element).toHaveClass(dropdownConfig.openClass);
var optionEl = element.find('ul').eq(0).find('a').eq(0);
@@ -577,7 +784,7 @@ describe('uib-dropdown', function() {
it('should not focus first list element when down arrow pressed if closed', function() {
$document.find('body').append(element);
triggerKeyDown($document, 40);
triggerKeyDown(getFocusedElement(), 40);
expect(element).not.toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a').eq(0);
@@ -587,8 +794,8 @@ describe('uib-dropdown', function() {
it('should focus second list element when down arrow pressed twice', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
triggerKeyDown($document, 40);
triggerKeyDown(getFocusedElement(), 40);
triggerKeyDown(getFocusedElement(), 40);
expect(element).toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a').eq(1);
@@ -600,7 +807,7 @@ describe('uib-dropdown', function() {
clickDropdownToggle();
expect(element).toHaveClass(dropdownConfig.openClass);
triggerKeyDown($document, 38);
triggerKeyDown(getFocusedElement(), 38);
var focusEl = element.find('ul').eq(0).find('a').eq(0);
expect(focusEl).not.toHaveFocus();
});
@@ -608,7 +815,7 @@ describe('uib-dropdown', function() {
it('should focus last list element when up arrow pressed after dropdown toggled', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 38);
triggerKeyDown(getFocusedElement(), 38);
expect(element).toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a').eq(1);
@@ -618,7 +825,7 @@ describe('uib-dropdown', function() {
it('should not change focus when other keys are pressed', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 37);
triggerKeyDown(getFocusedElement(), 37);
expect(element).toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a');
@@ -629,10 +836,10 @@ describe('uib-dropdown', function() {
it('should focus first list element when down arrow pressed 2x and up pressed 1x', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
triggerKeyDown($document, 40);
triggerKeyDown(getFocusedElement(), 40);
triggerKeyDown(getFocusedElement(), 40);
triggerKeyDown($document, 38);
triggerKeyDown(getFocusedElement(), 38);
expect(element).toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a').eq(0);
@@ -642,14 +849,14 @@ describe('uib-dropdown', function() {
it('should stay focused on final list element if down pressed at list end', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
triggerKeyDown($document, 40);
triggerKeyDown(getFocusedElement(), 40);
triggerKeyDown(getFocusedElement(), 40);
expect(element).toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a').eq(1);
expect(focusEl).toHaveFocus();
triggerKeyDown($document, 40);
triggerKeyDown(element, 40);
expect(focusEl).toHaveFocus();
});
@@ -658,19 +865,19 @@ describe('uib-dropdown', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
triggerKeyDown(getFocusedElement(), 40);
expect(element).toHaveClass(dropdownConfig.openClass);
var focusEl = element.find('ul').eq(0).find('a').eq(0);
expect(focusEl).toHaveFocus();
triggerKeyDown($document, 27);
triggerKeyDown(getFocusedElement(), 27);
expect(element).not.toHaveClass(dropdownConfig.openClass);
});
describe('with dropdown-append-to-body', function() {
function dropdown() {
return $compile('<li uib-dropdown dropdown-append-to-body keyboard-nav><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
return $compile('<li uib-dropdown dropdown-append-to-body keyboard-nav><a href uib-dropdown-toggle>foo</a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
}
beforeEach(function() {
@@ -678,23 +885,25 @@ describe('uib-dropdown', function() {
});
it('should focus first list element when down arrow pressed', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
var dropdownMenu = $document.find('#dropdown-menu');
triggerKeyDown(getFocusedElement(), 40);
expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);
var focusEl = $document.find('ul').eq(0).find('a');
expect(focusEl).toHaveFocus();
});
it('should focus second list element when down arrow pressed twice', function() {
$document.find('body').append(element);
clickDropdownToggle();
triggerKeyDown($document, 40);
triggerKeyDown($document, 40);
var dropdownMenu = $document.find('#dropdown-menu');
triggerKeyDown(getFocusedElement(), 40);
triggerKeyDown(getFocusedElement(), 40);
triggerKeyDown(getFocusedElement(), 40);
expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);
var elem1 = $document.find('ul');
@@ -704,4 +913,34 @@ describe('uib-dropdown', function() {
});
});
});
// issue #5942
describe('using dropdown-append-to-body with dropdown-menu-right class', function() {
function dropdown() {
return $compile('<li style="float: right;" uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle>Toggle menu</a><ul uib-dropdown-menu class="dropdown-menu-right" id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
}
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);
});
});
});
+6 -7
View File
@@ -13,7 +13,7 @@ function ($animate) {
return {
restrict: 'A',
compile: function (tElement, tAttrs) {
compile: function(tElement, tAttrs) {
var linkedScopes = [];
var instances = [];
var expToData = {};
@@ -32,7 +32,7 @@ function ($animate) {
element: element
});
exps.forEach(function (exp, k) {
exps.forEach(function(exp, k) {
addForExp(exp, scope);
});
@@ -45,9 +45,9 @@ function ($animate) {
var compareWithExp = matches[2];
var data = expToData[exp];
if (!data) {
var watchFn = function (compareWithVal) {
var watchFn = function(compareWithVal) {
var newActivated = null;
instances.some(function (instance) {
instances.some(function(instance) {
var thisVal = instance.scope.$eval(onExp);
if (thisVal === compareWithVal) {
newActivated = instance;
@@ -82,14 +82,13 @@ function ($animate) {
instances.splice(index, 1);
if (linkedScopes.length) {
var newWatchScope = linkedScopes[0];
angular.forEach(expToData, function (data) {
angular.forEach(expToData, function(data) {
if (data.scope === removedScope) {
data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
data.scope = newWatchScope;
}
});
}
else {
} else {
expToData = {};
}
}
+33 -14
View File
@@ -1,25 +1,44 @@
<div ng-controller="ModalDemoCtrl">
<div ng-controller="ModalDemoCtrl as $ctrl" class="modal-demo">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
<h3 class="modal-title" id="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<div class="modal-body" id="modal-body">
<ul>
<li ng-repeat="item in items">
<a href="#" ng-click="$event.preventDefault(); selected.item = item">{{ item }}</a>
<li ng-repeat="item in $ctrl.items">
<a href="#" ng-click="$event.preventDefault(); $ctrl.selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
Selected: <b>{{ $ctrl.selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>
<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
<button class="btn btn-primary" type="button" ng-click="$ctrl.ok()">OK</button>
<button class="btn btn-warning" type="button" ng-click="$ctrl.cancel()">Cancel</button>
</div>
</script>
<script type="text/ng-template" id="stackedModal.html">
<div class="modal-header">
<h3 class="modal-title" id="modal-title-{{name}}">The {{name}} modal!</h3>
</div>
<div class="modal-body" id="modal-body-{{name}}">
Having multiple modals open at once is probably bad UX but it's technically possible.
</div>
</script>
<button type="button" class="btn btn-default" ng-click="open()">Open me!</button>
<button type="button" class="btn btn-default" ng-click="open('lg')">Large modal</button>
<button type="button" class="btn btn-default" ng-click="open('sm')">Small modal</button>
<button type="button" class="btn btn-default" ng-click="toggleAnimation()">Toggle Animation ({{ animationsEnabled }})</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
<button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open me!</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.open('lg')">Large modal</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.open('sm')">Small modal</button>
<button type="button"
class="btn btn-default"
ng-click="$ctrl.open('sm', '.modal-parent')">
Modal appended to a custom parent
</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.toggleAnimation()">Toggle Animation ({{ $ctrl.animationsEnabled }})</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.openComponentModal()">Open a component modal!</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.openMultipleModals()">
Open multiple modals at once
</button>
<div ng-show="$ctrl.selected">Selection from a modal: {{ $ctrl.selected }}</div>
<div class="modal-parent">
</div>
</div>
+94 -19
View File
@@ -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'});
};
}
});
+36 -12
View File
@@ -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.
@@ -135,3 +157,5 @@ Events fired:
##### UI Router resolves
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. If using the component option, see details on how to access this object in component section of the modal documentation.
+11
View File
@@ -0,0 +1,11 @@
require('../multiMap');
require('../position/index-nocss.js');
require('../stackedMap');
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/window.html']);
module.exports = MODULE_NAME;
+2 -10
View File
@@ -1,10 +1,2 @@
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']);
module.exports = MODULE_NAME;
require('../position/position.css');
module.exports = require('./index-nocss.js');
+263 -168
View File
@@ -1,59 +1,4 @@
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
/**
* 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'])
.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'])
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'])
// {@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'])
/**
* 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,20 +175,20 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
};
})
.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',
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
var OPENED_MODAL_CLASS = 'modal-open';
var backdropDomEl, backdropScope;
@@ -257,13 +197,30 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
var $modalStack = {
NOW_CLOSING_EVENT: 'modal.stack.now-closing'
};
var topModalIndex = 0;
var previousTopOpenedModal = null;
var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count';
//Modal focus behavior
var focusableElementList;
var focusIndex = 0;
var tababbleSelector = '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 ||
element.offsetHeight ||
element.getClientRects().length);
}
function backdropIndex() {
var topBackdropIndex = -1;
@@ -273,6 +230,12 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
topBackdropIndex = i;
}
}
// If any backdrop exist, ensure that it's index is always
// right below the top modal
if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
topBackdropIndex = topModalIndex;
}
return topBackdropIndex;
}
@@ -288,11 +251,24 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
//clean up the stack
openedWindows.remove(modalInstance);
previousTopOpenedModal = openedWindows.top();
if (previousTopOpenedModal) {
topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
}
removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
openedClasses.remove(modalBodyClass, modalInstance);
appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
var areAnyOpen = openedClasses.hasKey(modalBodyClass);
appendToElement.toggleClass(modalBodyClass, areAnyOpen);
if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
if (scrollbarPadding.originalRight) {
appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
} else {
appendToElement.css({paddingRight: ''});
}
scrollbarPadding = null;
}
toggleTopWindowClass(true);
}, modalWindow.closedDeferred);
checkRemoveBackdrop();
@@ -354,6 +330,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
afterAnimating.done = true;
$animate.leave(domEl).then(function() {
if (done) {
done();
}
domEl.remove();
if (closedDeferred) {
closedDeferred.resolve();
@@ -361,9 +341,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
});
scope.$destroy();
if (done) {
done();
}
}
}
@@ -391,15 +368,15 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
break;
}
case 9: {
$modalStack.loadFocusElementList(modal);
var list = $modalStack.loadFocusElementList(modal);
var focusChanged = false;
if (evt.shiftKey) {
if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) {
focusChanged = $modalStack.focusLastFocusableElement();
if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
focusChanged = $modalStack.focusLastFocusableElement(list);
}
} else {
if ($modalStack.isFocusInLastItem(evt)) {
focusChanged = $modalStack.focusFirstFocusableElement();
if ($modalStack.isFocusInLastItem(evt, list)) {
focusChanged = $modalStack.focusFirstFocusableElement(list);
}
}
@@ -407,6 +384,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
evt.preventDefault();
evt.stopPropagation();
}
break;
}
}
@@ -419,6 +397,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
toggleTopWindowClass(false);
// Store the current top first, to determine what index we ought to use
// for the current top modal
previousTopOpenedModal = openedWindows.top();
openedWindows.add(modalInstance, {
deferred: modal.deferred,
renderDeferred: modal.renderDeferred,
@@ -437,66 +419,152 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
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('<div uib-modal-backdrop="modal-backdrop"></div>');
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);
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('<div uib-modal-window="modal-window"></div>');
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': openedWindows.length() - 1,
'animate': 'animate'
}).html(modal.content);
'index': topModalIndex,
'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');
}
$animate.enter($compile(angularDomEl)(modal.scope), appendToElement)
.then(function() {
if (!modal.scope.$$uibDestructionScheduled) {
$animate.addClass(appendToElement, modalBodyClass);
}
});
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;
$modalStack.clearFocusListCache();
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);
@@ -524,16 +592,17 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
}
};
$modalStack.focusFirstFocusableElement = function() {
if (focusableElementList.length > 0) {
focusableElementList[0].focus();
$modalStack.focusFirstFocusableElement = function(list) {
if (list.length > 0) {
list[0].focus();
return true;
}
return false;
};
$modalStack.focusLastFocusableElement = function() {
if (focusableElementList.length > 0) {
focusableElementList[focusableElementList.length - 1].focus();
$modalStack.focusLastFocusableElement = function(list) {
if (list.length > 0) {
list[list.length - 1].focus();
return true;
}
return false;
@@ -549,32 +618,29 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
return false;
};
$modalStack.isFocusInFirstItem = function(evt) {
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) === focusableElementList[0];
$modalStack.isFocusInFirstItem = function(evt, list) {
if (list.length > 0) {
return (evt.target || evt.srcElement) === list[0];
}
return false;
};
$modalStack.isFocusInLastItem = function(evt) {
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
$modalStack.isFocusInLastItem = function(evt, list) {
if (list.length > 0) {
return (evt.target || evt.srcElement) === list[list.length - 1];
}
return false;
};
$modalStack.clearFocusListCache = function() {
focusableElementList = [];
focusIndex = 0;
};
$modalStack.loadFocusElementList = function(modalWindow) {
if (focusableElementList === undefined || !focusableElementList.length) {
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
}
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
var elements = modalDomE1[0].querySelectorAll(tabbableSelector);
return elements ?
Array.prototype.filter.call(elements, function(element) {
return isVisible(element);
}) : elements;
}
}
};
@@ -629,13 +695,22 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
modalOptions.resolve = modalOptions.resolve || {};
modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
//verify options
if (!modalOptions.template && !modalOptions.templateUrl) {
throw new Error('One of template or templateUrl options is required.');
if (!modalOptions.appendTo.length) {
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
}
var templateAndResolvePromise =
$q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
//verify options
if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
throw new Error('One of component or template or templateUrl options is required.');
}
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;
@@ -661,46 +736,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
}
});
var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
//controllers
if (modalOptions.controller) {
ctrlLocals.$scope = modalScope;
ctrlLocals.$uibModalInstance = modalInstance;
angular.forEach(tplAndVars[1], function(value, key) {
ctrlLocals[key] = value;
});
// the third param will make the controller instantiate later,private api
// @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true);
if (modalOptions.controllerAs) {
ctrlInstance = ctrlInstantiate.instance;
if (modalOptions.bindToController) {
ctrlInstance.$close = modalScope.$close;
ctrlInstance.$dismiss = modalScope.$dismiss;
angular.extend(ctrlInstance, providedScope);
}
ctrlInstance = ctrlInstantiate();
modalScope[modalOptions.controllerAs] = ctrlInstance;
} else {
ctrlInstance = ctrlInstantiate();
}
if (angular.isFunction(ctrlInstance.$onInit)) {
ctrlInstance.$onInit();
}
}
$modalStack.open(modalInstance, {
var modal = {
scope: modalScope,
deferred: modalResultDeferred,
renderDeferred: modalRenderDeferred,
closedDeferred: modalClosedDeferred,
content: tplAndVars[0],
animation: modalOptions.animation,
backdrop: modalOptions.backdrop,
keyboard: modalOptions.keyboard,
@@ -708,12 +748,67 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
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 = {};
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
ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
if (modalOptions.controllerAs && modalOptions.bindToController) {
ctrlInstance = ctrlInstantiate.instance;
ctrlInstance.$close = modalScope.$close;
ctrlInstance.$dismiss = modalScope.$dismiss;
angular.extend(ctrlInstance, {
$resolve: ctrlLocals.$scope.$resolve
}, providedScope);
}
ctrlInstance = ctrlInstantiate();
if (angular.isFunction(ctrlInstance.$onInit)) {
ctrlInstance.$onInit();
}
}
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);
+553 -28
View File
@@ -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: '<div>Foo Bar</div>'
});
}));
@@ -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: '<div>Content<button>inside modal</button></div>'});
$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: '<div><input type="text" id="auto-focus-element" autofocus><input type="text" id="pre-focus-element" focus-me></div>'});
var modal = open({template: '<div><input type="text" id="pre-focus-element" focus-me><input type="text" id="auto-focus-element" autofocus></div>'});
$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');
@@ -715,6 +798,144 @@ describe('$uibModal', function() {
initialPage.remove();
});
it('should change focus to next proper element when DOM changes and tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link3').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
$('#tab-focus-button').remove();
triggerKeyDown(angular.element(document.activeElement), 9, false);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
initialPage.remove();
});
it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link1').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
$('#tab-focus-button').remove();
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
initialPage.remove();
});
it('should change focus to next non-hidden element when tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link3').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
$('#tab-focus-button').css('display', 'none');
triggerKeyDown(angular.element(document.activeElement), 9, false);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
initialPage.remove();
});
it('should change focus to previous non-hidden element when shift+tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link1').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
$('#tab-focus-button').css('display', 'none');
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
initialPage.remove();
});
it('should change focus to next tabbable element when tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<button id="tab-focus-button1" tabindex="-1">Skip me!</button><a href="#" id="tab-focus-link1">a</a>' +
'<a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button2" tabindex="-1">Skip me!</button>',
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('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<button id="tab-focus-button1" tabindex="-1">Skip me!</button><a href="#" id="tab-focus-link1">a</a>' +
'<a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button2" tabindex="-1">Skip me!</button>',
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() {
@@ -737,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('<div>Foo Bar</div>');
});
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', ' <div>Whitespaces</div> ');
open({templateUrl: 'whitespace.html'});
expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
@@ -961,6 +1255,20 @@ describe('$uibModal', function() {
});
expect($document).toHaveModalOpenWithContent('Content from root scope', 'div');
});
it('should expose $resolve in template', function() {
open({
controller: function($scope) {},
resolve: {
$foo: function() {
return 'Content from resolve';
}
},
template: '<div>{{$resolve.$foo}}</div>'
});
expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
});
});
describe('keyboard', function () {
@@ -1183,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: '<div>dummy modal</div>',
openedClass: 'foo'
@@ -1191,7 +1499,13 @@ describe('$uibModal', function() {
expect(body).toHaveClass('foo');
close(modal);
close(modal, null, true);
expect(body).toHaveClass('foo');
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect(body).not.toHaveClass('foo');
});
@@ -1252,6 +1566,28 @@ describe('$uibModal', function() {
expect(body).not.toHaveClass('modal-open');
});
});
describe('ariaLabelledBy', function() {
it('should add the aria-labelledby property to the modal', function() {
open({
template: '<div><h3 id="modal-label">Modal Label</h3><p id="modal-description">Modal description</p></div>',
ariaLabelledBy: 'modal-label'
});
expect($document.find('.modal').attr('aria-labelledby')).toEqual('modal-label');
});
});
describe('ariaDescribedBy', function() {
it('should add the aria-describedby property to the modal', function() {
open({
template: '<div><h3 id="modal-label">Modal Label</h3><p id="modal-description">Modal description</p></div>',
ariaDescribedBy: 'modal-description'
});
expect($document.find('.modal').attr('aria-describedby')).toEqual('modal-description');
});
});
});
describe('modal window', function() {
@@ -1263,15 +1599,6 @@ describe('$uibModal', function() {
expect($rootScope.foo).toBeTruthy();
});
it('should support custom CSS classes as string', function() {
$rootScope.animate = false;
var windowEl = $compile('<div uib-modal-window animate="animate" window-class="test foo">content</div>')($rootScope);
$rootScope.$digest();
expect(windowEl).toHaveClass('test');
expect(windowEl).toHaveClass('foo');
});
it('should support window top class', function () {
$rootScope.animate = false;
var windowEl = $compile('<div uib-modal-window animate="animate" window-top-class="test foo">content</div>')($rootScope);
@@ -1282,13 +1609,12 @@ describe('$uibModal', function() {
});
it('should support custom template url', inject(function($templateCache) {
$templateCache.put('window.html', '<div class="mywindow" ng-transclude></div>');
$templateCache.put('window.html', '<div ng-transclude></div>');
var windowEl = $compile('<div uib-modal-window template-url="window.html" window-class="test">content</div>')($rootScope);
var windowEl = $compile('<div uib-modal-window template-url="window.html">content</div>')($rootScope);
$rootScope.$digest();
expect(windowEl).toHaveClass('mywindow');
expect(windowEl).toHaveClass('test');
expect(windowEl.html()).toBe('<div ng-transclude="">content</div>');
}));
});
@@ -1412,16 +1738,19 @@ describe('$uibModal', function() {
ds[x] = {index: i, deferred: $q.defer(), reject: reject};
var scope = $rootScope.$new();
var failed = false;
scope.index = i;
open({
template: '<div>' + i + '</div>',
scope: scope,
resolve: {
x: function() { return ds[x].deferred.promise; }
x: function() { return ds[x].deferred.promise['catch'](function () {
failed = true;
}); }
}
}, true).opened.then(function() {
expect($uibModalStack.getTop().value.modalScope.index).toEqual(i);
actual += i;
if (!failed) { actual += i; }
});
});
@@ -1523,6 +1852,202 @@ describe('$uibModal', function() {
expect($document.find('div.modal1')).toHaveClass('modal-top');
expect($document).toHaveModalsOpen(1);
});
it('should have top modal with highest index', function() {
var modal2Index = null;
var modal3Index = null;
var modal1Instance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function(result) {
return $uibModalStack.close(modal1Instance, result);
},
dismiss: function(reason) {
return $uibModalStack.dismiss(modal1Instance, reason);
}
};
var modal2Instance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function(result) {
return $uibModalStack.close(modal2Instance, result);
},
dismiss: function(reason) {
return $uibModalStack.dismiss(modal2Instance, reason);
}
};
var modal3Instance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function(result) {
return $uibModalStack.close(modal13nstance, result);
},
dismiss: function(reason) {
return $uibModalStack.dismiss(modal3Instance, reason);
}
};
var modal1 = $uibModalStack.open(modal1Instance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modal1Instance.result,
renderDeferred: modal1Instance.rendered,
closedDeferred: modal1Instance.closed,
content: '<div>Modal1</div>'
});
$rootScope.$digest();
$animate.flush();
expect($document).toHaveModalsOpen(1);
expect(parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10)).toEqual(0);
var modal2 = $uibModalStack.open(modal2Instance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modal2Instance.result,
renderDeferred: modal2Instance.rendered,
closedDeferred: modal2Instance.closed,
content: '<div>Modal2</div>'
});
modal2Instance.rendered.promise.then(function() {
modal2Index = parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10);
});
$rootScope.$digest();
$animate.flush();
expect($document).toHaveModalsOpen(2);
expect(modal2Index).toEqual(1);
close(modal1Instance);
expect($document).toHaveModalsOpen(1);
var modal3 = $uibModalStack.open(modal3Instance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modal3Instance.result,
renderDeferred: modal3Instance.rendered,
closedDeferred: modal3Instance.closed,
content: '<div>Modal3</div>'
});
modal3Instance.rendered.promise.then(function() {
modal3Index = parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10);
});
$rootScope.$digest();
$animate.flush();
expect($document).toHaveModalsOpen(2);
expect(modal3Index).toEqual(2);
expect(modal2Index).toBeLessThan(modal3Index);
});
it('should have top modal with highest z-index', function() {
var modal2zIndex = null;
var modal3zIndex = null;
var modal1Instance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function(result) {
return $uibModalStack.close(modal1Instance, result);
},
dismiss: function(reason) {
return $uibModalStack.dismiss(modal1Instance, reason);
}
};
var modal2Instance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function(result) {
return $uibModalStack.close(modal2Instance, result);
},
dismiss: function(reason) {
return $uibModalStack.dismiss(modal2Instance, reason);
}
};
var modal3Instance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function(result) {
return $uibModalStack.close(modal3Instance, result);
},
dismiss: function(reason) {
return $uibModalStack.dismiss(modal3Instance, reason);
}
};
var modal1 = $uibModalStack.open(modal1Instance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modal1Instance.result,
renderDeferred: modal1Instance.rendered,
closedDeferred: modal1Instance.closed,
content: '<div>Modal1</div>'
});
$rootScope.$digest();
$animate.flush();
expect($document).toHaveModalsOpen(1);
expect(+$uibModalStack.getTop().value.modalDomEl[0].style.zIndex).toBe(1050);
var modal2 = $uibModalStack.open(modal2Instance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modal2Instance.result,
renderDeferred: modal2Instance.rendered,
closedDeferred: modal2Instance.closed,
content: '<div>Modal2</div>'
});
modal2Instance.rendered.promise.then(function() {
modal2zIndex = +$uibModalStack.getTop().value.modalDomEl[0].style.zIndex;
});
$rootScope.$digest();
$animate.flush();
expect($document).toHaveModalsOpen(2);
expect(modal2zIndex).toBe(1060);
close(modal1Instance);
expect($document).toHaveModalsOpen(1);
var modal3 = $uibModalStack.open(modal3Instance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modal3Instance.result,
renderDeferred: modal3Instance.rendered,
closedDeferred: modal3Instance.closed,
content: '<div>Modal3</div>'
});
modal3Instance.rendered.promise.then(function() {
modal3zIndex = +$uibModalStack.getTop().value.modalDomEl[0].style.zIndex;
});
$rootScope.$digest();
$animate.flush();
expect($document).toHaveModalsOpen(2);
expect(modal3zIndex).toBe(1070);
expect(modal2zIndex).toBeLessThan(modal3zIndex);
});
});
describe('modal.closing event', function() {
+1
View File
@@ -0,0 +1 @@
require('./multiMap.js');
+55
View File
@@ -0,0 +1,55 @@
angular.module('ui.bootstrap.multiMap', [])
/**
* 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];
}
}
};
}
};
});
@@ -1,7 +1,7 @@
describe('multi map', function() {
var multiMap;
beforeEach(module('ui.bootstrap.modal'));
beforeEach(module('ui.bootstrap.multiMap'));
beforeEach(inject(function($$multiMap) {
multiMap = $$multiMap.createNew();
}));
+1 -1
View File
@@ -1,5 +1,5 @@
<div ng-controller="PagerDemoCtrl">
<h4>Pager</h4>
<pre>You are currently on page {{currentPage}}</pre>
<uib-pager total-items="totalItems" ng-model="currentPage"></uib-pager>
<ul uib-pager total-items="totalItems" ng-model="currentPage"></ul>
</div>
+1
View File
@@ -1,4 +1,5 @@
require('../paging');
require('../tabindex');
require('../../template/pager/pager.html.js');
require('./pager');
+3 -2
View File
@@ -1,4 +1,4 @@
angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
$scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
@@ -22,13 +22,14 @@ angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
ngDisabled: '='
},
require: ['uibPager', '?ngModel'],
restrict: 'A',
controller: 'UibPagerController',
controllerAs: 'pager',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/pager/pager.html';
},
replace: true,
link: function(scope, element, attrs, ctrls) {
element.addClass('pager');
var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
if (!ngModelCtrl) {
+12 -12
View File
@@ -10,7 +10,7 @@ describe('pager directive', function() {
$document = _$document_;
$templateCache = _$templateCache_;
body = $document.find('body');
element = $compile('<uib-pager total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
}));
@@ -56,7 +56,7 @@ describe('pager directive', function() {
it('exposes the controller on the template', function() {
$templateCache.put('uib/template/pager/pager.html', '<div>{{pager.text}}</div>');
element = $compile('<uib-pager></uib-pager>')($rootScope);
element = $compile('<ul uib-pager></ul uib-pager>')($rootScope);
$rootScope.$digest();
var ctrl = element.controller('uibPager');
@@ -65,7 +65,7 @@ describe('pager directive', function() {
ctrl.text = 'foo';
$rootScope.$digest();
expect(element.html()).toBe('foo');
expect(element.html()).toBe('<div class="ng-binding">foo</div>');
});
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('<uib-pager total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></ul>')($rootScope);
$rootScope.$digest();
clickPaginationEl(-1);
@@ -147,16 +147,16 @@ describe('pager directive', function() {
it('allows custom templates', function() {
$templateCache.put('foo/bar.html', '<div>baz</div>');
element = $compile('<uib-pager template-url="foo/bar.html"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager template-url="foo/bar.html"></ul>')($rootScope);
$rootScope.$digest();
expect(element.html()).toBe('baz');
expect(element.html()).toBe('<div>baz</div>');
});
describe('`items-per-page`', function() {
beforeEach(function() {
$rootScope.perpage = 5;
element = $compile('<uib-pager total-items="total" items-per-page="perpage" ng-model="currentPage"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager total-items="total" items-per-page="perpage" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -190,7 +190,7 @@ describe('pager directive', function() {
describe('`num-pages`', function() {
beforeEach(function() {
$rootScope.numpg = null;
element = $compile('<uib-pager total-items="total" ng-model="currentPage" num-pages="numpg"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage" num-pages="numpg"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -206,7 +206,7 @@ describe('pager directive', function() {
uibPagerConfig.previousText = 'PR';
uibPagerConfig.nextText = 'NE';
uibPagerConfig.align = false;
element = $compile('<uib-pager total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
}));
afterEach(inject(function(uibPagerConfig) {
@@ -227,7 +227,7 @@ describe('pager directive', function() {
describe('override configuration from attributes', function() {
beforeEach(function() {
element = $compile('<uib-pager align="false" previous-text="<" next-text=">" total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager align="false" previous-text="<" next-text=">" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -248,7 +248,7 @@ describe('pager directive', function() {
it('changes "previous" & "next" text from interpolated attributes', function() {
$rootScope.previousText = '<<';
$rootScope.nextText = '>>';
element = $compile('<uib-pager align="false" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager align="false" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
expect(getPaginationEl(0).text()).toBe('<<');
@@ -259,7 +259,7 @@ describe('pager directive', function() {
it('disables the component when ng-disabled is true', function() {
$rootScope.disable = true;
element = $compile('<uib-pager total-items="total" ng-disabled="disable" ng-model="currentPage"></uib-pager>')($rootScope);
element = $compile('<ul uib-pager total-items="total" ng-disabled="disable" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
updateCurrentPage(2);
+9 -9
View File
@@ -1,24 +1,24 @@
<div ng-controller="PaginationDemoCtrl">
<h4>Default</h4>
<uib-pagination total-items="totalItems" ng-model="currentPage" ng-change="pageChanged()"></uib-pagination>
<uib-pagination boundary-links="true" total-items="totalItems" ng-model="currentPage" class="pagination-sm" previous-text="&lsaquo;" next-text="&rsaquo;" first-text="&laquo;" last-text="&raquo;"></uib-pagination>
<uib-pagination direction-links="false" boundary-links="true" total-items="totalItems" ng-model="currentPage"></uib-pagination>
<uib-pagination direction-links="false" total-items="totalItems" ng-model="currentPage" num-pages="smallnumPages"></uib-pagination>
<ul uib-pagination total-items="totalItems" ng-model="currentPage" ng-change="pageChanged()"></ul>
<ul uib-pagination boundary-links="true" total-items="totalItems" ng-model="currentPage" class="pagination-sm" previous-text="&lsaquo;" next-text="&rsaquo;" first-text="&laquo;" last-text="&raquo;"></ul>
<ul uib-pagination direction-links="false" boundary-links="true" total-items="totalItems" ng-model="currentPage"></ul>
<ul uib-pagination direction-links="false" total-items="totalItems" ng-model="currentPage" num-pages="smallnumPages"></ul>
<pre>The selected page no: {{currentPage}}</pre>
<button type="button" class="btn btn-info" ng-click="setPage(3)">Set current page to: 3</button>
<hr />
<h4>Limit the maximum visible buttons</h4>
<h6><code>rotate</code> defaulted to <code>true</code>:</h6>
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" num-pages="numPages"></uib-pagination>
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" num-pages="numPages"></ul>
<h6><code>rotate</code> defaulted to <code>true</code> and <code>force-ellipses</code> set to <code>true</code>:</h6>
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" force-ellipses="true"></uib-pagination>
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" force-ellipses="true"></ul>
<h6><code>rotate</code> set to <code>false</code>:</h6>
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" rotate="false"></uib-pagination>
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" rotate="false"></ul>
<h6><code>boundary-link-numbers</code> set to <code>true</code> and <code>rotate</code> defaulted to <code>true</code>:</h6>
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true"></uib-pagination>
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true"></ul>
<h6><code>boundary-link-numbers</code> set to <code>true</code> and <code>rotate</code> set to <code>false</code>:</h6>
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true" rotate="false"></uib-pagination>
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true" rotate="false"></ul>
<pre>Page: {{bigCurrentPage}} / {{numPages}}</pre>
</div>
+1
View File
@@ -1,4 +1,5 @@
require('../paging');
require('../tabindex');
require('../../template/pagination/pagination.html.js');
require('./pagination');
+4 -2
View File
@@ -1,4 +1,4 @@
angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
var ctrl = this;
// Setup configuration parameters
@@ -9,6 +9,7 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
$scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
$scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
$attrs.$set('role', 'menu');
uibPaging.create(this, $scope, $attrs);
@@ -132,13 +133,14 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
ngDisabled:'='
},
require: ['uibPagination', '?ngModel'],
restrict: 'A',
controller: 'UibPaginationController',
controllerAs: 'pagination',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/pagination/pagination.html';
},
replace: true,
link: function(scope, element, attrs, ctrls) {
element.addClass('pagination');
var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
if (!ngModelCtrl) {
+36 -27
View File
@@ -11,7 +11,7 @@ describe('pagination directive', function() {
$document = _$document_;
$templateCache = _$templateCache_;
body = $document.find('body');
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
}));
@@ -54,11 +54,20 @@ describe('pagination directive', function() {
expect(element.hasClass('pagination')).toBe(true);
});
it('has accessibility attributes', function() {
expect(element.attr('role')).toEqual('menu');
var li = element.find('li');
for (var i = 0; i < li.length; i++) {
expect(li.eq(i).attr('role')).toEqual('menuitem');
}
});
it('exposes the controller to the template', function() {
$templateCache.put('uib/template/pagination/pagination.html', '<div>{{pagination.randomText}}</div>');
var scope = $rootScope.$new();
element = $compile('<uib-pagination></uib-pagination>')(scope);
element = $compile('<ul uib-pagination></ul>')(scope);
$rootScope.$digest();
var ctrl = element.controller('uibPagination');
@@ -68,17 +77,17 @@ describe('pagination directive', function() {
ctrl.randomText = 'foo';
$rootScope.$digest();
expect(element.html()).toBe('foo');
expect(element.html()).toBe('<div class="ng-binding">foo</div>');
});
it('allows custom templates', function() {
$templateCache.put('foo/bar.html', '<div>baz</div>');
var scope = $rootScope.$new();
element = $compile('<uib-pagination template-url="foo/bar.html"></uib-pagination>')(scope);
element = $compile('<ul uib-pagination template-url="foo/bar.html"></ul>')(scope);
$rootScope.$digest();
expect(element.html()).toBe('baz');
expect(element.html()).toBe('<div>baz</div>');
});
it('contains num-pages + 2 li elements', function() {
@@ -213,7 +222,7 @@ describe('pagination directive', function() {
describe('`items-per-page`', function() {
beforeEach(function() {
$rootScope.perpage = 5;
element = $compile('<uib-pagination total-items="total" items-per-page="perpage" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" items-per-page="perpage" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -256,7 +265,7 @@ describe('pagination directive', function() {
describe('executes `ng-change` expression', function() {
beforeEach(function() {
$rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -286,7 +295,7 @@ describe('pagination directive', function() {
$rootScope.total = 98; // 10 pages
$rootScope.currentPage = 3;
$rootScope.maxSize = 5;
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -367,7 +376,7 @@ describe('pagination directive', function() {
$rootScope.total = 98; // 10 pages
$rootScope.currentPage = 3;
$rootScope.maxSize = 5;
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" force-ellipses="true"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" force-ellipses="true"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -429,7 +438,7 @@ describe('pagination directive', function() {
$rootScope.total = 98; // 10 pages
$rootScope.currentPage = 3;
$rootScope.maxSize = 5;
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" boundary-link-numbers="true"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" boundary-link-numbers="true"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -522,7 +531,7 @@ describe('pagination directive', function() {
$rootScope.currentPage = 7;
$rootScope.maxSize = 5;
$rootScope.rotate = false;
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" rotate="rotate"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" rotate="rotate"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -581,7 +590,7 @@ describe('pagination directive', function() {
describe('pagination directive with `boundary-links`', function() {
beforeEach(function() {
element = $compile('<uib-pagination boundary-links="true" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -636,7 +645,7 @@ describe('pagination directive', function() {
});
it('changes "first" & "last" text from attributes', function() {
element = $compile('<uib-pagination boundary-links="true" first-text="<<<" last-text=">>>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" first-text="<<<" last-text=">>>" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
expect(getPaginationEl(0).text()).toBe('<<<');
@@ -644,7 +653,7 @@ describe('pagination directive', function() {
});
it('changes "previous" & "next" text from attributes', function() {
element = $compile('<uib-pagination boundary-links="true" previous-text="<<" next-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" previous-text="<<" next-text=">>" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
expect(getPaginationEl(1).text()).toBe('<<');
@@ -654,7 +663,7 @@ describe('pagination directive', function() {
it('changes "first" & "last" text from interpolated attributes', function() {
$rootScope.myfirstText = '<<<';
$rootScope.mylastText = '>>>';
element = $compile('<uib-pagination boundary-links="true" first-text="{{myfirstText}}" last-text="{{mylastText}}" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" first-text="{{myfirstText}}" last-text="{{mylastText}}" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
expect(getPaginationEl(0).text()).toBe('<<<');
@@ -664,7 +673,7 @@ describe('pagination directive', function() {
it('changes "previous" & "next" text from interpolated attributes', function() {
$rootScope.previousText = '<<';
$rootScope.nextText = '>>';
element = $compile('<uib-pagination boundary-links="true" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
expect(getPaginationEl(1).text()).toBe('<<');
@@ -700,7 +709,7 @@ describe('pagination directive', function() {
describe('pagination directive with just number links', function() {
beforeEach(function() {
element = $compile('<uib-pagination direction-links="false" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination direction-links="false" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -752,7 +761,7 @@ describe('pagination directive', function() {
describe('with just boundary & number links', function() {
beforeEach(function() {
$rootScope.directions = false;
element = $compile('<uib-pagination boundary-links="true" direction-links="directions" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" direction-links="directions" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -784,7 +793,7 @@ describe('pagination directive', function() {
describe('`num-pages`', function() {
beforeEach(function() {
$rootScope.numpg = null;
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" num-pages="numpg"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" num-pages="numpg"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -827,7 +836,7 @@ describe('pagination directive', function() {
paginationConfig.previousText = 'PR';
paginationConfig.nextText = 'NE';
paginationConfig.lastText = 'LA';
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($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('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($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('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($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('<uib-pagination total-items="total" ng-model="currentPage" max-size="2"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="2"></ul>')($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('<uib-pagination total-items="total" ng-model="currentPage" max-size="3"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="3"></ul>')($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('<uib-pagination boundary-links="true" page-label="pageLabel($page)" first-text="<<" previous-text="<" next-text=">" last-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination boundary-links="true" page-label="pageLabel($page)" first-text="<<" previous-text="<" next-text=">" last-text=">>" total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
});
@@ -904,7 +913,7 @@ describe('pagination directive', function() {
describe('disabled with ngDisable', function() {
beforeEach(function() {
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" ng-disabled="disabled"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" ng-disabled="disabled"></ul>')($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('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
$rootScope.$digest();
expect($rootScope.currentPage).toBe(5);
+4 -2
View File
@@ -34,13 +34,15 @@
<hr />
<h4>Triggers</h4>
<p>
<button uib-popover="I appeared on mouse enter!" popover-trigger="mouseenter" type="button" class="btn btn-default">Mouseenter</button>
<button uib-popover="I appeared on mouse enter!" popover-trigger="'mouseenter'" type="button" class="btn btn-default">Mouseenter</button>
</p>
<input type="text" value="Click me!" uib-popover="I appeared on focus! Click away and I'll vanish..." popover-trigger="focus" class="form-control">
<input type="text" value="Click me!" uib-popover="I appeared on focus! Click away and I'll vanish..." popover-trigger="'focus'" class="form-control">
<hr />
<h4>Other</h4>
<button popover-animation="true" uib-popover="I fade in and out!" type="button" class="btn btn-default">fading</button>
<button uib-popover="I have a title!" popover-title="The title." type="button" class="btn btn-default">title</button>
<button uib-popover="I am activated manually" popover-is-open="popoverIsOpen" ng-click="popoverIsOpen = !popoverIsOpen" type="button" class="btn btn-default">Toggle popover</button>
<button uib-popover-html="htmlPopover" class="btn btn-default">HTML Popover</button>
<button uib-popover-html="'<b>HTML</b>, <i>inline</i>'" class="btn btn-default">HTML Popover (inline)</button>
</div>
+3 -1
View File
@@ -1,4 +1,4 @@
angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope) {
angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope, $sce) {
$scope.dynamicPopover = {
content: 'Hello, World!',
templateUrl: 'myPopoverTemplate.html',
@@ -22,4 +22,6 @@ angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($sco
],
selected: 'top'
};
$scope.htmlPopover = $sce.trustAsHtml('<b style="color: red">I can</b> have <div class="label label-success">HTML</div> content');
});
+18 -9
View File
@@ -7,7 +7,7 @@ module.
__Note to mobile developers__: Please note that while popovers may work correctly on mobile devices (including tablets),
we have made the decision to not officially support such a use-case because it does not make sense from a UX perspective.
There are three versions of the popover: `uib-popover` and `uib-popover-template`, and `uib-tooltip-html`:
There are three versions of the popover: `uib-popover` and `uib-popover-template`, and `uib-popover-html`:
* `uib-popover` -
Takes text only and will escape any HTML provided for the popover body.
@@ -27,13 +27,21 @@ All these settings are available for the three types of popovers.
<small class="badge">C</small>
_(Default: `true`, Config: `animation`)_ -
Should it fade in and out?
* `popover-append-to-body`
<small class="badge">$</small>
<small class="badge">C</small>
_(Default: `false`, Config: `appendToBody`)_ -
Should the popover be appended to '$body' instead of the parent element?
* `popover-class` -
Custom class to be applied to the popover.
* `popover-enable`
<small class="badge">$</small>
_(Default: `true`)_ -
Is it enabled? It will enable or disable the configured popover-trigger.
* `popover-is-open`
<i class="glyphicon glyphicon-eye-open"></i>
_(Default: `false`)_ -
@@ -43,7 +51,7 @@ All these settings are available for the three types of popovers.
<small class="badge">C</small>
_(Default: `top`, Config: `placement`)_ -
Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: "auto bottom-left". The popover will attempt to position where it fits in the closest scrollable ancestor. Accepts:
* `top` - popover on top, horizontally centered on host element.
* `top-left` - popover on top, left edge aligned with host element left edge.
* `top-right` - popover on top, right edge aligned with host element right edge.
@@ -56,7 +64,7 @@ All these settings are available for the three types of popovers.
* `right` - popover on right, vertically centered on host element.
* `right-top` - popover on right, top edge aligned with host element top edge.
* `right-bottom` - popover on right, bottom edge aligned with host element bottom edge.
* `popover-popup-close-delay`
<small class="badge">C</small>
_(Default: `0`, Config: `popupCloseDelay`)_ -
@@ -69,10 +77,11 @@ All these settings are available for the three types of popovers.
* `popover-title` -
A string to display as a fancy title.
* `popover-trigger`
_(Default: `click`)_ -
What should trigger a show of the popover? Supports a space separated list of event names (see below).
<small class="badge">$</small>
_(Default: `'click'`)_ -
What should trigger a show of the popover? Supports a space separated list of event names, or objects (see below).
**Note:** To configure the tooltips, you need to do it on `$uibTooltipProvider` (also see below).
@@ -101,7 +110,7 @@ methods are available:
* `setTriggers(obj)`
_(Example: `{ 'openTrigger': 'closeTrigger' }`)_ -
Extends the default trigger mappings mentioned above with mappings of your own.
* `options(obj)` -
Provide a set of defaults for certain tooltip and popover attributes. Currently supports the ones with the <small class="badge">C</small> badge.
+11
View File
@@ -0,0 +1,11 @@
require('../tooltip/index-nocss.js');
require('../../template/popover/popover.html.js');
require('../../template/popover/popover-html.html.js');
require('../../template/popover/popover-template.html.js');
require('./popover');
var MODULE_NAME = 'ui.bootstrap.module.popover';
angular.module(MODULE_NAME, ['ui.bootstrap.popover', 'uib/template/popover/popover.html', 'uib/template/popover/popover-html.html', 'uib/template/popover/popover-template.html']);
module.exports = MODULE_NAME;
+2 -11
View File
@@ -1,11 +1,2 @@
require('../tooltip');
require('../../template/popover/popover.html.js');
require('../../template/popover/popover-html.html.js');
require('../../template/popover/popover-template.html.js');
require('./popover');
var MODULE_NAME = 'ui.bootstrap.module.popover';
angular.module(MODULE_NAME, ['ui.bootstrap.popover', 'uib/template/popover/popover.html', 'uib/template/popover/popover-html.html', 'uib/template/popover/popover-template.html']);
module.exports = MODULE_NAME;
require('../tooltip/tooltip.css');
module.exports = require('./index-nocss.js');
+6 -7
View File
@@ -7,9 +7,8 @@ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
.directive('uibPopoverTemplatePopup', function() {
return {
replace: true,
scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
originScope: '&' },
restrict: 'A',
scope: { uibTitle: '@', contentExp: '&', originScope: '&' },
templateUrl: 'uib/template/popover/popover-template.html'
};
})
@@ -22,8 +21,8 @@ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
.directive('uibPopoverHtmlPopup', function() {
return {
replace: true,
scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
restrict: 'A',
scope: { contentExp: '&', uibTitle: '@' },
templateUrl: 'uib/template/popover/popover-html.html'
};
})
@@ -36,8 +35,8 @@ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
.directive('uibPopoverPopup', function() {
return {
replace: true,
scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
restrict: 'A',
scope: { uibTitle: '@', content: '@' },
templateUrl: 'uib/template/popover/popover.html'
};
})
+18
View File
@@ -21,6 +21,7 @@ describe('popover', function() {
scope.template = $sce.trustAsHtml('<span>My template</span>');
$compile(elmBody)(scope);
scope.$digest();
$document.find('body').append(elmBody);
elm = elmBody.find('span');
elmScope = elm.scope();
tooltipScope = elmScope.$$childTail;
@@ -87,6 +88,7 @@ describe('popover', function() {
it('should hide popover when template becomes empty', inject(function($timeout) {
elm.trigger('click');
tooltipScope.$digest();
$timeout.flush(0);
expect(tooltipScope.isOpen).toBe(true);
scope.template = '';
@@ -147,6 +149,22 @@ describe('popover', function() {
expect(elmBody.children().eq(1)).not.toHaveClass('fade');
}));
it ('should display the title', inject(function($compile) {
elmBody = angular.element(
'<div><span uib-popover-html="template" popover-title="popover title">Selector Text</span></div>'
);
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elm.trigger('click');
scope.$digest();
var titleEl = elmBody.find('.popover-title');
expect(titleEl.text()).toBe('popover title');
}));
describe('supports options', function() {
describe('placement', function() {
it('can specify an alternative, valid placement', inject(function($compile) {
+19
View File
@@ -25,6 +25,7 @@ describe('popover template', function() {
scope = $rootScope;
$compile(elmBody)(scope);
$document.find('body').append(elmBody);
scope.templateUrl = 'myUrl';
scope.$digest();
@@ -35,6 +36,7 @@ describe('popover template', function() {
afterEach(function() {
$document.off('keypress');
elmBody.remove();
});
it('should open on click', inject(function() {
@@ -75,6 +77,7 @@ describe('popover template', function() {
it('should hide popover when template becomes empty', inject(function($timeout) {
elm.trigger('click');
tooltipScope.$digest();
$timeout.flush(0);
expect(tooltipScope.isOpen).toBe(true);
scope.templateUrl = '';
@@ -86,6 +89,22 @@ describe('popover template', function() {
expect(elmBody.children().length).toBe(1);
}));
it ('should display the title', inject(function($compile) {
elmBody = angular.element(
'<div><span uib-popover-template="templateUrl" popover-title="popover title">Selector Text</span></div>'
);
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elm.trigger('click');
scope.$digest();
var titleEl = elmBody.find('.popover-title');
expect(titleEl.text()).toBe('popover title');
}));
describe('supports options', function() {
describe('placement', function() {
it('can specify an alternative, valid placement', inject(function($compile) {
+30
View File
@@ -103,6 +103,36 @@ describe('popover', function() {
expect(elmBody.children().eq(1)).not.toHaveClass('fade');
}));
it ('should display the title', inject(function($compile) {
elmBody = angular.element(
'<div><span uib-popover="popover text" popover-title="popover title">Trigger here</span></div>'
);
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elm.trigger('click');
scope.$digest();
var titleEl = elmBody.find('.popover-title');
expect(titleEl.text()).toBe('popover title');
}));
it ('should display the content', inject(function($compile) {
elmBody = angular.element(
'<div><span uib-popover="popover text" popover-title="popover title">Trigger here</span></div>'
);
$compile(elmBody)(scope);
scope.$digest();
elm = elmBody.find('span');
elm.trigger('click');
scope.$digest();
var contentEl = elmBody.find('.popover-content');
expect(contentEl.text()).toBe('popover text');
}));
describe('supports options', function() {
describe('placement', function() {
it('can specify an alternative, valid placement', inject(function($compile) {
+50 -2
View File
@@ -45,15 +45,60 @@ Gets the closest positioned ancestor.
* _(Type: `element`)_ -
The closest positioned ancestor.
#### scrollbarWidth()
#### scrollbarWidth(isBody)
Calculates the browser scrollbar width and caches the result for future calls. Concept from the TWBS measureScrollbar() function in [modal.js](https://github.com/twbs/bootstrap/blob/master/js/modal.js).
##### parameters
* `isBody`
_(Type: `boolean`, Default: `false`, optional)_ - Is the requested scrollbar width for the body/html element. IE and Edge overlay the scrollbar on the body/html element and should be considered 0.
##### returns
* _(Type: `number`)_ -
The width of the browser scrollbar.
#### scrollbarPadding(element)
Calculates the padding required to replace the scrollbar on an element.
##### parameters
* 'element' _(Type: `element`)_ - The element to calculate the padding on (should be a scrollable element).
##### returns
An object with the following properties:
* `scrollbarWidth`
_(Type: `number`)_ -
The width of the scrollbar.
* `widthOverflow`
_(Type: `boolean`)_ -
Whether the width is overflowing.
* `right`
_(Type: `number`)_ -
The total right padding required to replace the scrollbar.
* `originalRight`
_(Type: `number`)_ -
The oringal right padding on the element.
* `heightOverflow`
_(Type: `boolean`)_ -
Whether the height is overflowing.
* `bottom`
_(Type: `number`)_ -
The total bottom padding required to replace the scrollbar.
* `originalBottom`
_(Type: `number`)_ -
The oringal bottom padding on the element.
#### isScrollable(element, includeHidden)
Determines if an element is scrollable.
@@ -72,7 +117,7 @@ Determines if an element is scrollable.
* _(Type: `boolean`)_ -
Whether the element is scrollable.
#### scrollParent(element, includeHidden)
#### scrollParent(element, includeHidden, includeSelf)
Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.js](https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js).
@@ -85,6 +130,9 @@ Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.j
* `includeHidden`
_(Type: `boolean`, Default: `false`, optional)_ - Should scroll style of 'hidden' be considered.
* `includeSelf`
_(Type: `boolean`, Default: `false`, optional)_ - Should the element passed in be included in the scrollable lookup.
##### returns
* _(Type: `element`)_ -
+7
View File
@@ -0,0 +1,7 @@
require('./position');
var MODULE_NAME = 'ui.bootstrap.module.position';
angular.module(MODULE_NAME, ['ui.bootstrap.position']);
module.exports = MODULE_NAME;
+1 -8
View File
@@ -1,9 +1,2 @@
require('./position');
require('./position.css');
var MODULE_NAME = 'ui.bootstrap.module.position';
angular.module(MODULE_NAME, ['ui.bootstrap.position']);
module.exports = MODULE_NAME;
module.exports = require('./index-nocss.js');
+9 -5
View File
@@ -7,9 +7,13 @@
}
.uib-position-scrollbar-measure {
position: absolute;
top: -9999px;
width: 50px;
height: 50px;
overflow: scroll;
position: absolute !important;
top: -9999px !important;
width: 50px !important;
height: 50px !important;
overflow: scroll !important;
}
.uib-position-body-scrollbar-measure {
overflow: scroll !important;
}
+87 -9
View File
@@ -12,6 +12,11 @@ angular.module('ui.bootstrap.position', [])
* Do not access this variable directly, use scrollbarWidth() instead.
*/
var SCROLLBAR_WIDTH;
/**
* scrollbar on body and html element in IE and Edge overlay
* content and should be considered 0 width.
*/
var BODY_SCROLLBAR_WIDTH;
var OVERFLOW_REGEX = {
normal: /(auto|scroll)/,
hidden: /(auto|scroll|hidden)/
@@ -22,6 +27,7 @@ angular.module('ui.bootstrap.position', [])
secondary: /^(top|bottom|left|right|center)$/,
vertical: /^(top|bottom)$/
};
var BODY_REGEX = /(HTML|BODY)/;
return {
@@ -75,10 +81,23 @@ angular.module('ui.bootstrap.position', [])
/**
* Provides the scrollbar width, concept from TWBS measureScrollbar()
* function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
* In IE and Edge, scollbar on body and html element overlay and should
* return a width of 0.
*
* @returns {number} The width of the browser scollbar.
*/
scrollbarWidth: function() {
scrollbarWidth: function(isBody) {
if (isBody) {
if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
var bodyElem = $document.find('body');
bodyElem.addClass('uib-position-body-scrollbar-measure');
BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
bodyElem.removeClass('uib-position-body-scrollbar-measure');
}
return BODY_SCROLLBAR_WIDTH;
}
if (angular.isUndefined(SCROLLBAR_WIDTH)) {
var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
$document.find('body').append(scrollElem);
@@ -90,6 +109,40 @@ angular.module('ui.bootstrap.position', [])
return SCROLLBAR_WIDTH;
},
/**
* Provides the padding required on an element to replace the scrollbar.
*
* @returns {object} An object with the following properties:
* <ul>
* <li>**scrollbarWidth**: the width of the scrollbar</li>
* <li>**widthOverflow**: whether the the width is overflowing</li>
* <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
* <li>**rightOriginal**: the amount of right padding currently on the element</li>
* <li>**heightOverflow**: whether the the height is overflowing</li>
* <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
* <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
* </ul>
*/
scrollbarPadding: function(elem) {
elem = this.getRawNode(elem);
var elemStyle = $window.getComputedStyle(elem);
var paddingRight = this.parseStyle(elemStyle.paddingRight);
var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
var scrollParent = this.scrollParent(elem, false, true);
var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName));
return {
scrollbarWidth: scrollbarWidth,
widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
right: paddingRight + scrollbarWidth,
originalRight: paddingRight,
heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
bottom: paddingBottom + scrollbarWidth,
originalBottom: paddingBottom
};
},
/**
* Checks to see if the element is scrollable.
*
@@ -115,15 +168,20 @@ angular.module('ui.bootstrap.position', [])
* @param {element} elem - The element to find the scroll parent of.
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
* default is false.
* @param {boolean=} [includeSelf=false] - Should the element being passed be
* included in the scrollable llokup.
*
* @returns {element} A HTML element.
*/
scrollParent: function(elem, includeHidden) {
scrollParent: function(elem, includeHidden, includeSelf) {
elem = this.getRawNode(elem);
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
var documentEl = $document[0].documentElement;
var elemStyle = $window.getComputedStyle(elem);
if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
return elem;
}
var excludeStatic = elemStyle.position === 'absolute';
var scrollParent = elem.parentElement || documentEl;
@@ -471,13 +529,33 @@ angular.module('ui.bootstrap.position', [])
},
/**
* Provides a way for positioning tooltip & dropdown
* arrows when using placement options beyond the standard
* left, right, top, or bottom.
*
* @param {element} elem - The tooltip/dropdown element.
* @param {string} placement - The placement for the elem.
*/
* Provides a way to adjust the top positioning after first
* render to correctly align element to top after content
* rendering causes resized element height
*
* @param {array} placementClasses - The array of strings of classes
* element should have.
* @param {object} containerPosition - The object with container
* position information
* @param {number} initialHeight - The initial height for the elem.
* @param {number} currentHeight - The current height for the elem.
*/
adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) {
if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {
return {
top: containerPosition.top - currentHeight + 'px'
};
}
},
/**
* Provides a way for positioning tooltip & dropdown
* arrows when using placement options beyond the standard
* left, right, top, or bottom.
*
* @param {element} elem - The tooltip/dropdown element.
* @param {string} placement - The placement for the elem.
*/
positionArrow: function(elem, placement) {
elem = this.getRawNode(elem);
+1 -1
View File
@@ -58,7 +58,7 @@ It supports multiple (stacked) `<uib-bar>` into the same `<uib-progress>` elemen
* `type`
_(Default: `null`)_ -
Bootstrap style type. Possible values are 'success', 'info', 'warning', and, 'error' to use Bootstrap's pre-existing styling, or any desired custom suffix.
Bootstrap style type. Possible values are 'success', 'info', 'warning', and, 'danger' to use Bootstrap's pre-existing styling, or any desired custom suffix.
* `title`
_(Default: `progressbar`)_ -
+3 -3
View File
@@ -1,6 +1,6 @@
<div ng-controller="RatingDemoCtrl">
<h4>Default</h4>
<uib-rating ng-model="rate" max="max" read-only="isReadonly" on-hover="hoveringOver(value)" on-leave="overStar = null" titles="['one','two','three']" aria-labelledby="default-rating"></uib-rating>
<span uib-rating ng-model="rate" max="max" read-only="isReadonly" on-hover="hoveringOver(value)" on-leave="overStar = null" titles="['one','two','three']" aria-labelledby="default-rating"></span>
<span class="label" ng-class="{'label-warning': percent<30, 'label-info': percent>=30 && percent<70, 'label-success': percent>=70}" ng-show="overStar && !isReadonly">{{percent}}%</span>
<pre style="margin:15px 0;">Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i> - Hovering over: <b>{{overStar || "none"}}</b></pre>
@@ -10,6 +10,6 @@
<hr />
<h4>Custom icons</h4>
<div ng-init="x = 5"><uib-rating ng-model="x" max="15" state-on="'glyphicon-ok-sign'" state-off="'glyphicon-ok-circle'" aria-labelledby="custom-icons-1"></uib-rating> <b>(<i>Rate:</i> {{x}})</b></div>
<div ng-init="y = 2"><uib-rating ng-model="y" rating-states="ratingStates" aria-labelledby="custom-icons-2"></uib-rating> <b>(<i>Rate:</i> {{y}})</b></div>
<div ng-init="x = 5"><span uib-rating ng-model="x" max="15" state-on="'glyphicon-ok-sign'" state-off="'glyphicon-ok-circle'" aria-labelledby="custom-icons-1"></span> <b>(<i>Rate:</i> {{x}})</b></div>
<div ng-init="y = 2"><span uib-rating ng-model="y" rating-states="ratingStates" aria-labelledby="custom-icons-2"></span> <b>(<i>Rate:</i> {{y}})</b></div>
</div>
+5
View File
@@ -38,6 +38,11 @@ Rating directive that will take care of visualising a star rating bar.
_(Default: ['one', 'two', 'three', 'four', 'five']`)_ -
An array of strings defining titles for all icons.
* `enable-reset`
<small class="badge">$</small>
_(Default: `true`)_ -
Clicking the icon of the current rating will reset the rating to 0.
* `state-off`
<small class="badge">$</small>
<small class="badge">C</small>
+8 -4
View File
@@ -4,7 +4,8 @@ angular.module('ui.bootstrap.rating', [])
max: 5,
stateOn: null,
stateOff: null,
titles : ['one', 'two', 'three', 'four', 'five']
enableReset: true,
titles: ['one', 'two', 'three', 'four', 'five']
})
.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
@@ -25,7 +26,9 @@ angular.module('ui.bootstrap.rating', [])
this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
this.enableReset = angular.isDefined($attrs.enableReset) ?
$scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
tmpTitles : ratingConfig.titles;
@@ -52,7 +55,8 @@ angular.module('ui.bootstrap.rating', [])
$scope.rate = function(value) {
if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
ngModelCtrl.$setViewValue(newViewValue);
ngModelCtrl.$render();
}
};
@@ -86,6 +90,7 @@ angular.module('ui.bootstrap.rating', [])
.directive('uibRating', function() {
return {
require: ['uibRating', 'ngModel'],
restrict: 'A',
scope: {
readonly: '=?readOnly',
onHover: '&',
@@ -93,7 +98,6 @@ angular.module('ui.bootstrap.rating', [])
},
controller: 'UibRatingController',
templateUrl: 'uib/template/rating/rating.html',
replace: true,
link: function(scope, element, attrs, ctrls) {
var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
ratingCtrl.init(ngModelCtrl);
+68 -30
View File
@@ -1,17 +1,18 @@
describe('rating directive', function() {
var $rootScope, $compile, element;
var $rootScope, $compile, element, innerElem;
beforeEach(module('ui.bootstrap.rating'));
beforeEach(module('uib/template/rating/rating.html'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.rate = 3;
element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
}));
function getStars() {
return element.find('i');
return innerElem.find('i');
}
function getStar(number) {
@@ -38,17 +39,17 @@ describe('rating directive', function() {
function triggerKeyDown(keyCode) {
var e = $.Event('keydown');
e.which = keyCode;
element.trigger(e);
innerElem.trigger(e);
}
it('contains the default number of icons', function() {
expect(getStars().length).toBe(5);
expect(element.attr('aria-valuemax')).toBe('5');
expect(innerElem.attr('aria-valuemax')).toBe('5');
});
it('initializes the default star icons as selected', function() {
expect(getState()).toEqual([true, true, true, false, false]);
expect(element.attr('aria-valuenow')).toBe('3');
expect(innerElem.attr('aria-valuenow')).toBe('3');
});
it('handles correctly the click event', function() {
@@ -56,19 +57,19 @@ describe('rating directive', function() {
$rootScope.$digest();
expect(getState()).toEqual([true, true, false, false, false]);
expect($rootScope.rate).toBe(2);
expect(element.attr('aria-valuenow')).toBe('2');
expect(innerElem.attr('aria-valuenow')).toBe('2');
getStar(5).click();
$rootScope.$digest();
expect(getState()).toEqual([true, true, true, true, true]);
expect($rootScope.rate).toBe(5);
expect(element.attr('aria-valuenow')).toBe('5');
expect(innerElem.attr('aria-valuenow')).toBe('5');
getStar(5).click();
$rootScope.$digest();
expect(getState()).toEqual([false, false, false, false, false]);
expect($rootScope.rate).toBe(0);
expect(element.attr('aria-valuenow')).toBe('0');
expect(innerElem.attr('aria-valuenow')).toBe('0');
});
it('handles correctly the hover event', function() {
@@ -82,7 +83,7 @@ describe('rating directive', function() {
expect(getState()).toEqual([true, true, true, true, true]);
expect($rootScope.rate).toBe(3);
element.trigger('mouseout');
innerElem.trigger('mouseout');
expect(getState()).toEqual([true, true, true, false, false]);
expect($rootScope.rate).toBe(3);
});
@@ -92,13 +93,13 @@ describe('rating directive', function() {
$rootScope.$digest();
expect(getState()).toEqual([true, true, false, false, false]);
expect(element.attr('aria-valuenow')).toBe('2');
expect(innerElem.attr('aria-valuenow')).toBe('2');
$rootScope.rate = 2.5;
$rootScope.$digest();
expect(getState()).toEqual([true, true, true, false, false]);
expect(element.attr('aria-valuenow')).toBe('3');
expect(innerElem.attr('aria-valuenow')).toBe('3');
});
it('changes the number of selected icons when value changes', function() {
@@ -106,30 +107,33 @@ describe('rating directive', function() {
$rootScope.$digest();
expect(getState()).toEqual([true, true, false, false, false]);
expect(element.attr('aria-valuenow')).toBe('2');
expect(element.attr('aria-valuetext')).toBe('two');
expect(innerElem.attr('aria-valuenow')).toBe('2');
expect(innerElem.attr('aria-valuetext')).toBe('two');
});
it('shows different number of icons when `max` attribute is set', function() {
element = $compile('<uib-rating ng-model="rate" max="7"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" max="7"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
expect(getStars().length).toBe(7);
expect(element.attr('aria-valuemax')).toBe('7');
expect(innerElem.attr('aria-valuemax')).toBe('7');
});
it('shows different number of icons when `max` attribute is from scope variable', function() {
$rootScope.max = 15;
element = $compile('<uib-rating ng-model="rate" max="max"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" max="max"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
expect(getStars().length).toBe(15);
expect(element.attr('aria-valuemax')).toBe('15');
expect(innerElem.attr('aria-valuemax')).toBe('15');
});
it('handles read-only attribute', function() {
$rootScope.isReadonly = true;
element = $compile('<uib-rating ng-model="rate" read-only="isReadonly"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" read-only="isReadonly"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
expect(getState()).toEqual([true, true, true, false, false]);
@@ -146,10 +150,37 @@ describe('rating directive', function() {
expect(getState()).toEqual([true, true, true, true, true]);
});
it('handles enable-reset attribute', function() {
$rootScope.canReset = false;
element = $compile('<span uib-rating ng-model="rate" enable-reset="canReset"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
var star = {
states: [true, true, true, true, true],
rating: 5
};
var selectStar = getStar(star.rating);
selectStar.click();
$rootScope.$digest();
expect(getState()).toEqual(star.states);
expect($rootScope.rate).toBe(5);
expect(innerElem.attr('aria-valuenow')).toBe('5');
selectStar.click();
$rootScope.$digest();
expect(getState()).toEqual(star.states);
expect($rootScope.rate).toBe(5);
expect(innerElem.attr('aria-valuenow')).toBe('5');
});
it('should fire onHover', function() {
$rootScope.hoveringOver = jasmine.createSpy('hoveringOver');
element = $compile('<uib-rating ng-model="rate" on-hover="hoveringOver(value)"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" on-hover="hoveringOver(value)"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
getStar(3).trigger('mouseover');
$rootScope.$digest();
@@ -158,10 +189,11 @@ describe('rating directive', function() {
it('should fire onLeave', function() {
$rootScope.leaving = jasmine.createSpy('leaving');
element = $compile('<uib-rating ng-model="rate" on-leave="leaving()"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" on-leave="leaving()"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
element.trigger('mouseleave');
innerElem.trigger('mouseleave');
$rootScope.$digest();
expect($rootScope.leaving).toHaveBeenCalled();
});
@@ -218,8 +250,9 @@ describe('rating directive', function() {
beforeEach(inject(function() {
$rootScope.classOn = 'icon-ok-sign';
$rootScope.classOff = 'icon-ok-circle';
element = $compile('<uib-rating ng-model="rate" state-on="classOn" state-off="classOff"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" state-on="classOn" state-off="classOff"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
}));
it('changes the default icons', function() {
@@ -235,13 +268,14 @@ describe('rating directive', function() {
{stateOn: 'heart'},
{stateOff: 'off'}
];
element = $compile('<uib-rating ng-model="rate" rating-states="states"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" rating-states="states"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
}));
it('should define number of icon elements', function() {
expect(getStars().length).toBe(4);
expect(element.attr('aria-valuemax')).toBe('4');
expect(innerElem.attr('aria-valuemax')).toBe('4');
});
it('handles each icon', function() {
@@ -266,8 +300,9 @@ describe('rating directive', function() {
uibRatingConfig.max = 10;
uibRatingConfig.stateOn = 'on';
uibRatingConfig.stateOff = 'off';
element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
}));
afterEach(inject(function(uibRatingConfig) {
// return it to the original state
@@ -295,8 +330,9 @@ describe('rating directive', function() {
$rootScope.rate = 5;
angular.extend(originalConfig, uibRatingConfig);
uibRatingConfig.max = 10;
element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
}));
afterEach(inject(function(uibRatingConfig) {
// return it to the original state
@@ -311,18 +347,20 @@ describe('rating directive', function() {
describe('shows custom titles ', function() {
it('should return the custom title for each star', function() {
$rootScope.titles = [44,45,46];
element = $compile('<uib-rating ng-model="rate" titles="titles"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" titles="titles"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
expect(getTitles()).toEqual(['44', '45', '46', '4', '5']);
});
it('should return the default title if the custom title is empty', function() {
$rootScope.titles = [];
element = $compile('<uib-rating ng-model="rate" titles="titles"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" titles="titles"></span>')($rootScope);
$rootScope.$digest();
innerElem = element.children().eq(0);
expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
});
it('should return the default title if the custom title is not an array', function() {
element = $compile('<uib-rating ng-model="rate" titles="test"></uib-rating>')($rootScope);
element = $compile('<span uib-rating ng-model="rate" titles="test"></span>')($rootScope);
$rootScope.$digest();
expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
});
+1 -1
View File
@@ -43,7 +43,7 @@ angular.module('ui.bootstrap.stackedMap', [])
return stack.splice(idx, 1)[0];
},
removeTop: function() {
return stack.splice(stack.length - 1, 1)[0];
return stack.pop();
},
length: function() {
return stack.length;
+7
View File
@@ -0,0 +1,7 @@
require('./tabindex');
var MODULE_NAME = 'ui.bootstrap.module.tabindex';
angular.module(MODULE_NAME, ['ui.bootstrap.tabindex']);
module.exports = MODULE_NAME;

Some files were not shown because too many files have changed in this diff Show More