Compare commits

...

20 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
26 changed files with 509 additions and 172 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:
+29
View File
@@ -1,3 +1,32 @@
<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)
+1 -1
View File
@@ -9,7 +9,7 @@ module.exports = function(grunt) {
grunt.util.linefeed = '\n';
grunt.initConfig({
ngversion: '1.5.8',
ngversion: '1.6.1',
bsversion: '3.3.7',
modules: [],//to be filled in by build task
pkg: grunt.file.readJSON('package.json'),
+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
+1
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)
+11 -4
View File
@@ -1,8 +1,15 @@
{
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
"name": "angular-ui-bootstrap",
"version": "2.3.2",
"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/"
@@ -23,9 +30,9 @@
"url": "https://github.com/angular-ui/bootstrap.git"
},
"devDependencies": {
"angular": "1.5.8",
"angular-mocks": "1.5.8",
"angular-sanitize": "1.5.8",
"angular": "1.6.1",
"angular-mocks": "1.6.1",
"angular-sanitize": "1.6.1",
"grunt": "^0.4.5",
"grunt-cli": "^1.2.0",
"grunt-contrib-concat": "^1.0.0",
+11 -13
View File
@@ -1,18 +1,16 @@
<div ng-controller="AccordionDemoCtrl">
<script type="text/ng-template" id="group-template.html">
<div class="panel 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 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>
<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>
+2 -2
View File
@@ -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() {
@@ -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);
+1 -1
View File
@@ -36,7 +36,7 @@ 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() {
+1 -24
View File
@@ -5,7 +5,7 @@ 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');
@@ -67,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) {
@@ -95,7 +90,6 @@ angular.module('ui.bootstrap.carousel', [])
if (slides.length === 0) {
currentIndex = null;
$scope.active = null;
clearBufferedTransitions();
}
};
@@ -110,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]);
}
};
@@ -180,12 +172,6 @@ angular.module('ui.bootstrap.carousel', [])
}
});
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) {
@@ -221,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);
}
}
});
}
@@ -259,7 +237,6 @@ angular.module('ui.bootstrap.carousel', [])
function resetTransition(slides) {
if (!slides.length) {
$scope.$currentTransition = null;
clearBufferedTransitions();
}
}
+2 -2
View File
@@ -72,7 +72,7 @@ angular.module('ui.bootstrap.collapse', [])
to: getScrollFromElement(element[0])
}).then(expandDone);
}
});
}, angular.noop);
}
function expandDone() {
@@ -111,7 +111,7 @@ angular.module('ui.bootstrap.collapse', [])
to: cssTo
}).then(collapseDone);
}
});
}, angular.noop);
}
function collapseDone() {
+1 -1
View File
@@ -34,4 +34,4 @@
### Known Issues
When using the `horizontal` attribute with this directive, keep in mind that due to how CSS can reflow as the collapse element goes from 0px to its desired end width, this cause result in issues with a changing height. 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.
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.
+18 -1
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;
@@ -243,6 +243,23 @@ angular.module('ui.bootstrap.dateparser', [])
this.init();
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('');
+24
View File
@@ -791,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);
});
});
});
+45 -15
View File
@@ -104,7 +104,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
$scope.$watch('datepickerOptions.' + key, function(value) {
if (value) {
if (angular.isDate(value)) {
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.getOption('timezone'));
} else {
if ($datepickerLiteralWarning) {
$log.warn('Literal date support has been deprecated, please switch to date object usage');
@@ -114,7 +114,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
}
} else {
self[key] = datepickerConfig[key] ?
dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.getOption('timezone')) :
null;
}
@@ -161,14 +161,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
this.init = function(ngModelCtrl_) {
ngModelCtrl = ngModelCtrl_;
ngModelOptions = ngModelCtrl_.$options ||
$scope.datepickerOptions.ngModelOptions ||
datepickerConfig.ngModelOptions;
ngModelOptions = extractOptions(ngModelCtrl);
if ($scope.datepickerOptions.initDate) {
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date();
$scope.$watch('datepickerOptions.initDate', function(initDate) {
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone'));
self.refreshView();
}
});
@@ -178,8 +177,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
this.activeDate = !isNaN(date) ?
dateParser.fromTimezone(date, ngModelOptions.timezone) :
dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) :
dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
ngModelCtrl.$render = function() {
self.render();
@@ -192,7 +191,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
isValid = !isNaN(date);
if (isValid) {
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
} else if (!$datepickerSuppressError) {
$log.error('Datepicker directive: "ng-model" value must be a Date object');
}
@@ -209,7 +208,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
}
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
date = dateParser.fromTimezone(date, ngModelOptions.timezone);
date = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
ngModelCtrl.$setValidity('dateDisabled', !date ||
this.element && !this.isDisabled(date));
}
@@ -217,9 +216,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
this.createDateObject = function(date, format) {
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
model = dateParser.fromTimezone(model, ngModelOptions.timezone);
model = dateParser.fromTimezone(model, ngModelOptions.getOption('timezone'));
var today = new Date();
today = dateParser.fromTimezone(today, ngModelOptions.timezone);
today = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
var time = this.compare(date, today);
var dt = {
date: date,
@@ -265,9 +264,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
$scope.select = function(date) {
if ($scope.datepickerMode === self.minMode) {
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.getOption('timezone')) : new Date(0, 0, 0, 0, 0, 0, 0);
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
dt = dateParser.toTimezone(dt, ngModelOptions.getOption('timezone'));
ngModelCtrl.$setViewValue(dt);
ngModelCtrl.$render();
} else {
@@ -352,6 +351,37 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
$scope.datepickerMode = mode;
$scope.datepickerOptions.datepickerMode = mode;
}
function extractOptions(ngModelCtrl) {
var ngModelOptions;
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
// guarantee a value
ngModelOptions = ngModelCtrl.$options ||
$scope.datepickerOptions.ngModelOptions ||
datepickerConfig.ngModelOptions ||
{};
// mimic 1.6+ api
ngModelOptions.getOption = function (key) {
return ngModelOptions[key];
};
} else { // in angular >=1.6 $options is always present
// ng-model-options defaults timezone to null; don't let its precedence squash a non-null value
var timezone = ngModelCtrl.$options.getOption('timezone') ||
($scope.datepickerOptions.ngModelOptions ? $scope.datepickerOptions.ngModelOptions.timezone : null) ||
(datepickerConfig.ngModelOptions ? datepickerConfig.ngModelOptions.timezone : null);
// values passed to createChild override existing values
ngModelOptions = ngModelCtrl.$options // start with a ModelOptions instance
.createChild(datepickerConfig.ngModelOptions) // lowest precedence
.createChild($scope.datepickerOptions.ngModelOptions)
.createChild(ngModelCtrl.$options) // highest precedence
.createChild({timezone: timezone}); // to keep from squashing a non-null value
}
return ngModelOptions;
}
}])
.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
+29 -11
View File
@@ -32,11 +32,7 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
this.init = function(_ngModel_) {
ngModel = _ngModel_;
ngModelOptions = angular.isObject(_ngModel_.$options) ?
_ngModel_.$options :
{
timezone: null
};
ngModelOptions = extractOptions(ngModel);
closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
$scope.$parent.$eval($attrs.closeOnDateSelection) :
datepickerPopupConfig.closeOnDateSelection;
@@ -127,13 +123,13 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
value = new Date(value);
}
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
$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.timezone);
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
return value;
});
}
@@ -185,7 +181,7 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
$scope.isDisabled = function(date) {
if (date === 'today') {
date = dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
}
var dates = {};
@@ -242,7 +238,7 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
date = new Date($scope.date);
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
} else {
date = dateParser.fromTimezone(today, ngModelOptions.timezone);
date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
date.setHours(0, 0, 0, 0);
}
}
@@ -333,11 +329,11 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
if (angular.isString(viewValue)) {
var date = parseDateString(viewValue);
if (!isNaN(date)) {
return dateParser.toTimezone(date, ngModelOptions.timezone);
return dateParser.toTimezone(date, ngModelOptions.getOption('timezone'));
}
}
return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined;
}
function validator(modelValue, viewValue) {
@@ -412,6 +408,28 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
}
}
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);
});
+1 -1
View File
@@ -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>
+36 -22
View File
@@ -88,7 +88,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
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; }
@@ -144,8 +144,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
getIsOpen,
setIsOpen = angular.noop,
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
appendToBody = false,
appendTo = null,
keynavEnabled = false,
selectedOption = null,
body = $document.find('body');
@@ -162,26 +160,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
});
}
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) {
@@ -253,7 +232,42 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
}
};
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,
+233 -49
View File
@@ -215,43 +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();
$document.find('body').append(element);
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]);
});
});
});
});
afterEach(function() {
element.remove();
});
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('adds the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
});
element = dropdown();
$document.find('body').append(element);
});
it('focuses the dropdown element on close', function() {
var toggle = element.find('[uib-dropdown-toggle]');
var menu = $document.find('#dropdown-menu a');
toggle.trigger('click');
menu.focus();
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]);
});
menu.trigger('click');
describe('when toggled open', function() {
var toggle;
beforeEach(function() {
toggle = element.find('[uib-dropdown-toggle]');
toggle.trigger('click');
});
it('adds the menu to the body', function() {
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
});
expect(document.activeElement).toBe(toggle[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]);
});
});
it('removes the menu when the dropdown is removed', function() {
element.remove();
$rootScope.$digest();
expect($document.find('#dropdown-menu').length).toEqual(0);
describe('when closed by clicking on menu', function() {
var menu;
beforeEach(function() {
menu = $document.find('#dropdown-menu a');
menu.focus();
menu.trigger('click');
});
it('focuses the dropdown element on close', function() {
expect(document.activeElement).toBe(toggle[0]);
});
it('removes the menu from 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);
@@ -260,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);
@@ -271,35 +426,64 @@ 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('focuses the dropdown element on close', function() {
var toggle = element.find('[uib-dropdown-toggle]');
var menu = $document.find('#dropdown-menu a');
toggle.trigger('click');
menu.focus();
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');
});
menu.trigger('click');
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');
});
});
expect(document.activeElement).toBe(toggle[0]);
});
it('removes the menu when the dropdown is removed', function() {
element.remove();
$rootScope.$digest();
expect($document.find('#dropdown-menu').length).toEqual(0);
describe('when closed by clicking on menu', function() {
var menu;
beforeEach(function() {
menu = $document.find('#dropdown-menu a');
menu.focus();
menu.trigger('click');
});
it('focuses the dropdown element on close', function() {
expect(document.activeElement).toBe(toggle[0]);
});
it('removes the menu from the container', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
it('removes open class from container', function() {
expect(container).not.toHaveClass('uib-dropdown-open');
});
});
describe('when the dropdown is removed', function() {
beforeEach(function() {
element.remove();
$rootScope.$digest();
});
it('removes the menu from the container', function() {
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
});
});
});
});
+4 -4
View File
@@ -419,10 +419,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.sta
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;
@@ -699,6 +695,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.sta
modalOptions.resolve = modalOptions.resolve || {};
modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
if (!modalOptions.appendTo.length) {
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
}
//verify options
if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
throw new Error('One of component or template or templateUrl options is required.');
+8 -3
View File
@@ -296,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();
@@ -1612,7 +1614,7 @@ describe('$uibModal', function() {
var windowEl = $compile('<div uib-modal-window template-url="window.html">content</div>')($rootScope);
$rootScope.$digest();
expect(windowEl.html()).toBe('<div ng-transclude=""><span class="ng-scope">content</span></div>');
expect(windowEl.html()).toBe('<div ng-transclude="">content</div>');
}));
});
@@ -1736,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; }
});
});
+1
View File
@@ -9,6 +9,7 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.
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);
+9
View File
@@ -54,6 +54,15 @@ 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();
+9 -6
View File
@@ -496,6 +496,13 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
}
}
// KeyboardEvent handler to hide the tooltip on Escape key press
function hideOnEscapeKey(e) {
if (e.which === 27) {
hideTooltipBind();
}
}
var unregisterTriggers = function() {
triggers.show.forEach(function(trigger) {
if (trigger === 'outsideClick') {
@@ -504,6 +511,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
element.off(trigger, showTooltipBind);
element.off(trigger, toggleTooltipBind);
}
element.off('keypress', hideOnEscapeKey);
});
triggers.hide.forEach(function(trigger) {
if (trigger === 'outsideClick') {
@@ -543,12 +551,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
element.on(trigger, showTooltipBind);
element.on(triggers.hide[idx], hideTooltipBind);
}
element.on('keypress', function(e) {
if (e.which === 27) {
hideTooltipBind();
}
});
element.on('keypress', hideOnEscapeKey);
});
}
}
+24 -6
View File
@@ -94,7 +94,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap
var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
var $setModelValue = function(scope, newValue) {
if (angular.isFunction(parsedModel(originalScope)) &&
ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
ngModelOptions.getOption('getterSetter')) {
return invokeModelSetter(scope, {$$$p: newValue});
}
@@ -507,11 +507,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap
element.after($popup);
}
this.init = function(_modelCtrl, _ngModelOptions) {
this.init = function(_modelCtrl) {
modelCtrl = _modelCtrl;
ngModelOptions = _ngModelOptions;
ngModelOptions = extractOptions(modelCtrl);
scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
scope.debounceUpdate = $parse(ngModelOptions.getOption('debounce'))(originalScope);
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
@@ -571,14 +571,32 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap
return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
});
};
function extractOptions(ngModelCtrl) {
var ngModelOptions;
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
// guarantee a value
ngModelOptions = ngModelCtrl.$options || {};
// 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;
}
}])
.directive('uibTypeahead', function() {
return {
controller: 'UibTypeaheadController',
require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
require: ['ngModel', 'uibTypeahead'],
link: function(originalScope, element, attrs, ctrls) {
ctrls[2].init(ctrls[0], ctrls[1]);
ctrls[1].init(ctrls[0]);
}
};
})
+5 -5
View File
@@ -1,5 +1,5 @@
<li ng-if="::boundaryLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-first"><a href ng-click="selectPage(1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText('first')}}</a></li>
<li ng-if="::directionLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-prev"><a href ng-click="selectPage(page - 1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText('previous')}}</a></li>
<li ng-repeat="page in pages track by $index" ng-class="{active: page.active,disabled: ngDisabled&&!page.active}" class="pagination-page"><a href ng-click="selectPage(page.number, $event)" ng-disabled="ngDisabled&&!page.active" uib-tabindex-toggle>{{page.text}}</a></li>
<li ng-if="::directionLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-next"><a href ng-click="selectPage(page + 1, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText('next')}}</a></li>
<li ng-if="::boundaryLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-last"><a href ng-click="selectPage(totalPages, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText('last')}}</a></li>
<li role="menuitem" ng-if="::boundaryLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-first"><a href ng-click="selectPage(1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText('first')}}</a></li>
<li role="menuitem" ng-if="::directionLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-prev"><a href ng-click="selectPage(page - 1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText('previous')}}</a></li>
<li role="menuitem" ng-repeat="page in pages track by $index" ng-class="{active: page.active,disabled: ngDisabled&&!page.active}" class="pagination-page"><a href ng-click="selectPage(page.number, $event)" ng-disabled="ngDisabled&&!page.active" uib-tabindex-toggle>{{page.text}}</a></li>
<li role="menuitem" ng-if="::directionLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-next"><a href ng-click="selectPage(page + 1, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText('next')}}</a></li>
<li role="menuitem" ng-if="::boundaryLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-last"><a href ng-click="selectPage(totalPages, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText('last')}}</a></li>