diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js index 8874615..7b1638d 100644 --- a/assets/scripts/src/choices.js +++ b/assets/scripts/src/choices.js @@ -367,7 +367,7 @@ export default class Choices { * @return {Object} Class instance * @public */ - showDropdown() { + showDropdown(focusInput) { const body = document.body; const html = document.documentElement; const winHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); @@ -387,6 +387,10 @@ export default class Choices { this.containerOuter.classList.remove(this.config.classNames.flippedState); } + if (focusInput && this.canSearch) { + this.input.focus(); + } + return this; } @@ -1315,27 +1319,54 @@ export default class Choices { * @private */ _onFocus(e) { - const target = e.target || e.touches[0].target; + const target = e.target; const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState); - if (target === this.input && !hasActiveDropdown) { - this.containerOuter.classList.add(this.config.classNames.focusState); - if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') { - this.showDropdown(); - } - } else if (this.passedElement.type !== 'text' && (target === this.containerOuter || target === this.containerInner) && !hasActiveDropdown) { - // If element is a select box, the focussed element is the container and the dropdown - // isn't already open, focus and show dropdown - this.containerOuter.classList.add(this.config.classNames.focusState); - this.showDropdown(); + if (!hasActiveDropdown) { + switch (this.passedElement.type) { + case 'text': { + if (target === this.input) { + this.containerOuter.classList.add(this.config.classNames.focusState); + } - if (this.passedElement.type === 'select-one' && target === this.containerOuter) { - if (!this.focusAndHideDropdown) { - this.input.focus(); + break; } - this.focusAndHideDropdown = false; - } else if (this.canSearch) { - this.input.focus(); + case 'select-one': { + if (target === this.containerOuter) { + // If element is a select box, the focussed element is the container and the dropdown + // isn't already open, focus and show dropdown + this.containerOuter.classList.add(this.config.classNames.focusState); + this.showDropdown(); + + if (!this.focusAndHideDropdown && this.canSearch) { + this.input.focus(); + } + + this.focusAndHideDropdown = false; + } + + if (target === this.input) { + // If element is a select box, the focussed element is the container and the dropdown + // isn't already open, focus and show dropdown + this.containerOuter.classList.add(this.config.classNames.focusState); + this.showDropdown(); + } + + break; + } + case 'select-multiple': { + if (target === this.input) { + // If element is a select box, the focussed element is the container and the dropdown + // isn't already open, focus and show dropdown + this.containerOuter.classList.add(this.config.classNames.focusState); + this.showDropdown(true); + } + + break; + } + + default: + break; } } } @@ -1347,25 +1378,60 @@ export default class Choices { * @private */ _onBlur(e) { - // If the blurred element is this input or the outer container - if (e.target === this.input || (e.target === this.containerOuter && this.passedElement.type === 'select-one')) { - const activeItems = this.store.getItemsFilteredByActive(); - const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState); - const hasHighlightedItems = activeItems.some((item) => item.highlighted === true); + const target = e.target; + const activeItems = this.store.getItemsFilteredByActive(); + const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState); + const hasHighlightedItems = activeItems.some((item) => item.highlighted === true); - // Remove the focus state - this.containerOuter.classList.remove(this.config.classNames.focusState); + switch (this.passedElement.type) { + case 'text': { + if (target === this.input) { + // Remove the focus state + this.containerOuter.classList.remove(this.config.classNames.focusState); + // De-select any highlighted items + if (hasHighlightedItems) { + this.unhighlightAll(); + } + if (hasActiveDropdown) { + this.hideDropdown(); + } + } - // Close the dropdown if it is active, the input is the target (select-multiple, text, select-one (with search)) - // or the outer container is the target with no search (select-one) - if (hasActiveDropdown && (e.target === this.input || (e.target === this.containerOuter && !this.canSearch))) { - this.hideDropdown(); + break; + } + case 'select-one': { + if (target === this.containerOuter) { + if (hasActiveDropdown && (this.focusAndHideDropdown || !this.config.search)) { + this.hideDropdown(); + } + this.containerOuter.classList.remove(this.config.classNames.focusState); + } + + if (target === this.input) { + this.hideDropdown(); + this.containerOuter.classList.remove(this.config.classNames.focusState); + } + + break; + } + case 'select-multiple': { + if (target === this.input) { + // Remove the focus state + this.containerOuter.classList.remove(this.config.classNames.focusState); + if (hasActiveDropdown) { + this.hideDropdown(); + } + // De-select any highlighted items + if (hasHighlightedItems) { + this.unhighlightAll(); + } + } + + break; } - // De-select any highlighted items - if (hasHighlightedItems) { - this.unhighlightAll(); - } + default: + break; } } diff --git a/tests/spec/choices_spec.js b/tests/spec/choices_spec.js index 4006e37..8515565 100644 --- a/tests/spec/choices_spec.js +++ b/tests/spec/choices_spec.js @@ -16,7 +16,7 @@ describe('Choices', function() { this.input.className = 'js-choices'; document.body.appendChild(this.input); - + this.choices = new Choices(this.input); }); @@ -30,7 +30,7 @@ describe('Choices', function() { it('should not re-initialise if passed element again', function() { const reinitialise = new Choices(this.choices.passedElement); - spyOn(reinitialise, '_createTemplates'); + spyOn(reinitialise, '_createTemplates'); expect(reinitialise._createTemplates).not.toHaveBeenCalled(); }) @@ -132,7 +132,7 @@ describe('Choices', function() { this.input.placeholder = 'Placeholder text'; document.body.appendChild(this.input); - + this.choices = new Choices(this.input); }); @@ -165,12 +165,12 @@ describe('Choices', function() { option.value = `Value ${i}`; option.innerHTML = `Value ${i}`; - + this.input.appendChild(option); } document.body.appendChild(this.input); - + this.choices = new Choices(this.input); }); @@ -179,7 +179,7 @@ describe('Choices', function() { expect(this.choices.dropdown.classList).toContain(this.choices.config.classNames.activeState); }); - it('should select the first choice', function() { + it('should select the first choice', function() { expect(this.choices.currentState.items[0].value).toContain('Value 1'); }); @@ -209,7 +209,7 @@ describe('Choices', function() { ctrlKey: false, preventDefault: () => {} }); - + // Key down to select choice this.choices._onKeyDown({ target: this.choices.input, @@ -221,7 +221,7 @@ describe('Choices', function() { }); it('should trigger a change callback on selection', function() { - spyOn(this.choices.config, 'callbackOnChange'); + spyOn(this.choices.config, 'callbackOnChange'); this.choices.input.focus(); // Key down to second choice @@ -231,7 +231,7 @@ describe('Choices', function() { ctrlKey: false, preventDefault: () => {} }); - + // Key down to select choice this.choices._onKeyDown({ target: this.choices.input, @@ -300,15 +300,15 @@ describe('Choices', function() { option.value = `Value ${i}`; option.innerHTML = `Value ${i}`; - if(i % 2) { + if(i % 2) { option.selected = true; } - + this.input.appendChild(option); } document.body.appendChild(this.input); - + this.choices = new Choices(this.input, { placeholderValue: 'Placeholder text', choices: [ @@ -345,10 +345,10 @@ describe('Choices', function() { option.value = `Value ${i}`; option.innerHTML = `Value ${i}`; - if(i % 2) { + if(i % 2) { option.selected = true; } - + this.input.appendChild(option); } @@ -423,7 +423,7 @@ describe('Choices', function() { }); it('should handle toggleDropdown()', function() { - spyOn(this.choices, 'hideDropdown'); + spyOn(this.choices, 'hideDropdown'); this.choices.showDropdown(); this.choices.toggleDropdown(); expect(this.choices.hideDropdown).toHaveBeenCalled(); @@ -476,7 +476,7 @@ describe('Choices', function() { {value: 'Child Two', label: 'Child Two', disabled: true}, {value: 'Child Three', label: 'Child Three'}, ] - }, + }, { label: 'Group two', id: 2, @@ -487,8 +487,8 @@ describe('Choices', function() { {value: 'Child Six', label: 'Child Six'}, ] }], 'value', 'label'); - - + + const groups = this.choices.currentState.groups; const choices = this.choices.currentState.choices; @@ -523,7 +523,7 @@ describe('Choices', function() { }); it('should handle ajax()', function() { - spyOn(this.choices, 'ajax'); + spyOn(this.choices, 'ajax'); this.choices.ajax((callback) => { fetch('https://restcountries.eu/rest/v1/all') @@ -536,7 +536,7 @@ describe('Choices', function() { console.log(error); }); }); - + expect(this.choices.ajax).toHaveBeenCalledWith(jasmine.any(Function)); }); }); @@ -562,7 +562,7 @@ describe('Choices', function() { const randomItem = items[Math.floor(Math.random()*items.length)]; this.choices.removeItemsByValue(randomItem.value); - + expect(randomItem.active).toBe(false); });