diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js index 8acf29e..1c0b9c1 100644 --- a/assets/scripts/src/choices.js +++ b/assets/scripts/src/choices.js @@ -747,36 +747,9 @@ class Choices { * @public */ showDropdown(focusInput = false) { - const body = document.body; - const html = document.documentElement; - const winHeight = Math.max( - body.scrollHeight, - body.offsetHeight, - html.clientHeight, - html.scrollHeight, - html.offsetHeight, - ); - - this.containerOuter.element.classList.add(this.config.classNames.openState); - this.containerOuter.element.setAttribute('aria-expanded', 'true'); + this.containerOuter.open(this.dropdown.getPosition()); this.dropdown.show(); - const dimensions = this.dropdown.element.getBoundingClientRect(); - const dropdownPos = Math.ceil(dimensions.top + window.scrollY + this.dropdown.offsetHeight); - - // If flip is enabled and the dropdown bottom position is - // greater than the window height flip the dropdown. - let shouldFlip = false; - if (this.config.position === 'auto') { - shouldFlip = dropdownPos >= winHeight; - } else if (this.config.position === 'top') { - shouldFlip = true; - } - - if (shouldFlip) { - this.containerOuter.element.classList.add(this.config.classNames.flippedState); - } - // Optionally focus the input if we have a search input if (focusInput && this.canSearch && document.activeElement !== this.input) { this.input.focus(); @@ -793,26 +766,15 @@ class Choices { * @public */ hideDropdown(blurInput = false) { - // A dropdown flips if it does not have space within the page - const isFlipped = this.containerOuter.element.classList.contains( - this.config.classNames.flippedState, - ); - - this.containerOuter.element.classList.remove(this.config.classNames.openState); - this.containerOuter.element.setAttribute('aria-expanded', 'false'); + this.containerOuter.close(); this.dropdown.hide(); - if (isFlipped) { - this.containerOuter.element.classList.remove(this.config.classNames.flippedState); - } - // Optionally blur the input if we have a search input if (blurInput && this.canSearch && document.activeElement === this.input) { this.input.blur(); } triggerEvent(this.passedElement, 'hideDropdown', {}); - return this; } @@ -822,7 +784,12 @@ class Choices { * @public */ toggleDropdown() { - this.dropdown.toggle(); + if (this.dropdown.isActive) { + this.hideDropdown(); + } else { + this.showDropdown(true); + } + return this; } @@ -1945,7 +1912,7 @@ class Choices { } // Remove focus state - this.containerOuter.element.classList.remove(this.config.classNames.focusState); + this.containerOuter.blur() // Close all other dropdowns if (hasActiveDropdown) { @@ -1962,8 +1929,11 @@ class Choices { */ _onMouseOver(e) { // If the dropdown is either the target or one of its children is the target - if (e.target === this.dropdown || this.dropdown.element.contains(e.target)) { - if (e.target.hasAttribute('data-choice')) this._highlightChoice(e.target); + if ( + (e.target === this.dropdown || this.dropdown.element.contains(e.target)) && + e.target.hasAttribute('data-choice') + ) { + this._highlightChoice(e.target); } } @@ -1994,11 +1964,11 @@ class Choices { const focusActions = { text: () => { if (target === this.input) { - this.containerOuter.element.classList.add(this.config.classNames.focusState); + this.containerOuter.focus(); } }, 'select-one': () => { - this.containerOuter.element.classList.add(this.config.classNames.focusState); + this.containerOuter.focus(); if (target === this.input) { // Show dropdown if it isn't already showing if (!hasActiveDropdown) { @@ -2010,7 +1980,7 @@ class Choices { if (target === this.input) { // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown - this.containerOuter.element.classList.add(this.config.classNames.focusState); + this.containerOuter.focus(); if (!hasActiveDropdown) { this.showDropdown(true); @@ -2040,7 +2010,7 @@ class Choices { text: () => { if (target === this.input) { // Remove the focus state - this.containerOuter.element.classList.remove(this.config.classNames.focusState); + this.containerOuter.blur(); // De-select any highlighted items if (hasHighlightedItems) { this.unhighlightAll(); @@ -2052,7 +2022,7 @@ class Choices { } }, 'select-one': () => { - this.containerOuter.element.classList.remove(this.config.classNames.focusState); + this.containerOuter.blur(); if (target === this.containerOuter.element) { // Hide dropdown if it is showing if (hasActiveDropdown && !this.canSearch) { @@ -2067,7 +2037,7 @@ class Choices { 'select-multiple': () => { if (target === this.input) { // Remove the focus state - this.containerOuter.element.classList.remove(this.config.classNames.focusState); + this.containerOuter.blur(); // Hide dropdown if it is showing if (hasActiveDropdown) { this.hideDropdown(); @@ -2695,8 +2665,8 @@ class Choices { const input = this._getTemplate('input'); const dropdown = this._getTemplate('dropdown'); - this.containerOuter = new Container(this, containerOuter, this.config.classNames); - this.containerInner = new Container(this, containerInner, this.config.classNames); + this.containerOuter = new Container(this, containerOuter); + this.containerInner = new Container(this, containerInner); this.input = input; this.choiceList = choiceList; this.itemList = itemList; diff --git a/assets/scripts/src/components/container.js b/assets/scripts/src/components/container.js index c465e7e..76c8a72 100644 --- a/assets/scripts/src/components/container.js +++ b/assets/scripts/src/components/container.js @@ -2,9 +2,73 @@ * Container */ export default class Container { - constructor(instance, element, classNames) { + constructor(instance, element) { this.instance = instance; this.element = element; - this.classNames = classNames; + this.config = instance.config; + this.classNames = instance.config.classNames; + this.isOpen = false; + this.isFlipped = false; + this.isFocussed = false; + } + + shouldFlip(dropdownPos) { + if (!dropdownPos) { + return false; + } + + const body = document.body; + const html = document.documentElement; + const winHeight = Math.max( + body.scrollHeight, + body.offsetHeight, + html.clientHeight, + html.scrollHeight, + html.offsetHeight, + ); + + // If flip is enabled and the dropdown bottom position is + // greater than the window height flip the dropdown. + let shouldFlip = false; + if (this.config.position === 'auto') { + shouldFlip = dropdownPos >= winHeight; + } else if (this.config.position === 'top') { + shouldFlip = true; + } + + return shouldFlip; + } + + open(dropdownPos) { + this.element.classList.add(this.classNames.openState); + this.element.setAttribute('aria-expanded', 'true'); + this.isOpen = true; + + if (this.shouldFlip(dropdownPos)) { + this.element.classList.add(this.classNames.flippedState); + this.isFlipped = true; + } + } + + close() { + this.element.classList.remove(this.classNames.openState); + this.element.setAttribute('aria-expanded', 'false'); + this.isOpen = false; + + // A dropdown flips if it does not have space within the page + if (this.isFlipped) { + this.element.classList.remove(this.classNames.flippedState); + this.isFlipped = false; + } + } + + focus() { + this.element.classList.add(this.classNames.focusState); + this.isFocussed = true; + } + + blur() { + this.element.classList.remove(this.classNames.focusState); + this.isFocussed = false; } } diff --git a/assets/scripts/src/components/dropdown.js b/assets/scripts/src/components/dropdown.js index b74ce17..22c2d88 100644 --- a/assets/scripts/src/components/dropdown.js +++ b/assets/scripts/src/components/dropdown.js @@ -6,9 +6,15 @@ export default class Dropdown { this.instance = instance; this.element = element; this.classNames = classNames; + this.dimensions = null; + this.position = null; + this.isActive = false; + } + + getPosition() { this.dimensions = this.element.getBoundingClientRect(); this.position = Math.ceil(this.dimensions.top + window.scrollY + this.element.offsetHeight); - this.isActive = false; + return this.position; } /**