mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-17 04:56:34 +02:00
Merge with latest + housekeeping
This commit is contained in:
commit
affd67e542
|
@ -6,7 +6,7 @@ import Input from './components/input';
|
|||
import List from './components/list';
|
||||
import WrappedInput from './components/wrapped-input';
|
||||
import WrappedSelect from './components/wrapped-select';
|
||||
import { DEFAULT_CONFIG, DEFAULT_CLASSNAMES, EVENTS, KEY_CODES } from './constants';
|
||||
import { DEFAULT_CONFIG, DEFAULT_CLASSNAMES, EVENTS, KEY_CODES, SCROLLING_SPEED } from './constants';
|
||||
import { TEMPLATES } from './templates';
|
||||
import { addChoice, filterChoices, activateChoices, clearChoices } from './actions/choices';
|
||||
import { addItem, removeItem, highlightItem } from './actions/items';
|
||||
|
@ -24,7 +24,6 @@ import {
|
|||
sortByAlpha,
|
||||
sortByScore,
|
||||
generateId,
|
||||
triggerEvent,
|
||||
findAncestorByAttrName,
|
||||
regexFilter,
|
||||
} from './lib/utils';
|
||||
|
@ -121,9 +120,9 @@ class Choices {
|
|||
this.presetItems = this.config.items;
|
||||
|
||||
// Then add any values passed from attribute
|
||||
if (this.passedElement.element.value) {
|
||||
if (this.passedElement.getValue()) {
|
||||
this.presetItems = this.presetItems.concat(
|
||||
this.passedElement.element.value.split(this.config.delimiter),
|
||||
this.passedElement.getValue().split(this.config.delimiter),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -219,15 +218,7 @@ class Choices {
|
|||
// Remove all event listeners
|
||||
this._removeEventListeners();
|
||||
this.passedElement.reveal();
|
||||
|
||||
// Move passed element back to original position
|
||||
this.containerOuter.element.parentNode.insertBefore(
|
||||
this.passedElement.element,
|
||||
this.containerOuter.element,
|
||||
);
|
||||
|
||||
// Remove added elements
|
||||
this.containerOuter.element.parentNode.removeChild(this.containerOuter.element);
|
||||
this.containerOuter.revert(this.passedElement.element);
|
||||
|
||||
// Clear data store
|
||||
this.clearStore();
|
||||
|
@ -292,7 +283,7 @@ class Choices {
|
|||
(this.isSelectOneElement || !choice.selected) :
|
||||
true;
|
||||
if (shouldRender) {
|
||||
const dropdownItem = this._getTemplate('choice', choice);
|
||||
const dropdownItem = this._getTemplate('choice', choice, this.config.itemSelectText);
|
||||
choicesFragment.appendChild(dropdownItem);
|
||||
}
|
||||
};
|
||||
|
@ -359,9 +350,9 @@ class Choices {
|
|||
// Simplify store data to just values
|
||||
const itemsFiltered = this.store.getItemsReducedToValues(items);
|
||||
const itemsFilteredString = itemsFiltered.join(this.config.delimiter);
|
||||
|
||||
// Update the value of the hidden input
|
||||
this.passedElement.element.setAttribute('value', itemsFilteredString);
|
||||
this.passedElement.element.value = itemsFilteredString;
|
||||
this.passedElement.setValue(itemsFilteredString);
|
||||
} else {
|
||||
const selectedOptionsFragment = document.createDocumentFragment();
|
||||
const addOptionToFragment = (item) => {
|
||||
|
@ -373,15 +364,13 @@ class Choices {
|
|||
|
||||
// Add each list item to list
|
||||
items.forEach(item => addOptionToFragment(item));
|
||||
|
||||
// Update selected choices
|
||||
this.passedElement.element.innerHTML = '';
|
||||
this.passedElement.element.appendChild(selectedOptionsFragment);
|
||||
// Update the options of the hidden input
|
||||
this.passedElement.setOptions(selectedOptionsFragment);
|
||||
}
|
||||
|
||||
const addItemToFragment = (item) => {
|
||||
// Create new list element
|
||||
const listItem = this._getTemplate('item', item);
|
||||
const listItem = this._getTemplate('item', item, this.config.removeItemButton);
|
||||
// Append it to list
|
||||
itemListFragment.appendChild(listItem);
|
||||
};
|
||||
|
@ -399,104 +388,104 @@ class Choices {
|
|||
*/
|
||||
render() {
|
||||
this.currentState = this.store.getState();
|
||||
const stateChanged = (
|
||||
this.currentState.choices !== this.prevState.choices ||
|
||||
this.currentState.groups !== this.prevState.groups ||
|
||||
this.currentState.items !== this.prevState.items
|
||||
);
|
||||
|
||||
// Only render if our state has actually changed
|
||||
if (this.currentState !== this.prevState) {
|
||||
// Choices
|
||||
if (
|
||||
(this.currentState.choices !== this.prevState.choices ||
|
||||
this.currentState.groups !== this.prevState.groups ||
|
||||
this.currentState.items !== this.prevState.items) &&
|
||||
this.isSelectElement
|
||||
) {
|
||||
// Get active groups/choices
|
||||
const activeGroups = this.store.getGroupsFilteredByActive();
|
||||
const activeChoices = this.store.getChoicesFilteredByActive();
|
||||
|
||||
let choiceListFragment = document.createDocumentFragment();
|
||||
|
||||
// Clear choices
|
||||
this.choiceList.clear();
|
||||
|
||||
// Scroll back to top of choices list
|
||||
if (this.config.resetScrollPosition) {
|
||||
this.choiceList.scrollTo(0);
|
||||
}
|
||||
|
||||
// If we have grouped options
|
||||
if (activeGroups.length >= 1 && this.isSearching !== true) {
|
||||
// If we have a placeholder choice along with groups
|
||||
const activePlaceholders = activeChoices.filter(
|
||||
activeChoice => activeChoice.placeholder === true && activeChoice.groupId === -1,
|
||||
);
|
||||
if (activePlaceholders.length >= 1) {
|
||||
choiceListFragment = this.renderChoices(activePlaceholders, choiceListFragment);
|
||||
}
|
||||
choiceListFragment = this.renderGroups(activeGroups, activeChoices, choiceListFragment);
|
||||
} else if (activeChoices.length >= 1) {
|
||||
choiceListFragment = this.renderChoices(activeChoices, choiceListFragment);
|
||||
}
|
||||
|
||||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
const canAddItem = this._canAddItem(activeItems, this.input.getValue());
|
||||
|
||||
// If we have choices to show
|
||||
if (choiceListFragment.childNodes && choiceListFragment.childNodes.length > 0) {
|
||||
// ...and we can select them
|
||||
if (canAddItem.response) {
|
||||
// ...append them and highlight the first choice
|
||||
this.choiceList.append(choiceListFragment);
|
||||
this._highlightChoice();
|
||||
} else {
|
||||
// ...otherwise show a notice
|
||||
this.choiceList.append(this._getTemplate('notice', canAddItem.notice));
|
||||
}
|
||||
} else {
|
||||
// Otherwise show a notice
|
||||
let dropdownItem;
|
||||
let notice;
|
||||
|
||||
if (this.isSearching) {
|
||||
notice = isType('Function', this.config.noResultsText) ?
|
||||
this.config.noResultsText() :
|
||||
this.config.noResultsText;
|
||||
|
||||
dropdownItem = this._getTemplate('notice', notice, 'no-results');
|
||||
} else {
|
||||
notice = isType('Function', this.config.noChoicesText) ?
|
||||
this.config.noChoicesText() :
|
||||
this.config.noChoicesText;
|
||||
|
||||
dropdownItem = this._getTemplate('notice', notice, 'no-choices');
|
||||
}
|
||||
|
||||
this.choiceList.append(dropdownItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Items
|
||||
if (this.currentState.items !== this.prevState.items) {
|
||||
// Get active items (items that can be selected)
|
||||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
|
||||
// Clear list
|
||||
this.itemList.clear();
|
||||
|
||||
if (activeItems && activeItems.length) {
|
||||
// Create a fragment to store our list items
|
||||
// (so we don't have to update the DOM for each item)
|
||||
const itemListFragment = this.renderItems(activeItems);
|
||||
|
||||
// If we have items to add
|
||||
if (itemListFragment.childNodes) {
|
||||
// Update list
|
||||
this.itemList.append(itemListFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.prevState = this.currentState;
|
||||
if (!stateChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Choices */
|
||||
|
||||
if (this.isSelectElement) {
|
||||
// Get active groups/choices
|
||||
const activeGroups = this.store.getGroupsFilteredByActive();
|
||||
const activeChoices = this.store.getChoicesFilteredByActive();
|
||||
|
||||
let choiceListFragment = document.createDocumentFragment();
|
||||
|
||||
// Clear choices
|
||||
this.choiceList.clear();
|
||||
|
||||
// Scroll back to top of choices list
|
||||
if (this.config.resetScrollPosition) {
|
||||
this.choiceList.scrollTo(0);
|
||||
}
|
||||
|
||||
// If we have grouped options
|
||||
if (activeGroups.length >= 1 && !this.isSearching) {
|
||||
// If we have a placeholder choice along with groups
|
||||
const activePlaceholders = activeChoices.filter(
|
||||
activeChoice => activeChoice.placeholder === true && activeChoice.groupId === -1,
|
||||
);
|
||||
if (activePlaceholders.length >= 1) {
|
||||
choiceListFragment = this.renderChoices(activePlaceholders, choiceListFragment);
|
||||
}
|
||||
choiceListFragment = this.renderGroups(activeGroups, activeChoices, choiceListFragment);
|
||||
} else if (activeChoices.length >= 1) {
|
||||
choiceListFragment = this.renderChoices(activeChoices, choiceListFragment);
|
||||
}
|
||||
|
||||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
const canAddItem = this._canAddItem(activeItems, this.input.getValue());
|
||||
|
||||
// If we have choices to show
|
||||
if (choiceListFragment.childNodes && choiceListFragment.childNodes.length > 0) {
|
||||
// ...and we can select them
|
||||
if (canAddItem.response) {
|
||||
// ...append them and highlight the first choice
|
||||
this.choiceList.append(choiceListFragment);
|
||||
this._highlightChoice();
|
||||
} else {
|
||||
// ...otherwise show a notice
|
||||
this.choiceList.append(this._getTemplate('notice', canAddItem.notice));
|
||||
}
|
||||
} else {
|
||||
// Otherwise show a notice
|
||||
let dropdownItem;
|
||||
let notice;
|
||||
|
||||
if (this.isSearching) {
|
||||
notice = isType('Function', this.config.noResultsText) ?
|
||||
this.config.noResultsText() :
|
||||
this.config.noResultsText;
|
||||
|
||||
dropdownItem = this._getTemplate('notice', notice, 'no-results');
|
||||
} else {
|
||||
notice = isType('Function', this.config.noChoicesText) ?
|
||||
this.config.noChoicesText() :
|
||||
this.config.noChoicesText;
|
||||
|
||||
dropdownItem = this._getTemplate('notice', notice, 'no-choices');
|
||||
}
|
||||
|
||||
this.choiceList.append(dropdownItem);
|
||||
}
|
||||
}
|
||||
|
||||
/* Items */
|
||||
|
||||
// Get active items (items that can be selected)
|
||||
const activeItems = this.store.getItemsFilteredByActive() || [];
|
||||
// Clear list
|
||||
this.itemList.clear();
|
||||
|
||||
if (activeItems.length) {
|
||||
// Create a fragment to store our list items
|
||||
// (so we don't have to update the DOM for each item)
|
||||
const itemListFragment = this.renderItems(activeItems);
|
||||
|
||||
// If we have items to add
|
||||
if (itemListFragment.childNodes) {
|
||||
// Update list
|
||||
this.itemList.append(itemListFragment);
|
||||
}
|
||||
}
|
||||
|
||||
this.prevState = this.currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -530,7 +519,7 @@ class Choices {
|
|||
eventResponse.groupValue = group.value;
|
||||
}
|
||||
|
||||
triggerEvent(this.passedElement.element, EVENTS.highlightItem, eventResponse);
|
||||
this.passedElement.triggerEvent(EVENTS.highlightItem, eventResponse);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -560,11 +549,8 @@ class Choices {
|
|||
eventResponse.groupValue = group.value;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
highlightItem(id, false),
|
||||
);
|
||||
|
||||
triggerEvent(this.passedElement.element, EVENTS.highlightItem, eventResponse);
|
||||
this.store.dispatch(highlightItem(id, false));
|
||||
this.passedElement.triggerEvent(EVENTS.highlightItem, eventResponse);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -668,8 +654,8 @@ class Choices {
|
|||
this.containerOuter.open(this.dropdown.getVerticalPos());
|
||||
this.dropdown.show();
|
||||
this.input.activate(focusInput);
|
||||
this.passedElement.triggerEvent(EVENTS.showDropdown, {});
|
||||
|
||||
triggerEvent(this.passedElement.element, EVENTS.showDropdown, {});
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -686,8 +672,7 @@ class Choices {
|
|||
this.containerOuter.close();
|
||||
this.dropdown.hide();
|
||||
this.input.deactivate(blurInput);
|
||||
|
||||
triggerEvent(this.passedElement.element, EVENTS.hideDropdown, {});
|
||||
this.passedElement.triggerEvent(EVENTS.hideDropdown, {});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -922,11 +907,10 @@ class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
this.passedElement.element.disabled = false;
|
||||
this.passedElement.enable();
|
||||
|
||||
if (this.containerOuter.isDisabled) {
|
||||
this._addEventListeners();
|
||||
this.passedElement.element.removeAttribute('disabled');
|
||||
this.input.enable();
|
||||
this.containerOuter.enable();
|
||||
}
|
||||
|
@ -944,11 +928,10 @@ class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
this.passedElement.element.disabled = true;
|
||||
this.passedElement.disable();
|
||||
|
||||
if (!this.containerOuter.isDisabled) {
|
||||
this._removeEventListeners();
|
||||
this.passedElement.element.setAttribute('disabled', '');
|
||||
this.input.disable();
|
||||
this.containerOuter.disable();
|
||||
}
|
||||
|
@ -992,7 +975,7 @@ class Choices {
|
|||
return;
|
||||
}
|
||||
|
||||
triggerEvent(this.passedElement.element, EVENTS.change, {
|
||||
this.passedElement.triggerEvent(EVENTS.change, {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
@ -1102,7 +1085,7 @@ class Choices {
|
|||
// Update choice keyCode
|
||||
choice.keyCode = passedKeyCode;
|
||||
|
||||
triggerEvent(this.passedElement.element, EVENTS.choice, {
|
||||
this.passedElement.triggerEvent(EVENTS.choice, {
|
||||
choice,
|
||||
});
|
||||
|
||||
|
@ -1155,6 +1138,7 @@ class Choices {
|
|||
this._triggerChange(lastItem.value);
|
||||
} else {
|
||||
if (!hasHighlightedItems) {
|
||||
// Highlight last item if none already highlighted
|
||||
this.highlightItem(lastItem, false);
|
||||
}
|
||||
this.removeHighlightedItems(true);
|
||||
|
@ -1312,26 +1296,26 @@ class Choices {
|
|||
this.currentValue.trim() :
|
||||
this.currentValue;
|
||||
|
||||
// If new value matches the desired length and is not the same as the current value with a space
|
||||
if (newValue.length >= 1 && newValue !== `${currentValue} `) {
|
||||
const haystack = this.store.getSearchableChoices();
|
||||
const needle = newValue;
|
||||
const keys = isType('Array', this.config.searchFields) ?
|
||||
this.config.searchFields :
|
||||
[this.config.searchFields];
|
||||
const options = Object.assign(this.config.fuseOptions, { keys });
|
||||
const fuse = new Fuse(haystack, options);
|
||||
const results = fuse.search(needle);
|
||||
|
||||
this.currentValue = newValue;
|
||||
this.highlightPosition = 0;
|
||||
this.isSearching = true;
|
||||
this.store.dispatch(filterChoices(results));
|
||||
|
||||
return results.length;
|
||||
if (newValue.length < 1 && newValue === `${currentValue} `) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
// If new value matches the desired length and is not the same as the current value with a space
|
||||
const haystack = this.store.getSearchableChoices();
|
||||
const needle = newValue;
|
||||
const keys = isType('Array', this.config.searchFields) ?
|
||||
this.config.searchFields :
|
||||
[this.config.searchFields];
|
||||
const options = Object.assign(this.config.fuseOptions, { keys });
|
||||
const fuse = new Fuse(haystack, options);
|
||||
const results = fuse.search(needle);
|
||||
|
||||
this.currentValue = newValue;
|
||||
this.highlightPosition = 0;
|
||||
this.isSearching = true;
|
||||
this.store.dispatch(filterChoices(results));
|
||||
|
||||
return results.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1352,7 +1336,7 @@ class Choices {
|
|||
if (value && value.length >= this.config.searchFloor) {
|
||||
const resultCount = this.config.searchChoices ? this._searchChoices(value) : 0;
|
||||
// Trigger search event
|
||||
triggerEvent(this.passedElement.element, EVENTS.search, {
|
||||
this.passedElement.triggerEvent(EVENTS.search, {
|
||||
value,
|
||||
resultCount,
|
||||
});
|
||||
|
@ -1453,7 +1437,7 @@ class Choices {
|
|||
this.canSearch = false;
|
||||
if (
|
||||
this.config.removeItems &&
|
||||
!this.input.element.value &&
|
||||
!this.input.getValue() &&
|
||||
this.input.element === document.activeElement
|
||||
) {
|
||||
// Highlight items
|
||||
|
@ -1465,7 +1449,7 @@ class Choices {
|
|||
const onEnterKey = () => {
|
||||
// If enter key is pressed and the input has a value
|
||||
if (this.isTextElement && target.value) {
|
||||
const value = this.input.element.value;
|
||||
const value = this.input.getValue();
|
||||
const canAddItem = this._canAddItem(activeItems, value);
|
||||
|
||||
// All is good, add
|
||||
|
@ -1592,7 +1576,7 @@ class Choices {
|
|||
return;
|
||||
}
|
||||
|
||||
const value = this.input.element.value;
|
||||
const value = this.input.getValue();
|
||||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
const canAddItem = this._canAddItem(activeItems, value);
|
||||
|
||||
|
@ -1614,20 +1598,18 @@ class Choices {
|
|||
this.hideDropdown();
|
||||
}
|
||||
} else {
|
||||
const backKey = 46;
|
||||
const deleteKey = 8;
|
||||
const backKey = KEY_CODES.BACK_KEY;
|
||||
const deleteKey = KEY_CODES.DELETE_KEY;
|
||||
|
||||
// If user has removed value...
|
||||
if ((e.keyCode === backKey || e.keyCode === deleteKey) && !e.target.value) {
|
||||
// ...and it is a multiple select input, activate choices (if searching)
|
||||
if (!this.isTextElement && this.isSearching) {
|
||||
this.isSearching = false;
|
||||
this.store.dispatch(
|
||||
activateChoices(true),
|
||||
);
|
||||
this.store.dispatch(activateChoices(true));
|
||||
}
|
||||
} else if (this.canSearch && canAddItem.response) {
|
||||
this._handleSearch(this.input.element.value);
|
||||
this._handleSearch(this.input.getValue());
|
||||
}
|
||||
}
|
||||
// Re-establish canSearch value from changes in _onKeyDown
|
||||
|
@ -1784,33 +1766,34 @@ class Choices {
|
|||
*/
|
||||
_onFocus(e) {
|
||||
const target = e.target;
|
||||
// If target is something that concerns us
|
||||
if (this.containerOuter.element.contains(target)) {
|
||||
const focusActions = {
|
||||
text: () => {
|
||||
if (target === this.input.element) {
|
||||
this.containerOuter.addFocusState();
|
||||
}
|
||||
},
|
||||
'select-one': () => {
|
||||
this.containerOuter.addFocusState();
|
||||
if (target === this.input.element) {
|
||||
// Show dropdown if it isn't already showing
|
||||
this.showDropdown();
|
||||
}
|
||||
},
|
||||
'select-multiple': () => {
|
||||
if (target === this.input.element) {
|
||||
// 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.addFocusState();
|
||||
this.showDropdown(true);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
focusActions[this.passedElement.element.type]();
|
||||
if (!this.containerOuter.element.contains(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusActions = {
|
||||
text: () => {
|
||||
if (target === this.input.element) {
|
||||
this.containerOuter.addFocusState();
|
||||
}
|
||||
},
|
||||
'select-one': () => {
|
||||
this.containerOuter.addFocusState();
|
||||
if (target === this.input.element) {
|
||||
// Show dropdown if it isn't already showing
|
||||
this.showDropdown();
|
||||
}
|
||||
},
|
||||
'select-multiple': () => {
|
||||
if (target === this.input.element) {
|
||||
// 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.addFocusState();
|
||||
this.showDropdown(true);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
focusActions[this.passedElement.element.type]();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1897,7 +1880,7 @@ class Choices {
|
|||
choice.offsetTop;
|
||||
|
||||
const animateScroll = () => {
|
||||
const strength = 4;
|
||||
const strength = SCROLLING_SPEED;
|
||||
const choiceListScrollTop = this.choiceList.scrollPos;
|
||||
let continueAnimation = false;
|
||||
let easing;
|
||||
|
@ -2046,7 +2029,7 @@ class Choices {
|
|||
|
||||
// Trigger change event
|
||||
if (group && group.value) {
|
||||
triggerEvent(this.passedElement.element, EVENTS.addItem, {
|
||||
this.passedElement.triggerEvent(EVENTS.addItem, {
|
||||
id,
|
||||
value: passedValue,
|
||||
label: passedLabel,
|
||||
|
@ -2054,7 +2037,7 @@ class Choices {
|
|||
keyCode: passedKeyCode,
|
||||
});
|
||||
} else {
|
||||
triggerEvent(this.passedElement.element, EVENTS.addItem, {
|
||||
this.passedElement.triggerEvent(EVENTS.addItem, {
|
||||
id,
|
||||
value: passedValue,
|
||||
label: passedLabel,
|
||||
|
@ -2083,19 +2066,17 @@ class Choices {
|
|||
const groupId = item.groupId;
|
||||
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
|
||||
|
||||
this.store.dispatch(
|
||||
removeItem(id, choiceId),
|
||||
);
|
||||
this.store.dispatch(removeItem(id, choiceId));
|
||||
|
||||
if (group && group.value) {
|
||||
triggerEvent(this.passedElement.element, EVENTS.removeItem, {
|
||||
this.passedElement.triggerEvent(EVENTS.removeItem, {
|
||||
id,
|
||||
value,
|
||||
label,
|
||||
groupValue: group.value,
|
||||
});
|
||||
} else {
|
||||
triggerEvent(this.passedElement.element, EVENTS.removeItem, {
|
||||
this.passedElement.triggerEvent(EVENTS.removeItem, {
|
||||
id,
|
||||
value,
|
||||
label,
|
||||
|
@ -2263,10 +2244,16 @@ class Choices {
|
|||
*/
|
||||
_createInput() {
|
||||
const direction = this.passedElement.element.getAttribute('dir') || 'ltr';
|
||||
const containerOuter = this._getTemplate('containerOuter', direction);
|
||||
const containerOuter = this._getTemplate('containerOuter',
|
||||
direction,
|
||||
this.isSelectElement,
|
||||
this.isSelectOneElement,
|
||||
this.config.searchEnabled,
|
||||
this.passedElement.element.type,
|
||||
);
|
||||
const containerInner = this._getTemplate('containerInner');
|
||||
const itemList = this._getTemplate('itemList');
|
||||
const choiceList = this._getTemplate('choiceList');
|
||||
const itemList = this._getTemplate('itemList', this.isSelectOneElement);
|
||||
const choiceList = this._getTemplate('choiceList', this.isSelectOneElement);
|
||||
const input = this._getTemplate('input');
|
||||
const dropdown = this._getTemplate('dropdown');
|
||||
|
||||
|
@ -2281,7 +2268,6 @@ class Choices {
|
|||
|
||||
// Wrap input in container preserving DOM ordering
|
||||
wrap(this.passedElement.element, this.containerInner.element);
|
||||
|
||||
// Wrapper inner container with outer container
|
||||
wrap(this.containerInner.element, this.containerOuter.element);
|
||||
|
||||
|
@ -2304,21 +2290,21 @@ class Choices {
|
|||
dropdown.appendChild(choiceList);
|
||||
}
|
||||
|
||||
if (this.isSelectMultipleElement || this.isTextElement) {
|
||||
if (!this.isSelectOneElement) {
|
||||
this.containerInner.element.appendChild(this.input.element);
|
||||
} else if (this.canSearch) {
|
||||
dropdown.insertBefore(input, dropdown.firstChild);
|
||||
}
|
||||
|
||||
if (this.isSelectElement) {
|
||||
const passedGroups = Array.from(this.passedElement.element.getElementsByTagName('OPTGROUP'));
|
||||
const passedGroups = this.passedElement.getOptionGroups();
|
||||
|
||||
this.highlightPosition = 0;
|
||||
this.isSearching = false;
|
||||
|
||||
if (passedGroups && passedGroups.length) {
|
||||
// If we have a placeholder option
|
||||
const placeholderChoice = this.passedElement.element.querySelector('option[placeholder]');
|
||||
const placeholderChoice = this.passedElement.getPlaceholderOption();
|
||||
if (placeholderChoice && placeholderChoice.parentNode.tagName === 'SELECT') {
|
||||
this._addChoice(
|
||||
placeholderChoice.value,
|
||||
|
@ -2335,7 +2321,7 @@ class Choices {
|
|||
this._addGroup(group, (group.id || null));
|
||||
});
|
||||
} else {
|
||||
const passedOptions = Array.from(this.passedElement.element.options);
|
||||
const passedOptions = this.passedElement.getOptions();
|
||||
const filter = this.config.sortFilter;
|
||||
const allChoices = this.presetChoices;
|
||||
|
||||
|
|
|
@ -32,27 +32,27 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
it('is defined', () => {
|
||||
expect(instance).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('should have initialised', () => {
|
||||
it('initialises', () => {
|
||||
expect(instance.initialised).to.be.true;
|
||||
});
|
||||
|
||||
it('should not re-initialise if passed element again', () => {
|
||||
it('does not re-initialise if passed element again', () => {
|
||||
const reinitialise = new Choices(instance.passedElement.element);
|
||||
sinon.spy(reinitialise, '_createTemplates');
|
||||
expect(reinitialise._createTemplates.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('should have a blank state', () => {
|
||||
it('has a blank state', () => {
|
||||
expect(instance.currentState.items.length).to.equal(0);
|
||||
expect(instance.currentState.groups.length).to.equal(0);
|
||||
expect(instance.currentState.choices.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('should have config options', () => {
|
||||
it('has expected config options', () => {
|
||||
expect(instance.config.silent).to.be.a('boolean');
|
||||
expect(instance.config.items).to.be.an('array');
|
||||
expect(instance.config.choices).to.be.an('array');
|
||||
|
@ -90,7 +90,7 @@ describe('Choices', () => {
|
|||
expect(instance.config.callbackOnCreateTemplates).to.be.null;
|
||||
});
|
||||
|
||||
it('should expose public methods', () => {
|
||||
it('exposes public methods', () => {
|
||||
expect(instance.init).to.be.a('function');
|
||||
expect(instance.destroy).to.be.a('function');
|
||||
expect(instance.render).to.be.a('function');
|
||||
|
@ -118,35 +118,35 @@ describe('Choices', () => {
|
|||
expect(instance.clearInput).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should hide passed input', () => {
|
||||
it('hides passed input', () => {
|
||||
expect(instance.passedElement.element.style.display).to.equal('none');
|
||||
});
|
||||
|
||||
it('should create an outer container', () => {
|
||||
it('creates an outer container', () => {
|
||||
expect(instance.containerOuter).to.be.an.instanceof(Container);
|
||||
});
|
||||
|
||||
it('should create an inner container', () => {
|
||||
it('creates an inner container', () => {
|
||||
expect(instance.containerInner).to.be.an.instanceof(Container);
|
||||
});
|
||||
|
||||
it('should create a choice list', () => {
|
||||
it('creates a choice list', () => {
|
||||
expect(instance.choiceList).to.be.an.instanceof(List);
|
||||
});
|
||||
|
||||
it('should create an item list', () => {
|
||||
it('creates an item list', () => {
|
||||
expect(instance.itemList).to.be.an.instanceof(List);
|
||||
});
|
||||
|
||||
it('should create an input', () => {
|
||||
it('creates an input', () => {
|
||||
expect(instance.input).to.be.an.instanceof(Input);
|
||||
});
|
||||
|
||||
it('should create a dropdown', () => {
|
||||
it('creates a dropdown', () => {
|
||||
expect(instance.dropdown).to.be.an.instanceof(Dropdown);
|
||||
});
|
||||
|
||||
it('should backup and recover original styles', () => {
|
||||
it('backs up and recovers original styles', () => {
|
||||
const origStyle = 'background-color: #ccc; margin: 5px padding: 10px;';
|
||||
|
||||
instance.destroy();
|
||||
|
@ -165,7 +165,7 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should accept text inputs', () => {
|
||||
describe('text inputs', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -182,12 +182,12 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should wrap passed input', () => {
|
||||
it('wraps passed input', () => {
|
||||
instance = new Choices(input);
|
||||
expect(instance.passedElement).to.be.an.instanceof(WrappedInput);
|
||||
});
|
||||
|
||||
it('should accept a user inputted value', () => {
|
||||
it('accepts a user inputted value', () => {
|
||||
instance = new Choices(input);
|
||||
|
||||
instance.input.element.focus();
|
||||
|
@ -202,13 +202,13 @@ describe('Choices', () => {
|
|||
expect(instance.currentState.items[0].value).to.include(instance.input.element.value);
|
||||
});
|
||||
|
||||
it('should copy the passed placeholder to the cloned input', () => {
|
||||
it('copys the passed placeholder to the cloned input', () => {
|
||||
instance = new Choices(input);
|
||||
|
||||
expect(instance.input.element.placeholder).to.equal(input.placeholder);
|
||||
});
|
||||
|
||||
it('should not allow duplicates if duplicateItems is false', () => {
|
||||
it('doesn\'t allow duplicate items if duplicateItems is false', () => {
|
||||
instance = new Choices(input, {
|
||||
duplicateItems: false,
|
||||
items: ['test 1'],
|
||||
|
@ -226,7 +226,7 @@ describe('Choices', () => {
|
|||
expect(instance.currentState.items[instance.currentState.items.length - 1].value).to.equal(instance.input.element.value);
|
||||
});
|
||||
|
||||
it('should filter input if regexFilter is passed', () => {
|
||||
it('filters input if regexFilter is passed', () => {
|
||||
instance = new Choices(input, {
|
||||
regexFilter: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
});
|
||||
|
@ -255,7 +255,7 @@ describe('Choices', () => {
|
|||
expect(lastItem.value).not.to.equal('not an email address');
|
||||
});
|
||||
|
||||
it('should prepend and append values if passed', () => {
|
||||
it('prepends and appends values if passed', () => {
|
||||
instance = new Choices(input, {
|
||||
prependValue: 'item-',
|
||||
appendValue: '-value',
|
||||
|
@ -277,7 +277,7 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should accept single select inputs', () => {
|
||||
describe('single select inputs', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -302,23 +302,23 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should wrap passed input', () => {
|
||||
it('wraps passed input', () => {
|
||||
instance = new Choices(input);
|
||||
expect(instance.passedElement).to.be.an.instanceof(WrappedSelect);
|
||||
});
|
||||
|
||||
it('should open the choice list on focusing', () => {
|
||||
it('opens the choice list on focusing', () => {
|
||||
instance = new Choices(input);
|
||||
instance.input.element.focus();
|
||||
expect(instance.dropdown.element.classList.contains(instance.config.classNames.activeState)).to.be.true;
|
||||
});
|
||||
|
||||
it('should select the first choice', () => {
|
||||
it('selects the first choice', () => {
|
||||
instance = new Choices(input);
|
||||
expect(instance.currentState.items[0].value).to.include('Value 1');
|
||||
});
|
||||
|
||||
it('should highlight the choices on keydown', () => {
|
||||
it('highlights the choices on keydown', () => {
|
||||
instance = new Choices(input, {
|
||||
renderChoiceLimit: -1,
|
||||
});
|
||||
|
@ -337,7 +337,7 @@ describe('Choices', () => {
|
|||
expect(instance.highlightPosition).to.equal(2);
|
||||
});
|
||||
|
||||
it('should select choice on enter key press', () => {
|
||||
it('selects choice on enter key press', () => {
|
||||
instance = new Choices(input);
|
||||
instance.input.element.focus();
|
||||
|
||||
|
@ -360,7 +360,7 @@ describe('Choices', () => {
|
|||
expect(instance.currentState.items.length).to.equal(2);
|
||||
});
|
||||
|
||||
it('should trigger add/change event on selection', () => {
|
||||
it('triggers add/change event on selection', () => {
|
||||
instance = new Choices(input);
|
||||
|
||||
const onChangeStub = sinon.stub();
|
||||
|
@ -394,7 +394,7 @@ describe('Choices', () => {
|
|||
expect(addSpyStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should open the dropdown on click', () => {
|
||||
it('opens the dropdown on click', () => {
|
||||
instance = new Choices(input);
|
||||
const container = instance.containerOuter.element;
|
||||
instance._onClick({
|
||||
|
@ -406,7 +406,7 @@ describe('Choices', () => {
|
|||
expect(document.activeElement === instance.input.element && container.classList.contains('is-open')).to.be.true;
|
||||
});
|
||||
|
||||
it('should close the dropdown on double click', () => {
|
||||
it('closes the dropdown on double click', () => {
|
||||
instance = new Choices(input);
|
||||
const container = instance.containerOuter.element;
|
||||
const openState = instance.config.classNames.openState;
|
||||
|
@ -426,7 +426,7 @@ describe('Choices', () => {
|
|||
expect(document.activeElement === instance.input.element && container.classList.contains(openState)).to.be.false;
|
||||
});
|
||||
|
||||
it('should trigger showDropdown on dropdown opening', () => {
|
||||
it('triggers showDropdown on dropdown opening', () => {
|
||||
instance = new Choices(input);
|
||||
const container = instance.containerOuter.element;
|
||||
|
||||
|
@ -446,7 +446,7 @@ describe('Choices', () => {
|
|||
expect(showDropdownStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should trigger hideDropdown on dropdown closing', () => {
|
||||
it('triggers hideDropdown on dropdown closing', () => {
|
||||
instance = new Choices(input);
|
||||
|
||||
const container = instance.containerOuter.element;
|
||||
|
@ -472,7 +472,7 @@ describe('Choices', () => {
|
|||
expect(hideDropdownStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should filter choices when searching', () => {
|
||||
it('filters choices when searching', () => {
|
||||
instance = new Choices(input);
|
||||
|
||||
const onSearchStub = sinon.spy();
|
||||
|
@ -496,7 +496,7 @@ describe('Choices', () => {
|
|||
expect(onSearchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('shouldn\'t filter choices when searching', () => {
|
||||
it('doesn\'t filter choices when searching', () => {
|
||||
instance = new Choices(input, {
|
||||
searchChoices: false,
|
||||
});
|
||||
|
@ -524,7 +524,7 @@ describe('Choices', () => {
|
|||
expect(onSearchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('shouldn\'t sort choices if shouldSort is false', () => {
|
||||
it('doesn\'t sort choices if shouldSort is false', () => {
|
||||
instance = new Choices(input, {
|
||||
shouldSort: false,
|
||||
choices: [
|
||||
|
@ -544,7 +544,7 @@ describe('Choices', () => {
|
|||
expect(instance.currentState.choices[0].value).to.equal('Value 5');
|
||||
});
|
||||
|
||||
it('should sort choices if shouldSort is true', () => {
|
||||
it('sorts choices if shouldSort is true', () => {
|
||||
instance = new Choices(input, {
|
||||
shouldSort: true,
|
||||
choices: [
|
||||
|
@ -565,7 +565,7 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should accept multiple select inputs', () => {
|
||||
describe('multiple select inputs', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -613,24 +613,24 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should wrap passed input', () => {
|
||||
it('wraps passed input', () => {
|
||||
expect(instance.passedElement).to.be.an.instanceof(WrappedSelect);
|
||||
});
|
||||
|
||||
it('should add any pre-defined values', () => {
|
||||
it('adds any pre-defined values', () => {
|
||||
expect(instance.currentState.items.length).to.be.above(1);
|
||||
});
|
||||
|
||||
it('should add options defined in the config + pre-defined options', () => {
|
||||
it('adds options defined in the config + pre-defined options', () => {
|
||||
expect(instance.currentState.choices.length).to.equal(6);
|
||||
});
|
||||
|
||||
it('should add a placeholder defined in the config to the search input', () => {
|
||||
it('adds a placeholder defined in the config to the search input', () => {
|
||||
expect(instance.input.element.placeholder).to.equal('Placeholder text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle public methods on select input types', () => {
|
||||
describe('handles public methods on select input types', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -661,7 +661,7 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should handle highlightItem()', () => {
|
||||
it('handles highlightItem()', () => {
|
||||
const items = instance.currentState.items;
|
||||
const randomItem = items[Math.floor(Math.random() * items.length)];
|
||||
|
||||
|
@ -670,7 +670,7 @@ describe('Choices', () => {
|
|||
expect(randomItem.highlighted).to.be.true;
|
||||
});
|
||||
|
||||
it('should handle unhighlightItem()', () => {
|
||||
it('handles unhighlightItem()', () => {
|
||||
const items = instance.currentState.items;
|
||||
const randomItem = items[Math.floor(Math.random() * items.length)];
|
||||
|
||||
|
@ -679,7 +679,7 @@ describe('Choices', () => {
|
|||
expect(randomItem.highlighted).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle highlightAll()', () => {
|
||||
it('handles highlightAll()', () => {
|
||||
const items = instance.currentState.items;
|
||||
|
||||
instance.highlightAll();
|
||||
|
@ -689,7 +689,7 @@ describe('Choices', () => {
|
|||
expect(unhighlightedItems).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle unhighlightAll()', () => {
|
||||
it('handles unhighlightAll()', () => {
|
||||
const items = instance.currentState.items;
|
||||
|
||||
instance.unhighlightAll();
|
||||
|
@ -699,7 +699,7 @@ describe('Choices', () => {
|
|||
expect(highlightedItems).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle removeHighlightedItems()', () => {
|
||||
it('handles removeHighlightedItems()', () => {
|
||||
const items = instance.currentState.items;
|
||||
instance.highlightAll();
|
||||
instance.removeHighlightedItems();
|
||||
|
@ -709,7 +709,7 @@ describe('Choices', () => {
|
|||
expect(activeItems).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle showDropdown()', () => {
|
||||
it('handles showDropdown()', () => {
|
||||
instance.showDropdown();
|
||||
|
||||
const hasOpenState = instance.containerOuter.element.classList.contains(instance.config.classNames.openState);
|
||||
|
@ -719,7 +719,7 @@ describe('Choices', () => {
|
|||
expect(hasOpenState && hasAttr && hasActiveState).to.be.true;
|
||||
});
|
||||
|
||||
it('should handle hideDropdown()', () => {
|
||||
it('handles hideDropdown()', () => {
|
||||
instance.showDropdown();
|
||||
instance.hideDropdown();
|
||||
|
||||
|
@ -731,14 +731,14 @@ describe('Choices', () => {
|
|||
expect(hasOpenState && hasAttr && hasActiveState).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle toggleDropdown()', () => {
|
||||
it('handles toggleDropdown()', () => {
|
||||
sinon.spy(instance, 'hideDropdown');
|
||||
instance.showDropdown();
|
||||
instance.toggleDropdown();
|
||||
expect(instance.hideDropdown.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should handle getValue()', () => {
|
||||
it('handles getValue()', () => {
|
||||
const valueObjects = instance.getValue();
|
||||
const valueStrings = instance.getValue(true);
|
||||
|
||||
|
@ -748,7 +748,7 @@ describe('Choices', () => {
|
|||
expect(valueObjects.length).to.equal(5);
|
||||
});
|
||||
|
||||
it('should handle setValue()', () => {
|
||||
it('handles setValue()', () => {
|
||||
instance.setValue(['Set value 1', 'Set value 2', 'Set value 3']);
|
||||
const valueStrings = instance.getValue(true);
|
||||
|
||||
|
@ -757,7 +757,7 @@ describe('Choices', () => {
|
|||
expect(valueStrings[valueStrings.length - 3]).to.equal('Set value 1');
|
||||
});
|
||||
|
||||
it('should handle setValueByChoice()', () => {
|
||||
it('handles setValueByChoice()', () => {
|
||||
const choices = instance.store.getChoicesFilteredByActive();
|
||||
const randomChoice = choices[Math.floor(Math.random() * choices.length)];
|
||||
|
||||
|
@ -770,7 +770,7 @@ describe('Choices', () => {
|
|||
expect(value[0]).to.equal(randomChoice.value);
|
||||
});
|
||||
|
||||
it('should handle setChoices()', () => {
|
||||
it('handles setChoices()', () => {
|
||||
instance.setChoices([{
|
||||
label: 'Group one',
|
||||
id: 1,
|
||||
|
@ -818,7 +818,7 @@ describe('Choices', () => {
|
|||
expect(choices[choices.length - 2].value).to.equal('Child Five');
|
||||
});
|
||||
|
||||
it('should handle setChoices() with blank values', () => {
|
||||
it('handles setChoices() with blank values', () => {
|
||||
instance.setChoices([{
|
||||
label: 'Choice one',
|
||||
value: 'one',
|
||||
|
@ -833,7 +833,7 @@ describe('Choices', () => {
|
|||
expect(choices[1].value).to.equal('');
|
||||
});
|
||||
|
||||
it('should handle clearStore()', () => {
|
||||
it('handles clearStore()', () => {
|
||||
instance.clearStore();
|
||||
|
||||
expect(instance.currentState.items).to.have.lengthOf(0);
|
||||
|
@ -841,7 +841,7 @@ describe('Choices', () => {
|
|||
expect(instance.currentState.groups).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('should handle disable()', () => {
|
||||
it('handles disable()', () => {
|
||||
instance.disable();
|
||||
|
||||
expect(instance.input.element.disabled).to.be.true;
|
||||
|
@ -853,7 +853,7 @@ describe('Choices', () => {
|
|||
expect(instance.containerOuter.element.getAttribute('aria-disabled')).to.equal('true');
|
||||
});
|
||||
|
||||
it('should handle enable()', () => {
|
||||
it('handles enable()', () => {
|
||||
instance.enable();
|
||||
|
||||
expect(instance.input.element.disabled).to.be.false;
|
||||
|
@ -865,7 +865,7 @@ describe('Choices', () => {
|
|||
expect(instance.containerOuter.element.hasAttribute('aria-disabled')).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle ajax()', () => {
|
||||
it('handles ajax()', () => {
|
||||
const dummyFn = sinon.spy();
|
||||
|
||||
instance.ajax(dummyFn);
|
||||
|
@ -874,7 +874,7 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should handle public methods on select-one input types', () => {
|
||||
describe('handles public methods on select-one input types', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -904,20 +904,20 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should handle disable()', () => {
|
||||
it('handles disable()', () => {
|
||||
instance.disable();
|
||||
|
||||
expect(instance.containerOuter.element.getAttribute('tabindex')).to.equal('-1');
|
||||
});
|
||||
|
||||
it('should handle enable()', () => {
|
||||
it('handles enable()', () => {
|
||||
instance.enable();
|
||||
|
||||
expect(instance.containerOuter.element.getAttribute('tabindex')).to.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle public methods on text input types', () => {
|
||||
describe('handles public methods on text input types', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -935,12 +935,12 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should handle clearInput()', () => {
|
||||
it('handles clearInput()', () => {
|
||||
instance.clearInput();
|
||||
expect(instance.input.element.value).to.equal('');
|
||||
});
|
||||
|
||||
it('should handle removeItemsByValue()', () => {
|
||||
it('handles removeItemsByValue()', () => {
|
||||
const items = instance.currentState.items;
|
||||
const randomItem = items[Math.floor(Math.random() * items.length)];
|
||||
|
||||
|
@ -949,7 +949,7 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should react to config options', () => {
|
||||
describe('reacts to config options', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -1056,7 +1056,7 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('should allow custom properties provided by the user on items or choices', () => {
|
||||
describe('allows custom properties provided by the user on items or choices', () => {
|
||||
let input;
|
||||
let instance;
|
||||
|
||||
|
@ -1072,7 +1072,7 @@ describe('Choices', () => {
|
|||
instance.destroy();
|
||||
});
|
||||
|
||||
it('should allow the user to supply custom properties for a choice that will be inherited by the item when the user selects the choice', () => {
|
||||
it('allows the user to supply custom properties for a choice that will be inherited by the item when the user selects the choice', () => {
|
||||
const expectedCustomProperties = {
|
||||
isBestOptionEver: true,
|
||||
};
|
||||
|
@ -1093,7 +1093,7 @@ describe('Choices', () => {
|
|||
expect(selectedItems[0].customProperties).to.equal(expectedCustomProperties);
|
||||
});
|
||||
|
||||
it('should allow the user to supply custom properties when directly creating a selected item', () => {
|
||||
it('allows the user to supply custom properties when directly creating a selected item', () => {
|
||||
const expectedCustomProperties = {
|
||||
isBestOptionEver: true,
|
||||
};
|
||||
|
|
|
@ -13,6 +13,10 @@ export default class Container {
|
|||
this.onBlur = this.onBlur.bind(this);
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners
|
||||
*/
|
||||
|
@ -155,6 +159,16 @@ export default class Container {
|
|||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
revert(originalElement) {
|
||||
// Move passed element back to original position
|
||||
this.element.parentNode.insertBefore(
|
||||
originalElement,
|
||||
this.element,
|
||||
);
|
||||
// Remove container
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add loading state to element
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,10 @@ export default class Dropdown {
|
|||
this.isActive = false;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how far the top of our element is from
|
||||
* the top of the window
|
||||
|
|
|
@ -15,6 +15,10 @@ export default class Input {
|
|||
this.onBlur = this.onBlur.bind(this);
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.element.addEventListener('input', this.onInput);
|
||||
this.element.addEventListener('paste', this.onPaste);
|
||||
|
|
|
@ -8,6 +8,10 @@ export default class List {
|
|||
this.hasChildren = !!this.element.children;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear List contents
|
||||
*/
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
import { dispatchEvent } from '../lib/utils';
|
||||
|
||||
export default class WrappedElement {
|
||||
constructor(instance, element, classNames) {
|
||||
this.parentInstance = instance;
|
||||
this.element = element;
|
||||
this.classNames = classNames;
|
||||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.element.value;
|
||||
}
|
||||
|
||||
conceal() {
|
||||
|
@ -50,4 +61,20 @@ export default class WrappedElement {
|
|||
// Re-assign values - this is weird, I know
|
||||
this.element.value = this.element.value;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.element.removeAttribute('disabled');
|
||||
this.element.disabled = false;
|
||||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.element.setAttribute('disabled', '');
|
||||
this.element.disabled = true;
|
||||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
triggerEvent(eventType, data) {
|
||||
dispatchEvent(this.element, eventType, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@ export default class WrappedInput extends WrappedElement {
|
|||
this.classNames = classNames;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
super.getElement();
|
||||
}
|
||||
|
||||
conceal() {
|
||||
super.conceal();
|
||||
}
|
||||
|
@ -15,4 +19,17 @@ export default class WrappedInput extends WrappedElement {
|
|||
reveal() {
|
||||
super.reveal();
|
||||
}
|
||||
|
||||
enable() {
|
||||
super.enable();
|
||||
}
|
||||
|
||||
disable() {
|
||||
super.enable();
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.element.setAttribute('value', value);
|
||||
this.element.value = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@ export default class WrappedSelect extends WrappedElement {
|
|||
this.classNames = classNames;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
super.getElement();
|
||||
}
|
||||
|
||||
conceal() {
|
||||
super.conceal();
|
||||
}
|
||||
|
@ -15,4 +19,29 @@ export default class WrappedSelect extends WrappedElement {
|
|||
reveal() {
|
||||
super.reveal();
|
||||
}
|
||||
|
||||
enable() {
|
||||
super.enable();
|
||||
}
|
||||
|
||||
disable() {
|
||||
super.enable();
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
this.element.innerHTML = '';
|
||||
this.element.appendChild(options);
|
||||
}
|
||||
|
||||
getPlaceholderOption() {
|
||||
return this.element.querySelector('option[placeholder]');
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return Array.from(this.element.options);
|
||||
}
|
||||
|
||||
getOptionGroups() {
|
||||
return Array.from(this.element.getElementsByTagName('OPTGROUP'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,3 +103,5 @@ export const KEY_CODES = {
|
|||
PAGE_UP_KEY: 33,
|
||||
PAGE_DOWN_KEY: 34,
|
||||
};
|
||||
|
||||
export const SCROLLING_SPEED = 4;
|
||||
|
|
|
@ -552,13 +552,13 @@ export const sortByAlpha = (a, b) => {
|
|||
export const sortByScore = (a, b) => a.score - b.score;
|
||||
|
||||
/**
|
||||
* Trigger native event
|
||||
* Dispatch native event
|
||||
* @param {NodeElement} element Element to trigger event on
|
||||
* @param {String} type Type of event to trigger
|
||||
* @param {Object} customArgs Data to pass with event
|
||||
* @return {Object} Triggered event
|
||||
*/
|
||||
export const triggerEvent = (element, type, customArgs = null) => {
|
||||
export const dispatchEvent = (element, type, customArgs = null) => {
|
||||
const event = new CustomEvent(type, {
|
||||
detail: customArgs,
|
||||
bubbles: true,
|
||||
|
|
|
@ -2,12 +2,19 @@ import classNames from 'classnames';
|
|||
import { strToEl } from './lib/utils';
|
||||
|
||||
export const TEMPLATES = {
|
||||
containerOuter(globalClasses, direction) {
|
||||
const tabIndex = this.isSelectOneElement ? 'tabindex="0"' : '';
|
||||
let role = this.isSelectElement ? 'role="listbox"' : '';
|
||||
containerOuter(
|
||||
globalClasses,
|
||||
direction,
|
||||
isSelectElement,
|
||||
isSelectOneElement,
|
||||
searchEnabled,
|
||||
passedElementType,
|
||||
) {
|
||||
const tabIndex = isSelectOneElement ? 'tabindex="0"' : '';
|
||||
let role = isSelectElement ? 'role="listbox"' : '';
|
||||
let ariaAutoComplete = '';
|
||||
|
||||
if (this.isSelectElement && this.config.searchEnabled) {
|
||||
if (isSelectElement && searchEnabled) {
|
||||
role = 'role="combobox"';
|
||||
ariaAutoComplete = 'aria-autocomplete="list"';
|
||||
}
|
||||
|
@ -15,7 +22,7 @@ export const TEMPLATES = {
|
|||
return strToEl(`
|
||||
<div
|
||||
class="${globalClasses.containerOuter}"
|
||||
data-type="${this.passedElement.element.type}"
|
||||
data-type="${passedElementType}"
|
||||
${role}
|
||||
${tabIndex}
|
||||
${ariaAutoComplete}
|
||||
|
@ -31,11 +38,11 @@ export const TEMPLATES = {
|
|||
<div class="${globalClasses.containerInner}"></div>
|
||||
`);
|
||||
},
|
||||
itemList(globalClasses) {
|
||||
itemList(globalClasses, isSelectOneElement) {
|
||||
const localClasses = classNames(
|
||||
globalClasses.list, {
|
||||
[globalClasses.listSingle]: (this.isSelectOneElement),
|
||||
[globalClasses.listItems]: (!this.isSelectOneElement),
|
||||
[globalClasses.listSingle]: (isSelectOneElement),
|
||||
[globalClasses.listItems]: (!isSelectOneElement),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -50,7 +57,7 @@ export const TEMPLATES = {
|
|||
</div>
|
||||
`);
|
||||
},
|
||||
item(globalClasses, data) {
|
||||
item(globalClasses, data, removeItemButton) {
|
||||
const ariaSelected = data.active ? 'aria-selected="true"' : '';
|
||||
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
|
||||
|
||||
|
@ -62,7 +69,7 @@ export const TEMPLATES = {
|
|||
},
|
||||
);
|
||||
|
||||
if (this.config.removeItemButton) {
|
||||
if (removeItemButton) {
|
||||
localClasses = classNames(
|
||||
globalClasses.item, {
|
||||
[globalClasses.highlightedState]: data.highlighted,
|
||||
|
@ -107,8 +114,8 @@ export const TEMPLATES = {
|
|||
</div>
|
||||
`);
|
||||
},
|
||||
choiceList(globalClasses) {
|
||||
const ariaMultiSelectable = !this.isSelectOneElement ?
|
||||
choiceList(globalClasses, isSelectOneElement) {
|
||||
const ariaMultiSelectable = !isSelectOneElement ?
|
||||
'aria-multiselectable="true"' :
|
||||
'';
|
||||
|
||||
|
@ -143,7 +150,7 @@ export const TEMPLATES = {
|
|||
</div>
|
||||
`);
|
||||
},
|
||||
choice(globalClasses, data) {
|
||||
choice(globalClasses, data, itemSelectText) {
|
||||
const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"';
|
||||
const localClasses = classNames(
|
||||
globalClasses.item,
|
||||
|
@ -157,7 +164,7 @@ export const TEMPLATES = {
|
|||
return strToEl(`
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-select-text="${this.config.itemSelectText}"
|
||||
data-select-text="${itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
|
|
Loading…
Reference in a new issue