diff --git a/README.md b/README.md index d0eef2d..186caad 100644 --- a/README.md +++ b/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` diff --git a/cypress/integration/select-multiple.spec.js b/cypress/integration/select-multiple.spec.js index d7db618..ade66bb 100644 --- a/cypress/integration/select-multiple.spec.js +++ b/cypress/integration/select-multiple.spec.js @@ -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', () => { diff --git a/cypress/integration/select-one.spec.js b/cypress/integration/select-one.spec.js index 2ff287d..1ac30dc 100644 --- a/cypress/integration/select-one.spec.js +++ b/cypress/integration/select-one.spec.js @@ -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', () => { diff --git a/cypress/integration/text.spec.js b/cypress/integration/text.spec.js index abdc4a2..a74afc8 100644 --- a/cypress/integration/text.spec.js +++ b/cypress/integration/text.spec.js @@ -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]') diff --git a/public/index.html b/public/index.html index a9b7905..dc6a92f 100644 --- a/public/index.html +++ b/public/index.html @@ -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', { diff --git a/public/test/select-multiple.html b/public/test/select-multiple.html index a2a7621..2b0b4a8 100644 --- a/public/test/select-multiple.html +++ b/public/test/select-multiple.html @@ -33,7 +33,6 @@ - @@ -220,10 +219,6 @@ choicesBasic.hideDropdown(); }); - document.querySelector('button.toggle-dropdown').addEventListener('click', () => { - choicesBasic.toggleDropdown(true); - }); - document.querySelector('button.disable').addEventListener('click', () => { choicesBasic.disable(); }); diff --git a/public/test/text.html b/public/test/text.html index dd7427f..1d4ea24 100644 --- a/public/test/text.html +++ b/public/test/text.html @@ -54,9 +54,9 @@ -
- - +
+ +
@@ -64,11 +64,6 @@
-
- - -
-
@@ -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', { diff --git a/src/scripts/choices.js b/src/scripts/choices.js index 170c968..bcb8096 100644 --- a/src/scripts/choices.js +++ b/src/scripts/choices.js @@ -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 = { diff --git a/src/scripts/choices.test.js b/src/scripts/choices.test.js index 01991cb..12c21fa 100644 --- a/src/scripts/choices.test.js +++ b/src/scripts/choices.test.js @@ -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; diff --git a/src/scripts/constants.js b/src/scripts/constants.js index 64aac00..73b9707 100644 --- a/src/scripts/constants.js +++ b/src/scripts/constants.js @@ -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 "${stripHTML(value)}"`, 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, }; diff --git a/src/scripts/constants.test.js b/src/scripts/constants.test.js index 01a1f6e..89623dc 100644 --- a/src/scripts/constants.test.js +++ b/src/scripts/constants.test.js @@ -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); }); diff --git a/src/scripts/lib/utils.js b/src/scripts/lib/utils.js index 14437a5..2eda11d 100644 --- a/src/scripts/lib/utils.js +++ b/src/scripts/lib/utils.js @@ -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; + }); } diff --git a/src/scripts/lib/utils.test.js b/src/scripts/lib/utils.test.js index b128d0a..53f5889 100644 --- a/src/scripts/lib/utils.test.js +++ b/src/scripts/lib/utils.test.js @@ -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 = [ diff --git a/types/index.d.ts b/types/index.d.ts index 6c8a987..626fa9d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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. *