Merge with latest + housekeeping

This commit is contained in:
Josh Johnson 2017-10-29 18:57:54 +00:00
commit affd67e542
12 changed files with 381 additions and 287 deletions

View file

@ -6,7 +6,7 @@ import Input from './components/input';
import List from './components/list'; import List from './components/list';
import WrappedInput from './components/wrapped-input'; import WrappedInput from './components/wrapped-input';
import WrappedSelect from './components/wrapped-select'; 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 { TEMPLATES } from './templates';
import { addChoice, filterChoices, activateChoices, clearChoices } from './actions/choices'; import { addChoice, filterChoices, activateChoices, clearChoices } from './actions/choices';
import { addItem, removeItem, highlightItem } from './actions/items'; import { addItem, removeItem, highlightItem } from './actions/items';
@ -24,7 +24,6 @@ import {
sortByAlpha, sortByAlpha,
sortByScore, sortByScore,
generateId, generateId,
triggerEvent,
findAncestorByAttrName, findAncestorByAttrName,
regexFilter, regexFilter,
} from './lib/utils'; } from './lib/utils';
@ -121,9 +120,9 @@ class Choices {
this.presetItems = this.config.items; this.presetItems = this.config.items;
// Then add any values passed from attribute // Then add any values passed from attribute
if (this.passedElement.element.value) { if (this.passedElement.getValue()) {
this.presetItems = this.presetItems.concat( 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 // Remove all event listeners
this._removeEventListeners(); this._removeEventListeners();
this.passedElement.reveal(); this.passedElement.reveal();
this.containerOuter.revert(this.passedElement.element);
// 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);
// Clear data store // Clear data store
this.clearStore(); this.clearStore();
@ -292,7 +283,7 @@ class Choices {
(this.isSelectOneElement || !choice.selected) : (this.isSelectOneElement || !choice.selected) :
true; true;
if (shouldRender) { if (shouldRender) {
const dropdownItem = this._getTemplate('choice', choice); const dropdownItem = this._getTemplate('choice', choice, this.config.itemSelectText);
choicesFragment.appendChild(dropdownItem); choicesFragment.appendChild(dropdownItem);
} }
}; };
@ -359,9 +350,9 @@ class Choices {
// Simplify store data to just values // Simplify store data to just values
const itemsFiltered = this.store.getItemsReducedToValues(items); const itemsFiltered = this.store.getItemsReducedToValues(items);
const itemsFilteredString = itemsFiltered.join(this.config.delimiter); const itemsFilteredString = itemsFiltered.join(this.config.delimiter);
// Update the value of the hidden input // Update the value of the hidden input
this.passedElement.element.setAttribute('value', itemsFilteredString); this.passedElement.setValue(itemsFilteredString);
this.passedElement.element.value = itemsFilteredString;
} else { } else {
const selectedOptionsFragment = document.createDocumentFragment(); const selectedOptionsFragment = document.createDocumentFragment();
const addOptionToFragment = (item) => { const addOptionToFragment = (item) => {
@ -373,15 +364,13 @@ class Choices {
// Add each list item to list // Add each list item to list
items.forEach(item => addOptionToFragment(item)); items.forEach(item => addOptionToFragment(item));
// Update the options of the hidden input
// Update selected choices this.passedElement.setOptions(selectedOptionsFragment);
this.passedElement.element.innerHTML = '';
this.passedElement.element.appendChild(selectedOptionsFragment);
} }
const addItemToFragment = (item) => { const addItemToFragment = (item) => {
// Create new list element // Create new list element
const listItem = this._getTemplate('item', item); const listItem = this._getTemplate('item', item, this.config.removeItemButton);
// Append it to list // Append it to list
itemListFragment.appendChild(listItem); itemListFragment.appendChild(listItem);
}; };
@ -399,104 +388,104 @@ class Choices {
*/ */
render() { render() {
this.currentState = this.store.getState(); 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 (!stateChanged) {
if (this.currentState !== this.prevState) { return;
// 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;
} }
/* 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; eventResponse.groupValue = group.value;
} }
triggerEvent(this.passedElement.element, EVENTS.highlightItem, eventResponse); this.passedElement.triggerEvent(EVENTS.highlightItem, eventResponse);
} }
return this; return this;
@ -560,11 +549,8 @@ class Choices {
eventResponse.groupValue = group.value; eventResponse.groupValue = group.value;
} }
this.store.dispatch( this.store.dispatch(highlightItem(id, false));
highlightItem(id, false), this.passedElement.triggerEvent(EVENTS.highlightItem, eventResponse);
);
triggerEvent(this.passedElement.element, EVENTS.highlightItem, eventResponse);
return this; return this;
} }
@ -668,8 +654,8 @@ class Choices {
this.containerOuter.open(this.dropdown.getVerticalPos()); this.containerOuter.open(this.dropdown.getVerticalPos());
this.dropdown.show(); this.dropdown.show();
this.input.activate(focusInput); this.input.activate(focusInput);
this.passedElement.triggerEvent(EVENTS.showDropdown, {});
triggerEvent(this.passedElement.element, EVENTS.showDropdown, {});
return this; return this;
} }
@ -686,8 +672,7 @@ class Choices {
this.containerOuter.close(); this.containerOuter.close();
this.dropdown.hide(); this.dropdown.hide();
this.input.deactivate(blurInput); this.input.deactivate(blurInput);
this.passedElement.triggerEvent(EVENTS.hideDropdown, {});
triggerEvent(this.passedElement.element, EVENTS.hideDropdown, {});
return this; return this;
} }
@ -922,11 +907,10 @@ class Choices {
return this; return this;
} }
this.passedElement.element.disabled = false; this.passedElement.enable();
if (this.containerOuter.isDisabled) { if (this.containerOuter.isDisabled) {
this._addEventListeners(); this._addEventListeners();
this.passedElement.element.removeAttribute('disabled');
this.input.enable(); this.input.enable();
this.containerOuter.enable(); this.containerOuter.enable();
} }
@ -944,11 +928,10 @@ class Choices {
return this; return this;
} }
this.passedElement.element.disabled = true; this.passedElement.disable();
if (!this.containerOuter.isDisabled) { if (!this.containerOuter.isDisabled) {
this._removeEventListeners(); this._removeEventListeners();
this.passedElement.element.setAttribute('disabled', '');
this.input.disable(); this.input.disable();
this.containerOuter.disable(); this.containerOuter.disable();
} }
@ -992,7 +975,7 @@ class Choices {
return; return;
} }
triggerEvent(this.passedElement.element, EVENTS.change, { this.passedElement.triggerEvent(EVENTS.change, {
value, value,
}); });
} }
@ -1102,7 +1085,7 @@ class Choices {
// Update choice keyCode // Update choice keyCode
choice.keyCode = passedKeyCode; choice.keyCode = passedKeyCode;
triggerEvent(this.passedElement.element, EVENTS.choice, { this.passedElement.triggerEvent(EVENTS.choice, {
choice, choice,
}); });
@ -1155,6 +1138,7 @@ class Choices {
this._triggerChange(lastItem.value); this._triggerChange(lastItem.value);
} else { } else {
if (!hasHighlightedItems) { if (!hasHighlightedItems) {
// Highlight last item if none already highlighted
this.highlightItem(lastItem, false); this.highlightItem(lastItem, false);
} }
this.removeHighlightedItems(true); this.removeHighlightedItems(true);
@ -1312,26 +1296,26 @@ class Choices {
this.currentValue.trim() : this.currentValue.trim() :
this.currentValue; 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} `) {
if (newValue.length >= 1 && newValue !== `${currentValue} `) { return 0;
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;
} }
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) { if (value && value.length >= this.config.searchFloor) {
const resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; const resultCount = this.config.searchChoices ? this._searchChoices(value) : 0;
// Trigger search event // Trigger search event
triggerEvent(this.passedElement.element, EVENTS.search, { this.passedElement.triggerEvent(EVENTS.search, {
value, value,
resultCount, resultCount,
}); });
@ -1453,7 +1437,7 @@ class Choices {
this.canSearch = false; this.canSearch = false;
if ( if (
this.config.removeItems && this.config.removeItems &&
!this.input.element.value && !this.input.getValue() &&
this.input.element === document.activeElement this.input.element === document.activeElement
) { ) {
// Highlight items // Highlight items
@ -1465,7 +1449,7 @@ class Choices {
const onEnterKey = () => { const onEnterKey = () => {
// If enter key is pressed and the input has a value // If enter key is pressed and the input has a value
if (this.isTextElement && target.value) { if (this.isTextElement && target.value) {
const value = this.input.element.value; const value = this.input.getValue();
const canAddItem = this._canAddItem(activeItems, value); const canAddItem = this._canAddItem(activeItems, value);
// All is good, add // All is good, add
@ -1592,7 +1576,7 @@ class Choices {
return; return;
} }
const value = this.input.element.value; const value = this.input.getValue();
const activeItems = this.store.getItemsFilteredByActive(); const activeItems = this.store.getItemsFilteredByActive();
const canAddItem = this._canAddItem(activeItems, value); const canAddItem = this._canAddItem(activeItems, value);
@ -1614,20 +1598,18 @@ class Choices {
this.hideDropdown(); this.hideDropdown();
} }
} else { } else {
const backKey = 46; const backKey = KEY_CODES.BACK_KEY;
const deleteKey = 8; const deleteKey = KEY_CODES.DELETE_KEY;
// If user has removed value... // If user has removed value...
if ((e.keyCode === backKey || e.keyCode === deleteKey) && !e.target.value) { if ((e.keyCode === backKey || e.keyCode === deleteKey) && !e.target.value) {
// ...and it is a multiple select input, activate choices (if searching) // ...and it is a multiple select input, activate choices (if searching)
if (!this.isTextElement && this.isSearching) { if (!this.isTextElement && this.isSearching) {
this.isSearching = false; this.isSearching = false;
this.store.dispatch( this.store.dispatch(activateChoices(true));
activateChoices(true),
);
} }
} else if (this.canSearch && canAddItem.response) { } 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 // Re-establish canSearch value from changes in _onKeyDown
@ -1784,33 +1766,34 @@ class Choices {
*/ */
_onFocus(e) { _onFocus(e) {
const target = e.target; const target = e.target;
// If target is something that concerns us if (!this.containerOuter.element.contains(target)) {
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]();
} }
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; choice.offsetTop;
const animateScroll = () => { const animateScroll = () => {
const strength = 4; const strength = SCROLLING_SPEED;
const choiceListScrollTop = this.choiceList.scrollPos; const choiceListScrollTop = this.choiceList.scrollPos;
let continueAnimation = false; let continueAnimation = false;
let easing; let easing;
@ -2046,7 +2029,7 @@ class Choices {
// Trigger change event // Trigger change event
if (group && group.value) { if (group && group.value) {
triggerEvent(this.passedElement.element, EVENTS.addItem, { this.passedElement.triggerEvent(EVENTS.addItem, {
id, id,
value: passedValue, value: passedValue,
label: passedLabel, label: passedLabel,
@ -2054,7 +2037,7 @@ class Choices {
keyCode: passedKeyCode, keyCode: passedKeyCode,
}); });
} else { } else {
triggerEvent(this.passedElement.element, EVENTS.addItem, { this.passedElement.triggerEvent(EVENTS.addItem, {
id, id,
value: passedValue, value: passedValue,
label: passedLabel, label: passedLabel,
@ -2083,19 +2066,17 @@ class Choices {
const groupId = item.groupId; const groupId = item.groupId;
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null; const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch( this.store.dispatch(removeItem(id, choiceId));
removeItem(id, choiceId),
);
if (group && group.value) { if (group && group.value) {
triggerEvent(this.passedElement.element, EVENTS.removeItem, { this.passedElement.triggerEvent(EVENTS.removeItem, {
id, id,
value, value,
label, label,
groupValue: group.value, groupValue: group.value,
}); });
} else { } else {
triggerEvent(this.passedElement.element, EVENTS.removeItem, { this.passedElement.triggerEvent(EVENTS.removeItem, {
id, id,
value, value,
label, label,
@ -2263,10 +2244,16 @@ class Choices {
*/ */
_createInput() { _createInput() {
const direction = this.passedElement.element.getAttribute('dir') || 'ltr'; 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 containerInner = this._getTemplate('containerInner');
const itemList = this._getTemplate('itemList'); const itemList = this._getTemplate('itemList', this.isSelectOneElement);
const choiceList = this._getTemplate('choiceList'); const choiceList = this._getTemplate('choiceList', this.isSelectOneElement);
const input = this._getTemplate('input'); const input = this._getTemplate('input');
const dropdown = this._getTemplate('dropdown'); const dropdown = this._getTemplate('dropdown');
@ -2281,7 +2268,6 @@ class Choices {
// Wrap input in container preserving DOM ordering // Wrap input in container preserving DOM ordering
wrap(this.passedElement.element, this.containerInner.element); wrap(this.passedElement.element, this.containerInner.element);
// Wrapper inner container with outer container // Wrapper inner container with outer container
wrap(this.containerInner.element, this.containerOuter.element); wrap(this.containerInner.element, this.containerOuter.element);
@ -2304,21 +2290,21 @@ class Choices {
dropdown.appendChild(choiceList); dropdown.appendChild(choiceList);
} }
if (this.isSelectMultipleElement || this.isTextElement) { if (!this.isSelectOneElement) {
this.containerInner.element.appendChild(this.input.element); this.containerInner.element.appendChild(this.input.element);
} else if (this.canSearch) { } else if (this.canSearch) {
dropdown.insertBefore(input, dropdown.firstChild); dropdown.insertBefore(input, dropdown.firstChild);
} }
if (this.isSelectElement) { if (this.isSelectElement) {
const passedGroups = Array.from(this.passedElement.element.getElementsByTagName('OPTGROUP')); const passedGroups = this.passedElement.getOptionGroups();
this.highlightPosition = 0; this.highlightPosition = 0;
this.isSearching = false; this.isSearching = false;
if (passedGroups && passedGroups.length) { if (passedGroups && passedGroups.length) {
// If we have a placeholder option // 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') { if (placeholderChoice && placeholderChoice.parentNode.tagName === 'SELECT') {
this._addChoice( this._addChoice(
placeholderChoice.value, placeholderChoice.value,
@ -2335,7 +2321,7 @@ class Choices {
this._addGroup(group, (group.id || null)); this._addGroup(group, (group.id || null));
}); });
} else { } else {
const passedOptions = Array.from(this.passedElement.element.options); const passedOptions = this.passedElement.getOptions();
const filter = this.config.sortFilter; const filter = this.config.sortFilter;
const allChoices = this.presetChoices; const allChoices = this.presetChoices;

View file

@ -32,27 +32,27 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should be defined', () => { it('is defined', () => {
expect(instance).to.not.be.undefined; expect(instance).to.not.be.undefined;
}); });
it('should have initialised', () => { it('initialises', () => {
expect(instance.initialised).to.be.true; 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); const reinitialise = new Choices(instance.passedElement.element);
sinon.spy(reinitialise, '_createTemplates'); sinon.spy(reinitialise, '_createTemplates');
expect(reinitialise._createTemplates.callCount).to.equal(0); 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.items.length).to.equal(0);
expect(instance.currentState.groups.length).to.equal(0); expect(instance.currentState.groups.length).to.equal(0);
expect(instance.currentState.choices.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.silent).to.be.a('boolean');
expect(instance.config.items).to.be.an('array'); expect(instance.config.items).to.be.an('array');
expect(instance.config.choices).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; 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.init).to.be.a('function');
expect(instance.destroy).to.be.a('function'); expect(instance.destroy).to.be.a('function');
expect(instance.render).to.be.a('function'); expect(instance.render).to.be.a('function');
@ -118,35 +118,35 @@ describe('Choices', () => {
expect(instance.clearInput).to.be.a('function'); 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'); 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); 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); 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); 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); 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); 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); 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;'; const origStyle = 'background-color: #ccc; margin: 5px padding: 10px;';
instance.destroy(); instance.destroy();
@ -165,7 +165,7 @@ describe('Choices', () => {
}); });
}); });
describe('should accept text inputs', () => { describe('text inputs', () => {
let input; let input;
let instance; let instance;
@ -182,12 +182,12 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should wrap passed input', () => { it('wraps passed input', () => {
instance = new Choices(input); instance = new Choices(input);
expect(instance.passedElement).to.be.an.instanceof(WrappedInput); 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 = new Choices(input);
instance.input.element.focus(); instance.input.element.focus();
@ -202,13 +202,13 @@ describe('Choices', () => {
expect(instance.currentState.items[0].value).to.include(instance.input.element.value); 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); instance = new Choices(input);
expect(instance.input.element.placeholder).to.equal(input.placeholder); 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, { instance = new Choices(input, {
duplicateItems: false, duplicateItems: false,
items: ['test 1'], 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); 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, { 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,}))$/, 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'); 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, { instance = new Choices(input, {
prependValue: 'item-', prependValue: 'item-',
appendValue: '-value', appendValue: '-value',
@ -277,7 +277,7 @@ describe('Choices', () => {
}); });
}); });
describe('should accept single select inputs', () => { describe('single select inputs', () => {
let input; let input;
let instance; let instance;
@ -302,23 +302,23 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should wrap passed input', () => { it('wraps passed input', () => {
instance = new Choices(input); instance = new Choices(input);
expect(instance.passedElement).to.be.an.instanceof(WrappedSelect); 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 = new Choices(input);
instance.input.element.focus(); instance.input.element.focus();
expect(instance.dropdown.element.classList.contains(instance.config.classNames.activeState)).to.be.true; 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); instance = new Choices(input);
expect(instance.currentState.items[0].value).to.include('Value 1'); 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, { instance = new Choices(input, {
renderChoiceLimit: -1, renderChoiceLimit: -1,
}); });
@ -337,7 +337,7 @@ describe('Choices', () => {
expect(instance.highlightPosition).to.equal(2); 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 = new Choices(input);
instance.input.element.focus(); instance.input.element.focus();
@ -360,7 +360,7 @@ describe('Choices', () => {
expect(instance.currentState.items.length).to.equal(2); 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); instance = new Choices(input);
const onChangeStub = sinon.stub(); const onChangeStub = sinon.stub();
@ -394,7 +394,7 @@ describe('Choices', () => {
expect(addSpyStub.callCount).to.equal(1); expect(addSpyStub.callCount).to.equal(1);
}); });
it('should open the dropdown on click', () => { it('opens the dropdown on click', () => {
instance = new Choices(input); instance = new Choices(input);
const container = instance.containerOuter.element; const container = instance.containerOuter.element;
instance._onClick({ instance._onClick({
@ -406,7 +406,7 @@ describe('Choices', () => {
expect(document.activeElement === instance.input.element && container.classList.contains('is-open')).to.be.true; 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); instance = new Choices(input);
const container = instance.containerOuter.element; const container = instance.containerOuter.element;
const openState = instance.config.classNames.openState; 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; 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); instance = new Choices(input);
const container = instance.containerOuter.element; const container = instance.containerOuter.element;
@ -446,7 +446,7 @@ describe('Choices', () => {
expect(showDropdownStub.callCount).to.equal(1); expect(showDropdownStub.callCount).to.equal(1);
}); });
it('should trigger hideDropdown on dropdown closing', () => { it('triggers hideDropdown on dropdown closing', () => {
instance = new Choices(input); instance = new Choices(input);
const container = instance.containerOuter.element; const container = instance.containerOuter.element;
@ -472,7 +472,7 @@ describe('Choices', () => {
expect(hideDropdownStub.callCount).to.equal(1); expect(hideDropdownStub.callCount).to.equal(1);
}); });
it('should filter choices when searching', () => { it('filters choices when searching', () => {
instance = new Choices(input); instance = new Choices(input);
const onSearchStub = sinon.spy(); const onSearchStub = sinon.spy();
@ -496,7 +496,7 @@ describe('Choices', () => {
expect(onSearchStub.callCount).to.equal(1); expect(onSearchStub.callCount).to.equal(1);
}); });
it('shouldn\'t filter choices when searching', () => { it('doesn\'t filter choices when searching', () => {
instance = new Choices(input, { instance = new Choices(input, {
searchChoices: false, searchChoices: false,
}); });
@ -524,7 +524,7 @@ describe('Choices', () => {
expect(onSearchStub.callCount).to.equal(1); 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, { instance = new Choices(input, {
shouldSort: false, shouldSort: false,
choices: [ choices: [
@ -544,7 +544,7 @@ describe('Choices', () => {
expect(instance.currentState.choices[0].value).to.equal('Value 5'); 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, { instance = new Choices(input, {
shouldSort: true, shouldSort: true,
choices: [ choices: [
@ -565,7 +565,7 @@ describe('Choices', () => {
}); });
}); });
describe('should accept multiple select inputs', () => { describe('multiple select inputs', () => {
let input; let input;
let instance; let instance;
@ -613,24 +613,24 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should wrap passed input', () => { it('wraps passed input', () => {
expect(instance.passedElement).to.be.an.instanceof(WrappedSelect); 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); 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); 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'); 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 input;
let instance; let instance;
@ -661,7 +661,7 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should handle highlightItem()', () => { it('handles highlightItem()', () => {
const items = instance.currentState.items; const items = instance.currentState.items;
const randomItem = items[Math.floor(Math.random() * items.length)]; const randomItem = items[Math.floor(Math.random() * items.length)];
@ -670,7 +670,7 @@ describe('Choices', () => {
expect(randomItem.highlighted).to.be.true; expect(randomItem.highlighted).to.be.true;
}); });
it('should handle unhighlightItem()', () => { it('handles unhighlightItem()', () => {
const items = instance.currentState.items; const items = instance.currentState.items;
const randomItem = items[Math.floor(Math.random() * items.length)]; const randomItem = items[Math.floor(Math.random() * items.length)];
@ -679,7 +679,7 @@ describe('Choices', () => {
expect(randomItem.highlighted).to.be.false; expect(randomItem.highlighted).to.be.false;
}); });
it('should handle highlightAll()', () => { it('handles highlightAll()', () => {
const items = instance.currentState.items; const items = instance.currentState.items;
instance.highlightAll(); instance.highlightAll();
@ -689,7 +689,7 @@ describe('Choices', () => {
expect(unhighlightedItems).to.be.false; expect(unhighlightedItems).to.be.false;
}); });
it('should handle unhighlightAll()', () => { it('handles unhighlightAll()', () => {
const items = instance.currentState.items; const items = instance.currentState.items;
instance.unhighlightAll(); instance.unhighlightAll();
@ -699,7 +699,7 @@ describe('Choices', () => {
expect(highlightedItems).to.be.false; expect(highlightedItems).to.be.false;
}); });
it('should handle removeHighlightedItems()', () => { it('handles removeHighlightedItems()', () => {
const items = instance.currentState.items; const items = instance.currentState.items;
instance.highlightAll(); instance.highlightAll();
instance.removeHighlightedItems(); instance.removeHighlightedItems();
@ -709,7 +709,7 @@ describe('Choices', () => {
expect(activeItems).to.be.false; expect(activeItems).to.be.false;
}); });
it('should handle showDropdown()', () => { it('handles showDropdown()', () => {
instance.showDropdown(); instance.showDropdown();
const hasOpenState = instance.containerOuter.element.classList.contains(instance.config.classNames.openState); const hasOpenState = instance.containerOuter.element.classList.contains(instance.config.classNames.openState);
@ -719,7 +719,7 @@ describe('Choices', () => {
expect(hasOpenState && hasAttr && hasActiveState).to.be.true; expect(hasOpenState && hasAttr && hasActiveState).to.be.true;
}); });
it('should handle hideDropdown()', () => { it('handles hideDropdown()', () => {
instance.showDropdown(); instance.showDropdown();
instance.hideDropdown(); instance.hideDropdown();
@ -731,14 +731,14 @@ describe('Choices', () => {
expect(hasOpenState && hasAttr && hasActiveState).to.be.false; expect(hasOpenState && hasAttr && hasActiveState).to.be.false;
}); });
it('should handle toggleDropdown()', () => { it('handles toggleDropdown()', () => {
sinon.spy(instance, 'hideDropdown'); sinon.spy(instance, 'hideDropdown');
instance.showDropdown(); instance.showDropdown();
instance.toggleDropdown(); instance.toggleDropdown();
expect(instance.hideDropdown.callCount).to.equal(1); expect(instance.hideDropdown.callCount).to.equal(1);
}); });
it('should handle getValue()', () => { it('handles getValue()', () => {
const valueObjects = instance.getValue(); const valueObjects = instance.getValue();
const valueStrings = instance.getValue(true); const valueStrings = instance.getValue(true);
@ -748,7 +748,7 @@ describe('Choices', () => {
expect(valueObjects.length).to.equal(5); expect(valueObjects.length).to.equal(5);
}); });
it('should handle setValue()', () => { it('handles setValue()', () => {
instance.setValue(['Set value 1', 'Set value 2', 'Set value 3']); instance.setValue(['Set value 1', 'Set value 2', 'Set value 3']);
const valueStrings = instance.getValue(true); const valueStrings = instance.getValue(true);
@ -757,7 +757,7 @@ describe('Choices', () => {
expect(valueStrings[valueStrings.length - 3]).to.equal('Set value 1'); expect(valueStrings[valueStrings.length - 3]).to.equal('Set value 1');
}); });
it('should handle setValueByChoice()', () => { it('handles setValueByChoice()', () => {
const choices = instance.store.getChoicesFilteredByActive(); const choices = instance.store.getChoicesFilteredByActive();
const randomChoice = choices[Math.floor(Math.random() * choices.length)]; const randomChoice = choices[Math.floor(Math.random() * choices.length)];
@ -770,7 +770,7 @@ describe('Choices', () => {
expect(value[0]).to.equal(randomChoice.value); expect(value[0]).to.equal(randomChoice.value);
}); });
it('should handle setChoices()', () => { it('handles setChoices()', () => {
instance.setChoices([{ instance.setChoices([{
label: 'Group one', label: 'Group one',
id: 1, id: 1,
@ -818,7 +818,7 @@ describe('Choices', () => {
expect(choices[choices.length - 2].value).to.equal('Child Five'); 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([{ instance.setChoices([{
label: 'Choice one', label: 'Choice one',
value: 'one', value: 'one',
@ -833,7 +833,7 @@ describe('Choices', () => {
expect(choices[1].value).to.equal(''); expect(choices[1].value).to.equal('');
}); });
it('should handle clearStore()', () => { it('handles clearStore()', () => {
instance.clearStore(); instance.clearStore();
expect(instance.currentState.items).to.have.lengthOf(0); expect(instance.currentState.items).to.have.lengthOf(0);
@ -841,7 +841,7 @@ describe('Choices', () => {
expect(instance.currentState.groups).to.have.lengthOf(0); expect(instance.currentState.groups).to.have.lengthOf(0);
}); });
it('should handle disable()', () => { it('handles disable()', () => {
instance.disable(); instance.disable();
expect(instance.input.element.disabled).to.be.true; expect(instance.input.element.disabled).to.be.true;
@ -853,7 +853,7 @@ describe('Choices', () => {
expect(instance.containerOuter.element.getAttribute('aria-disabled')).to.equal('true'); expect(instance.containerOuter.element.getAttribute('aria-disabled')).to.equal('true');
}); });
it('should handle enable()', () => { it('handles enable()', () => {
instance.enable(); instance.enable();
expect(instance.input.element.disabled).to.be.false; expect(instance.input.element.disabled).to.be.false;
@ -865,7 +865,7 @@ describe('Choices', () => {
expect(instance.containerOuter.element.hasAttribute('aria-disabled')).to.be.false; expect(instance.containerOuter.element.hasAttribute('aria-disabled')).to.be.false;
}); });
it('should handle ajax()', () => { it('handles ajax()', () => {
const dummyFn = sinon.spy(); const dummyFn = sinon.spy();
instance.ajax(dummyFn); 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 input;
let instance; let instance;
@ -904,20 +904,20 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should handle disable()', () => { it('handles disable()', () => {
instance.disable(); instance.disable();
expect(instance.containerOuter.element.getAttribute('tabindex')).to.equal('-1'); expect(instance.containerOuter.element.getAttribute('tabindex')).to.equal('-1');
}); });
it('should handle enable()', () => { it('handles enable()', () => {
instance.enable(); instance.enable();
expect(instance.containerOuter.element.getAttribute('tabindex')).to.equal('0'); 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 input;
let instance; let instance;
@ -935,12 +935,12 @@ describe('Choices', () => {
instance.destroy(); instance.destroy();
}); });
it('should handle clearInput()', () => { it('handles clearInput()', () => {
instance.clearInput(); instance.clearInput();
expect(instance.input.element.value).to.equal(''); expect(instance.input.element.value).to.equal('');
}); });
it('should handle removeItemsByValue()', () => { it('handles removeItemsByValue()', () => {
const items = instance.currentState.items; const items = instance.currentState.items;
const randomItem = items[Math.floor(Math.random() * items.length)]; 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 input;
let instance; 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 input;
let instance; let instance;
@ -1072,7 +1072,7 @@ describe('Choices', () => {
instance.destroy(); 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 = { const expectedCustomProperties = {
isBestOptionEver: true, isBestOptionEver: true,
}; };
@ -1093,7 +1093,7 @@ describe('Choices', () => {
expect(selectedItems[0].customProperties).to.equal(expectedCustomProperties); 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 = { const expectedCustomProperties = {
isBestOptionEver: true, isBestOptionEver: true,
}; };

View file

@ -13,6 +13,10 @@ export default class Container {
this.onBlur = this.onBlur.bind(this); this.onBlur = this.onBlur.bind(this);
} }
getElement() {
return this.element;
}
/** /**
* Add event listeners * Add event listeners
*/ */
@ -155,6 +159,16 @@ export default class Container {
this.isDisabled = true; 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 * Add loading state to element
*/ */

View file

@ -8,6 +8,10 @@ export default class Dropdown {
this.isActive = false; this.isActive = false;
} }
getElement() {
return this.element;
}
/** /**
* Determine how far the top of our element is from * Determine how far the top of our element is from
* the top of the window * the top of the window

View file

@ -15,6 +15,10 @@ export default class Input {
this.onBlur = this.onBlur.bind(this); this.onBlur = this.onBlur.bind(this);
} }
getElement() {
return this.element;
}
addEventListeners() { addEventListeners() {
this.element.addEventListener('input', this.onInput); this.element.addEventListener('input', this.onInput);
this.element.addEventListener('paste', this.onPaste); this.element.addEventListener('paste', this.onPaste);

View file

@ -8,6 +8,10 @@ export default class List {
this.hasChildren = !!this.element.children; this.hasChildren = !!this.element.children;
} }
getElement() {
return this.element;
}
/** /**
* Clear List contents * Clear List contents
*/ */

View file

@ -1,8 +1,19 @@
import { dispatchEvent } from '../lib/utils';
export default class WrappedElement { export default class WrappedElement {
constructor(instance, element, classNames) { constructor(instance, element, classNames) {
this.parentInstance = instance; this.parentInstance = instance;
this.element = element; this.element = element;
this.classNames = classNames; this.classNames = classNames;
this.isDisabled = false;
}
getElement() {
return this.element;
}
getValue() {
return this.element.value;
} }
conceal() { conceal() {
@ -50,4 +61,20 @@ export default class WrappedElement {
// Re-assign values - this is weird, I know // Re-assign values - this is weird, I know
this.element.value = this.element.value; 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);
}
} }

View file

@ -8,6 +8,10 @@ export default class WrappedInput extends WrappedElement {
this.classNames = classNames; this.classNames = classNames;
} }
getElement() {
super.getElement();
}
conceal() { conceal() {
super.conceal(); super.conceal();
} }
@ -15,4 +19,17 @@ export default class WrappedInput extends WrappedElement {
reveal() { reveal() {
super.reveal(); super.reveal();
} }
enable() {
super.enable();
}
disable() {
super.enable();
}
setValue(value) {
this.element.setAttribute('value', value);
this.element.value = value;
}
} }

View file

@ -8,6 +8,10 @@ export default class WrappedSelect extends WrappedElement {
this.classNames = classNames; this.classNames = classNames;
} }
getElement() {
super.getElement();
}
conceal() { conceal() {
super.conceal(); super.conceal();
} }
@ -15,4 +19,29 @@ export default class WrappedSelect extends WrappedElement {
reveal() { reveal() {
super.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'));
}
} }

View file

@ -103,3 +103,5 @@ export const KEY_CODES = {
PAGE_UP_KEY: 33, PAGE_UP_KEY: 33,
PAGE_DOWN_KEY: 34, PAGE_DOWN_KEY: 34,
}; };
export const SCROLLING_SPEED = 4;

View file

@ -552,13 +552,13 @@ export const sortByAlpha = (a, b) => {
export const sortByScore = (a, b) => a.score - b.score; export const sortByScore = (a, b) => a.score - b.score;
/** /**
* Trigger native event * Dispatch native event
* @param {NodeElement} element Element to trigger event on * @param {NodeElement} element Element to trigger event on
* @param {String} type Type of event to trigger * @param {String} type Type of event to trigger
* @param {Object} customArgs Data to pass with event * @param {Object} customArgs Data to pass with event
* @return {Object} Triggered event * @return {Object} Triggered event
*/ */
export const triggerEvent = (element, type, customArgs = null) => { export const dispatchEvent = (element, type, customArgs = null) => {
const event = new CustomEvent(type, { const event = new CustomEvent(type, {
detail: customArgs, detail: customArgs,
bubbles: true, bubbles: true,

View file

@ -2,12 +2,19 @@ import classNames from 'classnames';
import { strToEl } from './lib/utils'; import { strToEl } from './lib/utils';
export const TEMPLATES = { export const TEMPLATES = {
containerOuter(globalClasses, direction) { containerOuter(
const tabIndex = this.isSelectOneElement ? 'tabindex="0"' : ''; globalClasses,
let role = this.isSelectElement ? 'role="listbox"' : ''; direction,
isSelectElement,
isSelectOneElement,
searchEnabled,
passedElementType,
) {
const tabIndex = isSelectOneElement ? 'tabindex="0"' : '';
let role = isSelectElement ? 'role="listbox"' : '';
let ariaAutoComplete = ''; let ariaAutoComplete = '';
if (this.isSelectElement && this.config.searchEnabled) { if (isSelectElement && searchEnabled) {
role = 'role="combobox"'; role = 'role="combobox"';
ariaAutoComplete = 'aria-autocomplete="list"'; ariaAutoComplete = 'aria-autocomplete="list"';
} }
@ -15,7 +22,7 @@ export const TEMPLATES = {
return strToEl(` return strToEl(`
<div <div
class="${globalClasses.containerOuter}" class="${globalClasses.containerOuter}"
data-type="${this.passedElement.element.type}" data-type="${passedElementType}"
${role} ${role}
${tabIndex} ${tabIndex}
${ariaAutoComplete} ${ariaAutoComplete}
@ -31,11 +38,11 @@ export const TEMPLATES = {
<div class="${globalClasses.containerInner}"></div> <div class="${globalClasses.containerInner}"></div>
`); `);
}, },
itemList(globalClasses) { itemList(globalClasses, isSelectOneElement) {
const localClasses = classNames( const localClasses = classNames(
globalClasses.list, { globalClasses.list, {
[globalClasses.listSingle]: (this.isSelectOneElement), [globalClasses.listSingle]: (isSelectOneElement),
[globalClasses.listItems]: (!this.isSelectOneElement), [globalClasses.listItems]: (!isSelectOneElement),
}, },
); );
@ -50,7 +57,7 @@ export const TEMPLATES = {
</div> </div>
`); `);
}, },
item(globalClasses, data) { item(globalClasses, data, removeItemButton) {
const ariaSelected = data.active ? 'aria-selected="true"' : ''; const ariaSelected = data.active ? 'aria-selected="true"' : '';
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : ''; const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
@ -62,7 +69,7 @@ export const TEMPLATES = {
}, },
); );
if (this.config.removeItemButton) { if (removeItemButton) {
localClasses = classNames( localClasses = classNames(
globalClasses.item, { globalClasses.item, {
[globalClasses.highlightedState]: data.highlighted, [globalClasses.highlightedState]: data.highlighted,
@ -107,8 +114,8 @@ export const TEMPLATES = {
</div> </div>
`); `);
}, },
choiceList(globalClasses) { choiceList(globalClasses, isSelectOneElement) {
const ariaMultiSelectable = !this.isSelectOneElement ? const ariaMultiSelectable = !isSelectOneElement ?
'aria-multiselectable="true"' : 'aria-multiselectable="true"' :
''; '';
@ -143,7 +150,7 @@ export const TEMPLATES = {
</div> </div>
`); `);
}, },
choice(globalClasses, data) { choice(globalClasses, data, itemSelectText) {
const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"'; const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"';
const localClasses = classNames( const localClasses = classNames(
globalClasses.item, globalClasses.item,
@ -157,7 +164,7 @@ export const TEMPLATES = {
return strToEl(` return strToEl(`
<div <div
class="${localClasses}" class="${localClasses}"
data-select-text="${this.config.itemSelectText}" data-select-text="${itemSelectText}"
data-choice data-choice
data-id="${data.id}" data-id="${data.id}"
data-value="${data.value}" data-value="${data.value}"