diff --git a/src/js/select2/core.js b/src/js/select2/core.js index ee687d6d..628087ae 100644 --- a/src/js/select2/core.js +++ b/src/js/select2/core.js @@ -214,12 +214,16 @@ define([ Select2.prototype._registerSelectionEvents = function () { var self = this; - var nonRelayEvents = ['toggle']; + var nonRelayEvents = ['toggle', 'focus']; this.selection.on('toggle', function () { self.toggleDropdown(); }); + this.selection.on('focus', function (params) { + self.focus(params); + }); + this.selection.on('*', function (name, params) { if ($.inArray(name, nonRelayEvents) !== -1) { return; @@ -264,10 +268,6 @@ define([ self.$container.addClass('select2-container--disabled'); }); - this.on('focus', function () { - self.$container.addClass('select2-container--focus'); - }); - this.on('blur', function () { self.$container.removeClass('select2-container--focus'); }); @@ -411,6 +411,20 @@ define([ return this.$container.hasClass('select2-container--open'); }; + Select2.prototype.hasFocus = function () { + return this.$container.hasClass('select2-container--focus'); + }; + + Select2.prototype.focus = function (data) { + // No need to re-trigger focus events if we are already focused + if (this.hasFocus()) { + return; + } + + this.$container.addClass('select2-container--focus'); + this.trigger('focus'); + }; + Select2.prototype.enable = function (args) { if (this.options.get('debug') && window.console && console.warn) { console.warn( diff --git a/src/js/select2/selection/base.js b/src/js/select2/selection/base.js index 70cac9c5..a63658af 100644 --- a/src/js/select2/selection/base.js +++ b/src/js/select2/selection/base.js @@ -48,7 +48,7 @@ define([ }); this.$selection.on('blur', function (evt) { - self.trigger('blur', evt); + self._handleBlur(evt); }); this.$selection.on('keydown', function (evt) { @@ -95,6 +95,24 @@ define([ }); }; + BaseSelection.prototype._handleBlur = function (evt) { + var self = this; + + // This needs to be delayed as the actve element is the body when the tab + // key is pressed, possibly along with others. + window.setTimeout(function () { + // Don't trigger `blur` if the focus is still in the selection + if ( + (document.activeElement == self.$selection[0]) || + ($.contains(self.$selection[0], document.activeElement)) + ) { + return; + } + + self.trigger('blur', evt); + }, 1); + }; + BaseSelection.prototype._attachCloseHandler = function (container) { var self = this; diff --git a/src/js/select2/selection/search.js b/src/js/select2/selection/search.js index 7dbcf939..be306552 100644 --- a/src/js/select2/selection/search.js +++ b/src/js/select2/selection/search.js @@ -21,6 +21,8 @@ define([ var $rendered = decorated.call(this); + this._transferTabIndex(); + return $rendered; }; @@ -30,36 +32,34 @@ define([ decorated.call(this, container, $container); container.on('open', function () { - self.$search.attr('tabindex', 0); - - self.$search.focus(); + self.$search.trigger('focus'); }); container.on('close', function () { - self.$search.attr('tabindex', -1); - self.$search.val(''); - self.$search.focus(); + self.$search.trigger('focus'); }); container.on('enable', function () { self.$search.prop('disabled', false); + + self._transferTabIndex(); }); container.on('disable', function () { self.$search.prop('disabled', true); }); + container.on('focus', function (evt) { + self.$search.trigger('focus'); + }); + this.$selection.on('focusin', '.select2-search--inline', function (evt) { self.trigger('focus', evt); }); - this.$selection.on('focus', function (evt) { - self.$search.trigger('focus'); - }); - this.$selection.on('focusout', '.select2-search--inline', function (evt) { - self.trigger('blur', evt); + self._handleBlur(evt); }); this.$selection.on('keydown', '.select2-search--inline', function (evt) { @@ -95,10 +95,34 @@ define([ this.$selection.on('keyup.search input', '.select2-search--inline', function (evt) { + var key = evt.which; + + // We can freely ignore events from modifier keys + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { + return; + } + + // Tabbing will be handled during the `keydown` phase + if (key == KEYS.TAB) { + return; + } + self.handleSearch(evt); }); }; + /** + * This method will transfer the tabindex attribute from the rendered + * selection to the search box. This allows for the search box to be used as + * the primary focus instead of the selection container. + * + * @private + */ + Search.prototype._transferTabIndex = function (decorated) { + this.$search.attr('tabindex', this.$selection.attr('tabindex')); + this.$selection.attr('tabindex', '-1'); + }; + Search.prototype.createPlaceholder = function (decorated, placeholder) { this.$search.attr('placeholder', placeholder.text); };