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:
Josh Johnson 2019-02-12 18:35:46 +00:00 committed by GitHub
parent 55b356ec69
commit 8540d5aabd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 129 additions and 335 deletions

View file

@ -78,6 +78,7 @@ will be returned. If you target one element, that instance will be returned.
renderChoiceLimit: -1, renderChoiceLimit: -1,
maxItemCount: -1, maxItemCount: -1,
addItems: true, addItems: true,
addItemFilterFn: null,
removeItems: true, removeItems: true,
removeItemButton: false, removeItemButton: false,
editItems: false, editItems: false,
@ -91,8 +92,6 @@ will be returned. If you target one element, that instance will be returned.
searchFields: ['label', 'value'], searchFields: ['label', 'value'],
position: 'auto', position: 'auto',
resetScrollPosition: true, resetScrollPosition: true,
regexFilter: null,
addItemFilter: null,
shouldSort: true, shouldSort: true,
shouldSortItems: false, shouldSortItems: false,
sortFn: () => {...}, sortFn: () => {...},
@ -340,31 +339,24 @@ Pass an array of objects:
**Usage:** Whether the scroll position should reset after adding an item. **Usage:** Whether the scroll position should reset after adding an item.
### addItemFilter ### addItemFilterFn
**Type:** `Function` **Default:** `null` **Type:** `Function` **Default:** `null`
**Input types affected:** `text` **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:** **Example:**
```js ```js
// Only adds items matching the text test // Only adds items matching the text test
new Choices(element, { new Choices(element, {
addItemFilter: function (value) { addItemFilterFn: (value) => {
return (value !== 'test') 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 ### shouldSort
**Type:** `Boolean` **Default:** `true` **Type:** `Boolean` **Default:** `true`
@ -752,12 +744,6 @@ choices.disable();
**Usage:** Hide option list dropdown (only affects select inputs). **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); ### setChoices(choices, value, label, replaceChoices);
**Input types affected:** `select-one`, `select-multiple` **Input types affected:** `select-one`, `select-multiple`

View file

@ -216,7 +216,7 @@ describe('Choices - select multiple', () => {
@todo Investigate why @todo Investigate why
*/ */
describe.skip('interacting with dropdown', () => { describe('interacting with dropdown', () => {
describe('opening dropdown', () => { describe('opening dropdown', () => {
it('opens dropdown', () => { it('opens dropdown', () => {
cy.get('[data-test-hook=basic]') cy.get('[data-test-hook=basic]')
@ -247,44 +247,6 @@ describe('Choices - select multiple', () => {
.should('not.be.visible'); .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', () => { describe('disabling', () => {

View file

@ -134,7 +134,7 @@ describe('Choices - select one', () => {
@todo Investigate why @todo Investigate why
*/ */
describe.skip('interacting with dropdown', () => { describe('interacting with dropdown', () => {
describe('opening dropdown', () => { describe('opening dropdown', () => {
it('opens dropdown', () => { it('opens dropdown', () => {
cy.get('[data-test-hook=basic]') cy.get('[data-test-hook=basic]')
@ -165,44 +165,6 @@ describe('Choices - select one', () => {
.should('not.be.visible'); .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', () => { describe('disabling', () => {

View file

@ -197,8 +197,9 @@ describe('Choices - text element', () => {
}); });
describe('input limit', () => { describe('input limit', () => {
const inputLimit = 5;
beforeEach(() => { beforeEach(() => {
for (let index = 0; index < 6; index++) { for (let index = 0; index < inputLimit + 1; index++) {
cy.get('[data-test-hook=input-limit]') cy.get('[data-test-hook=input-limit]')
.find('.choices__input--cloned') .find('.choices__input--cloned')
.type(`${textInput} + ${index}`) .type(`${textInput} + ${index}`)
@ -212,29 +213,36 @@ describe('Choices - text element', () => {
.first() .first()
.children() .children()
.should($items => { .should($items => {
expect($items.length).to.equal(5); expect($items.length).to.equal(inputLimit);
}); });
}); });
it('hides dropdown prompt once limit has been reached', () => { describe('reaching input limit', () => {
cy.wait(500); // allow for animation frame it('displays dropdown prompt', () => {
cy.get('[data-test-hook=input-limit]') cy.get('[data-test-hook=input-limit]')
.find('.choices__list--dropdown') .find('.choices__list--dropdown')
.should('not.be.visible'); .should('be.visible')
.should($dropdown => {
const dropdownText = $dropdown.text().trim();
expect(dropdownText).to.equal(
`Only ${inputLimit} values can be added`,
);
});
});
}); });
}); });
describe('regex filter', () => { describe('add item filter', () => {
describe('inputting a value that satisfies the regex', () => { describe('inputting a value that satisfies the filter', () => {
const input = 'joe@bloggs.com'; const input = 'joe@bloggs.com';
it('allows me to add choice', () => { 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') .find('.choices__input--cloned')
.type(input) .type(input)
.type('{enter}'); .type('{enter}');
cy.get('[data-test-hook=regex-filter]') cy.get('[data-test-hook=add-item-filter]')
.find('.choices__list--multiple .choices__item') .find('.choices__list--multiple .choices__item')
.last() .last()
.should($choice => { .should($choice => {
@ -245,14 +253,20 @@ describe('Choices - text element', () => {
describe('inputting a value that does not satisfy the regex', () => { describe('inputting a value that does not satisfy the regex', () => {
it('displays dropdown prompt', () => { it('displays dropdown prompt', () => {
cy.get('[data-test-hook=regex-filter]') cy.get('[data-test-hook=add-item-filter]')
.find('.choices__input--cloned') .find('.choices__input--cloned')
.type(`this is not an email address`) .type(`this is not an email address`)
.type('{enter}'); .type('{enter}');
cy.get('[data-test-hook=regex-filter]') cy.get('[data-test-hook=add-item-filter]')
.find('.choices__list--dropdown') .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', () => { describe('disabled via attribute', () => {
it('does not allow me to input data', () => { it('does not allow me to input data', () => {
cy.get('[data-test-hook=disabled-via-attr]') cy.get('[data-test-hook=disabled-via-attr]')

View file

@ -344,7 +344,15 @@
var textEmailFilter = new Choices('#choices-text-email-filter', { var textEmailFilter = new Choices('#choices-text-email-filter', {
editItems: true, 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']); }).setValue(['joe@bloggs.com']);
var textDisabled = new Choices('#choices-text-disabled', { var textDisabled = new Choices('#choices-text-disabled', {

View file

@ -33,7 +33,6 @@
<label for="choices-basic">Basic</label> <label for="choices-basic">Basic</label>
<button class="open-dropdown push-bottom">Open dropdown</button> <button class="open-dropdown push-bottom">Open dropdown</button>
<button class="close-dropdown push-bottom">Close 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="disable push-bottom">Disable</button>
<button class="enable push-bottom">Enable</button> <button class="enable push-bottom">Enable</button>
<select class="form-control" name="choices-basic" id="choices-basic" multiple> <select class="form-control" name="choices-basic" id="choices-basic" multiple>
@ -216,10 +215,6 @@
choicesBasic.hideDropdown(); choicesBasic.hideDropdown();
}); });
document.querySelector('button.toggle-dropdown').addEventListener('click', () => {
choicesBasic.toggleDropdown();
});
document.querySelector('button.disable').addEventListener('click', () => { document.querySelector('button.disable').addEventListener('click', () => {
choicesBasic.disable(); choicesBasic.disable();
}); });

View file

@ -33,7 +33,6 @@
<label for="choices-basic">Basic</label> <label for="choices-basic">Basic</label>
<button class="open-dropdown push-bottom">Open dropdown</button> <button class="open-dropdown push-bottom">Open dropdown</button>
<button class="close-dropdown push-bottom">Close 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="disable push-bottom">Disable</button>
<button class="enable push-bottom">Enable</button> <button class="enable push-bottom">Enable</button>
<select class="form-control" name="choices-basic" id="choices-basic"> <select class="form-control" name="choices-basic" id="choices-basic">
@ -220,10 +219,6 @@
choicesBasic.hideDropdown(); choicesBasic.hideDropdown();
}); });
document.querySelector('button.toggle-dropdown').addEventListener('click', () => {
choicesBasic.toggleDropdown(true);
});
document.querySelector('button.disable').addEventListener('click', () => { document.querySelector('button.disable').addEventListener('click', () => {
choicesBasic.disable(); choicesBasic.disable();
}); });

View file

@ -54,9 +54,9 @@
<input class="form-control" id="choices-input-limit" type="text"> <input class="form-control" id="choices-input-limit" type="text">
</div> </div>
<div data-test-hook="regex-filter"> <div data-test-hook="add-item-filter">
<label for="choices-regex-filter">Regex filter</label> <label for="choices-add-item-filter">Add item filter</label>
<input class="form-control" id="choices-regex-filter" type="text"> <input class="form-control" id="choices-add-item-filter" type="text">
</div> </div>
<div data-test-hook="adding-items-disabled"> <div data-test-hook="adding-items-disabled">
@ -64,11 +64,6 @@
<input class="form-control" id="choices-adding-items-disabled" type="text"> <input class="form-control" id="choices-adding-items-disabled" type="text">
</div> </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"> <div data-test-hook="disabled-via-attr">
<label for="choices-disabled-via-attr">Disabled via attribute</label> <label for="choices-disabled-via-attr">Disabled via attribute</label>
<input class="form-control" id="choices-disabled-via-attr" type="text" disabled> <input class="form-control" id="choices-disabled-via-attr" type="text" disabled>
@ -117,20 +112,19 @@
maxItemCount: 5, maxItemCount: 5,
}); });
new Choices('#choices-regex-filter', { new Choices('#choices-add-item-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,}))$/, 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', { new Choices('#choices-adding-items-disabled', {
addItems: false, addItems: false,
}); });
new Choices('#choices-add-item-callback', {
addItemFilter: function (value) {
return (value !== 'test')
}
});
new Choices('#choices-disabled-via-attr'); new Choices('#choices-disabled-via-attr');
new Choices('#choices-prepend-append', { new Choices('#choices-prepend-append', {

View file

@ -33,12 +33,11 @@ import {
sortByScore, sortByScore,
generateId, generateId,
findAncestorByAttrName, findAncestorByAttrName,
regexFilter,
fetchFromObject, fetchFromObject,
isIE11, isIE11,
existsInArray, existsInArray,
cloneObject, cloneObject,
doKeysMatch, diff,
} from './lib/utils'; } from './lib/utils';
/** /**
@ -64,8 +63,12 @@ class Choices {
{ arrayMerge: (destinationArray, sourceArray) => [...sourceArray] }, { arrayMerge: (destinationArray, sourceArray) => [...sourceArray] },
); );
if (!doKeysMatch(this.config, DEFAULT_CONFIG)) { const invalidConfigOptions = diff(this.config, DEFAULT_CONFIG);
console.warn('Unknown config option(s) passed'); if (invalidConfigOptions.length) {
console.warn(
'Unknown config option(s) passed',
invalidConfigOptions.join(', '),
);
} }
if (!['auto', 'always'].includes(this.config.renderSelectedChoices)) { if (!['auto', 'always'].includes(this.config.renderSelectedChoices)) {
@ -375,11 +378,6 @@ class Choices {
return this; return this;
} }
toggleDropdown() {
this.dropdown.isActive ? this.hideDropdown() : this.showDropdown();
return this;
}
getValue(valueOnly = false) { getValue(valueOnly = false) {
const values = this._store.activeItems.reduce((selectedItems, item) => { const values = this._store.activeItems.reduce((selectedItems, item) => {
const itemValue = valueOnly ? item.value : item; const itemValue = valueOnly ? item.value : item;
@ -965,18 +963,6 @@ class Choices {
: this.config.maxItemText; : 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 ( if (
!this.config.duplicateItemsAllowed && !this.config.duplicateItemsAllowed &&
isDuplicateValue && isDuplicateValue &&
@ -989,11 +975,11 @@ class Choices {
} }
if ( if (
isType('Function', this.config.addItemFilter) &&
this.config.addItemFilter(value) &&
this._isTextElement && this._isTextElement &&
this.config.addItems && this.config.addItems &&
canAddItem canAddItem &&
isType('Function', this.config.addItemFilterFn) &&
!this.config.addItemFilterFn(value)
) { ) {
canAddItem = false; canAddItem = false;
notice = isType('Function', this.config.customAddItemText) notice = isType('Function', this.config.customAddItemText)
@ -1202,35 +1188,29 @@ class Choices {
const value = this.input.value; const value = this.input.value;
const activeItems = this._store.activeItems; const activeItems = this._store.activeItems;
const canAddItem = this._canAddItem(activeItems, value); 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 // We are typing into a text input and have a value, we want to show a dropdown
// notice. Otherwise hide the dropdown // notice. Otherwise hide the dropdown
if (this._isTextElement) { if (this._isTextElement) {
if (value) { const canShowDropdownNotice = canAddItem.notice && value;
if (canAddItem.notice) { if (canShowDropdownNotice) {
const dropdownItem = this._getTemplate('notice', canAddItem.notice); const dropdownItem = this._getTemplate('notice', canAddItem.notice);
this.dropdown.element.innerHTML = dropdownItem.outerHTML; this.dropdown.element.innerHTML = dropdownItem.outerHTML;
} this.showDropdown(true);
if (canAddItem.response === true) {
this.showDropdown(true);
} else if (!canAddItem.notice) {
this.hideDropdown(true);
}
} else { } else {
this.hideDropdown(true); this.hideDropdown(true);
} }
} else { } else {
const backKey = KEY_CODES.BACK_KEY; const userHasRemovedValue =
const deleteKey = KEY_CODES.DELETE_KEY; (keyCode === backKey || keyCode === deleteKey) && !target.value;
const canReactivateChoices = !this._isTextElement && this._isSearching;
const canSearch = this._canSearch && canAddItem.response;
// If user has removed value... if (userHasRemovedValue && canReactivateChoices) {
if ((keyCode === backKey || keyCode === deleteKey) && !target.value) { this._isSearching = false;
if (!this._isTextElement && this._isSearching) { this._store.dispatch(activateChoices(true));
this._isSearching = false; } else if (canSearch) {
this._store.dispatch(activateChoices(true));
}
} else if (this._canSearch && canAddItem.response) {
this._handleSearch(this.input.value); 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 CTRL + A or CMD + A have been pressed and there are items to select
if (hasCtrlDownKeyPressed && hasItems) { if (hasCtrlDownKeyPressed && hasItems) {
this._canSearch = false; this._canSearch = false;
if (
const shouldHightlightAll =
this.config.removeItems && this.config.removeItems &&
!this.input.value && !this.input.value &&
this.input.element === document.activeElement this.input.element === document.activeElement;
) {
// Highlight items if (shouldHightlightAll) {
this.highlightAll(); this.highlightAll();
} }
} }
@ -1255,12 +1236,12 @@ class Choices {
_onEnterKey({ event, target, activeItems, hasActiveDropdown }) { _onEnterKey({ event, target, activeItems, hasActiveDropdown }) {
const { ENTER_KEY: enterKey } = KEY_CODES; 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) { if (this._isTextElement && target.value) {
const value = this.input.value; const value = this.input.value;
const canAddItem = this._canAddItem(activeItems, value); const canAddItem = this._canAddItem(activeItems, value);
// All is good, add
if (canAddItem.response) { if (canAddItem.response) {
this.hideDropdown(true); this.hideDropdown(true);
this._addItem({ value }); this._addItem({ value });
@ -1269,28 +1250,26 @@ class Choices {
} }
} }
if (target.hasAttribute('data-button')) { if (targetWasButton) {
this._handleButtonAction(activeItems, target); this._handleButtonAction(activeItems, target);
event.preventDefault(); event.preventDefault();
} }
if (hasActiveDropdown) { if (hasActiveDropdown) {
const highlighted = this.dropdown.getChild( const highlightedChoice = this.dropdown.getChild(
`.${this.config.classNames.highlightedState}`, `.${this.config.classNames.highlightedState}`,
); );
// If we have a highlighted choice if (highlightedChoice) {
if (highlighted) {
// add enter keyCode value // add enter keyCode value
if (activeItems[0]) { if (activeItems[0]) {
activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign
} }
this._handleChoiceAction(activeItems, highlighted); this._handleChoiceAction(activeItems, highlightedChoice);
} }
event.preventDefault(); event.preventDefault();
} else if (this._isSelectOneElement) { } else if (this._isSelectOneElement) {
// Open single select dropdown if it's not active
this.showDropdown(); this.showDropdown();
event.preventDefault(); event.preventDefault();
} }
@ -1375,31 +1354,29 @@ class Choices {
} }
_onTouchMove() { _onTouchMove() {
if (this._wasTap === true) { if (this._wasTap) {
this._wasTap = false; this._wasTap = false;
} }
} }
_onTouchEnd(event) { _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 (touchWasWithinContainer) {
if (this._wasTap === true && this.containerOuter.element.contains(target)) { const containerWasExactTarget =
// ...and we aren't dealing with a single select box, show dropdown/focus input
const containerWasTarget =
target === this.containerOuter.element || target === this.containerOuter.element ||
target === this.containerInner.element; target === this.containerInner.element;
if (containerWasTarget && !this._isSelectOneElement) { if (containerWasExactTarget) {
if (this._isTextElement) { if (this._isTextElement) {
// If text element, we only want to focus the input
this.input.focus(); this.input.focus();
} else { } else if (this._isSelectMultipleElement) {
// If a select box, we want to show the dropdown
this.showDropdown(); this.showDropdown();
} }
} }
// Prevents focus event firing // Prevents focus event firing
event.stopPropagation(); event.stopPropagation();
} }
@ -1423,7 +1400,6 @@ class Choices {
const activeItems = this._store.activeItems; const activeItems = this._store.activeItems;
const hasShiftKey = shiftKey; const hasShiftKey = shiftKey;
const buttonTarget = findAncestorByAttrName(target, 'data-button'); const buttonTarget = findAncestorByAttrName(target, 'data-button');
const itemTarget = findAncestorByAttrName(target, 'data-item'); const itemTarget = findAncestorByAttrName(target, 'data-item');
const choiceTarget = findAncestorByAttrName(target, 'data-choice'); const choiceTarget = findAncestorByAttrName(target, 'data-choice');
@ -1451,7 +1427,11 @@ class Choices {
} }
_onClick({ target }) { _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.dropdown.isActive && !this.containerOuter.isDisabled) {
if (this._isTextElement) { if (this._isTextElement) {
if (document.activeElement !== this.input.element) { if (document.activeElement !== this.input.element) {
@ -1481,7 +1461,11 @@ class Choices {
} }
_onFocus({ target }) { _onFocus({ target }) {
if (!this.containerOuter.element.contains(target)) { const focusWasWithinContainer = this.containerOuter.element.contains(
target,
);
if (!focusWasWithinContainer) {
return; return;
} }
@ -1511,11 +1495,9 @@ class Choices {
} }
_onBlur({ target }) { _onBlur({ target }) {
// If target is something that concerns us const blurWasWithinContainer = this.containerOuter.element.contains(target);
if (
this.containerOuter.element.contains(target) && if (blurWasWithinContainer && !this._isScrollingOnIe) {
!this._isScrollingOnIe
) {
const activeItems = this._store.activeItems; const activeItems = this._store.activeItems;
const hasHighlightedItems = activeItems.some(item => item.highlighted); const hasHighlightedItems = activeItems.some(item => item.highlighted);
const blurActions = { const blurActions = {

View file

@ -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', () => { describe('highlightItem', () => {
let passedElementTriggerEventStub; let passedElementTriggerEventStub;
let storeDispatchSpy; let storeDispatchSpy;

View file

@ -36,6 +36,7 @@ export const DEFAULT_CONFIG = {
renderChoiceLimit: -1, renderChoiceLimit: -1,
maxItemCount: -1, maxItemCount: -1,
addItems: true, addItems: true,
addItemFilterFn: null,
removeItems: true, removeItems: true,
removeItemButton: false, removeItemButton: false,
editItems: false, editItems: false,
@ -49,7 +50,6 @@ export const DEFAULT_CONFIG = {
searchFields: ['label', 'value'], searchFields: ['label', 'value'],
position: 'auto', position: 'auto',
resetScrollPosition: true, resetScrollPosition: true,
regexFilter: null,
shouldSort: true, shouldSort: true,
shouldSortItems: false, shouldSortItems: false,
sortFn: sortByAlpha, sortFn: sortByAlpha,
@ -64,7 +64,7 @@ export const DEFAULT_CONFIG = {
noChoicesText: 'No choices to choose from', noChoicesText: 'No choices to choose from',
itemSelectText: 'Press to select', itemSelectText: 'Press to select',
uniqueItemText: 'Only unique values can be added', 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>`, addItemText: value => `Press Enter to add <b>"${stripHTML(value)}"</b>`,
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added`, maxItemText: maxItemCount => `Only ${maxItemCount} values can be added`,
itemComparer: (choice, item) => choice === item, itemComparer: (choice, item) => choice === item,
@ -72,7 +72,6 @@ export const DEFAULT_CONFIG = {
includeScore: true, includeScore: true,
}, },
callbackOnInit: null, callbackOnInit: null,
addItemFilter: null,
callbackOnCreateTemplates: null, callbackOnCreateTemplates: null,
classNames: DEFAULT_CLASSNAMES, classNames: DEFAULT_CLASSNAMES,
}; };

View file

@ -56,6 +56,7 @@ describe('constants', () => {
expect(DEFAULT_CONFIG.renderChoiceLimit).to.be.a('number'); expect(DEFAULT_CONFIG.renderChoiceLimit).to.be.a('number');
expect(DEFAULT_CONFIG.maxItemCount).to.be.a('number'); expect(DEFAULT_CONFIG.maxItemCount).to.be.a('number');
expect(DEFAULT_CONFIG.addItems).to.be.a('boolean'); 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.removeItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean'); expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean');
expect(DEFAULT_CONFIG.editItems).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.searchResultLimit).to.be.a('number');
expect(DEFAULT_CONFIG.searchFields).to.be.an('array'); expect(DEFAULT_CONFIG.searchFields).to.be.an('array');
expect(DEFAULT_CONFIG.position).to.be.a('string'); 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.shouldSort).to.be.a('boolean');
expect(DEFAULT_CONFIG.shouldSortItems).to.be.a('boolean'); expect(DEFAULT_CONFIG.shouldSortItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.placeholder).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.addItemText).to.be.a('function');
expect(DEFAULT_CONFIG.maxItemText).to.be.a('function'); expect(DEFAULT_CONFIG.maxItemText).to.be.a('function');
expect(DEFAULT_CONFIG.fuseOptions).to.be.an('object'); 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.callbackOnInit).to.equal(null);
expect(DEFAULT_CONFIG.callbackOnCreateTemplates).to.equal(null); expect(DEFAULT_CONFIG.callbackOnCreateTemplates).to.equal(null);
}); });

View file

@ -229,15 +229,6 @@ export const dispatchEvent = (element, type, customArgs = null) => {
return element.dispatchEvent(event); 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 = () => { export const getWindowHeight = () => {
const body = document.body; const body = document.body;
const html = document.documentElement; 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 cloneObject = obj => JSON.parse(JSON.stringify(obj));
export const doKeysMatch = (a, b) => { export const diff = (a, b) => {
const aKeys = Object.keys(a).sort(); const aKeys = Object.keys(a).sort();
const bKeys = Object.keys(b).sort(); const bKeys = Object.keys(b).sort();
return JSON.stringify(aKeys) === JSON.stringify(bKeys);
return aKeys.filter((i) => {
return bKeys.indexOf(i) < 0;
});
} }

View file

@ -14,7 +14,6 @@ import {
fetchFromObject, fetchFromObject,
existsInArray, existsInArray,
cloneObject, cloneObject,
regexFilter,
dispatchEvent, dispatchEvent,
} from './utils'; } 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', () => { describe('reduceToValues', () => {
it('reduces an array of objects to an array of values using given key', () => { it('reduces an array of objects to an array of values using given key', () => {
const values = [ const values = [

25
types/index.d.ts vendored
View file

@ -29,6 +29,15 @@ declare namespace Choices {
*/ */
"addItem": CustomEvent; "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). * Triggered each time an item is removed (programmatically or by the user).
* *
@ -405,15 +414,6 @@ declare namespace Choices {
*/ */
resetScrollPosition?: boolean; 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. * 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; 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. * 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.
* *