diff --git a/dist/select.css b/dist/select.css index 0e713bb1b..c13049672 100644 --- a/dist/select.css +++ b/dist/select.css @@ -1,7 +1,7 @@ /*! * ui-select * http://github.com/angular-ui/ui-select - * Version: 0.19.7 - 2017-04-15T14:28:36.790Z + * Version: 0.19.8 - 2017-10-31T13:51:49.662Z * License: MIT */ @@ -21,11 +21,10 @@ overflow: hidden !important; position: absolute !important; outline: 0 !important; - left: 0px !important; - top: 0px !important; + left: 0 !important; + top: 0 !important; } - .ui-select-choices-row:hover { background-color: #f5f5f5; } @@ -34,68 +33,84 @@ /* Mark invalid Select2 */ .ng-dirty.ng-invalid > a.select2-choice { - border-color: #D44950; + border-color: #D44950; } .select2-result-single { padding-left: 0; } -.select2-locked > .select2-search-choice-close{ - display:none; +.select2-locked > .select2-search-choice-close { + display: none; } -.select-locked > .ui-select-match-close{ - display:none; +.select-locked > .ui-select-match-close { + display: none; } body > .select2-container.open { z-index: 9999; /* The z-index Select2 applies to the select2-drop */ } +.select2 .ui-select-footer, +.select2 .ui-select-header { + padding: 4px 10px; +} + +.select2 .ui-select-footer { + border-top: 1px solid #dfe5eb; +} + +.select2 .ui-select-header { + border-bottom: 1px solid #dfe5eb; +} + /* Handle up direction Select2 */ .ui-select-container[theme="select2"].direction-up .ui-select-match, .ui-select-container.select2.direction-up .ui-select-match { - border-radius: 4px; /* FIXME hardcoded value :-/ */ - border-top-left-radius: 0; - border-top-right-radius: 0; + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-top-left-radius: 0; + border-top-right-radius: 0; } + .ui-select-container[theme="select2"].direction-up .ui-select-dropdown, .ui-select-container.select2.direction-up .ui-select-dropdown { - border-radius: 4px; /* FIXME hardcoded value :-/ */ - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; - border-top-width: 1px; /* FIXME hardcoded value :-/ */ - border-top-style: solid; + border-top-width: 1px; /* FIXME hardcoded value :-/ */ + border-top-style: solid; - box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); - margin-top: -4px; /* FIXME hardcoded value :-/ */ + margin-top: -4px; /* FIXME hardcoded value :-/ */ } + .ui-select-container[theme="select2"].direction-up .ui-select-dropdown .select2-search, .ui-select-container.select2.direction-up .ui-select-dropdown .select2-search { - margin-top: 4px; /* FIXME hardcoded value :-/ */ + margin-top: 4px; /* FIXME hardcoded value :-/ */ } + .ui-select-container[theme="select2"].direction-up.select2-dropdown-open .ui-select-match, .ui-select-container.select2.direction-up.select2-dropdown-open .ui-select-match { - border-bottom-color: #5897fb; + border-bottom-color: #5897fb; } .ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden, -.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden input{ - opacity: 0; - height: 0; - min-height: 0; - padding: 0; - margin: 0; - border:0; +.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden input { + opacity: 0; + height: 0; + min-height: 0; + padding: 0; + margin: 0; + border: 0; } /* Selectize theme */ /* Helper class to show styles when focus */ -.selectize-input.selectize-focus{ +.selectize-input.selectize-focus { border-color: #007FBB !important; } @@ -114,25 +129,47 @@ body > .select2-container.open { width: 100%; } +.selectize-dropdown .ui-select-footer, +.selectize-dropdown .ui-select-header { + padding: 5px 8px; +} + +.selectize-dropdown .ui-select-header { + border-bottom: 1px solid #b8b8b8; +} + +.selectize-dropdown .ui-select-footer { + border-top: 1px solid #b8b8b8; +} + /* Mark invalid Selectize */ .ng-dirty.ng-invalid > div.selectize-input { - border-color: #D44950; + border-color: #D44950; } /* Handle up direction Selectize */ .ui-select-container[theme="selectize"].direction-up .ui-select-dropdown { - box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); - margin-top: -2px; /* FIXME hardcoded value :-/ */ + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); + margin-top: -2px; /* FIXME hardcoded value :-/ */ } -.ui-select-container[theme="selectize"] input.ui-select-search-hidden{ - opacity: 0; - height: 0; - min-height: 0; - padding: 0; - margin: 0; - border:0; - width: 0; +.ui-select-container[theme="selectize"] input.ui-select-search-hidden { + opacity: 0; + height: 0; + min-height: 0; + padding: 0; + margin: 0; + border: 0; + width: 0; +} + +.ui-select-container[theme="selectize"] .ui-select-header-group-selectable:hover { + background-color: #f5f5f5; +} + +.ui-select-container[theme="selectize"] .ui-select-header-group-selectable { + cursor: pointer; + padding-left: 15px; } /* Bootstrap theme */ @@ -171,22 +208,23 @@ body > .select2-container.open { border-top-right-radius: 0; border-bottom-right-radius: 0; } + .input-group > .ui-select-bootstrap > input.ui-select-search.form-control.direction-up { border-radius: 4px !important; /* FIXME hardcoded value :-/ */ border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } -.ui-select-bootstrap .ui-select-search-hidden{ - opacity: 0; - height: 0; - min-height: 0; - padding: 0; - margin: 0; - border:0; +.ui-select-bootstrap .ui-select-search-hidden { + opacity: 0; + height: 0; + min-height: 0; + padding: 0; + margin: 0; + border: 0; } -.ui-select-bootstrap > .ui-select-match > .btn{ +.ui-select-bootstrap > .ui-select-match > .btn { /* Instead of center because of .btn */ text-align: left !important; } @@ -198,7 +236,8 @@ body > .select2-container.open { } /* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */ -.ui-select-bootstrap > .ui-select-choices ,.ui-select-bootstrap > .ui-select-no-choice { +.ui-select-bootstrap .ui-select-choices, +.ui-select-bootstrap .ui-select-no-choice { width: 100%; height: auto; max-height: 200px; @@ -261,6 +300,23 @@ body > .ui-select-bootstrap.open { border-right: 1px solid #428bca; } + +.ui-select-bootstrap .ui-select-choices { + padding: 5px 0; + margin: 0; + list-style: none; +} + +.ui-select-bootstrap .ui-select-header-group-selectable:hover { + background-color: #f5f5f5; +} + +.ui-select-bootstrap .ui-select-header-group-selectable { + color: black; + cursor: pointer; + padding: 3px 10px; +} + .ui-select-bootstrap .ui-select-choices-row>span { cursor: pointer; display: block; @@ -272,51 +328,87 @@ body > .ui-select-bootstrap.open { white-space: nowrap; } -.ui-select-bootstrap .ui-select-choices-row>span:hover, .ui-select-bootstrap .ui-select-choices-row>span:focus { - text-decoration: none; - color: #262626; - background-color: #f5f5f5; +.ui-select-bootstrap .ui-select-choices-row > span { + cursor: pointer; + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} + +.ui-select-bootstrap .ui-select-choices-row > span:hover, .ui-select-bootstrap .ui-select-choices-row > span:focus { + text-decoration: none; + color: #262626; + background-color: #f5f5f5; +} + +.ui-select-bootstrap .ui-select-choices-row.active > span { + color: #fff; + text-decoration: none; + outline: 0; + background-color: #428bca; +} + +.ui-select-bootstrap .ui-select-choices-row.disabled > span, +.ui-select-bootstrap .ui-select-choices-row.active.disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} + +.ui-select-bootstrap .ui-select-footer, +.ui-select-bootstrap .ui-select-header { + display: block; + padding: 5px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; +} + +.ui-select-bootstrap .ui-select-footer { + border-top: 1px solid #dfe5eb; } -.ui-select-bootstrap .ui-select-choices-row.active>span { - color: #fff; - text-decoration: none; - outline: 0; - background-color: #428bca; +.ui-select-bootstrap .ui-select-header { + border-bottom: 1px solid #dfe5eb; } -.ui-select-bootstrap .ui-select-choices-row.disabled>span, -.ui-select-bootstrap .ui-select-choices-row.active.disabled>span { - color: #777; - cursor: not-allowed; - background-color: #fff; +.ui-select-bootstrap .ui-select-dropdown { + width: 100%; + padding: 0; + margin-top: -1px; } /* fix hide/show angular animation */ .ui-select-match.ng-hide-add, .ui-select-search.ng-hide-add { - display: none !important; + display: none !important; } /* Mark invalid Bootstrap */ .ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match { - border-color: #D44950; + border-color: #D44950; } /* Handle up direction Bootstrap */ .ui-select-container[theme="bootstrap"].direction-up .ui-select-dropdown { - box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); } .ui-select-bootstrap .ui-select-match-text { - width: 100%; - padding-right: 1em; + width: 100%; + padding-right: 1em; } + .ui-select-bootstrap .ui-select-match-text span { - display: inline-block; - width: 100%; - overflow: hidden; + display: inline-block; + width: 100%; + overflow: hidden; } + .ui-select-bootstrap .ui-select-toggle > a.btn { position: absolute; height: 10px; @@ -326,10 +418,10 @@ body > .ui-select-bootstrap.open { /* Spinner */ .ui-select-refreshing.glyphicon { - position: absolute; - right: 0; - padding: 8px 27px; - } + position: absolute; + right: 0; + padding: 8px 27px; +} @-webkit-keyframes ui-select-spin { 0% { @@ -341,6 +433,7 @@ body > .ui-select-bootstrap.open { transform: rotate(359deg); } } + @keyframes ui-select-spin { 0% { -webkit-transform: rotate(0deg); diff --git a/dist/select.js b/dist/select.js index 5ac4e0b70..ddf017487 100644 --- a/dist/select.js +++ b/dist/select.js @@ -1,7 +1,7 @@ /*! * ui-select * http://github.com/angular-ui/ui-select - * Version: 0.19.7 - 2017-04-15T14:28:36.649Z + * Version: 0.19.8 - 2017-10-31T13:51:49.567Z * License: MIT */ @@ -184,31 +184,6 @@ var uis = angular.module('ui.select', []) }; }]); -/** - * Debounces functions - * - * Taken from UI Bootstrap $$debounce source code - * See https://github.com/angular-ui/bootstrap/blob/master/src/debounce/debounce.js - * - */ -uis.factory('$$uisDebounce', ['$timeout', function($timeout) { - return function(callback, debounceTime) { - var timeoutPromise; - - return function() { - var self = this; - var args = Array.prototype.slice.call(arguments); - if (timeoutPromise) { - $timeout.cancel(timeoutPromise); - } - - timeoutPromise = $timeout(function() { - callback.apply(self, args); - }, debounceTime); - }; - }; -}]); - uis.directive('uiSelectChoices', ['uiSelectConfig', 'uisRepeatParser', 'uiSelectMinErr', '$compile', '$window', function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile, $window) { @@ -248,9 +223,7 @@ uis.directive('uiSelectChoices', throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length); } - choices.attr('ng-repeat', parserResult.repeatExpression(groupByExp)) - .attr('ng-if', '$select.open'); //Prevent unnecessary watches when dropdown is closed - + choices.attr('ng-repeat', parserResult.repeatExpression(groupByExp)); var rowsInner = tElement.querySelectorAll('.ui-select-choices-row-inner'); if (rowsInner.length !== 1) { @@ -307,8 +280,8 @@ uis.directive('uiSelectChoices', * put as much logic in the controller (instead of the link functions) as possible so it can be easily tested. */ uis.controller('uiSelectCtrl', - ['$scope', '$element', '$timeout', '$filter', '$$uisDebounce', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig', '$parse', '$injector', '$window', - function($scope, $element, $timeout, $filter, $$uisDebounce, RepeatParser, uiSelectMinErr, uiSelectConfig, $parse, $injector, $window) { + ['$scope', '$element', '$timeout', '$filter', '$$uisDebounce', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig', '$parse', '$injector', '$window', 'uisOffset', + function($scope, $element, $timeout, $filter, $$uisDebounce, RepeatParser, uiSelectMinErr, uiSelectConfig, $parse, $injector, $window, uisOffset) { var ctrl = this; @@ -367,6 +340,11 @@ uis.controller('uiSelectCtrl', return isNil(ctrl.selected) || ctrl.selected === '' || (ctrl.multiple && ctrl.selected.length === 0); }; + ctrl.getPlaceholder = function(){ + if(ctrl.selected && ctrl.selected.length) return; + return ctrl.placeholder; + }; + function _findIndex(collection, predicate, thisArg){ if (collection.findIndex){ return collection.findIndex(predicate, thisArg); @@ -390,10 +368,14 @@ uis.controller('uiSelectCtrl', if (ctrl.resetSearchInput) { ctrl.search = EMPTY_SEARCH; //reset activeIndex - if (ctrl.selected && ctrl.items.length && !ctrl.multiple) { - ctrl.activeIndex = _findIndex(ctrl.items, function(item){ - return angular.equals(this, item); - }, ctrl.selected); + if (!ctrl.multiple) { + if (ctrl.selected && ctrl.items.length) { + ctrl.activeIndex = _findIndex(ctrl.items, function(item){ + return angular.equals(this, item); + }, ctrl.selected); + } else { + ctrl.activeIndex = 0; + } } } } @@ -451,7 +433,7 @@ uis.controller('uiSelectCtrl', } else { $timeout(function () { ctrl.focusSearchInput(initSearchValue); - if(!ctrl.tagging.isActivated && ctrl.items.length > 1) { + if(!ctrl.tagging.isActivated && ctrl.items.length > 1 && ctrl.open) { _ensureHighlightVisible(); } }); @@ -468,9 +450,12 @@ uis.controller('uiSelectCtrl', ctrl.searchInput[0].focus(); }; - ctrl.findGroupByName = function(name) { + ctrl.findGroupByName = function(name, noStrict) { return ctrl.groups && ctrl.groups.filter(function(group) { - return group.name === name; + if (noStrict) + return group.name == name; + else + return group.name === name; })[0]; }; @@ -764,14 +749,14 @@ uis.controller('uiSelectCtrl', }; // Toggle dropdown - ctrl.toggle = function(e) { + ctrl.toggle = function(e, skipFocusser) { if (ctrl.open) { - ctrl.close(); - e.preventDefault(); - e.stopPropagation(); + ctrl.close(skipFocusser); } else { ctrl.activate(); } + e.preventDefault(); + e.stopPropagation(); }; // Set default function for locked choices - avoids unnecessary @@ -836,9 +821,8 @@ uis.controller('uiSelectCtrl', if (containerWidth === 0) { return false; } - var inputWidth = containerWidth - input.offsetLeft; - if (inputWidth < 50) inputWidth = containerWidth; - ctrl.searchInput.css('width', inputWidth+'px'); + + ctrl.searchInput.css('width', '100%'); return true; }; @@ -861,6 +845,28 @@ uis.controller('uiSelectCtrl', }); }; + ctrl.resizeInput = function () { + var placeholder = angular.element('.ui-select-placeholder'); + if (placeholder.length === 0) { + return; + } + var offset; + if (ctrl.open) { + placeholder[0].style.width = '100%'; + offset = uisOffset(placeholder); + + placeholder[0].style.width = offset.width + 'px'; + placeholder[0].style.height = offset.height + 'px'; + + $element[0].style.position = 'absolute'; + $element[0].style.left = offset.left + 'px'; + $element[0].style.top = offset.top + 'px'; + } else { + offset = uisOffset($element); + } + $element[0].style.width = offset.width + 'px'; + }; + function _handleDropDownSelection(key) { var processed = true; switch (key) { @@ -1035,13 +1041,37 @@ uis.controller('uiSelectCtrl', var onResize = $$uisDebounce(function() { ctrl.sizeSearchInput(); + ctrl.resizeInput(); }, 50); + var closeOnEvent = function() { + if (ctrl.open) { + ctrl.open = false; + if (!$scope.$root || !$scope.$root.$$phase) { + $scope.$apply(); + } + } + }; + + var onEscape = function (event) { + if (ctrl.open && event.keyCode === 27) { + closeOnEvent(); + } + }; angular.element($window).bind('resize', onResize); + angular.element($window).bind('scroll', closeOnEvent); + angular.element($window).bind('popstate', closeOnEvent); + angular.element($window).bind('keydown', onEscape); + + angular.element('.modal-body').bind('scroll', closeOnEvent); $scope.$on('$destroy', function() { ctrl.searchInput.off('keyup keydown tagged blur paste'); angular.element($window).off('resize', onResize); + angular.element($window).off('scroll', onResize); + angular.element($window).off('popstate', onResize); + angular.element($window).off('keydown', onEscape); + angular.element('.modal-body').off('scroll', onResize); }); $scope.$watch('$select.activeIndex', function(activeIndex) { @@ -1058,8 +1088,8 @@ uis.controller('uiSelectCtrl', }]); uis.directive('uiSelect', - ['$document', 'uiSelectConfig', 'uiSelectMinErr', 'uisOffset', '$compile', '$parse', '$timeout', - function($document, uiSelectConfig, uiSelectMinErr, uisOffset, $compile, $parse, $timeout) { + ['$document', 'uiSelectConfig', 'uiSelectMinErr', 'uisOffset', '$compile', '$parse', '$timeout', '$window', + function($document, uiSelectConfig, uiSelectMinErr, uisOffset, $compile, $parse, $timeout, $window) { return { restrict: 'EA', @@ -1265,11 +1295,15 @@ uis.directive('uiSelect', $select.clickTriggeredSelect = false; } - // See Click everywhere but here event http://stackoverflow.com/questions/12931369 - $document.on('click', onDocumentClick); + // See Click everywhere but here. Similar approach to http://stackoverflow.com/questions/12931369 + // but using the capture phase instead of bubble phase of the event propagation. + // + // Using the capture phase avoids problems that araise when event.stopPropatagion() + // is called before the event reaches the `document`. + $window.document.addEventListener('click', onDocumentClick, true); scope.$on('$destroy', function() { - $document.off('click', onDocumentClick); + $window.document.removeEventListener('click', onDocumentClick, true); }); // Move transcluded elements to their correct position in main template @@ -1303,6 +1337,24 @@ uis.directive('uiSelect', if (transcludedNoChoice.length == 1) { element.querySelectorAll('.ui-select-no-choice').replaceWith(transcludedNoChoice); } + + var transcludedHeader = transcluded.querySelectorAll('.ui-select-header'); + transcludedHeader.removeAttr('ui-select-header'); // To avoid loop in case directive as attr + transcludedHeader.removeAttr('data-ui-select-header'); // Properly handle HTML5 data-attributes + if (transcludedHeader.length == 1) { + element.querySelectorAll('.ui-select-header').replaceWith(transcludedHeader); + } else { + element.querySelectorAll('.ui-select-header').remove(); + } + + var transcludedFooter = transcluded.querySelectorAll('.ui-select-footer'); + transcludedFooter.removeAttr('ui-select-footer'); // To avoid loop in case directive as attr + transcludedFooter.removeAttr('data-ui-select-footer'); // Properly handle HTML5 data-attributes + if (transcludedFooter.length == 1) { + element.querySelectorAll('.ui-select-footer').replaceWith(transcludedFooter); + } else { + element.querySelectorAll('.ui-select-footer').remove(); + } }); // Support for appending the select field to the body when its open @@ -1437,7 +1489,7 @@ uis.directive('uiSelect', }; var opened = false; - + scope.calculateDropdownPos = function() { if ($select.open) { dropdown = angular.element(element).querySelectorAll('.ui-select-dropdown'); @@ -1481,6 +1533,117 @@ uis.directive('uiSelect', }; }]); +uis.directive('uiSelectFooter', ['uiSelectConfig', function (uiSelectConfig) { + return { + templateUrl: function (tElement) { + // Needed so the uiSelect can detect the transcluded content + tElement.addClass('ui-select-footer'); + + // Gets theme attribute from parent (ui-select) + var theme = tElement.parent().attr('theme') || uiSelectConfig.theme; + return theme + '/footer.tpl.html'; + }, + restrict: 'EA', + transclude: true, + replace: true + }; +}]); + +uis.directive('uiSelectHeader', ['uiSelectConfig', function (uiSelectConfig) { + return { + templateUrl: function (tElement) { + // Needed so the uiSelect can detect the transcluded content + tElement.addClass('ui-select-header'); + + // Gets theme attribute from parent (ui-select) + var theme = tElement.parent().attr('theme') || uiSelectConfig.theme; + return theme + '/header.tpl.html'; + }, + restrict: 'EA', + transclude: true, + replace: true + }; +}]); + +uis.directive('uiSelectHeaderGroupSelectable', ['$timeout', function($timeout) { + return { + restrict: 'EA', + require: ['^uiSelect'], + scope: { + isEnabled: " 0\">