mirror of
https://github.com/Choices-js/Choices.git
synced 2024-06-01 21:42:30 +02:00
Begin extracting dropdown from core class
This commit is contained in:
parent
0f81a03e19
commit
b86b8f0ed0
|
@ -1,6 +1,7 @@
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Store from './store/index';
|
import Store from './store/index';
|
||||||
|
import Dropdown from './components/dropdown';
|
||||||
import {
|
import {
|
||||||
addItem,
|
addItem,
|
||||||
removeItem,
|
removeItem,
|
||||||
|
@ -11,8 +12,7 @@ import {
|
||||||
addGroup,
|
addGroup,
|
||||||
clearAll,
|
clearAll,
|
||||||
clearChoices,
|
clearChoices,
|
||||||
}
|
} from './actions/index';
|
||||||
from './actions/index';
|
|
||||||
import {
|
import {
|
||||||
isScrolledIntoView,
|
isScrolledIntoView,
|
||||||
getAdjacentEl,
|
getAdjacentEl,
|
||||||
|
@ -29,8 +29,7 @@ import {
|
||||||
triggerEvent,
|
triggerEvent,
|
||||||
findAncestorByAttrName,
|
findAncestorByAttrName,
|
||||||
regexFilter,
|
regexFilter,
|
||||||
}
|
} from './lib/utils';
|
||||||
from './lib/utils';
|
|
||||||
import './lib/polyfills';
|
import './lib/polyfills';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -755,10 +754,10 @@ class Choices {
|
||||||
|
|
||||||
this.containerOuter.classList.add(this.config.classNames.openState);
|
this.containerOuter.classList.add(this.config.classNames.openState);
|
||||||
this.containerOuter.setAttribute('aria-expanded', 'true');
|
this.containerOuter.setAttribute('aria-expanded', 'true');
|
||||||
this.dropdown.classList.add(this.config.classNames.activeState);
|
this.dropdown.element.classList.add(this.config.classNames.activeState);
|
||||||
this.dropdown.setAttribute('aria-expanded', 'true');
|
this.dropdown.element.setAttribute('aria-expanded', 'true');
|
||||||
|
|
||||||
const dimensions = this.dropdown.getBoundingClientRect();
|
const dimensions = this.dropdown.element.getBoundingClientRect();
|
||||||
const dropdownPos = Math.ceil(dimensions.top + window.scrollY + this.dropdown.offsetHeight);
|
const dropdownPos = Math.ceil(dimensions.top + window.scrollY + this.dropdown.offsetHeight);
|
||||||
|
|
||||||
// If flip is enabled and the dropdown bottom position is
|
// If flip is enabled and the dropdown bottom position is
|
||||||
|
@ -795,8 +794,8 @@ class Choices {
|
||||||
|
|
||||||
this.containerOuter.classList.remove(this.config.classNames.openState);
|
this.containerOuter.classList.remove(this.config.classNames.openState);
|
||||||
this.containerOuter.setAttribute('aria-expanded', 'false');
|
this.containerOuter.setAttribute('aria-expanded', 'false');
|
||||||
this.dropdown.classList.remove(this.config.classNames.activeState);
|
this.dropdown.element.classList.remove(this.config.classNames.activeState);
|
||||||
this.dropdown.setAttribute('aria-expanded', 'false');
|
this.dropdown.element.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
if (isFlipped) {
|
if (isFlipped) {
|
||||||
this.containerOuter.classList.remove(this.config.classNames.flippedState);
|
this.containerOuter.classList.remove(this.config.classNames.flippedState);
|
||||||
|
@ -818,7 +817,7 @@ class Choices {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
toggleDropdown() {
|
toggleDropdown() {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.element.classList.contains(this.config.classNames.activeState);
|
||||||
if (hasActiveDropdown) {
|
if (hasActiveDropdown) {
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1247,7 +1246,7 @@ class Choices {
|
||||||
const id = element.getAttribute('data-id');
|
const id = element.getAttribute('data-id');
|
||||||
const choice = this.store.getChoiceById(id);
|
const choice = this.store.getChoiceById(id);
|
||||||
const passedKeyCode = activeItems[0] && activeItems[0].keyCode ? activeItems[0].keyCode : null;
|
const passedKeyCode = activeItems[0] && activeItems[0].keyCode ? activeItems[0].keyCode : null;
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.element.classList.contains(this.config.classNames.activeState);
|
||||||
|
|
||||||
// Update choice keyCode
|
// Update choice keyCode
|
||||||
choice.keyCode = passedKeyCode;
|
choice.keyCode = passedKeyCode;
|
||||||
|
@ -1603,7 +1602,7 @@ class Choices {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
const hasFocusedInput = this.input === document.activeElement;
|
const hasFocusedInput = this.input === document.activeElement;
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.element.classList.contains(this.config.classNames.activeState);
|
||||||
const hasItems = this.itemList && this.itemList.children;
|
const hasItems = this.itemList && this.itemList.children;
|
||||||
const keyString = String.fromCharCode(e.keyCode);
|
const keyString = String.fromCharCode(e.keyCode);
|
||||||
|
|
||||||
|
@ -1660,7 +1659,7 @@ class Choices {
|
||||||
|
|
||||||
if (hasActiveDropdown) {
|
if (hasActiveDropdown) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const highlighted = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
const highlighted = this.dropdown.element.querySelector(`.${this.config.classNames.highlightedState}`);
|
||||||
|
|
||||||
// If we have a highlighted choice
|
// If we have a highlighted choice
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
|
@ -1700,16 +1699,16 @@ class Choices {
|
||||||
let nextEl;
|
let nextEl;
|
||||||
if (skipKey) {
|
if (skipKey) {
|
||||||
if (directionInt > 0) {
|
if (directionInt > 0) {
|
||||||
nextEl = Array.from(this.dropdown.querySelectorAll('[data-choice-selectable]')).pop();
|
nextEl = Array.from(this.dropdown.element.querySelectorAll('[data-choice-selectable]')).pop();
|
||||||
} else {
|
} else {
|
||||||
nextEl = this.dropdown.querySelector('[data-choice-selectable]');
|
nextEl = this.dropdown.element.querySelector('[data-choice-selectable]');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const currentEl = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
const currentEl = this.dropdown.element.querySelector(`.${this.config.classNames.highlightedState}`);
|
||||||
if (currentEl) {
|
if (currentEl) {
|
||||||
nextEl = getAdjacentEl(currentEl, '[data-choice-selectable]', directionInt);
|
nextEl = getAdjacentEl(currentEl, '[data-choice-selectable]', directionInt);
|
||||||
} else {
|
} else {
|
||||||
nextEl = this.dropdown.querySelector('[data-choice-selectable]');
|
nextEl = this.dropdown.element.querySelector('[data-choice-selectable]');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1773,7 +1772,7 @@ class Choices {
|
||||||
// We are typing into a text input and have a value, we want to show a dropdown
|
// We are typing into a text input and have a value, we want to show a dropdown
|
||||||
// notice. Otherwise hide the dropdown
|
// notice. Otherwise hide the dropdown
|
||||||
if (this.isTextElement) {
|
if (this.isTextElement) {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(
|
const hasActiveDropdown = this.dropdown.element.classList.contains(
|
||||||
this.config.classNames.activeState,
|
this.config.classNames.activeState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1844,7 +1843,7 @@ class Choices {
|
||||||
*/
|
*/
|
||||||
_onTouchEnd(e) {
|
_onTouchEnd(e) {
|
||||||
const target = e.target || e.touches[0].target;
|
const target = e.target || e.touches[0].target;
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.element.classList.contains(this.config.classNames.activeState);
|
||||||
|
|
||||||
// If a user tapped within our container...
|
// If a user tapped within our container...
|
||||||
if (this.wasTap === true && this.containerOuter.contains(target)) {
|
if (this.wasTap === true && this.containerOuter.contains(target)) {
|
||||||
|
@ -1912,7 +1911,7 @@ class Choices {
|
||||||
*/
|
*/
|
||||||
_onClick(e) {
|
_onClick(e) {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.element.classList.contains(this.config.classNames.activeState);
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
|
|
||||||
// If target is something that concerns us
|
// If target is something that concerns us
|
||||||
|
@ -1936,7 +1935,7 @@ class Choices {
|
||||||
} else if (
|
} else if (
|
||||||
this.isSelectOneElement &&
|
this.isSelectOneElement &&
|
||||||
target !== this.input &&
|
target !== this.input &&
|
||||||
!this.dropdown.contains(target)
|
!this.dropdown.element.contains(target)
|
||||||
) {
|
) {
|
||||||
this.hideDropdown(true);
|
this.hideDropdown(true);
|
||||||
}
|
}
|
||||||
|
@ -1966,7 +1965,7 @@ class Choices {
|
||||||
*/
|
*/
|
||||||
_onMouseOver(e) {
|
_onMouseOver(e) {
|
||||||
// If the dropdown is either the target or one of its children is the target
|
// If the dropdown is either the target or one of its children is the target
|
||||||
if (e.target === this.dropdown || this.dropdown.contains(e.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.hasAttribute('data-choice')) this._highlightChoice(e.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1994,7 +1993,7 @@ class Choices {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
// If target is something that concerns us
|
// If target is something that concerns us
|
||||||
if (this.containerOuter.contains(target)) {
|
if (this.containerOuter.contains(target)) {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(
|
const hasActiveDropdown = this.dropdown.element.classList.contains(
|
||||||
this.config.classNames.activeState,
|
this.config.classNames.activeState,
|
||||||
);
|
);
|
||||||
const focusActions = {
|
const focusActions = {
|
||||||
|
@ -2040,7 +2039,7 @@ class Choices {
|
||||||
// If target is something that concerns us
|
// If target is something that concerns us
|
||||||
if (this.containerOuter.contains(target) && !this.isScrollingOnIe) {
|
if (this.containerOuter.contains(target) && !this.isScrollingOnIe) {
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(
|
const hasActiveDropdown = this.dropdown.element.classList.contains(
|
||||||
this.config.classNames.activeState,
|
this.config.classNames.activeState,
|
||||||
);
|
);
|
||||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
||||||
|
@ -2166,11 +2165,11 @@ class Choices {
|
||||||
*/
|
*/
|
||||||
_highlightChoice(el = null) {
|
_highlightChoice(el = null) {
|
||||||
// Highlight first element in dropdown
|
// Highlight first element in dropdown
|
||||||
const choices = Array.from(this.dropdown.querySelectorAll('[data-choice-selectable]'));
|
const choices = Array.from(this.dropdown.element.querySelectorAll('[data-choice-selectable]'));
|
||||||
let passedEl = el;
|
let passedEl = el;
|
||||||
|
|
||||||
if (choices && choices.length) {
|
if (choices && choices.length) {
|
||||||
const highlightedChoices = Array.from(this.dropdown.querySelectorAll(`.${this.config.classNames.highlightedState}`));
|
const highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(`.${this.config.classNames.highlightedState}`));
|
||||||
|
|
||||||
// Remove any highlighted choices
|
// Remove any highlighted choices
|
||||||
highlightedChoices.forEach((choice) => {
|
highlightedChoices.forEach((choice) => {
|
||||||
|
@ -2708,7 +2707,7 @@ class Choices {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.choiceList = choiceList;
|
this.choiceList = choiceList;
|
||||||
this.itemList = itemList;
|
this.itemList = itemList;
|
||||||
this.dropdown = dropdown;
|
this.dropdown = new Dropdown(this, dropdown);
|
||||||
|
|
||||||
// Hide passed input
|
// Hide passed input
|
||||||
this.passedElement.classList.add(
|
this.passedElement.classList.add(
|
||||||
|
@ -2748,7 +2747,7 @@ class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
containerOuter.appendChild(containerInner);
|
containerOuter.appendChild(containerInner);
|
||||||
containerOuter.appendChild(dropdown);
|
containerOuter.appendChild(this.dropdown.element);
|
||||||
containerInner.appendChild(itemList);
|
containerInner.appendChild(itemList);
|
||||||
|
|
||||||
if (!this.isTextElement) {
|
if (!this.isTextElement) {
|
||||||
|
|
48
assets/scripts/src/components/dropdown.js
Normal file
48
assets/scripts/src/components/dropdown.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
export default class Dropdown {
|
||||||
|
constructor(instance, element) {
|
||||||
|
this.instance = instance;
|
||||||
|
this.element = element;
|
||||||
|
this.dimensions = this.element.getBoundingClientRect();
|
||||||
|
this.position = Math.ceil(this.dimensions.top + window.scrollY + this.element.offsetHeight);
|
||||||
|
this.isActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether to hide or show dropdown based on its current state
|
||||||
|
* @return {Object} Class instance
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
toggle() {
|
||||||
|
if (this.isActive) {
|
||||||
|
this.hideDropdown();
|
||||||
|
} else {
|
||||||
|
this.showDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show dropdown to user by adding active state class
|
||||||
|
* @return {Object} Class instance
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
show() {
|
||||||
|
this.dropdown.classList.add(this.config.classNames.activeState);
|
||||||
|
this.dropdown.setAttribute('aria-expanded', 'true');
|
||||||
|
this.isActive = true;
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide dropdown from user
|
||||||
|
* @return {Object} Class instance
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
this.element.classList.remove(this.config.classNames.activeState);
|
||||||
|
this.element.setAttribute('aria-expanded', 'false');
|
||||||
|
this.isActive = false;
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,7 +135,7 @@ describe('Choices', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a dropdown', function() {
|
it('should create a dropdown', function() {
|
||||||
expect(this.choices.dropdown).toEqual(jasmine.any(HTMLElement));
|
expect(this.choices.dropdown.element).toEqual(jasmine.any(HTMLElement));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should backup and recover original styles', function () {
|
it('should backup and recover original styles', function () {
|
||||||
|
@ -312,7 +312,7 @@ describe('Choices', () => {
|
||||||
it('should open the choice list on focusing', function() {
|
it('should open the choice list on focusing', function() {
|
||||||
this.choices = new Choices(this.input);
|
this.choices = new Choices(this.input);
|
||||||
this.choices.input.focus();
|
this.choices.input.focus();
|
||||||
expect(this.choices.dropdown.classList).toContain(this.choices.config.classNames.activeState);
|
expect(this.choices.dropdown.element.classList).toContain(this.choices.config.classNames.activeState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select the first choice', function() {
|
it('should select the first choice', function() {
|
||||||
|
@ -748,7 +748,7 @@ describe('Choices', () => {
|
||||||
this.choices.showDropdown();
|
this.choices.showDropdown();
|
||||||
const hasOpenState = this.choices.containerOuter.classList.contains(this.choices.config.classNames.openState);
|
const hasOpenState = this.choices.containerOuter.classList.contains(this.choices.config.classNames.openState);
|
||||||
const hasAttr = this.choices.containerOuter.getAttribute('aria-expanded') === 'true';
|
const hasAttr = this.choices.containerOuter.getAttribute('aria-expanded') === 'true';
|
||||||
const hasActiveState = this.choices.dropdown.classList.contains(this.choices.config.classNames.activeState);
|
const hasActiveState = this.choices.dropdown.element.classList.contains(this.choices.config.classNames.activeState);
|
||||||
expect(hasOpenState && hasAttr && hasActiveState).toBe(true);
|
expect(hasOpenState && hasAttr && hasActiveState).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -757,7 +757,7 @@ describe('Choices', () => {
|
||||||
this.choices.hideDropdown();
|
this.choices.hideDropdown();
|
||||||
const hasOpenState = this.choices.containerOuter.classList.contains(this.choices.config.classNames.openState);
|
const hasOpenState = this.choices.containerOuter.classList.contains(this.choices.config.classNames.openState);
|
||||||
const hasAttr = this.choices.containerOuter.getAttribute('aria-expanded') === 'true';
|
const hasAttr = this.choices.containerOuter.getAttribute('aria-expanded') === 'true';
|
||||||
const hasActiveState = this.choices.dropdown.classList.contains(this.choices.config.classNames.activeState);
|
const hasActiveState = this.choices.dropdown.element.classList.contains(this.choices.config.classNames.activeState);
|
||||||
|
|
||||||
expect(hasOpenState && hasAttr && hasActiveState).toBe(false);
|
expect(hasOpenState && hasAttr && hasActiveState).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue