choices.js
Advanced tools
Comparing version 8.0.0 to 9.0.0
{ | ||
"name": "choices.js", | ||
"version": "8.0.0", | ||
"version": "9.0.0", | ||
"description": "A vanilla JS customisable text input/select box plugin", | ||
@@ -12,5 +12,5 @@ "main": "./public/assets/scripts/choices.js", | ||
"bundlesize": "bundlesize", | ||
"cypress:run": "$(npm bin)/cypress run", | ||
"cypress:open": "$(npm bin)/cypress open", | ||
"cypress:ci": "cypress run --record --group --ci-build-id $GITHUB_SHA", | ||
"cypress:run": "cypress run", | ||
"cypress:open": "cypress open", | ||
"cypress:ci": "cypress run --record --group $GITHUB_REF --ci-build-id $GITHUB_SHA", | ||
"test": "run-s test:unit test:e2e", | ||
@@ -66,3 +66,3 @@ "test:unit": "NODE_ENV=test mocha", | ||
"csso-cli": "^3.0.0", | ||
"cypress": "3.5.0", | ||
"cypress": "3.6.0", | ||
"eslint": "^6.6.0", | ||
@@ -89,2 +89,3 @@ "eslint-config-airbnb-base": "^14.0.0", | ||
"sinon": "^7.5.0", | ||
"sinon-chai": "^3.3.0", | ||
"webpack": "^4.41.2", | ||
@@ -91,0 +92,0 @@ "webpack-cli": "^3.3.9", |
@@ -1,2 +0,1 @@ | ||
// get polyfill settings from top level config | ||
@@ -9,17 +8,21 @@ // @ts-ignore | ||
settings.polyfills.push('Symbol.toStringTag', 'Symbol.for', 'Object.getOwnPropertySymbols', 'Object.getOwnPropertyDescriptors') | ||
settings.polyfills.push( | ||
'Symbol.toStringTag', | ||
'Symbol.for', | ||
'Object.getOwnPropertySymbols', | ||
'Object.getOwnPropertyDescriptors', | ||
'Promise', // Promise is gate checked | ||
); | ||
module.exports = /** @type {import('eslint').Linter.Config} */({ | ||
module.exports = /** @type {import('eslint').Linter.Config} */ ({ | ||
root: true, | ||
extends: [ | ||
"plugin:compat/recommended" | ||
], | ||
extends: ['plugin:compat/recommended'], | ||
parserOptions: { | ||
// ensure that it's compatible with ES5 browsers, so, no `const`, etc | ||
ecmaVersion: 5 | ||
ecmaVersion: 5, | ||
}, | ||
env: { | ||
browser: true | ||
browser: true, | ||
}, | ||
settings | ||
}) | ||
settings, | ||
}); |
@@ -1,2 +0,2 @@ | ||
# Choices.js [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Unit%20Tests/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![npm](https://img.shields.io/npm/v/choices.js.svg)](https://www.npmjs.com/package/choices.js) [![codebeat badge](https://codebeat.co/badges/55120150-5866-42d8-8010-6aaaff5d3fa1)](https://codebeat.co/projects/github-com-jshjohnson-choices-master) | ||
# Choices.js [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Build%20and%20test/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Bundle%20size%20checks/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![npm](https://img.shields.io/npm/v/choices.js.svg)](https://www.npmjs.com/package/choices.js) | ||
@@ -108,3 +108,3 @@ A vanilla, lightweight (~19kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency. | ||
shouldSortItems: false, | ||
sortFn: () => {...}, | ||
sorter: () => {...}, | ||
placeholder: true, | ||
@@ -126,4 +126,4 @@ placeholderValue: null, | ||
}, | ||
itemComparer: (choice, item) => { | ||
return choice === item; | ||
valueComparer: (value1, value2) => { | ||
return value1 === value2; | ||
}, | ||
@@ -152,2 +152,3 @@ classNames: { | ||
highlightedState: 'is-highlighted', | ||
selectedState: 'is-selected', | ||
flippedState: 'is-flipped', | ||
@@ -414,3 +415,3 @@ loadingState: 'is-loading', | ||
### sortFn | ||
### sorter | ||
@@ -428,3 +429,3 @@ **Type:** `Function` **Default:** sortByAlpha | ||
const example = new Choices(element, { | ||
sortFn: function(a, b) { | ||
sorter: function(a, b) { | ||
return b.label.length - a.label.length; | ||
@@ -439,7 +440,7 @@ }, | ||
**Input types affected:** `text`, `select-multiple` | ||
**Input types affected:** `text` | ||
**Usage:** Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value. | ||
**Note:** For single select boxes, the recommended way of adding a placeholder is as follows: | ||
**Note:** For select boxes, the recommended way of adding a placeholder is as follows: | ||
@@ -545,3 +546,3 @@ ```html | ||
### itemComparer | ||
### valueComparer | ||
@@ -552,4 +553,12 @@ **Type:** `Function` **Default:** `strict equality` | ||
**Usage:** Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `itemComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example). | ||
**Usage:** A custom compare function used when finding choices by value (using `setChoiceByValue`). | ||
**Example:** | ||
```js | ||
const example = new Choices(element, { | ||
valueComparer: (a, b) => value.trim() === b.trim(), | ||
}; | ||
``` | ||
### classNames | ||
@@ -581,2 +590,3 @@ | ||
highlightedState: 'is-highlighted', | ||
selectedState: 'is-selected', | ||
flippedState: 'is-flipped', | ||
@@ -1065,3 +1075,3 @@ selectedState: 'is-highlighted', | ||
To setup a local environment: clone this repo, navigate into it's directory in a terminal window and run the following command: | ||
To setup a local environment: clone this repo, navigate into its directory in a terminal window and run the following command: | ||
@@ -1068,0 +1078,0 @@ `npm install` |
@@ -0,3 +1,12 @@ | ||
/** | ||
* @typedef {import('redux').Action} Action | ||
* @typedef {import('../../../types/index').Choices.Choice} Choice | ||
*/ | ||
import { ACTION_TYPES } from '../constants'; | ||
/** | ||
* @argument {Choice} choice | ||
* @returns {Action & Choice} | ||
*/ | ||
export const addChoice = ({ | ||
@@ -26,2 +35,6 @@ value, | ||
/** | ||
* @argument {Choice[]} results | ||
* @returns {Action & { results: Choice[] }} | ||
*/ | ||
export const filterChoices = results => ({ | ||
@@ -32,2 +45,6 @@ type: ACTION_TYPES.FILTER_CHOICES, | ||
/** | ||
* @argument {boolean} active | ||
* @returns {Action & { active: boolean }} | ||
*/ | ||
export const activateChoices = (active = true) => ({ | ||
@@ -38,4 +55,7 @@ type: ACTION_TYPES.ACTIVATE_CHOICES, | ||
/** | ||
* @returns {Action} | ||
*/ | ||
export const clearChoices = () => ({ | ||
type: ACTION_TYPES.CLEAR_CHOICES, | ||
}); |
import { ACTION_TYPES } from '../constants'; | ||
export const addGroup = (value, id, active, disabled) => ({ | ||
/** | ||
* @typedef {import('redux').Action} Action | ||
* @typedef {import('../../../types/index').Choices.Group} Group | ||
*/ | ||
/** | ||
* @param {Group} group | ||
* @returns {Action & Group} | ||
*/ | ||
export const addGroup = ({ value, id, active, disabled }) => ({ | ||
type: ACTION_TYPES.ADD_GROUP, | ||
@@ -5,0 +14,0 @@ value, |
import { ACTION_TYPES } from '../constants'; | ||
/** | ||
* @typedef {import('redux').Action} Action | ||
* @typedef {import('../../../types/index').Choices.Item} Item | ||
*/ | ||
/** | ||
* @param {Item} item | ||
* @returns {Action & Item} | ||
*/ | ||
export const addItem = ({ | ||
@@ -24,2 +33,7 @@ value, | ||
/** | ||
* @param {string} id | ||
* @param {string} choiceId | ||
* @returns {Action & { id: string, choiceId: string }} | ||
*/ | ||
export const removeItem = (id, choiceId) => ({ | ||
@@ -31,2 +45,7 @@ type: ACTION_TYPES.REMOVE_ITEM, | ||
/** | ||
* @param {string} id | ||
* @param {boolean} highlighted | ||
* @returns {Action & { id: string, highlighted: boolean }} | ||
*/ | ||
export const highlightItem = (id, highlighted) => ({ | ||
@@ -33,0 +52,0 @@ type: ACTION_TYPES.HIGHLIGHT_ITEM, |
@@ -0,1 +1,8 @@ | ||
/** | ||
* @typedef {import('redux').Action} Action | ||
*/ | ||
/** | ||
* @returns {Action} | ||
*/ | ||
export const clearAll = () => ({ | ||
@@ -5,2 +12,6 @@ type: 'CLEAR_ALL', | ||
/** | ||
* @param {any} state | ||
* @returns {Action & { state: object }} | ||
*/ | ||
export const resetTo = state => ({ | ||
@@ -10,1 +21,10 @@ type: 'RESET_TO', | ||
}); | ||
/** | ||
* @param {boolean} isLoading | ||
* @returns {Action & { isLoading: boolean }} | ||
*/ | ||
export const setIsLoading = isLoading => ({ | ||
type: 'SET_IS_LOADING', | ||
isLoading, | ||
}); |
@@ -13,3 +13,10 @@ import Fuse from 'fuse.js'; | ||
} from './components'; | ||
import { DEFAULT_CONFIG, EVENTS, KEY_CODES } from './constants'; | ||
import { | ||
DEFAULT_CONFIG, | ||
EVENTS, | ||
KEY_CODES, | ||
TEXT_TYPE, | ||
SELECT_ONE_TYPE, | ||
SELECT_MULTIPLE_TYPE, | ||
} from './constants'; | ||
import { TEMPLATES } from './templates'; | ||
@@ -24,4 +31,3 @@ import { | ||
import { addGroup } from './actions/groups'; | ||
import { clearAll, resetTo } from './actions/misc'; | ||
import { setIsLoading } from './actions/general'; | ||
import { clearAll, resetTo, setIsLoading } from './actions/misc'; | ||
import { | ||
@@ -35,4 +41,2 @@ isScrolledIntoView, | ||
generateId, | ||
findAncestorByAttrName, | ||
isIE11, | ||
existsInArray, | ||
@@ -43,11 +47,20 @@ cloneObject, | ||
const USER_DEFAULTS = /** @type {Partial<import('../../types/index').Choices.Options>} */ ({}); | ||
/** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ | ||
const IS_IE11 = | ||
'-ms-scroll-limit' in document.documentElement.style && | ||
'-ms-ime-align' in document.documentElement.style; | ||
/** | ||
* Choices | ||
* @author Josh Johnson<josh@joshuajohnson.co.uk> | ||
* @typedef {import('../../types/index').Choices.Choice} Choice | ||
* @typedef {import('../../types/index').Choices.Item} Item | ||
* @typedef {import('../../types/index').Choices.Group} Group | ||
* @typedef {import('../../types/index').Choices.Options} Options | ||
*/ | ||
/** @type {Partial<Options>} */ | ||
const USER_DEFAULTS = {}; | ||
/** | ||
* @typedef {import('../../types/index').Choices.Choice} Choice | ||
* Choices | ||
* @author Josh Johnson<josh@joshuajohnson.co.uk> | ||
*/ | ||
@@ -68,5 +81,6 @@ class Choices { | ||
* @param {string | HTMLInputElement | HTMLSelectElement} element | ||
* @param {Partial<import('../../types/index').Choices.Options>} userConfig | ||
* @param {Partial<Options>} userConfig | ||
*/ | ||
constructor(element = '[data-choice]', userConfig = {}) { | ||
/** @type {Partial<Options>} */ | ||
this.config = merge.all( | ||
@@ -76,18 +90,5 @@ [DEFAULT_CONFIG, Choices.defaults.options, userConfig], | ||
// instead of concatenating with the default array | ||
{ arrayMerge: (destinationArray, sourceArray) => [...sourceArray] }, | ||
{ arrayMerge: (_, sourceArray) => [...sourceArray] }, | ||
); | ||
// Convert addItemFilter to function | ||
if ( | ||
userConfig.addItemFilter && | ||
typeof userConfig.addItemFilter !== 'function' | ||
) { | ||
const re = | ||
userConfig.addItemFilter instanceof RegExp | ||
? userConfig.addItemFilter | ||
: new RegExp(userConfig.addItemFilter); | ||
this.config.addItemFilter = re.test.bind(re); | ||
} | ||
const invalidConfigOptions = diff(this.config, DEFAULT_CONFIG); | ||
@@ -101,6 +102,2 @@ if (invalidConfigOptions.length) { | ||
if (!['auto', 'always'].includes(this.config.renderSelectedChoices)) { | ||
this.config.renderSelectedChoices = 'auto'; | ||
} | ||
const passedElement = | ||
@@ -120,8 +117,27 @@ typeof element === 'string' ? document.querySelector(element) : element; | ||
this._isTextElement = passedElement.type === 'text'; | ||
this._isSelectOneElement = passedElement.type === 'select-one'; | ||
this._isSelectMultipleElement = passedElement.type === 'select-multiple'; | ||
this._isTextElement = passedElement.type === TEXT_TYPE; | ||
this._isSelectOneElement = passedElement.type === SELECT_ONE_TYPE; | ||
this._isSelectMultipleElement = passedElement.type === SELECT_MULTIPLE_TYPE; | ||
this._isSelectElement = | ||
this._isSelectOneElement || this._isSelectMultipleElement; | ||
this.config.searchEnabled = | ||
this._isSelectMultipleElement || this.config.searchEnabled; | ||
if (!['auto', 'always'].includes(this.config.renderSelectedChoices)) { | ||
this.config.renderSelectedChoices = 'auto'; | ||
} | ||
if ( | ||
userConfig.addItemFilter && | ||
typeof userConfig.addItemFilter !== 'function' | ||
) { | ||
const re = | ||
userConfig.addItemFilter instanceof RegExp | ||
? userConfig.addItemFilter | ||
: new RegExp(userConfig.addItemFilter); | ||
this.config.addItemFilter = re.test.bind(re); | ||
} | ||
if (this._isTextElement) { | ||
@@ -159,3 +175,4 @@ this.passedElement = new WrappedInput({ | ||
*/ | ||
this._direction = this.passedElement.element.dir; | ||
this._direction = this.passedElement.dir; | ||
if (!this._direction) { | ||
@@ -172,5 +189,8 @@ const { direction: elementDirection } = window.getComputedStyle( | ||
} | ||
this._idNames = { | ||
itemChoice: 'item-choice', | ||
}; | ||
// Assign preset groups from passed element | ||
this._presetGroups = this.passedElement.optionGroups; | ||
// Assign preset choices from passed object | ||
@@ -180,3 +200,3 @@ this._presetChoices = this.config.choices; | ||
this._presetItems = this.config.items; | ||
// Then add any values passed from attribute | ||
// Add any values passed from attribute | ||
if (this.passedElement.value) { | ||
@@ -187,2 +207,15 @@ this._presetItems = this._presetItems.concat( | ||
} | ||
// Create array of choices from option elements | ||
if (this.passedElement.options) { | ||
this.passedElement.options.forEach(o => { | ||
this._presetChoices.push({ | ||
value: o.value, | ||
label: o.innerHTML, | ||
selected: o.selected, | ||
disabled: o.disabled || o.parentNode.disabled, | ||
placeholder: o.value === '' || o.hasAttribute('placeholder'), | ||
customProperties: o.getAttribute('data-custom-properties'), | ||
}); | ||
}); | ||
} | ||
@@ -206,12 +239,4 @@ this._render = this._render.bind(this); | ||
if (this.config.shouldSortItems === true && this._isSelectOneElement) { | ||
if (!this.config.silent) { | ||
console.warn( | ||
"shouldSortElements: Type of passed element is 'select-one', falling back to false.", | ||
); | ||
} | ||
} | ||
// If element has already been initialised with Choices, fail silently | ||
if (this.passedElement.element.getAttribute('data-choice') === 'active') { | ||
if (this.passedElement.isActive) { | ||
if (!this.config.silent) { | ||
@@ -400,3 +425,3 @@ console.warn( | ||
this.dropdown.show(); | ||
this.containerOuter.open(this.dropdown.distanceFromTopWindow()); | ||
this.containerOuter.open(this.dropdown.distanceFromTopWindow); | ||
@@ -480,3 +505,3 @@ if (!preventInputFocus && this._canSearch) { | ||
* | ||
* @template {object[] | ((instance: Choices) => object[] | Promise<object[]>)} T | ||
* @template {Choice[] | ((instance: Choices) => object[] | Promise<object[]>)} T | ||
* @param {T} [choicesArrayOrFetcher] | ||
@@ -568,15 +593,12 @@ * @param {string} [value = 'value'] - name of `value` field | ||
if (!Array.isArray(choicesArrayOrFetcher)) { | ||
if (typeof choicesArrayOrFetcher !== 'function') { | ||
throw new TypeError( | ||
`.setChoices must be called either with array of choices with a function resulting into Promise of array of choices`, | ||
); | ||
} | ||
if (typeof choicesArrayOrFetcher === 'function') { | ||
// it's a choices fetcher function | ||
const fetcher = choicesArrayOrFetcher(this); | ||
// it's a choices fetcher | ||
requestAnimationFrame(() => this._handleLoadingState(true)); | ||
const fetcher = choicesArrayOrFetcher(this); | ||
if (typeof fetcher === 'object' && typeof fetcher.then === 'function') { | ||
if (typeof Promise === 'function' && fetcher instanceof Promise) { | ||
// that's a promise | ||
return fetcher | ||
// eslint-disable-next-line compat/compat | ||
return new Promise(resolve => requestAnimationFrame(resolve)) | ||
.then(() => this._handleLoadingState(true)) | ||
.then(() => fetcher) | ||
.then(data => this.setChoices(data, value, label, replaceChoices)) | ||
@@ -591,2 +613,3 @@ .catch(err => { | ||
} | ||
// function returned something else than promise, let's check if it's an array of choices | ||
@@ -603,9 +626,17 @@ if (!Array.isArray(fetcher)) { | ||
if (!Array.isArray(choicesArrayOrFetcher)) { | ||
throw new TypeError( | ||
`.setChoices must be called either with array of choices with a function resulting into Promise of array of choices`, | ||
); | ||
} | ||
this.containerOuter.removeLoadingState(); | ||
const addGroupsAndChoices = groupOrChoice => { | ||
this._startLoading(); | ||
choicesArrayOrFetcher.forEach(groupOrChoice => { | ||
if (groupOrChoice.choices) { | ||
this._addGroup({ | ||
id: parseInt(groupOrChoice.id, 10) || null, | ||
group: groupOrChoice, | ||
id: groupOrChoice.id || null, | ||
valueKey: value, | ||
@@ -624,7 +655,5 @@ labelKey: label, | ||
} | ||
}; | ||
}); | ||
this._setLoading(true); | ||
choicesArrayOrFetcher.forEach(addGroupsAndChoices); | ||
this._setLoading(false); | ||
this._stopLoading(); | ||
@@ -798,3 +827,3 @@ return this; | ||
if (this.config.shouldSort) { | ||
groups.sort(this.config.sortFn); | ||
groups.sort(this.config.sorter); | ||
} | ||
@@ -825,3 +854,3 @@ | ||
} = this.config; | ||
const filter = this._isSearching ? sortByScore : this.config.sortFn; | ||
const filter = this._isSearching ? sortByScore : this.config.sorter; | ||
const appendChoice = choice => { | ||
@@ -870,7 +899,9 @@ const shouldRender = | ||
// Prepend placeholeder | ||
const sortedChoices = [...placeholderChoices, ...normalChoices]; | ||
const sortedChoices = this._isSelectOneElement | ||
? [...placeholderChoices, ...normalChoices] | ||
: normalChoices; | ||
if (this._isSearching) { | ||
choiceLimit = searchResultLimit; | ||
} else if (renderChoiceLimit > 0 && !withinGroup) { | ||
} else if (renderChoiceLimit && renderChoiceLimit > 0 && !withinGroup) { | ||
choiceLimit = renderChoiceLimit; | ||
@@ -891,7 +922,7 @@ } | ||
// Create fragment to add elements to | ||
const { shouldSortItems, sortFn, removeItemButton } = this.config; | ||
const { shouldSortItems, sorter, removeItemButton } = this.config; | ||
// If sorting is enabled, filter items | ||
if (shouldSortItems && !this._isSelectOneElement) { | ||
items.sort(sortFn); | ||
items.sort(sorter); | ||
} | ||
@@ -915,3 +946,3 @@ | ||
// Add each list item to list | ||
items.forEach(item => addItemToFragment(item)); | ||
items.forEach(addItemToFragment); | ||
@@ -1072,6 +1103,10 @@ return fragment; | ||
_setLoading(isLoading) { | ||
this._store.dispatch(setIsLoading(isLoading)); | ||
_startLoading() { | ||
this._store.dispatch(setIsLoading(true)); | ||
} | ||
_stopLoading() { | ||
this._store.dispatch(setIsLoading(false)); | ||
} | ||
_handleLoadingState(setLoading = true) { | ||
@@ -1222,5 +1257,13 @@ let placeholderItem = this.itemList.getChild( | ||
// capture events - can cancel event processing or propagation | ||
documentElement.addEventListener('keydown', this._onKeyDown, true); | ||
documentElement.addEventListener('touchend', this._onTouchEnd, true); | ||
documentElement.addEventListener('mousedown', this._onMouseDown, true); | ||
this.containerOuter.element.addEventListener( | ||
'keydown', | ||
this._onKeyDown, | ||
true, | ||
); | ||
this.containerOuter.element.addEventListener( | ||
'mousedown', | ||
this._onMouseDown, | ||
true, | ||
); | ||
@@ -1232,3 +1275,3 @@ // passive events - doesn't call `preventDefault` or `stopPropagation` | ||
}); | ||
documentElement.addEventListener('mouseover', this._onMouseOver, { | ||
this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { | ||
passive: true, | ||
@@ -1269,39 +1312,29 @@ }); | ||
documentElement.removeEventListener('keydown', this._onKeyDown, true); | ||
documentElement.removeEventListener('touchend', this._onTouchEnd, true); | ||
documentElement.removeEventListener('mousedown', this._onMouseDown, true); | ||
this.containerOuter.element.removeEventListener( | ||
'keydown', | ||
this._onKeyDown, | ||
true, | ||
); | ||
this.containerOuter.element.removeEventListener( | ||
'mousedown', | ||
this._onMouseDown, | ||
true, | ||
); | ||
documentElement.removeEventListener('keyup', this._onKeyUp, { | ||
passive: true, | ||
}); | ||
documentElement.removeEventListener('click', this._onClick, { | ||
passive: true, | ||
}); | ||
documentElement.removeEventListener('touchmove', this._onTouchMove, { | ||
passive: true, | ||
}); | ||
documentElement.removeEventListener('mouseover', this._onMouseOver, { | ||
passive: true, | ||
}); | ||
documentElement.removeEventListener('click', this._onClick); | ||
documentElement.removeEventListener('touchmove', this._onTouchMove); | ||
this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); | ||
if (this._isSelectOneElement) { | ||
this.containerOuter.element.removeEventListener('focus', this._onFocus, { | ||
passive: true, | ||
}); | ||
this.containerOuter.element.removeEventListener('blur', this._onBlur, { | ||
passive: true, | ||
}); | ||
this.containerOuter.element.removeEventListener('focus', this._onFocus); | ||
this.containerOuter.element.removeEventListener('blur', this._onBlur); | ||
} | ||
this.input.element.removeEventListener('focus', this._onFocus, { | ||
passive: true, | ||
}); | ||
this.input.element.removeEventListener('blur', this._onBlur, { | ||
passive: true, | ||
}); | ||
this.input.element.removeEventListener('keyup', this._onKeyUp); | ||
this.input.element.removeEventListener('focus', this._onFocus); | ||
this.input.element.removeEventListener('blur', this._onBlur); | ||
if (this.input.element.form) { | ||
this.input.element.form.removeEventListener('reset', this._onFormReset, { | ||
passive: true, | ||
}); | ||
this.input.element.form.removeEventListener('reset', this._onFormReset); | ||
} | ||
@@ -1312,12 +1345,7 @@ | ||
/** | ||
* @param {KeyboardEvent} event | ||
*/ | ||
_onKeyDown(event) { | ||
const { target, keyCode, ctrlKey, metaKey } = event; | ||
if ( | ||
target !== this.input.element && | ||
!this.containerOuter.element.contains(target) | ||
) { | ||
return; | ||
} | ||
const { activeItems } = this._store; | ||
@@ -1495,5 +1523,5 @@ const hasFocusedInput = this.input.isFocussed; | ||
if (directionInt > 0) { | ||
nextEl = Array.from( | ||
this.dropdown.element.querySelectorAll(selectableChoiceIdentifier), | ||
).pop(); | ||
nextEl = this.dropdown.element.querySelector( | ||
`${selectableChoiceIdentifier}:last-of-type`, | ||
); | ||
} else { | ||
@@ -1527,3 +1555,3 @@ nextEl = this.dropdown.element.querySelector( | ||
) { | ||
this.choiceList.scrollToChoice(nextEl, directionInt); | ||
this.choiceList.scrollToChildElement(nextEl, directionInt); | ||
} | ||
@@ -1578,43 +1606,51 @@ this._highlightChoice(nextEl); | ||
/** | ||
* Handles mousedown event in capture mode for containetOuter.element | ||
* @param {MouseEvent} event | ||
*/ | ||
_onMouseDown(event) { | ||
const { target, shiftKey } = event; | ||
const { target } = event; | ||
if (!(target instanceof HTMLElement)) { | ||
return; | ||
} | ||
// If we have our mouse down on the scrollbar and are on IE11... | ||
if ( | ||
this.choiceList.element.contains(target) && | ||
isIE11(navigator.userAgent) | ||
) { | ||
this._isScrollingOnIe = true; | ||
if (IS_IE11 && this.choiceList.element.contains(target)) { | ||
// check if click was on a scrollbar area | ||
const firstChoice = /** @type {HTMLElement} */ (this.choiceList.element | ||
.firstElementChild); | ||
const isOnScrollbar = | ||
this._direction === 'ltr' | ||
? event.offsetX >= firstChoice.offsetWidth | ||
: event.offsetX < firstChoice.offsetLeft; | ||
this._isScrollingOnIe = isOnScrollbar; | ||
} | ||
if ( | ||
!this.containerOuter.element.contains(target) || | ||
target === this.input.element | ||
) { | ||
if (target === this.input.element) { | ||
return; | ||
} | ||
const { activeItems } = this._store; | ||
const hasShiftKey = shiftKey; | ||
const buttonTarget = findAncestorByAttrName(target, 'data-button'); | ||
const itemTarget = findAncestorByAttrName(target, 'data-item'); | ||
const choiceTarget = findAncestorByAttrName(target, 'data-choice'); | ||
const item = target.closest('[data-button],[data-item],[data-choice]'); | ||
if (item instanceof HTMLElement) { | ||
const hasShiftKey = event.shiftKey; | ||
const { activeItems } = this._store; | ||
const { dataset } = item; | ||
if (buttonTarget) { | ||
this._handleButtonAction(activeItems, buttonTarget); | ||
} else if (itemTarget) { | ||
this._handleItemAction(activeItems, itemTarget, hasShiftKey); | ||
} else if (choiceTarget) { | ||
this._handleChoiceAction(activeItems, choiceTarget); | ||
if ('button' in dataset) { | ||
this._handleButtonAction(activeItems, item); | ||
} else if ('item' in dataset) { | ||
this._handleItemAction(activeItems, item, hasShiftKey); | ||
} else if ('choice' in dataset) { | ||
this._handleChoiceAction(activeItems, item); | ||
} | ||
} | ||
event.preventDefault(); | ||
} | ||
/** | ||
* Handles mouseover event over this.dropdown | ||
* @param {MouseEvent} event | ||
*/ | ||
_onMouseOver({ target }) { | ||
const targetWithinDropdown = | ||
target === this.dropdown || this.dropdown.element.contains(target); | ||
const shouldHighlightChoice = | ||
targetWithinDropdown && target.hasAttribute('data-choice'); | ||
if (shouldHighlightChoice) { | ||
if (target instanceof HTMLElement && 'choice' in target.dataset) { | ||
this._highlightChoice(target); | ||
@@ -1668,3 +1704,3 @@ } | ||
const focusActions = { | ||
text: () => { | ||
[TEXT_TYPE]: () => { | ||
if (target === this.input.element) { | ||
@@ -1674,3 +1710,3 @@ this.containerOuter.addFocusState(); | ||
}, | ||
'select-one': () => { | ||
[SELECT_ONE_TYPE]: () => { | ||
this.containerOuter.addFocusState(); | ||
@@ -1681,3 +1717,3 @@ if (target === this.input.element) { | ||
}, | ||
'select-multiple': () => { | ||
[SELECT_MULTIPLE_TYPE]: () => { | ||
if (target === this.input.element) { | ||
@@ -1702,3 +1738,3 @@ this.showDropdown(true); | ||
const blurActions = { | ||
text: () => { | ||
[TEXT_TYPE]: () => { | ||
if (target === this.input.element) { | ||
@@ -1712,3 +1748,3 @@ this.containerOuter.removeFocusState(); | ||
}, | ||
'select-one': () => { | ||
[SELECT_ONE_TYPE]: () => { | ||
this.containerOuter.removeFocusState(); | ||
@@ -1722,3 +1758,3 @@ if ( | ||
}, | ||
'select-multiple': () => { | ||
[SELECT_MULTIPLE_TYPE]: () => { | ||
if (target === this.input.element) { | ||
@@ -1814,3 +1850,3 @@ this.containerOuter.removeFocusState(); | ||
const passedLabel = label || passedValue; | ||
const passedOptionId = parseInt(choiceId, 10) || -1; | ||
const passedOptionId = choiceId || -1; | ||
const group = groupId >= 0 ? this._store.getGroupById(groupId) : null; | ||
@@ -1909,8 +1945,8 @@ const id = items ? items.length + 1 : 1; | ||
addChoice({ | ||
id: choiceId, | ||
groupId, | ||
elementId: choiceElementId, | ||
value, | ||
label: choiceLabel, | ||
id: choiceId, | ||
groupId, | ||
disabled: isDisabled, | ||
elementId: choiceElementId, | ||
customProperties, | ||
@@ -1942,3 +1978,10 @@ placeholder, | ||
if (groupChoices) { | ||
this._store.dispatch(addGroup(group.label, groupId, true, isDisabled)); | ||
this._store.dispatch( | ||
addGroup({ | ||
value: group.label, | ||
id: groupId, | ||
active: true, | ||
disabled: isDisabled, | ||
}), | ||
); | ||
@@ -1963,3 +2006,8 @@ const addGroupChoices = choice => { | ||
this._store.dispatch( | ||
addGroup(group.label, group.id, false, group.disabled), | ||
addGroup({ | ||
value: group.label, | ||
id: group.id, | ||
active: false, | ||
disabled: group.disabled, | ||
}), | ||
); | ||
@@ -2049,3 +2097,3 @@ } | ||
this.input.placeholder = this._placeholderValue; | ||
this.input.setWidth(true); | ||
this.input.setWidth(); | ||
} | ||
@@ -2071,95 +2119,85 @@ | ||
if (this._isSelectElement) { | ||
this._addPredefinedChoices(); | ||
} else if (this._isTextElement) { | ||
this._addPredefinedItems(); | ||
this._highlightPosition = 0; | ||
this._isSearching = false; | ||
this._startLoading(); | ||
if (this._presetGroups.length) { | ||
this._addPredefinedGroups(this._presetGroups); | ||
} else { | ||
this._addPredefinedChoices(this._presetChoices); | ||
} | ||
this._stopLoading(); | ||
} | ||
if (this._isTextElement) { | ||
this._addPredefinedItems(this._presetItems); | ||
} | ||
} | ||
_addPredefinedChoices() { | ||
const passedGroups = this.passedElement.optionGroups; | ||
_addPredefinedGroups(groups) { | ||
// If we have a placeholder option | ||
const placeholderChoice = this.passedElement.placeholderOption; | ||
if ( | ||
placeholderChoice && | ||
placeholderChoice.parentNode.tagName === 'SELECT' | ||
) { | ||
this._addChoice({ | ||
value: placeholderChoice.value, | ||
label: placeholderChoice.innerHTML, | ||
isSelected: placeholderChoice.selected, | ||
isDisabled: placeholderChoice.disabled, | ||
placeholder: true, | ||
}); | ||
} | ||
this._highlightPosition = 0; | ||
this._isSearching = false; | ||
this._setLoading(true); | ||
groups.forEach(group => | ||
this._addGroup({ | ||
group, | ||
id: group.id || null, | ||
}), | ||
); | ||
} | ||
if (passedGroups && passedGroups.length) { | ||
// If we have a placeholder option | ||
const placeholderChoice = this.passedElement.placeholderOption; | ||
if ( | ||
placeholderChoice && | ||
placeholderChoice.parentNode.tagName === 'SELECT' | ||
) { | ||
this._addChoice({ | ||
value: placeholderChoice.value, | ||
label: placeholderChoice.innerHTML, | ||
isSelected: placeholderChoice.selected, | ||
isDisabled: placeholderChoice.disabled, | ||
placeholder: true, | ||
}); | ||
} | ||
_addPredefinedChoices(choices) { | ||
// If sorting is enabled or the user is searching, filter choices | ||
if (this.config.shouldSort) { | ||
choices.sort(this.config.sorter); | ||
} | ||
passedGroups.forEach(group => | ||
this._addGroup({ | ||
group, | ||
id: group.id || null, | ||
}), | ||
); | ||
} else { | ||
const passedOptions = this.passedElement.options; | ||
const filter = this.config.sortFn; | ||
const allChoices = this._presetChoices; | ||
const hasSelectedChoice = choices.some(choice => choice.selected); | ||
const firstEnabledChoiceIndex = choices.findIndex( | ||
choice => choice.disabled === undefined || !choice.disabled, | ||
); | ||
// Create array of options from option elements | ||
passedOptions.forEach(o => { | ||
allChoices.push({ | ||
value: o.value, | ||
label: o.innerHTML, | ||
selected: o.selected, | ||
disabled: o.disabled || o.parentNode.disabled, | ||
placeholder: o.hasAttribute('placeholder'), | ||
customProperties: o.getAttribute('data-custom-properties'), | ||
}); | ||
}); | ||
choices.forEach((choice, index) => { | ||
const { value, label, customProperties, placeholder } = choice; | ||
// If sorting is enabled or the user is searching, filter choices | ||
if (this.config.shouldSort) { | ||
allChoices.sort(filter); | ||
} | ||
if (this._isSelectElement) { | ||
// If the choice is actually a group | ||
if (choice.choices) { | ||
this._addGroup({ | ||
group: choice, | ||
id: choice.id || null, | ||
}); | ||
} else { | ||
/** | ||
* If there is a selected choice already or the choice is not the first in | ||
* the array, add each choice normally. | ||
* | ||
* Otherwise we pre-select the first enabled choice in the array ("select-one" only) | ||
*/ | ||
const shouldPreselect = | ||
this._isSelectOneElement && | ||
!hasSelectedChoice && | ||
index === firstEnabledChoiceIndex; | ||
// Determine whether there is a selected choice | ||
const hasSelectedChoice = allChoices.some(choice => choice.selected); | ||
const handleChoice = (choice, index) => { | ||
const { value, label, customProperties, placeholder } = choice; | ||
const isSelected = shouldPreselect ? true : choice.selected; | ||
const isDisabled = choice.disabled; | ||
if (this._isSelectElement) { | ||
// If the choice is actually a group | ||
if (choice.choices) { | ||
this._addGroup({ | ||
group: choice, | ||
id: choice.id || null, | ||
}); | ||
} else { | ||
// If there is a selected choice already or the choice is not | ||
// the first in the array, add each choice normally | ||
// Otherwise pre-select the first choice in the array if it's a single select | ||
const shouldPreselect = | ||
this._isSelectOneElement && !hasSelectedChoice && index === 0; | ||
const isSelected = shouldPreselect ? true : choice.selected; | ||
const isDisabled = shouldPreselect ? false : choice.disabled; | ||
this._addChoice({ | ||
value, | ||
label, | ||
isSelected, | ||
isDisabled, | ||
customProperties, | ||
placeholder, | ||
}); | ||
} | ||
} else { | ||
this._addChoice({ | ||
value, | ||
label, | ||
isSelected: choice.selected, | ||
isDisabled: choice.disabled, | ||
isSelected, | ||
isDisabled, | ||
customProperties, | ||
@@ -2169,15 +2207,21 @@ placeholder, | ||
} | ||
}; | ||
// Add each choice | ||
allChoices.forEach((choice, index) => handleChoice(choice, index)); | ||
} | ||
this._setLoading(false); | ||
} else { | ||
this._addChoice({ | ||
value, | ||
label, | ||
isSelected: choice.selected, | ||
isDisabled: choice.disabled, | ||
customProperties, | ||
placeholder, | ||
}); | ||
} | ||
}); | ||
} | ||
_addPredefinedItems() { | ||
const handlePresetItem = item => { | ||
const itemType = getType(item); | ||
if (itemType === 'Object' && item.value) { | ||
/** | ||
* @param {Item[]} items | ||
*/ | ||
_addPredefinedItems(items) { | ||
items.forEach(item => { | ||
if (typeof item === 'object' && item.value) { | ||
this._addItem({ | ||
@@ -2190,3 +2234,5 @@ value: item.value, | ||
}); | ||
} else if (itemType === 'String') { | ||
} | ||
if (typeof item === 'string') { | ||
this._addItem({ | ||
@@ -2196,5 +2242,3 @@ value: item, | ||
} | ||
}; | ||
this._presetItems.forEach(item => handlePresetItem(item)); | ||
}); | ||
} | ||
@@ -2254,3 +2298,3 @@ | ||
const foundChoice = choices.find(choice => | ||
this.config.itemComparer(choice.value, val), | ||
this.config.valueComparer(choice.value, val), | ||
); | ||
@@ -2272,15 +2316,27 @@ | ||
_generatePlaceholderValue() { | ||
if (this._isSelectOneElement) { | ||
return false; | ||
if (this._isSelectElement) { | ||
const { placeholderOption } = this.passedElement; | ||
return placeholderOption ? placeholderOption.text : false; | ||
} | ||
return this.config.placeholder | ||
? this.config.placeholderValue || | ||
this.passedElement.element.getAttribute('placeholder') | ||
: false; | ||
const { placeholder, placeholderValue } = this.config; | ||
const { | ||
element: { dataset }, | ||
} = this.passedElement; | ||
if (placeholder) { | ||
if (placeholderValue) { | ||
return placeholderValue; | ||
} | ||
if (dataset.placeholder) { | ||
return dataset.placeholder; | ||
} | ||
} | ||
return false; | ||
} | ||
/* ===== End of Private functions ====== */ | ||
} | ||
export default Choices; |
@@ -1,7 +0,22 @@ | ||
import { getWindowHeight, wrap } from '../lib/utils'; | ||
import { wrap } from '../lib/utils'; | ||
import { SELECT_ONE_TYPE } from '../constants'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.passedElement} passedElement | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
*/ | ||
export default class Container { | ||
/** | ||
* @param {{ | ||
* element: HTMLElement, | ||
* type: passedElement['type'], | ||
* classNames: ClassNames, | ||
* position | ||
* }} args | ||
*/ | ||
constructor({ element, type, classNames, position }) { | ||
Object.assign(this, { element, classNames, type, position }); | ||
this.element = element; | ||
this.classNames = classNames; | ||
this.type = type; | ||
this.position = position; | ||
this.isOpen = false; | ||
@@ -12,3 +27,2 @@ this.isFlipped = false; | ||
this.isLoading = false; | ||
this._onFocus = this._onFocus.bind(this); | ||
@@ -18,5 +32,2 @@ this._onBlur = this._onBlur.bind(this); | ||
/** | ||
* Add event listeners | ||
*/ | ||
addEventListeners() { | ||
@@ -27,7 +38,2 @@ this.element.addEventListener('focus', this._onFocus); | ||
/** | ||
* Remove event listeners | ||
*/ | ||
/** */ | ||
removeEventListeners() { | ||
@@ -39,9 +45,9 @@ this.element.removeEventListener('focus', this._onFocus); | ||
/** | ||
* Determine whether container should be flipped | ||
* based on passed dropdown position | ||
* @param {Number} dropdownPos | ||
* @returns | ||
* Determine whether container should be flipped based on passed | ||
* dropdown position | ||
* @param {number} dropdownPos | ||
* @returns {boolean} | ||
*/ | ||
shouldFlip(dropdownPos, windowHeight = getWindowHeight()) { | ||
if (dropdownPos === undefined) { | ||
shouldFlip(dropdownPos) { | ||
if (typeof dropdownPos !== 'number') { | ||
return false; | ||
@@ -54,3 +60,4 @@ } | ||
if (this.position === 'auto') { | ||
shouldFlip = dropdownPos >= windowHeight; | ||
shouldFlip = !window.matchMedia(`(min-height: ${dropdownPos + 1}px)`) | ||
.matches; | ||
} else if (this.position === 'top') { | ||
@@ -64,4 +71,3 @@ shouldFlip = true; | ||
/** | ||
* Set active descendant attribute | ||
* @param {Number} activeDescendant ID of active descendant | ||
* @param {string} activeDescendantID | ||
*/ | ||
@@ -72,5 +78,2 @@ setActiveDescendant(activeDescendantID) { | ||
/** | ||
* Remove active descendant attribute | ||
*/ | ||
removeActiveDescendant() { | ||
@@ -80,2 +83,5 @@ this.element.removeAttribute('aria-activedescendant'); | ||
/** | ||
* @param {number} dropdownPos | ||
*/ | ||
open(dropdownPos) { | ||
@@ -119,9 +125,6 @@ this.element.classList.add(this.classNames.openState); | ||
/** | ||
* Remove disabled state | ||
*/ | ||
enable() { | ||
this.element.classList.remove(this.classNames.disabledState); | ||
this.element.removeAttribute('aria-disabled'); | ||
if (this.type === 'select-one') { | ||
if (this.type === SELECT_ONE_TYPE) { | ||
this.element.setAttribute('tabindex', '0'); | ||
@@ -132,9 +135,6 @@ } | ||
/** | ||
* Set disabled state | ||
*/ | ||
disable() { | ||
this.element.classList.add(this.classNames.disabledState); | ||
this.element.setAttribute('aria-disabled', 'true'); | ||
if (this.type === 'select-one') { | ||
if (this.type === SELECT_ONE_TYPE) { | ||
this.element.setAttribute('tabindex', '-1'); | ||
@@ -145,2 +145,5 @@ } | ||
/** | ||
* @param {HTMLElement} element | ||
*/ | ||
wrap(element) { | ||
@@ -150,2 +153,5 @@ wrap(element, this.element); | ||
/** | ||
* @param {Element} element | ||
*/ | ||
unwrap(element) { | ||
@@ -158,5 +164,2 @@ // Move passed element outside this element | ||
/** | ||
* Add loading state to element | ||
*/ | ||
addLoadingState() { | ||
@@ -168,5 +171,2 @@ this.element.classList.add(this.classNames.loadingState); | ||
/** | ||
* Remove loading state from element | ||
*/ | ||
removeLoadingState() { | ||
@@ -178,5 +178,2 @@ this.element.classList.remove(this.classNames.loadingState); | ||
/** | ||
* Set focussed state | ||
*/ | ||
_onFocus() { | ||
@@ -186,5 +183,2 @@ this.isFocussed = true; | ||
/** | ||
* Remove blurred state | ||
*/ | ||
_onBlur() { | ||
@@ -191,0 +185,0 @@ this.isFocussed = false; |
@@ -0,5 +1,18 @@ | ||
/** | ||
* @typedef {import('../../../types/index').Choices.passedElement} passedElement | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
*/ | ||
export default class Dropdown { | ||
/** | ||
* @param {{ | ||
* element: HTMLElement, | ||
* type: passedElement['type'], | ||
* classNames: ClassNames, | ||
* }} args | ||
*/ | ||
constructor({ element, type, classNames }) { | ||
Object.assign(this, { element, type, classNames }); | ||
this.element = element; | ||
this.classNames = classNames; | ||
this.type = type; | ||
this.isActive = false; | ||
@@ -9,13 +22,7 @@ } | ||
/** | ||
* Determine how far the top of our element is from | ||
* the top of the window | ||
* @return {Number} Vertical position | ||
* Bottom position of dropdown in viewport coordinates | ||
* @returns {number} Vertical position | ||
*/ | ||
distanceFromTopWindow() { | ||
this.dimensions = this.element.getBoundingClientRect(); | ||
this.position = Math.ceil( | ||
this.dimensions.top + window.pageYOffset + this.element.offsetHeight, | ||
); | ||
return this.position; | ||
get distanceFromTopWindow() { | ||
return this.element.getBoundingClientRect().bottom; | ||
} | ||
@@ -25,3 +32,4 @@ | ||
* Find element that matches passed selector | ||
* @return {HTMLElement} | ||
* @param {string} selector | ||
* @returns {HTMLElement | null} | ||
*/ | ||
@@ -34,4 +42,3 @@ getChild(selector) { | ||
* Show dropdown to user by adding active state class | ||
* @return {Object} Class instance | ||
* @public | ||
* @returns {this} | ||
*/ | ||
@@ -48,4 +55,3 @@ show() { | ||
* Hide dropdown from user | ||
* @return {Object} Class instance | ||
* @public | ||
* @returns {this} | ||
*/ | ||
@@ -52,0 +58,0 @@ hide() { |
import { sanitise } from '../lib/utils'; | ||
import { SELECT_ONE_TYPE } from '../constants'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.passedElement} passedElement | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
*/ | ||
export default class Input { | ||
/** | ||
* | ||
* @typedef {import('../../../types/index').Choices.passedElement} passedElement | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
* @param {{element: HTMLInputElement, type: passedElement['type'], classNames: ClassNames, preventPaste: boolean }} p | ||
* @param {{ | ||
* element: HTMLInputElement, | ||
* type: passedElement['type'], | ||
* classNames: ClassNames, | ||
* preventPaste: boolean | ||
* }} args | ||
*/ | ||
@@ -24,2 +32,5 @@ constructor({ element, type, classNames, preventPaste }) { | ||
/** | ||
* @param {string} placeholder | ||
*/ | ||
set placeholder(placeholder) { | ||
@@ -29,2 +40,5 @@ this.element.placeholder = placeholder; | ||
/** | ||
* @returns {string} | ||
*/ | ||
get value() { | ||
@@ -34,2 +48,5 @@ return sanitise(this.element.value); | ||
/** | ||
* @param {string} value | ||
*/ | ||
set value(value) { | ||
@@ -89,4 +106,4 @@ this.element.value = value; | ||
* Set value of input to blank | ||
* @return {Object} Class instance | ||
* @public | ||
* @param {boolean} setWidth | ||
* @returns {this} | ||
*/ | ||
@@ -116,2 +133,5 @@ clear(setWidth = true) { | ||
/** | ||
* @param {string} activeDescendantID | ||
*/ | ||
setActiveDescendant(activeDescendantID) { | ||
@@ -126,3 +146,3 @@ this.element.setAttribute('aria-activedescendant', activeDescendantID); | ||
_onInput() { | ||
if (this.type !== 'select-one') { | ||
if (this.type !== SELECT_ONE_TYPE) { | ||
this.setWidth(); | ||
@@ -132,2 +152,5 @@ } | ||
/** | ||
* @param {Event} event | ||
*/ | ||
_onPaste(event) { | ||
@@ -134,0 +157,0 @@ if (this.preventPaste) { |
import { SCROLLING_SPEED } from '../constants'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.Choice} Choice | ||
*/ | ||
export default class List { | ||
/** | ||
* @param {{ element: HTMLElement }} args | ||
*/ | ||
constructor({ element }) { | ||
Object.assign(this, { element }); | ||
this.element = element; | ||
this.scrollPos = this.element.scrollTop; | ||
@@ -15,2 +20,5 @@ this.height = this.element.offsetHeight; | ||
/** | ||
* @param {Element | DocumentFragment} node | ||
*/ | ||
append(node) { | ||
@@ -20,2 +28,6 @@ this.element.appendChild(node); | ||
/** | ||
* @param {string} selector | ||
* @returns {Element | null} | ||
*/ | ||
getChild(selector) { | ||
@@ -25,2 +37,5 @@ return this.element.querySelector(selector); | ||
/** | ||
* @returns {boolean} | ||
*/ | ||
hasChildren() { | ||
@@ -34,24 +49,35 @@ return this.element.hasChildNodes(); | ||
scrollToChoice(choice, direction) { | ||
if (!choice) { | ||
/** | ||
* @param {Element} element | ||
* @param {1 | -1} direction | ||
*/ | ||
scrollToChildElement(element, direction) { | ||
if (!element) { | ||
return; | ||
} | ||
const dropdownHeight = this.element.offsetHeight; | ||
const choiceHeight = choice.offsetHeight; | ||
const listHeight = this.element.offsetHeight; | ||
// Scroll position of dropdown | ||
const listScrollPosition = this.element.scrollTop + listHeight; | ||
const elementHeight = element.offsetHeight; | ||
// Distance from bottom of element to top of parent | ||
const choicePos = choice.offsetTop + choiceHeight; | ||
// Scroll position of dropdown | ||
const containerScrollPos = this.element.scrollTop + dropdownHeight; | ||
// Difference between the choice and scroll position | ||
const elementPos = element.offsetTop + elementHeight; | ||
// Difference between the element and scroll position | ||
const destination = | ||
direction > 0 | ||
? this.element.scrollTop + choicePos - containerScrollPos | ||
: choice.offsetTop; | ||
? this.element.scrollTop + elementPos - listScrollPosition | ||
: element.offsetTop; | ||
requestAnimationFrame(time => { | ||
this._animateScroll(time, destination, direction); | ||
requestAnimationFrame(() => { | ||
this._animateScroll(destination, direction); | ||
}); | ||
} | ||
/** | ||
* @param {number} scrollPos | ||
* @param {number} strength | ||
* @param {number} destination | ||
*/ | ||
_scrollDown(scrollPos, strength, destination) { | ||
@@ -64,2 +90,7 @@ const easing = (destination - scrollPos) / strength; | ||
/** | ||
* @param {number} scrollPos | ||
* @param {number} strength | ||
* @param {number} destination | ||
*/ | ||
_scrollUp(scrollPos, strength, destination) { | ||
@@ -72,3 +103,7 @@ const easing = (scrollPos - destination) / strength; | ||
_animateScroll(time, destination, direction) { | ||
/** | ||
* @param {*} destination | ||
* @param {*} direction | ||
*/ | ||
_animateScroll(destination, direction) { | ||
const strength = SCROLLING_SPEED; | ||
@@ -94,3 +129,3 @@ const choiceListScrollTop = this.element.scrollTop; | ||
requestAnimationFrame(() => { | ||
this._animateScroll(time, destination, direction); | ||
this._animateScroll(destination, direction); | ||
}); | ||
@@ -97,0 +132,0 @@ } |
import { dispatchEvent } from '../lib/utils'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.passedElement} passedElement | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
*/ | ||
export default class WrappedElement { | ||
/** | ||
* @param {{ | ||
* element: HTMLInputElement | HTMLSelectElement, | ||
* classNames: ClassNames, | ||
* }} args | ||
*/ | ||
constructor({ element, classNames }) { | ||
Object.assign(this, { element, classNames }); | ||
this.element = element; | ||
this.classNames = classNames; | ||
if (!(element instanceof Element)) { | ||
if ( | ||
!(element instanceof HTMLInputElement) && | ||
!(element instanceof HTMLSelectElement) | ||
) { | ||
throw new TypeError('Invalid element passed'); | ||
@@ -14,2 +29,10 @@ } | ||
get isActive() { | ||
return this.element.dataset.choice === 'active'; | ||
} | ||
get dir() { | ||
return this.element.dir; | ||
} | ||
get value() { | ||
@@ -30,3 +53,3 @@ return this.element.value; | ||
// Remove element from tab index | ||
this.element.tabIndex = '-1'; | ||
this.element.tabIndex = -1; | ||
@@ -33,0 +56,0 @@ // Backup original styles if any |
import WrappedElement from './wrapped-element'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
* @typedef {import('../../../types/index').Choices.Item} Item | ||
*/ | ||
export default class WrappedInput extends WrappedElement { | ||
/** | ||
* @param {{ | ||
* element: HTMLInputElement, | ||
* classNames: ClassNames, | ||
* delimiter: string | ||
* }} args | ||
*/ | ||
constructor({ element, classNames, delimiter }) { | ||
@@ -9,2 +21,5 @@ super({ element, classNames }); | ||
/** | ||
* @returns {string} | ||
*/ | ||
get value() { | ||
@@ -14,2 +29,5 @@ return this.element.value; | ||
/** | ||
* @param {Item[]} items | ||
*/ | ||
set value(items) { | ||
@@ -16,0 +34,0 @@ const itemValues = items.map(({ value }) => value); |
import WrappedElement from './wrapped-element'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames | ||
* @typedef {import('../../../types/index').Choices.Item} Item | ||
* @typedef {import('../../../types/index').Choices.Choice} Choice | ||
*/ | ||
export default class WrappedSelect extends WrappedElement { | ||
/** | ||
* @param {{ | ||
* element: HTMLSelectElement, | ||
* classNames: ClassNames, | ||
* delimiter: string | ||
* template: function | ||
* }} args | ||
*/ | ||
constructor({ element, classNames, template }) { | ||
@@ -17,2 +31,5 @@ super({ element, classNames }); | ||
/** | ||
* @returns {Element[]} | ||
*/ | ||
get optionGroups() { | ||
@@ -22,2 +39,5 @@ return Array.from(this.element.getElementsByTagName('OPTGROUP')); | ||
/** | ||
* @returns {Item[] | Choice[]} | ||
*/ | ||
get options() { | ||
@@ -27,2 +47,5 @@ return Array.from(this.element.options); | ||
/** | ||
* @param {Item[] | Choice[]} options | ||
*/ | ||
set options(options) { | ||
@@ -43,2 +66,5 @@ const fragment = document.createDocumentFragment(); | ||
/** | ||
* @param {DocumentFragment} fragment | ||
*/ | ||
appendDocFragment(fragment) { | ||
@@ -45,0 +71,0 @@ this.element.innerHTML = ''; |
import { sanitise, sortByAlpha } from './lib/utils'; | ||
/** | ||
* @typedef {import('../../types/index').Choices.ClassNames} ClassNames | ||
* @typedef {import('../../types/index').Choices.Options} Options | ||
*/ | ||
/** @type {ClassNames} */ | ||
export const DEFAULT_CLASSNAMES = { | ||
@@ -25,2 +31,3 @@ containerOuter: 'choices', | ||
highlightedState: 'is-highlighted', | ||
selectedState: 'is-selected', | ||
flippedState: 'is-flipped', | ||
@@ -32,2 +39,3 @@ loadingState: 'is-loading', | ||
/** @type {Options} */ | ||
export const DEFAULT_CONFIG = { | ||
@@ -56,3 +64,3 @@ items: [], | ||
shouldSortItems: false, | ||
sortFn: sortByAlpha, | ||
sorter: sortByAlpha, | ||
placeholder: true, | ||
@@ -72,3 +80,3 @@ placeholderValue: null, | ||
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added`, | ||
itemComparer: (choice, item) => choice === item, | ||
valueComparer: (value1, value2) => value1 === value2, | ||
fuseOptions: { | ||
@@ -118,2 +126,6 @@ includeScore: true, | ||
export const TEXT_TYPE = 'text'; | ||
export const SELECT_ONE_TYPE = 'select-one'; | ||
export const SELECT_MULTIPLE_TYPE = 'select-multiple'; | ||
export const SCROLLING_SPEED = 4; |
@@ -0,15 +1,21 @@ | ||
/** | ||
* @param {number} min | ||
* @param {number} max | ||
* @returns {number} | ||
*/ | ||
export const getRandomNumber = (min, max) => | ||
Math.floor(Math.random() * (max - min) + min); | ||
export const generateChars = length => { | ||
let chars = ''; | ||
/** | ||
* @param {number} length | ||
* @returns {string} | ||
*/ | ||
export const generateChars = length => | ||
Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join(''); | ||
for (let i = 0; i < length; i++) { | ||
const randomChar = getRandomNumber(0, 36); | ||
chars += randomChar.toString(36); | ||
} | ||
return chars; | ||
}; | ||
/** | ||
* @param {HTMLInputElement | HTMLSelectElement} element | ||
* @param {string} prefix | ||
* @returns {string} | ||
*/ | ||
export const generateId = (element, prefix) => { | ||
@@ -26,7 +32,21 @@ let id = | ||
/** | ||
* @param {any} obj | ||
* @returns {string} | ||
*/ | ||
export const getType = obj => Object.prototype.toString.call(obj).slice(8, -1); | ||
/** | ||
* @param {string} type | ||
* @param {any} obj | ||
* @returns {boolean} | ||
*/ | ||
export const isType = (type, obj) => | ||
obj !== undefined && obj !== null && getType(obj) === type; | ||
/** | ||
* @param {HTMLElement} element | ||
* @param {HTMLElement} [wrapper={HTMLDivElement}] | ||
* @returns {HTMLElement} | ||
*/ | ||
export const wrap = (element, wrapper = document.createElement('div')) => { | ||
@@ -43,24 +63,34 @@ if (element.nextSibling) { | ||
/** | ||
* @param {HTMLElement} el | ||
* @param {string} attr | ||
* @param {Element} startEl | ||
* @param {string} selector | ||
* @param {1 | -1} direction | ||
* @returns {Element | undefined} | ||
*/ | ||
export const findAncestorByAttrName = (el, attr) => el.closest(`[${attr}]`); | ||
export const getAdjacentEl = (startEl, className, direction = 1) => { | ||
if (!startEl || !className) { | ||
return; | ||
export const getAdjacentEl = (startEl, selector, direction = 1) => { | ||
if (!(startEl instanceof Element) || typeof selector !== 'string') { | ||
return undefined; | ||
} | ||
const parent = startEl.parentNode.parentNode; | ||
const children = Array.from(parent.querySelectorAll(className)); | ||
const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`; | ||
const startPos = children.indexOf(startEl); | ||
const operatorDirection = direction > 0 ? 1 : -1; | ||
let sibling = startEl[prop]; | ||
while (sibling) { | ||
if (sibling.matches(selector)) { | ||
return sibling; | ||
} | ||
sibling = sibling[prop]; | ||
} | ||
return children[startPos + operatorDirection]; | ||
return sibling; | ||
}; | ||
export const isScrolledIntoView = (el, parent, direction = 1) => { | ||
if (!el) { | ||
return; | ||
/** | ||
* @param {Element} element | ||
* @param {Element} parent | ||
* @param {-1 | 1} direction | ||
* @returns {boolean} | ||
*/ | ||
export const isScrolledIntoView = (element, parent, direction = 1) => { | ||
if (!element) { | ||
return false; | ||
} | ||
@@ -73,6 +103,7 @@ | ||
isVisible = | ||
parent.scrollTop + parent.offsetHeight >= el.offsetTop + el.offsetHeight; | ||
parent.scrollTop + parent.offsetHeight >= | ||
element.offsetTop + element.offsetHeight; | ||
} else { | ||
// In view from top | ||
isVisible = el.offsetTop >= parent.scrollTop; | ||
isVisible = element.offsetTop >= parent.scrollTop; | ||
} | ||
@@ -83,2 +114,6 @@ | ||
/** | ||
* @param {any} value | ||
* @returns {any} | ||
*/ | ||
export const sanitise = value => { | ||
@@ -96,2 +131,5 @@ if (typeof value !== 'string') { | ||
/** | ||
* @returns {() => (str: string) => Element} | ||
*/ | ||
export const strToEl = (() => { | ||
@@ -113,17 +151,28 @@ const tmpEl = document.createElement('div'); | ||
export const sortByAlpha = | ||
/** | ||
* @param {{ label?: string, value: string }} a | ||
* @param {{ label?: string, value: string }} b | ||
* @returns {number} | ||
*/ | ||
({ value, label = value }, { value: value2, label: label2 = value2 }) => | ||
label.localeCompare(label2, [], { | ||
sensitivity: 'base', | ||
ignorePunctuation: true, | ||
numeric: true, | ||
}); | ||
/** | ||
* @param {{ label?: string, value: string }} a | ||
* @param {{ label?: string, value: string }} b | ||
* @returns {number} | ||
*/ | ||
export const sortByAlpha = ( | ||
{ value, label = value }, | ||
{ value: value2, label: label2 = value2 }, | ||
) => | ||
label.localeCompare(label2, [], { | ||
sensitivity: 'base', | ||
ignorePunctuation: true, | ||
numeric: true, | ||
}); | ||
/** | ||
* @param {{ score: number }} a | ||
* @param {{ score: number }} b | ||
*/ | ||
export const sortByScore = (a, b) => a.score - b.score; | ||
/** | ||
* @param {HTMLElement} element | ||
* @param {string} type | ||
* @param {object} customArgs | ||
*/ | ||
export const dispatchEvent = (element, type, customArgs = null) => { | ||
@@ -139,18 +188,8 @@ const event = new CustomEvent(type, { | ||
export const getWindowHeight = () => { | ||
const { body } = document; | ||
const html = document.documentElement; | ||
return Math.max( | ||
body.scrollHeight, | ||
body.offsetHeight, | ||
html.clientHeight, | ||
html.scrollHeight, | ||
html.offsetHeight, | ||
); | ||
}; | ||
export const isIE11 = userAgent => | ||
!!(userAgent.match(/Trident/) && userAgent.match(/rv[ :]11/)); | ||
/** | ||
* @param {array} array | ||
* @param {any} value | ||
* @param {string} [key="value"] | ||
* @returns {boolean} | ||
*/ | ||
export const existsInArray = (array, value, key = 'value') => | ||
@@ -165,4 +204,14 @@ array.some(item => { | ||
/** | ||
* @param {any} obj | ||
* @returns {any} | ||
*/ | ||
export const cloneObject = obj => JSON.parse(JSON.stringify(obj)); | ||
/** | ||
* Returns an array of keys present on the first but missing on the second object | ||
* @param {object} a | ||
* @param {object} b | ||
* @returns {string[]} | ||
*/ | ||
export const diff = (a, b) => { | ||
@@ -169,0 +218,0 @@ const aKeys = Object.keys(a).sort(); |
import { createStore } from 'redux'; | ||
import rootReducer from '../reducers/index'; | ||
/** | ||
* @typedef {import('../../../types/index').Choices.Choice} Choice | ||
* @typedef {import('../../../types/index').Choices.Group} Group | ||
* @typedef {import('../../../types/index').Choices.Item} Item | ||
*/ | ||
export default class Store { | ||
@@ -24,3 +30,3 @@ constructor() { | ||
* Dispatch event to store (wrapped Redux method) | ||
* @param {Function} action Action function to trigger | ||
* @param {{ type: string, [x: string]: any }} action Action to trigger | ||
* @return | ||
@@ -34,3 +40,3 @@ */ | ||
* Get store object (wrapping Redux method) | ||
* @return {Object} State | ||
* @returns {object} State | ||
*/ | ||
@@ -43,3 +49,3 @@ get state() { | ||
* Get items from store | ||
* @return {Array} Item objects | ||
* @returns {Item[]} Item objects | ||
*/ | ||
@@ -52,3 +58,3 @@ get items() { | ||
* Get active items from store | ||
* @return {Array} Item objects | ||
* @returns {Item[]} Item objects | ||
*/ | ||
@@ -61,3 +67,3 @@ get activeItems() { | ||
* Get highlighted items from store | ||
* @return {Array} Item objects | ||
* @returns {Item[]} Item objects | ||
*/ | ||
@@ -70,3 +76,3 @@ get highlightedActiveItems() { | ||
* Get choices from store | ||
* @return {Array} Option objects | ||
* @returns {Choice[]} Option objects | ||
*/ | ||
@@ -79,9 +85,6 @@ get choices() { | ||
* Get active choices from store | ||
* @return {Array} Option objects | ||
* @returns {Choice[]} Option objects | ||
*/ | ||
get activeChoices() { | ||
const { choices } = this; | ||
const values = choices.filter(choice => choice.active === true); | ||
return values; | ||
return this.choices.filter(choice => choice.active === true); | ||
} | ||
@@ -91,3 +94,3 @@ | ||
* Get selectable choices from store | ||
* @return {Array} Option objects | ||
* @returns {Choice[]} Option objects | ||
*/ | ||
@@ -100,3 +103,3 @@ get selectableChoices() { | ||
* Get choices that can be searched (excluding placeholders) | ||
* @return {Array} Option objects | ||
* @returns {Choice[]} Option objects | ||
*/ | ||
@@ -109,3 +112,3 @@ get searchableChoices() { | ||
* Get placeholder choice from store | ||
* @return {Object} Found placeholder | ||
* @returns {Choice | undefined} Found placeholder | ||
*/ | ||
@@ -120,3 +123,3 @@ get placeholderChoice() { | ||
* Get groups from store | ||
* @return {Array} Group objects | ||
* @returns {Group[]} Group objects | ||
*/ | ||
@@ -129,3 +132,3 @@ get groups() { | ||
* Get active groups from store | ||
* @return {Array} Group objects | ||
* @returns {Group[]} Group objects | ||
*/ | ||
@@ -147,3 +150,3 @@ get activeGroups() { | ||
* Get loading state from store | ||
* @return {Boolean} Loading State | ||
* @returns {boolean} Loading State | ||
*/ | ||
@@ -156,13 +159,7 @@ isLoading() { | ||
* Get single choice by it's ID | ||
* @param {id} string | ||
* @return {import('../../../types/index').Choices.Choice | false} Found choice | ||
* @param {string} id | ||
* @returns {Choice | undefined} Found choice | ||
*/ | ||
getChoiceById(id) { | ||
if (id) { | ||
const n = parseInt(id, 10); | ||
return this.activeChoices.find(choice => choice.id === n); | ||
} | ||
return false; | ||
return this.activeChoices.find(choice => choice.id === parseInt(id, 10)); | ||
} | ||
@@ -172,8 +169,8 @@ | ||
* Get group by group id | ||
* @param {Number} id Group ID | ||
* @return {Object} Group data | ||
* @param {number} id Group ID | ||
* @returns {Group | undefined} Group data | ||
*/ | ||
getGroupById(id) { | ||
return this.groups.find(group => group.id === parseInt(id, 10)); | ||
return this.groups.find(group => group.id === id); | ||
} | ||
} |
@@ -5,5 +5,18 @@ /** | ||
* @typedef {import('../../types/index').Choices.Templates} Templates | ||
* @typedef {import('../../types/index').Choices.ClassNames} ClassNames | ||
* @typedef {import('../../types/index').Choices.Options} Options | ||
* @typedef {import('../../types/index').Choices.Item} Item | ||
* @typedef {import('../../types/index').Choices.Choice} Choice | ||
* @typedef {import('../../types/index').Choices.Group} Group | ||
*/ | ||
export const TEMPLATES = /** @type {Templates} */ ({ | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {"ltr" | "rtl" | "auto"} dir | ||
* @param {boolean} isSelectElement | ||
* @param {boolean} isSelectOneElement | ||
* @param {boolean} searchEnabled | ||
* @param {"select-one" | "select-multiple" | "text"} passedElementType | ||
*/ | ||
containerOuter( | ||
@@ -43,2 +56,6 @@ { containerOuter }, | ||
}, | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
*/ | ||
containerInner({ containerInner }) { | ||
@@ -49,2 +66,7 @@ return Object.assign(document.createElement('div'), { | ||
}, | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {boolean} isSelectOneElement | ||
*/ | ||
itemList({ list, listSingle, listItems }, isSelectOneElement) { | ||
@@ -55,2 +77,7 @@ return Object.assign(document.createElement('div'), { | ||
}, | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {string} value | ||
*/ | ||
placeholder({ placeholder }, value) { | ||
@@ -63,2 +90,7 @@ return Object.assign(document.createElement('div'), { | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {Item} item | ||
* @param {boolean} removeItemButton | ||
*/ | ||
item( | ||
@@ -101,2 +133,3 @@ { item, button, highlightedState, itemSelectable, placeholder }, | ||
} | ||
div.classList.add(highlighted ? highlightedState : itemSelectable); | ||
@@ -126,2 +159,7 @@ | ||
}, | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {boolean} isSelectOneElement | ||
*/ | ||
choiceList({ list }, isSelectOneElement) { | ||
@@ -140,2 +178,6 @@ const div = Object.assign(document.createElement('div'), { | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {Group} group | ||
*/ | ||
choiceGroup({ group, groupHeading, itemDisabled }, { id, value, disabled }) { | ||
@@ -168,5 +210,17 @@ const div = Object.assign(document.createElement('div'), { | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {Choice} choice | ||
* @param {Options['itemSelectText']} selectText | ||
*/ | ||
choice( | ||
{ item, itemChoice, itemSelectable, itemDisabled, placeholder }, | ||
{ | ||
item, | ||
itemChoice, | ||
itemSelectable, | ||
selectedState, | ||
itemDisabled, | ||
placeholder, | ||
}, | ||
{ | ||
id, | ||
@@ -177,3 +231,4 @@ value, | ||
elementId, | ||
disabled, | ||
disabled: isDisabled, | ||
selected: isSelected, | ||
placeholder: isPlaceholder, | ||
@@ -186,7 +241,13 @@ }, | ||
innerHTML: label, | ||
className: `${item} ${itemChoice} ${ | ||
disabled ? itemDisabled : itemSelectable | ||
} ${isPlaceholder ? placeholder : ''}`, | ||
className: `${item} ${itemChoice}`, | ||
}); | ||
if (isSelected) { | ||
div.classList.add(selectedState); | ||
} | ||
if (isPlaceholder) { | ||
div.classList.add(placeholder); | ||
} | ||
div.setAttribute('role', groupId > 0 ? 'treeitem' : 'option'); | ||
@@ -201,6 +262,8 @@ | ||
if (disabled) { | ||
if (isDisabled) { | ||
div.classList.add(itemDisabled); | ||
div.dataset.choiceDisabled = ''; | ||
div.setAttribute('aria-disabled', 'true'); | ||
} else { | ||
div.classList.add(itemSelectable); | ||
div.dataset.choiceSelectable = ''; | ||
@@ -211,2 +274,7 @@ } | ||
}, | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {string} placeholderValue | ||
*/ | ||
input({ input, inputCloned }, placeholderValue) { | ||
@@ -227,2 +295,6 @@ const inp = Object.assign(document.createElement('input'), { | ||
}, | ||
/** | ||
* @param {Partial<ClassNames>} classNames | ||
*/ | ||
dropdown({ list, listDropdown }) { | ||
@@ -236,2 +308,9 @@ const div = document.createElement('div'); | ||
}, | ||
/** | ||
* | ||
* @param {Partial<ClassNames>} classNames | ||
* @param {string} innerHTML | ||
* @param {"no-choices" | "no-results" | ""} type | ||
*/ | ||
notice({ item, itemChoice, noResults, noChoices }, innerHTML, type = '') { | ||
@@ -251,2 +330,6 @@ const classes = [item, itemChoice]; | ||
}, | ||
/** | ||
* @param {Item} option | ||
*/ | ||
option({ label, value, customProperties, active, disabled }) { | ||
@@ -253,0 +336,0 @@ const opt = new Option(label, value, false, active); |
@@ -1,2 +0,2 @@ | ||
// Type definitions for Choices.js 7.1.x | ||
// Type definitions for Choices.js | ||
// Project: https://github.com/jshjohnson/Choices | ||
@@ -22,10 +22,12 @@ // Definitions by: | ||
type filterFunction = (value: string) => boolean; | ||
type valueCompareFunction = (value1: string, value2: string) => boolean; | ||
} | ||
interface Choice { | ||
id?: number; | ||
customProperties?: Record<string, any>; | ||
disabled?: boolean; | ||
active?: boolean; | ||
elementId?: string; | ||
groupId?: string; | ||
id?: string; | ||
groupId?: number; | ||
keyCode?: number; | ||
@@ -38,2 +40,15 @@ label: string; | ||
interface Group { | ||
id?: number; | ||
active?: boolean; | ||
disabled?: boolean; | ||
value: any; | ||
} | ||
interface Item extends Choice { | ||
choiceId?: number; | ||
keyCode?: number; | ||
highlighted?: boolean; | ||
} | ||
/** | ||
@@ -51,7 +66,7 @@ * Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object. | ||
addItem: CustomEvent<{ | ||
id: string; | ||
id: number; | ||
value: string; | ||
label: string; | ||
groupValue: string; | ||
keyCode: string; | ||
keyCode: number; | ||
}>; | ||
@@ -67,3 +82,3 @@ | ||
removeItem: CustomEvent<{ | ||
id: string; | ||
id: number; | ||
value: string; | ||
@@ -82,3 +97,3 @@ label: string; | ||
highlightItem: CustomEvent<{ | ||
id: string; | ||
id: number; | ||
value: string; | ||
@@ -97,3 +112,3 @@ label: string; | ||
unhighlightItem: CustomEvent<{ | ||
id: string; | ||
id: number; | ||
value: string; | ||
@@ -158,14 +173,2 @@ label: string; | ||
interface Group { | ||
active?: boolean; | ||
disabled?: boolean; | ||
id?: string; | ||
value: any; | ||
} | ||
interface Item extends Choice { | ||
choiceId?: string; | ||
keyCode?: number; | ||
} | ||
interface Templates { | ||
@@ -273,2 +276,4 @@ containerOuter: ( | ||
highlightedState: string; | ||
/** @default 'is-selected' */ | ||
selectedState: string; | ||
/** @default 'is-flipped' */ | ||
@@ -415,3 +420,3 @@ flippedState: string; | ||
*/ | ||
addItemFilter: string | RegExp | Choices.Types.filterFunction; | ||
addItemFilter: string | RegExp | Choices.Types.filterFunction | null; | ||
@@ -576,3 +581,3 @@ /** | ||
* const example = new Choices(element, { | ||
* sortFilter: function(a, b) { | ||
* sorter: function(a, b) { | ||
* return b.label.length - a.label.length; | ||
@@ -585,3 +590,3 @@ * }, | ||
*/ | ||
sortFilter: (current: Choice, next: Choice) => number; | ||
sorter: (current: Choice, next: Choice) => number; | ||
@@ -614,3 +619,3 @@ /** | ||
*/ | ||
placeholderValue: string; | ||
placeholderValue: string | null; | ||
@@ -624,3 +629,3 @@ /** | ||
*/ | ||
searchPlaceholderValue: string; | ||
searchPlaceholderValue: string | null; | ||
@@ -634,3 +639,3 @@ /** | ||
*/ | ||
prependValue: string; | ||
prependValue: string | null; | ||
@@ -644,3 +649,3 @@ /** | ||
*/ | ||
appendValue: string; | ||
appendValue: string | null; | ||
@@ -707,3 +712,3 @@ /** | ||
* | ||
* @default 'Only unique values can be added.' | ||
* @default 'Only unique values can be added' | ||
*/ | ||
@@ -713,2 +718,23 @@ uniqueItemText: string | Choices.Types.noticeStringFunction; | ||
/** | ||
* The text that is shown when addItemFilter is passed and it returns false | ||
* | ||
* **Input types affected:** text | ||
* | ||
* @default 'Only values matching specific conditions can be added' | ||
*/ | ||
customAddItemText: string | Choices.Types.noticeStringFunction; | ||
/** | ||
* Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `valueComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example). | ||
* | ||
* **Input types affected:** select-one, select-multiple | ||
* | ||
* @default | ||
* ``` | ||
* (choice, item) => choice === item; | ||
* ``` | ||
*/ | ||
valueComparer: Choices.Types.valueCompareFunction; | ||
/** | ||
* Classes added to HTML generated by Choices. By default classnames follow the BEM notation. | ||
@@ -718,3 +744,3 @@ * | ||
*/ | ||
classNames: Partial<Choices.ClassNames>; | ||
classNames: Choices.ClassNames; | ||
@@ -735,3 +761,3 @@ /** | ||
*/ | ||
callbackOnInit: (this: Choices) => void; | ||
callbackOnInit: ((this: Choices) => void) | null; | ||
@@ -772,5 +798,5 @@ /** | ||
*/ | ||
callbackOnCreateTemplates: ( | ||
template: Choices.Types.strToEl, | ||
) => Partial<Choices.Templates>; | ||
callbackOnCreateTemplates: | ||
| ((template: Choices.Types.strToEl) => Partial<Choices.Templates>) | ||
| null; | ||
} | ||
@@ -777,0 +803,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
479855
40
9846
1096
35