mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-09 09:06:37 +02:00
Add remaining components
This commit is contained in:
parent
4c7cc047dc
commit
809b62c60d
|
@ -4,6 +4,7 @@ import Store from './store/index';
|
|||
import Dropdown from './components/dropdown';
|
||||
import Container from './components/container';
|
||||
import Input from './components/input';
|
||||
import List from './components/list';
|
||||
import {
|
||||
addItem,
|
||||
removeItem,
|
||||
|
@ -496,11 +497,11 @@ class Choices {
|
|||
let choiceListFragment = document.createDocumentFragment();
|
||||
|
||||
// Clear choices
|
||||
this.choiceList.innerHTML = '';
|
||||
this.choiceList.clear();
|
||||
|
||||
// Scroll back to top of choices list
|
||||
if (this.config.resetScrollPosition) {
|
||||
this.choiceList.scrollTop = 0;
|
||||
this.choiceList.scrollTo(0);
|
||||
}
|
||||
|
||||
// If we have grouped options
|
||||
|
@ -518,11 +519,11 @@ class Choices {
|
|||
// ...and we can select them
|
||||
if (canAddItem.response) {
|
||||
// ...append them and highlight the first choice
|
||||
this.choiceList.appendChild(choiceListFragment);
|
||||
this.choiceList.append(choiceListFragment);
|
||||
this._highlightChoice();
|
||||
} else {
|
||||
// ...otherwise show a notice
|
||||
this.choiceList.appendChild(this._getTemplate('notice', canAddItem.notice));
|
||||
this.choiceList.append(this._getTemplate('notice', canAddItem.notice));
|
||||
}
|
||||
} else {
|
||||
// Otherwise show a notice
|
||||
|
@ -543,7 +544,7 @@ class Choices {
|
|||
dropdownItem = this._getTemplate('notice', notice, 'no-choices');
|
||||
}
|
||||
|
||||
this.choiceList.appendChild(dropdownItem);
|
||||
this.choiceList.append(dropdownItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -554,7 +555,7 @@ class Choices {
|
|||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
|
||||
// Clear list
|
||||
this.itemList.innerHTML = '';
|
||||
this.itemList.clear();
|
||||
|
||||
if (activeItems && activeItems) {
|
||||
// Create a fragment to store our list items
|
||||
|
@ -564,7 +565,7 @@ class Choices {
|
|||
// If we have items to add
|
||||
if (itemListFragment.childNodes) {
|
||||
// Update list
|
||||
this.itemList.appendChild(itemListFragment);
|
||||
this.itemList.append(itemListFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -742,7 +743,7 @@ class Choices {
|
|||
* @public
|
||||
*/
|
||||
showDropdown(focusInput = false) {
|
||||
this.containerOuter.open(this.dropdown.getPosition());
|
||||
this.containerOuter.open(this.dropdown.getVerticalPos());
|
||||
this.dropdown.show();
|
||||
this.input.activate(focusInput);
|
||||
|
||||
|
@ -1308,13 +1309,13 @@ class Choices {
|
|||
* @private
|
||||
*/
|
||||
_handleLoadingState(isLoading = true) {
|
||||
let placeholderItem = this.itemList.querySelector(`.${this.config.classNames.placeholder}`);
|
||||
let placeholderItem = this.itemList.getChild(`.${this.config.classNames.placeholder}`);
|
||||
if (isLoading) {
|
||||
this.containerOuter.addLoadingState();
|
||||
if (this.isSelectOneElement) {
|
||||
if (!placeholderItem) {
|
||||
placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
|
||||
this.itemList.appendChild(placeholderItem);
|
||||
this.itemList.append(placeholderItem);
|
||||
} else {
|
||||
placeholderItem.innerHTML = this.config.loadingText;
|
||||
}
|
||||
|
@ -1520,7 +1521,7 @@ class Choices {
|
|||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
const hasFocusedInput = this.input.isFocussed;
|
||||
const hasActiveDropdown = this.dropdown.isActive;
|
||||
const hasItems = this.itemList && this.itemList.children;
|
||||
const hasItems = this.itemList.hasChildren;
|
||||
const keyString = String.fromCharCode(e.keyCode);
|
||||
|
||||
const backKey = 46;
|
||||
|
@ -1580,7 +1581,7 @@ class Choices {
|
|||
|
||||
if (hasActiveDropdown) {
|
||||
e.preventDefault();
|
||||
const highlighted = this.dropdown.getHighlightedChildren();
|
||||
const highlighted = this.dropdown.getChild(`.${this.config.classNames.highlightedState}`);
|
||||
|
||||
// If we have a highlighted choice
|
||||
if (highlighted) {
|
||||
|
@ -1997,20 +1998,20 @@ class Choices {
|
|||
return;
|
||||
}
|
||||
|
||||
const dropdownHeight = this.choiceList.offsetHeight;
|
||||
const dropdownHeight = this.choiceList.height;
|
||||
const choiceHeight = choice.offsetHeight;
|
||||
// Distance from bottom of element to top of parent
|
||||
const choicePos = choice.offsetTop + choiceHeight;
|
||||
// Scroll position of dropdown
|
||||
const containerScrollPos = this.choiceList.scrollTop + dropdownHeight;
|
||||
const containerScrollPos = this.choiceList.scrollPos + dropdownHeight;
|
||||
// Difference between the choice and scroll position
|
||||
const endPoint = direction > 0 ? (
|
||||
(this.choiceList.scrollTop + choicePos) - containerScrollPos) :
|
||||
(this.choiceList.scrollPos + choicePos) - containerScrollPos) :
|
||||
choice.offsetTop;
|
||||
|
||||
const animateScroll = () => {
|
||||
const strength = 4;
|
||||
const choiceListScrollTop = this.choiceList.scrollTop;
|
||||
const choiceListScrollTop = this.choiceList.scrollPos;
|
||||
let continueAnimation = false;
|
||||
let easing;
|
||||
let distance;
|
||||
|
@ -2019,7 +2020,7 @@ class Choices {
|
|||
easing = (endPoint - choiceListScrollTop) / strength;
|
||||
distance = easing > 1 ? easing : 1;
|
||||
|
||||
this.choiceList.scrollTop = choiceListScrollTop + distance;
|
||||
this.choiceList.scrollTo(choiceListScrollTop + distance);
|
||||
if (choiceListScrollTop < endPoint) {
|
||||
continueAnimation = true;
|
||||
}
|
||||
|
@ -2027,7 +2028,7 @@ class Choices {
|
|||
easing = (choiceListScrollTop - endPoint) / strength;
|
||||
distance = easing > 1 ? easing : 1;
|
||||
|
||||
this.choiceList.scrollTop = choiceListScrollTop - distance;
|
||||
this.choiceList.scrollTo(choiceListScrollTop - distance);
|
||||
if (choiceListScrollTop > endPoint) {
|
||||
continueAnimation = true;
|
||||
}
|
||||
|
@ -2604,8 +2605,8 @@ class Choices {
|
|||
this.containerOuter = new Container(this, containerOuter);
|
||||
this.containerInner = new Container(this, containerInner);
|
||||
this.input = new Input(this, input);
|
||||
this.choiceList = choiceList;
|
||||
this.itemList = itemList;
|
||||
this.choiceList = new List(this, choiceList);
|
||||
this.itemList = new List(this, itemList);
|
||||
this.dropdown = new Dropdown(this, dropdown, this.config.classNames);
|
||||
|
||||
// Hide passed input
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/**
|
||||
* Container
|
||||
*/
|
||||
export default class Container {
|
||||
constructor(instance, element) {
|
||||
this.instance = instance;
|
||||
|
@ -11,28 +8,49 @@ export default class Container {
|
|||
this.isFlipped = false;
|
||||
this.isFocussed = false;
|
||||
this.isDisabled = false;
|
||||
this.isLoading = false;
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners
|
||||
*/
|
||||
addEventListeners() {
|
||||
this.element.addEventListener('focus', this.onFocus);
|
||||
this.element.addEventListener('blur', this.onBlur);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listeners
|
||||
*/
|
||||
|
||||
/** */
|
||||
removeEventListeners() {
|
||||
this.element.removeEventListener('focus', this.onFocus);
|
||||
this.element.removeEventListener('blur', this.onBlur);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focussed state
|
||||
*/
|
||||
onFocus() {
|
||||
this.isFocussed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove blurred state
|
||||
*/
|
||||
onBlur() {
|
||||
this.isFocussed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether container should be flipped
|
||||
* based on passed dropdown position
|
||||
* @param {Number} dropdownPos
|
||||
* @returns
|
||||
*/
|
||||
shouldFlip(dropdownPos) {
|
||||
if (!dropdownPos) {
|
||||
return false;
|
||||
|
@ -60,10 +78,17 @@ export default class Container {
|
|||
return shouldFlip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set active descendant attribute
|
||||
* @param {Number} activeDescendant ID of active descendant
|
||||
*/
|
||||
setActiveDescendant(activeDescendant) {
|
||||
this.element.setAttribute('aria-activedescendant', activeDescendant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove active descendant attribute
|
||||
*/
|
||||
removeActiveDescendant() {
|
||||
this.element.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
|
@ -106,6 +131,9 @@ export default class Container {
|
|||
this.element.classList.remove(this.classNames.focusState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove disabled state
|
||||
*/
|
||||
enable() {
|
||||
this.element.classList.remove(this.config.classNames.disabledState);
|
||||
this.element.removeAttribute('aria-disabled');
|
||||
|
@ -115,6 +143,9 @@ export default class Container {
|
|||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set disabled state
|
||||
*/
|
||||
disable() {
|
||||
this.element.classList.add(this.config.classNames.disabledState);
|
||||
this.element.setAttribute('aria-disabled', 'true');
|
||||
|
@ -124,13 +155,21 @@ export default class Container {
|
|||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add loading state to element
|
||||
*/
|
||||
addLoadingState() {
|
||||
this.element.classList.add(this.classNames.loadingState);
|
||||
this.element.setAttribute('aria-busy', 'true');
|
||||
this.isLoading = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove loading state from element
|
||||
*/
|
||||
removeLoadingState() {
|
||||
this.element.classList.remove(this.classNames.loadingState);
|
||||
this.element.removeAttribute('aria-busy');
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/**
|
||||
* Dropdown
|
||||
*/
|
||||
export default class Dropdown {
|
||||
constructor(instance, element, classNames) {
|
||||
this.instance = instance;
|
||||
|
@ -11,16 +8,23 @@ export default class Dropdown {
|
|||
this.isActive = false;
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
/**
|
||||
* Determine how far the top of our element is from
|
||||
* the top of the window
|
||||
* @return {Number} Vertical position
|
||||
*/
|
||||
getVerticalPos() {
|
||||
this.dimensions = this.element.getBoundingClientRect();
|
||||
this.position = Math.ceil(this.dimensions.top + window.scrollY + this.element.offsetHeight);
|
||||
return this.position;
|
||||
}
|
||||
|
||||
getHighlightedChildren() {
|
||||
return this.element.querySelector(
|
||||
`.${this.classNames.highlightedState}`,
|
||||
);
|
||||
/**
|
||||
* Find element that matches passed selector
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
getChild(selector) {
|
||||
return this.element.querySelector(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { getWidthOfInput } from '../lib/utils';
|
||||
|
||||
/**
|
||||
* Input
|
||||
*/
|
||||
export default class Input {
|
||||
constructor(instance, element, classNames) {
|
||||
constructor(instance, element) {
|
||||
this.instance = instance;
|
||||
this.element = element;
|
||||
this.classNames = classNames;
|
||||
this.isFocussed = this.element === document.activeElement;
|
||||
this.isDisabled = false;
|
||||
|
||||
// Bind event listeners
|
||||
this.onPaste = this.onPaste.bind(this);
|
||||
|
@ -55,10 +52,16 @@ export default class Input {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focussed state
|
||||
*/
|
||||
onFocus() {
|
||||
this.isFocussed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove focussed state
|
||||
*/
|
||||
onBlur() {
|
||||
this.isFocussed = false;
|
||||
}
|
||||
|
@ -80,10 +83,12 @@ export default class Input {
|
|||
|
||||
enable() {
|
||||
this.element.removeAttribute('disabled');
|
||||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.element.setAttribute('disabled', '');
|
||||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
focus() {
|
||||
|
|
39
assets/scripts/src/components/list.js
Normal file
39
assets/scripts/src/components/list.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
export default class List {
|
||||
constructor(instance, element) {
|
||||
this.instance = instance;
|
||||
this.element = element;
|
||||
this.classNames = this.instance.config.classNames;
|
||||
this.scrollPos = this.element.scrollTop;
|
||||
this.height = this.element.offsetHeight;
|
||||
this.hasChildren = !!this.element.children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear List contents
|
||||
*/
|
||||
clear() {
|
||||
this.element.innerHTML = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to passed position on Y axis
|
||||
*/
|
||||
scrollTo(scrollPos) {
|
||||
this.element.scrollTop = scrollPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append node to element
|
||||
*/
|
||||
append(node) {
|
||||
this.element.appendChild(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find element that matches passed selector
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
getChild(selector) {
|
||||
return this.element.querySelector(selector);
|
||||
}
|
||||
}
|
|
@ -124,11 +124,11 @@ describe('Choices', () => {
|
|||
});
|
||||
|
||||
it('should create a choice list', function() {
|
||||
expect(this.choices.choiceList).toEqual(jasmine.any(HTMLElement));
|
||||
expect(this.choices.choiceList.element).toEqual(jasmine.any(HTMLElement));
|
||||
});
|
||||
|
||||
it('should create an item list', function() {
|
||||
expect(this.choices.itemList).toEqual(jasmine.any(HTMLElement));
|
||||
expect(this.choices.itemList.element).toEqual(jasmine.any(HTMLElement));
|
||||
});
|
||||
|
||||
it('should create an input', function() {
|
||||
|
@ -219,7 +219,7 @@ describe('Choices', () => {
|
|||
});
|
||||
|
||||
expect(
|
||||
this.choices.currentState.items[this.choices.currentState.items.length - 1]
|
||||
this.choices.currentState.items[this.choices.currentState.items.length - 1],
|
||||
).not.toContain(this.choices.input.element.value);
|
||||
});
|
||||
|
||||
|
@ -435,7 +435,7 @@ describe('Choices', () => {
|
|||
|
||||
expect(
|
||||
document.activeElement === this.choices.input.element &&
|
||||
container.classList.contains(openState)
|
||||
container.classList.contains(openState),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -896,7 +896,7 @@ describe('Choices', () => {
|
|||
expect(this.choices.input.element.disabled).toBe(true);
|
||||
expect(
|
||||
this.choices.containerOuter.element.classList.contains(
|
||||
this.choices.config.classNames.disabledState
|
||||
this.choices.config.classNames.disabledState,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(this.choices.containerOuter.element.getAttribute('aria-disabled')).toBe('true');
|
||||
|
@ -1053,7 +1053,7 @@ describe('Choices', () => {
|
|||
renderSelectedChoices: 'always',
|
||||
renderChoiceLimit: -1,
|
||||
});
|
||||
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
|
||||
const renderedChoices = this.choices.choiceList.element.querySelectorAll('.choices__item');
|
||||
expect(renderedChoices.length).toEqual(3);
|
||||
});
|
||||
|
||||
|
@ -1062,7 +1062,7 @@ describe('Choices', () => {
|
|||
renderSelectedChoices: 'auto',
|
||||
renderChoiceLimit: -1,
|
||||
});
|
||||
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
|
||||
const renderedChoices = this.choices.choiceList.element.querySelectorAll('.choices__item');
|
||||
expect(renderedChoices.length).toEqual(1);
|
||||
});
|
||||
|
||||
|
@ -1099,7 +1099,7 @@ describe('Choices', () => {
|
|||
renderChoiceLimit: 4,
|
||||
});
|
||||
|
||||
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
|
||||
const renderedChoices = this.choices.choiceList.element.querySelectorAll('.choices__item');
|
||||
expect(renderedChoices.length).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue