mirror of
https://github.com/Choices-js/Choices.git
synced 2024-04-27 19:42:51 +02:00
Api changes (#515)
* Combine regexFilter and addItemFilter + minor tweaks * Update tests to accomodate fixed dropdown notice * Remove broken `toggleDropdown` method * Unskip dropdown interaction tests * Remove reference to removed method
This commit is contained in:
parent
55b356ec69
commit
8540d5aabd
26
README.md
26
README.md
|
@ -78,6 +78,7 @@ will be returned. If you target one element, that instance will be returned.
|
|||
renderChoiceLimit: -1,
|
||||
maxItemCount: -1,
|
||||
addItems: true,
|
||||
addItemFilterFn: null,
|
||||
removeItems: true,
|
||||
removeItemButton: false,
|
||||
editItems: false,
|
||||
|
@ -91,8 +92,6 @@ will be returned. If you target one element, that instance will be returned.
|
|||
searchFields: ['label', 'value'],
|
||||
position: 'auto',
|
||||
resetScrollPosition: true,
|
||||
regexFilter: null,
|
||||
addItemFilter: null,
|
||||
shouldSort: true,
|
||||
shouldSortItems: false,
|
||||
sortFn: () => {...},
|
||||
|
@ -340,31 +339,24 @@ Pass an array of objects:
|
|||
|
||||
**Usage:** Whether the scroll position should reset after adding an item.
|
||||
|
||||
### addItemFilter
|
||||
### addItemFilterFn
|
||||
**Type:** `Function` **Default:** `null`
|
||||
|
||||
**Input types affected:** `text`
|
||||
|
||||
**Usage:** A callback function that will need to return `true` for a user to successfully add an item.
|
||||
**Usage:** A filter function that will need to return `true` for a user to successfully add an item.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
// Only adds items matching the text test
|
||||
new Choices(element, {
|
||||
addItemFilter: function (value) {
|
||||
return (value !== 'test')
|
||||
}
|
||||
addItemFilterFn: (value) => {
|
||||
return (value !== 'test');
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### regexFilter
|
||||
**Type:** `Regex` **Default:** `null`
|
||||
|
||||
**Input types affected:** `text`
|
||||
|
||||
**Usage:** A filter that will need to pass for a user to successfully add an item.
|
||||
|
||||
### shouldSort
|
||||
**Type:** `Boolean` **Default:** `true`
|
||||
|
||||
|
@ -752,12 +744,6 @@ choices.disable();
|
|||
|
||||
**Usage:** Hide option list dropdown (only affects select inputs).
|
||||
|
||||
|
||||
### toggleDropdown();
|
||||
**Input types affected:** `text`, `select-multiple`
|
||||
|
||||
**Usage:** Toggle dropdown between showing/hidden.
|
||||
|
||||
### setChoices(choices, value, label, replaceChoices);
|
||||
**Input types affected:** `select-one`, `select-multiple`
|
||||
|
||||
|
|
|
@ -216,7 +216,7 @@ describe('Choices - select multiple', () => {
|
|||
|
||||
@todo Investigate why
|
||||
*/
|
||||
describe.skip('interacting with dropdown', () => {
|
||||
describe('interacting with dropdown', () => {
|
||||
describe('opening dropdown', () => {
|
||||
it('opens dropdown', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
|
@ -247,44 +247,6 @@ describe('Choices - select multiple', () => {
|
|||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggling dropdown', () => {
|
||||
describe('when open', () => {
|
||||
it('closes dropdown', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.open-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.toggle-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closed', () => {
|
||||
it('opens dropdown', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.close-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.toggle-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabling', () => {
|
||||
|
|
|
@ -134,7 +134,7 @@ describe('Choices - select one', () => {
|
|||
|
||||
@todo Investigate why
|
||||
*/
|
||||
describe.skip('interacting with dropdown', () => {
|
||||
describe('interacting with dropdown', () => {
|
||||
describe('opening dropdown', () => {
|
||||
it('opens dropdown', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
|
@ -165,44 +165,6 @@ describe('Choices - select one', () => {
|
|||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggling dropdown', () => {
|
||||
describe('when open', () => {
|
||||
it('closes dropdown', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.open-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.toggle-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closed', () => {
|
||||
it('opens dropdown', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.close-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('button.toggle-dropdown')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabling', () => {
|
||||
|
|
|
@ -197,8 +197,9 @@ describe('Choices - text element', () => {
|
|||
});
|
||||
|
||||
describe('input limit', () => {
|
||||
const inputLimit = 5;
|
||||
beforeEach(() => {
|
||||
for (let index = 0; index < 6; index++) {
|
||||
for (let index = 0; index < inputLimit + 1; index++) {
|
||||
cy.get('[data-test-hook=input-limit]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`${textInput} + ${index}`)
|
||||
|
@ -212,29 +213,36 @@ describe('Choices - text element', () => {
|
|||
.first()
|
||||
.children()
|
||||
.should($items => {
|
||||
expect($items.length).to.equal(5);
|
||||
expect($items.length).to.equal(inputLimit);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides dropdown prompt once limit has been reached', () => {
|
||||
cy.wait(500); // allow for animation frame
|
||||
cy.get('[data-test-hook=input-limit]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('not.be.visible');
|
||||
describe('reaching input limit', () => {
|
||||
it('displays dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=input-limit]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible')
|
||||
.should($dropdown => {
|
||||
const dropdownText = $dropdown.text().trim();
|
||||
expect(dropdownText).to.equal(
|
||||
`Only ${inputLimit} values can be added`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('regex filter', () => {
|
||||
describe('inputting a value that satisfies the regex', () => {
|
||||
describe('add item filter', () => {
|
||||
describe('inputting a value that satisfies the filter', () => {
|
||||
const input = 'joe@bloggs.com';
|
||||
|
||||
it('allows me to add choice', () => {
|
||||
cy.get('[data-test-hook=regex-filter]')
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(input)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=regex-filter]')
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should($choice => {
|
||||
|
@ -245,14 +253,20 @@ describe('Choices - text element', () => {
|
|||
|
||||
describe('inputting a value that does not satisfy the regex', () => {
|
||||
it('displays dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=regex-filter]')
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`this is not an email address`)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=regex-filter]')
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('not.be.visible');
|
||||
.should('be.visible')
|
||||
.should($dropdown => {
|
||||
const dropdownText = $dropdown.text().trim();
|
||||
expect(dropdownText).to.equal(
|
||||
'Only values matching specific conditions can be added',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -293,39 +307,6 @@ describe('Choices - text element', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('custom add item callback', () => {
|
||||
describe('inputting a value that satisfies the addItemFilter', () => {
|
||||
const input = 'test';
|
||||
|
||||
it('allows me to add choice', () => {
|
||||
cy.get('[data-test-hook=add-item-callback]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(input)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=add-item-callback]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should($choice => {
|
||||
expect($choice.text().trim()).to.equal(input);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inputting a value that does not satisfy the callback', () => {
|
||||
it('displays dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=add-item-callback]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`this is not the allowed text`)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=add-item-callback]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled via attribute', () => {
|
||||
it('does not allow me to input data', () => {
|
||||
cy.get('[data-test-hook=disabled-via-attr]')
|
||||
|
|
|
@ -344,7 +344,15 @@
|
|||
|
||||
var textEmailFilter = new Choices('#choices-text-email-filter', {
|
||||
editItems: true,
|
||||
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,}))$/,
|
||||
addItemFilterFn: (value) => {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regex = /^(([^<>()\[\]\\.,;:\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,}))$/
|
||||
const expression = new RegExp(regex.source, 'i');
|
||||
return expression.test(value);
|
||||
},
|
||||
}).setValue(['joe@bloggs.com']);
|
||||
|
||||
var textDisabled = new Choices('#choices-text-disabled', {
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
<label for="choices-basic">Basic</label>
|
||||
<button class="open-dropdown push-bottom">Open dropdown</button>
|
||||
<button class="close-dropdown push-bottom">Close dropdown</button>
|
||||
<button class="toggle-dropdown push-bottom">Toggle dropdown</button>
|
||||
<button class="disable push-bottom">Disable</button>
|
||||
<button class="enable push-bottom">Enable</button>
|
||||
<select class="form-control" name="choices-basic" id="choices-basic" multiple>
|
||||
|
@ -216,10 +215,6 @@
|
|||
choicesBasic.hideDropdown();
|
||||
});
|
||||
|
||||
document.querySelector('button.toggle-dropdown').addEventListener('click', () => {
|
||||
choicesBasic.toggleDropdown();
|
||||
});
|
||||
|
||||
document.querySelector('button.disable').addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
<label for="choices-basic">Basic</label>
|
||||
<button class="open-dropdown push-bottom">Open dropdown</button>
|
||||
<button class="close-dropdown push-bottom">Close dropdown</button>
|
||||
<button class="toggle-dropdown push-bottom">Toggle dropdown</button>
|
||||
<button class="disable push-bottom">Disable</button>
|
||||
<button class="enable push-bottom">Enable</button>
|
||||
<select class="form-control" name="choices-basic" id="choices-basic">
|
||||
|
@ -220,10 +219,6 @@
|
|||
choicesBasic.hideDropdown();
|
||||
});
|
||||
|
||||
document.querySelector('button.toggle-dropdown').addEventListener('click', () => {
|
||||
choicesBasic.toggleDropdown(true);
|
||||
});
|
||||
|
||||
document.querySelector('button.disable').addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
|
|
|
@ -54,9 +54,9 @@
|
|||
<input class="form-control" id="choices-input-limit" type="text">
|
||||
</div>
|
||||
|
||||
<div data-test-hook="regex-filter">
|
||||
<label for="choices-regex-filter">Regex filter</label>
|
||||
<input class="form-control" id="choices-regex-filter" type="text">
|
||||
<div data-test-hook="add-item-filter">
|
||||
<label for="choices-add-item-filter">Add item filter</label>
|
||||
<input class="form-control" id="choices-add-item-filter" type="text">
|
||||
</div>
|
||||
|
||||
<div data-test-hook="adding-items-disabled">
|
||||
|
@ -64,11 +64,6 @@
|
|||
<input class="form-control" id="choices-adding-items-disabled" type="text">
|
||||
</div>
|
||||
|
||||
<div data-test-hook="add-item-callback">
|
||||
<label for="choices-add-item-callback">Callback on Add Item</label>
|
||||
<input class="form-control" id="choices-add-item-callback" type="text">
|
||||
</div>
|
||||
|
||||
<div data-test-hook="disabled-via-attr">
|
||||
<label for="choices-disabled-via-attr">Disabled via attribute</label>
|
||||
<input class="form-control" id="choices-disabled-via-attr" type="text" disabled>
|
||||
|
@ -117,20 +112,19 @@
|
|||
maxItemCount: 5,
|
||||
});
|
||||
|
||||
new Choices('#choices-regex-filter', {
|
||||
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,}))$/,
|
||||
new Choices('#choices-add-item-filter', {
|
||||
addItems: true,
|
||||
addItemFilterFn: (value) => {
|
||||
const regex = /^(([^<>()\[\]\\.,;:\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,}))$/
|
||||
const expression = new RegExp(regex.source, 'i');
|
||||
return expression.test(value);
|
||||
},
|
||||
});
|
||||
|
||||
new Choices('#choices-adding-items-disabled', {
|
||||
addItems: false,
|
||||
});
|
||||
|
||||
new Choices('#choices-add-item-callback', {
|
||||
addItemFilter: function (value) {
|
||||
return (value !== 'test')
|
||||
}
|
||||
});
|
||||
|
||||
new Choices('#choices-disabled-via-attr');
|
||||
|
||||
new Choices('#choices-prepend-append', {
|
||||
|
|
|
@ -33,12 +33,11 @@ import {
|
|||
sortByScore,
|
||||
generateId,
|
||||
findAncestorByAttrName,
|
||||
regexFilter,
|
||||
fetchFromObject,
|
||||
isIE11,
|
||||
existsInArray,
|
||||
cloneObject,
|
||||
doKeysMatch,
|
||||
diff,
|
||||
} from './lib/utils';
|
||||
|
||||
/**
|
||||
|
@ -64,8 +63,12 @@ class Choices {
|
|||
{ arrayMerge: (destinationArray, sourceArray) => [...sourceArray] },
|
||||
);
|
||||
|
||||
if (!doKeysMatch(this.config, DEFAULT_CONFIG)) {
|
||||
console.warn('Unknown config option(s) passed');
|
||||
const invalidConfigOptions = diff(this.config, DEFAULT_CONFIG);
|
||||
if (invalidConfigOptions.length) {
|
||||
console.warn(
|
||||
'Unknown config option(s) passed',
|
||||
invalidConfigOptions.join(', '),
|
||||
);
|
||||
}
|
||||
|
||||
if (!['auto', 'always'].includes(this.config.renderSelectedChoices)) {
|
||||
|
@ -375,11 +378,6 @@ class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
toggleDropdown() {
|
||||
this.dropdown.isActive ? this.hideDropdown() : this.showDropdown();
|
||||
return this;
|
||||
}
|
||||
|
||||
getValue(valueOnly = false) {
|
||||
const values = this._store.activeItems.reduce((selectedItems, item) => {
|
||||
const itemValue = valueOnly ? item.value : item;
|
||||
|
@ -965,18 +963,6 @@ class Choices {
|
|||
: this.config.maxItemText;
|
||||
}
|
||||
|
||||
if (
|
||||
this.config.regexFilter &&
|
||||
this._isTextElement &&
|
||||
this.config.addItems &&
|
||||
canAddItem
|
||||
) {
|
||||
// If a user has supplied a regular expression filter
|
||||
// determine whether we can update based on whether
|
||||
// our regular expression passes
|
||||
canAddItem = regexFilter(value, this.config.regexFilter);
|
||||
}
|
||||
|
||||
if (
|
||||
!this.config.duplicateItemsAllowed &&
|
||||
isDuplicateValue &&
|
||||
|
@ -989,11 +975,11 @@ class Choices {
|
|||
}
|
||||
|
||||
if (
|
||||
isType('Function', this.config.addItemFilter) &&
|
||||
this.config.addItemFilter(value) &&
|
||||
this._isTextElement &&
|
||||
this.config.addItems &&
|
||||
canAddItem
|
||||
canAddItem &&
|
||||
isType('Function', this.config.addItemFilterFn) &&
|
||||
!this.config.addItemFilterFn(value)
|
||||
) {
|
||||
canAddItem = false;
|
||||
notice = isType('Function', this.config.customAddItemText)
|
||||
|
@ -1202,35 +1188,29 @@ class Choices {
|
|||
const value = this.input.value;
|
||||
const activeItems = this._store.activeItems;
|
||||
const canAddItem = this._canAddItem(activeItems, value);
|
||||
const { BACK_KEY: backKey, DELETE_KEY: deleteKey } = KEY_CODES;
|
||||
|
||||
// We are typing into a text input and have a value, we want to show a dropdown
|
||||
// notice. Otherwise hide the dropdown
|
||||
if (this._isTextElement) {
|
||||
if (value) {
|
||||
if (canAddItem.notice) {
|
||||
const dropdownItem = this._getTemplate('notice', canAddItem.notice);
|
||||
this.dropdown.element.innerHTML = dropdownItem.outerHTML;
|
||||
}
|
||||
|
||||
if (canAddItem.response === true) {
|
||||
this.showDropdown(true);
|
||||
} else if (!canAddItem.notice) {
|
||||
this.hideDropdown(true);
|
||||
}
|
||||
const canShowDropdownNotice = canAddItem.notice && value;
|
||||
if (canShowDropdownNotice) {
|
||||
const dropdownItem = this._getTemplate('notice', canAddItem.notice);
|
||||
this.dropdown.element.innerHTML = dropdownItem.outerHTML;
|
||||
this.showDropdown(true);
|
||||
} else {
|
||||
this.hideDropdown(true);
|
||||
}
|
||||
} else {
|
||||
const backKey = KEY_CODES.BACK_KEY;
|
||||
const deleteKey = KEY_CODES.DELETE_KEY;
|
||||
const userHasRemovedValue =
|
||||
(keyCode === backKey || keyCode === deleteKey) && !target.value;
|
||||
const canReactivateChoices = !this._isTextElement && this._isSearching;
|
||||
const canSearch = this._canSearch && canAddItem.response;
|
||||
|
||||
// If user has removed value...
|
||||
if ((keyCode === backKey || keyCode === deleteKey) && !target.value) {
|
||||
if (!this._isTextElement && this._isSearching) {
|
||||
this._isSearching = false;
|
||||
this._store.dispatch(activateChoices(true));
|
||||
}
|
||||
} else if (this._canSearch && canAddItem.response) {
|
||||
if (userHasRemovedValue && canReactivateChoices) {
|
||||
this._isSearching = false;
|
||||
this._store.dispatch(activateChoices(true));
|
||||
} else if (canSearch) {
|
||||
this._handleSearch(this.input.value);
|
||||
}
|
||||
}
|
||||
|
@ -1242,12 +1222,13 @@ class Choices {
|
|||
// If CTRL + A or CMD + A have been pressed and there are items to select
|
||||
if (hasCtrlDownKeyPressed && hasItems) {
|
||||
this._canSearch = false;
|
||||
if (
|
||||
|
||||
const shouldHightlightAll =
|
||||
this.config.removeItems &&
|
||||
!this.input.value &&
|
||||
this.input.element === document.activeElement
|
||||
) {
|
||||
// Highlight items
|
||||
this.input.element === document.activeElement;
|
||||
|
||||
if (shouldHightlightAll) {
|
||||
this.highlightAll();
|
||||
}
|
||||
}
|
||||
|
@ -1255,12 +1236,12 @@ class Choices {
|
|||
|
||||
_onEnterKey({ event, target, activeItems, hasActiveDropdown }) {
|
||||
const { ENTER_KEY: enterKey } = KEY_CODES;
|
||||
// If enter key is pressed and the input has a value
|
||||
const targetWasButton = target.hasAttribute('data-button');
|
||||
|
||||
if (this._isTextElement && target.value) {
|
||||
const value = this.input.value;
|
||||
const canAddItem = this._canAddItem(activeItems, value);
|
||||
|
||||
// All is good, add
|
||||
if (canAddItem.response) {
|
||||
this.hideDropdown(true);
|
||||
this._addItem({ value });
|
||||
|
@ -1269,28 +1250,26 @@ class Choices {
|
|||
}
|
||||
}
|
||||
|
||||
if (target.hasAttribute('data-button')) {
|
||||
if (targetWasButton) {
|
||||
this._handleButtonAction(activeItems, target);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (hasActiveDropdown) {
|
||||
const highlighted = this.dropdown.getChild(
|
||||
const highlightedChoice = this.dropdown.getChild(
|
||||
`.${this.config.classNames.highlightedState}`,
|
||||
);
|
||||
|
||||
// If we have a highlighted choice
|
||||
if (highlighted) {
|
||||
if (highlightedChoice) {
|
||||
// add enter keyCode value
|
||||
if (activeItems[0]) {
|
||||
activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
this._handleChoiceAction(activeItems, highlighted);
|
||||
this._handleChoiceAction(activeItems, highlightedChoice);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
} else if (this._isSelectOneElement) {
|
||||
// Open single select dropdown if it's not active
|
||||
this.showDropdown();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
@ -1375,31 +1354,29 @@ class Choices {
|
|||
}
|
||||
|
||||
_onTouchMove() {
|
||||
if (this._wasTap === true) {
|
||||
if (this._wasTap) {
|
||||
this._wasTap = false;
|
||||
}
|
||||
}
|
||||
|
||||
_onTouchEnd(event) {
|
||||
const target = event.target || event.touches[0].target;
|
||||
const { target } = event || event.touches[0];
|
||||
const touchWasWithinContainer =
|
||||
this._wasTap && this.containerOuter.element.contains(target);
|
||||
|
||||
// If a user tapped within our container...
|
||||
if (this._wasTap === true && this.containerOuter.element.contains(target)) {
|
||||
// ...and we aren't dealing with a single select box, show dropdown/focus input
|
||||
|
||||
const containerWasTarget =
|
||||
if (touchWasWithinContainer) {
|
||||
const containerWasExactTarget =
|
||||
target === this.containerOuter.element ||
|
||||
target === this.containerInner.element;
|
||||
|
||||
if (containerWasTarget && !this._isSelectOneElement) {
|
||||
if (containerWasExactTarget) {
|
||||
if (this._isTextElement) {
|
||||
// If text element, we only want to focus the input
|
||||
this.input.focus();
|
||||
} else {
|
||||
// If a select box, we want to show the dropdown
|
||||
} else if (this._isSelectMultipleElement) {
|
||||
this.showDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents focus event firing
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
@ -1423,7 +1400,6 @@ class Choices {
|
|||
|
||||
const activeItems = this._store.activeItems;
|
||||
const hasShiftKey = shiftKey;
|
||||
|
||||
const buttonTarget = findAncestorByAttrName(target, 'data-button');
|
||||
const itemTarget = findAncestorByAttrName(target, 'data-item');
|
||||
const choiceTarget = findAncestorByAttrName(target, 'data-choice');
|
||||
|
@ -1451,7 +1427,11 @@ class Choices {
|
|||
}
|
||||
|
||||
_onClick({ target }) {
|
||||
if (this.containerOuter.element.contains(target)) {
|
||||
const clickWasWithinContainer = this.containerOuter.element.contains(
|
||||
target,
|
||||
);
|
||||
|
||||
if (clickWasWithinContainer) {
|
||||
if (!this.dropdown.isActive && !this.containerOuter.isDisabled) {
|
||||
if (this._isTextElement) {
|
||||
if (document.activeElement !== this.input.element) {
|
||||
|
@ -1481,7 +1461,11 @@ class Choices {
|
|||
}
|
||||
|
||||
_onFocus({ target }) {
|
||||
if (!this.containerOuter.element.contains(target)) {
|
||||
const focusWasWithinContainer = this.containerOuter.element.contains(
|
||||
target,
|
||||
);
|
||||
|
||||
if (!focusWasWithinContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1511,11 +1495,9 @@ class Choices {
|
|||
}
|
||||
|
||||
_onBlur({ target }) {
|
||||
// If target is something that concerns us
|
||||
if (
|
||||
this.containerOuter.element.contains(target) &&
|
||||
!this._isScrollingOnIe
|
||||
) {
|
||||
const blurWasWithinContainer = this.containerOuter.element.contains(target);
|
||||
|
||||
if (blurWasWithinContainer && !this._isScrollingOnIe) {
|
||||
const activeItems = this._store.activeItems;
|
||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
||||
const blurActions = {
|
||||
|
|
|
@ -491,50 +491,6 @@ describe('choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('toggleDropdown', () => {
|
||||
let hideDropdownStub;
|
||||
let showDropdownStub;
|
||||
|
||||
beforeEach(() => {
|
||||
hideDropdownStub = stub();
|
||||
showDropdownStub = stub();
|
||||
|
||||
instance.hideDropdown = hideDropdownStub;
|
||||
instance.showDropdown = showDropdownStub;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.hideDropdown.reset();
|
||||
instance.showDropdown.reset();
|
||||
});
|
||||
|
||||
describe('dropdown active', () => {
|
||||
beforeEach(() => {
|
||||
instance.dropdown.isActive = true;
|
||||
output = instance.toggleDropdown();
|
||||
});
|
||||
|
||||
it('hides dropdown', () => {
|
||||
expect(hideDropdownStub.called).to.equal(true);
|
||||
});
|
||||
|
||||
returnsInstance(output);
|
||||
});
|
||||
|
||||
describe('dropdown inactive', () => {
|
||||
beforeEach(() => {
|
||||
instance.dropdown.isActive = false;
|
||||
output = instance.toggleDropdown();
|
||||
});
|
||||
|
||||
it('shows dropdown', () => {
|
||||
expect(showDropdownStub.called).to.equal(true);
|
||||
});
|
||||
|
||||
returnsInstance(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('highlightItem', () => {
|
||||
let passedElementTriggerEventStub;
|
||||
let storeDispatchSpy;
|
||||
|
|
|
@ -36,6 +36,7 @@ export const DEFAULT_CONFIG = {
|
|||
renderChoiceLimit: -1,
|
||||
maxItemCount: -1,
|
||||
addItems: true,
|
||||
addItemFilterFn: null,
|
||||
removeItems: true,
|
||||
removeItemButton: false,
|
||||
editItems: false,
|
||||
|
@ -49,7 +50,6 @@ export const DEFAULT_CONFIG = {
|
|||
searchFields: ['label', 'value'],
|
||||
position: 'auto',
|
||||
resetScrollPosition: true,
|
||||
regexFilter: null,
|
||||
shouldSort: true,
|
||||
shouldSortItems: false,
|
||||
sortFn: sortByAlpha,
|
||||
|
@ -64,7 +64,7 @@ export const DEFAULT_CONFIG = {
|
|||
noChoicesText: 'No choices to choose from',
|
||||
itemSelectText: 'Press to select',
|
||||
uniqueItemText: 'Only unique values can be added',
|
||||
customAddItemText: 'Only values matching specific conditions can be added.',
|
||||
customAddItemText: 'Only values matching specific conditions can be added',
|
||||
addItemText: value => `Press Enter to add <b>"${stripHTML(value)}"</b>`,
|
||||
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added`,
|
||||
itemComparer: (choice, item) => choice === item,
|
||||
|
@ -72,7 +72,6 @@ export const DEFAULT_CONFIG = {
|
|||
includeScore: true,
|
||||
},
|
||||
callbackOnInit: null,
|
||||
addItemFilter: null,
|
||||
callbackOnCreateTemplates: null,
|
||||
classNames: DEFAULT_CLASSNAMES,
|
||||
};
|
||||
|
|
|
@ -56,6 +56,7 @@ describe('constants', () => {
|
|||
expect(DEFAULT_CONFIG.renderChoiceLimit).to.be.a('number');
|
||||
expect(DEFAULT_CONFIG.maxItemCount).to.be.a('number');
|
||||
expect(DEFAULT_CONFIG.addItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.addItemFilterFn).to.equal(null);
|
||||
expect(DEFAULT_CONFIG.removeItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.editItems).to.be.a('boolean');
|
||||
|
@ -68,7 +69,6 @@ describe('constants', () => {
|
|||
expect(DEFAULT_CONFIG.searchResultLimit).to.be.a('number');
|
||||
expect(DEFAULT_CONFIG.searchFields).to.be.an('array');
|
||||
expect(DEFAULT_CONFIG.position).to.be.a('string');
|
||||
expect(DEFAULT_CONFIG.regexFilter).to.equal(null);
|
||||
expect(DEFAULT_CONFIG.shouldSort).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.shouldSortItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.placeholder).to.be.a('boolean');
|
||||
|
@ -86,7 +86,6 @@ describe('constants', () => {
|
|||
expect(DEFAULT_CONFIG.addItemText).to.be.a('function');
|
||||
expect(DEFAULT_CONFIG.maxItemText).to.be.a('function');
|
||||
expect(DEFAULT_CONFIG.fuseOptions).to.be.an('object');
|
||||
expect(DEFAULT_CONFIG.addItemFilter).to.equal(null);
|
||||
expect(DEFAULT_CONFIG.callbackOnInit).to.equal(null);
|
||||
expect(DEFAULT_CONFIG.callbackOnCreateTemplates).to.equal(null);
|
||||
});
|
||||
|
|
|
@ -229,15 +229,6 @@ export const dispatchEvent = (element, type, customArgs = null) => {
|
|||
return element.dispatchEvent(event);
|
||||
};
|
||||
|
||||
export const regexFilter = (value, regex) => {
|
||||
if (!value || !regex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expression = new RegExp(regex.source, 'i');
|
||||
return expression.test(value);
|
||||
};
|
||||
|
||||
export const getWindowHeight = () => {
|
||||
const body = document.body;
|
||||
const html = document.documentElement;
|
||||
|
@ -289,8 +280,11 @@ export const existsInArray = (array, value, key = 'value') =>
|
|||
|
||||
export const cloneObject = obj => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
export const doKeysMatch = (a, b) => {
|
||||
export const diff = (a, b) => {
|
||||
const aKeys = Object.keys(a).sort();
|
||||
const bKeys = Object.keys(b).sort();
|
||||
return JSON.stringify(aKeys) === JSON.stringify(bKeys);
|
||||
|
||||
return aKeys.filter((i) => {
|
||||
return bKeys.indexOf(i) < 0;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
fetchFromObject,
|
||||
existsInArray,
|
||||
cloneObject,
|
||||
regexFilter,
|
||||
dispatchEvent,
|
||||
} from './utils';
|
||||
|
||||
|
@ -248,17 +247,6 @@ describe('utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('regexFilter', () => {
|
||||
it('tests given regex against given value', () => {
|
||||
// An email address regex
|
||||
// eslint-disable-next-line
|
||||
const regex = /^(([^<>()\[\]\\.,;:\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,}))$/;
|
||||
|
||||
expect(regexFilter('joe@bloggs.com', regex)).to.equal(true);
|
||||
expect(regexFilter('joe bloggs', regex)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reduceToValues', () => {
|
||||
it('reduces an array of objects to an array of values using given key', () => {
|
||||
const values = [
|
||||
|
|
25
types/index.d.ts
vendored
25
types/index.d.ts
vendored
|
@ -29,6 +29,15 @@ declare namespace Choices {
|
|||
*/
|
||||
"addItem": CustomEvent;
|
||||
|
||||
/**
|
||||
* A filter that will need to pass for a user to successfully add an item.
|
||||
*
|
||||
* **Input types affected:** text
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
addItemFilterFn?: () => any;
|
||||
|
||||
/**
|
||||
* Triggered each time an item is removed (programmatically or by the user).
|
||||
*
|
||||
|
@ -405,15 +414,6 @@ declare namespace Choices {
|
|||
*/
|
||||
resetScrollPosition?: boolean;
|
||||
|
||||
/**
|
||||
* A filter that will need to pass for a user to successfully add an item.
|
||||
*
|
||||
* **Input types affected:** text
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
regexFilter?: RegExp;
|
||||
|
||||
/**
|
||||
* Whether choices and groups should be sorted. If false, choices/groups will appear in the order they were given.
|
||||
*
|
||||
|
@ -764,13 +764,6 @@ export default class Choices {
|
|||
*/
|
||||
hideDropdown(blurInput?: boolean): this;
|
||||
|
||||
/**
|
||||
* Toggle dropdown between showing/hidden.
|
||||
*
|
||||
* **Input types affected:** text, select-multiple
|
||||
*/
|
||||
toggleDropdown(): this;
|
||||
|
||||
/**
|
||||
* Get value(s) of input (i.e. inputted items (text) or selected choices (select)). Optionally pass an argument of `true` to only return values rather than value objects.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue