mirror of
https://github.com/Choices-js/Choices.git
synced 2024-06-08 08:52:19 +02:00
Update dependencies, fix linting issues, split interfaces and default objects to resolve dependency cycles
This commit is contained in:
parent
46deb9abe5
commit
3d921621b7
|
@ -3,6 +3,7 @@
|
||||||
"plugins": ["@typescript-eslint", "prettier", "sort-class-members"],
|
"plugins": ["@typescript-eslint", "prettier", "sort-class-members"],
|
||||||
"extends": [
|
"extends": [
|
||||||
"airbnb-base",
|
"airbnb-base",
|
||||||
|
"airbnb-typescript",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"plugin:compat/recommended",
|
"plugin:compat/recommended",
|
||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
@ -61,7 +62,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lines-between-class-members": "off",
|
"lines-between-class-members": "off",
|
||||||
"@typescript-eslint/no-namespace": "off"
|
"@typescript-eslint/no-namespace": "off",
|
||||||
|
"react/jsx-filename-extension": [0]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
@ -75,7 +77,16 @@
|
||||||
"no-new": "off",
|
"no-new": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-non-null-assertion": "off"
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "default",
|
||||||
|
"format": ["camelCase", "PascalCase", "UPPER_CASE"],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,7 +62,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.then($choice => {
|
.then(($choice) => {
|
||||||
selectedChoiceText = $choice.text().trim();
|
selectedChoiceText = $choice.text().trim();
|
||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
@ -72,7 +72,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=basic]')
|
cy.get('[data-test-hook=basic]')
|
||||||
.find('.choices__list--multiple .choices__item')
|
.find('.choices__list--multiple .choices__item')
|
||||||
.last()
|
.last()
|
||||||
.should($item => {
|
.should(($item) => {
|
||||||
expect($item).to.contain(selectedChoiceText);
|
expect($item).to.contain(selectedChoiceText);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -80,7 +80,7 @@ describe('Choices - select multiple', () => {
|
||||||
it('updates the value of the original input', () => {
|
it('updates the value of the original input', () => {
|
||||||
cy.get('[data-test-hook=basic]')
|
cy.get('[data-test-hook=basic]')
|
||||||
.find('.choices__input[hidden]')
|
.find('.choices__input[hidden]')
|
||||||
.should($select => {
|
.should(($select) => {
|
||||||
expect($select.val()).to.contain(selectedChoiceText);
|
expect($select.val()).to.contain(selectedChoiceText);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -89,7 +89,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=basic]')
|
cy.get('[data-test-hook=basic]')
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.each($choice => {
|
.each(($choice) => {
|
||||||
expect($choice.text().trim()).to.not.equal(selectedChoiceText);
|
expect($choice.text().trim()).to.not.equal(selectedChoiceText);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -114,7 +114,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=basic]')
|
cy.get('[data-test-hook=basic]')
|
||||||
.find('.choices__list--dropdown')
|
.find('.choices__list--dropdown')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.should($dropdown => {
|
.should(($dropdown) => {
|
||||||
const dropdownText = $dropdown.text().trim();
|
const dropdownText = $dropdown.text().trim();
|
||||||
expect(dropdownText).to.equal('No choices to choose from');
|
expect(dropdownText).to.equal('No choices to choose from');
|
||||||
});
|
});
|
||||||
|
@ -130,7 +130,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.last()
|
.last()
|
||||||
.then($choice => {
|
.then(($choice) => {
|
||||||
removedChoiceText = $choice.text().trim();
|
removedChoiceText = $choice.text().trim();
|
||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
@ -151,7 +151,7 @@ describe('Choices - select multiple', () => {
|
||||||
it('updates the value of the original input', () => {
|
it('updates the value of the original input', () => {
|
||||||
cy.get('[data-test-hook=basic]')
|
cy.get('[data-test-hook=basic]')
|
||||||
.find('.choices__input[hidden]')
|
.find('.choices__input[hidden]')
|
||||||
.should($select => {
|
.should(($select) => {
|
||||||
const val = $select.val() || [];
|
const val = $select.val() || [];
|
||||||
expect(val).to.not.contain(removedChoiceText);
|
expect(val).to.not.contain(removedChoiceText);
|
||||||
});
|
});
|
||||||
|
@ -171,7 +171,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal('Choice 2');
|
expect($choice.text().trim()).to.equal('Choice 2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -187,7 +187,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal('Choice 3');
|
expect($choice.text().trim()).to.equal('Choice 3');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -202,7 +202,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=basic]')
|
cy.get('[data-test-hook=basic]')
|
||||||
.find('.choices__list--dropdown')
|
.find('.choices__list--dropdown')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.should($dropdown => {
|
.should(($dropdown) => {
|
||||||
const dropdownText = $dropdown.text().trim();
|
const dropdownText = $dropdown.text().trim();
|
||||||
expect(dropdownText).to.equal('No results found');
|
expect(dropdownText).to.equal('No results found');
|
||||||
});
|
});
|
||||||
|
@ -346,10 +346,10 @@ describe('Choices - select multiple', () => {
|
||||||
|
|
||||||
describe('selection limit', () => {
|
describe('selection limit', () => {
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
maxItemCount: 5,
|
maxItemCount: 5,
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const selectionLimit = 5;
|
const selectionLimit = 5;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -370,7 +370,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=selection-limit]')
|
cy.get('[data-test-hook=selection-limit]')
|
||||||
.find('.choices__list--dropdown')
|
.find('.choices__list--dropdown')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.should($dropdown => {
|
.should(($dropdown) => {
|
||||||
const dropdownText = $dropdown.text().trim();
|
const dropdownText = $dropdown.text().trim();
|
||||||
expect(dropdownText).to.equal(
|
expect(dropdownText).to.equal(
|
||||||
`Only ${selectionLimit} values can be added`,
|
`Only ${selectionLimit} values can be added`,
|
||||||
|
@ -397,7 +397,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.last()
|
.last()
|
||||||
.then($choice => {
|
.then(($choice) => {
|
||||||
selectedChoiceText = $choice.text().trim();
|
selectedChoiceText = $choice.text().trim();
|
||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
@ -407,7 +407,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=prepend-append]')
|
cy.get('[data-test-hook=prepend-append]')
|
||||||
.find('.choices__list--multiple .choices__item')
|
.find('.choices__list--multiple .choices__item')
|
||||||
.last()
|
.last()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.data('value')).to.equal(
|
expect($choice.data('value')).to.equal(
|
||||||
`before-${selectedChoiceText}-after`,
|
`before-${selectedChoiceText}-after`,
|
||||||
);
|
);
|
||||||
|
@ -418,7 +418,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=prepend-append]')
|
cy.get('[data-test-hook=prepend-append]')
|
||||||
.find('.choices__list--multiple .choices__item')
|
.find('.choices__list--multiple .choices__item')
|
||||||
.last()
|
.last()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text()).to.not.contain(
|
expect($choice.text()).to.not.contain(
|
||||||
`before-${selectedChoiceText}-after`,
|
`before-${selectedChoiceText}-after`,
|
||||||
);
|
);
|
||||||
|
@ -460,7 +460,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.not.contain(searchTerm);
|
expect($choice.text().trim()).to.not.contain(searchTerm);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -478,7 +478,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.contain(searchTerm);
|
expect($choice.text().trim()).to.contain(searchTerm);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -565,13 +565,15 @@ describe('Choices - select multiple', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('dropdown scrolling', () => {
|
describe('dropdown scrolling', () => {
|
||||||
let choicesCount;
|
let choicesCount: number;
|
||||||
|
// let choicesItems: number[];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.get('[data-test-hook=scrolling-dropdown]')
|
cy.get('[data-test-hook=scrolling-dropdown]')
|
||||||
.find('.choices__list--dropdown .choices__list .choices__item')
|
.find('.choices__list--dropdown .choices__list .choices__item')
|
||||||
.then($choices => {
|
.then(($choices) => {
|
||||||
choicesCount = $choices.length;
|
choicesCount = $choices.length;
|
||||||
|
// choicesItems = Array.from({ length: 10 }, (_, i) => i + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('[data-test-hook=scrolling-dropdown]')
|
cy.get('[data-test-hook=scrolling-dropdown]')
|
||||||
|
@ -582,7 +584,7 @@ describe('Choices - select multiple', () => {
|
||||||
it('highlights first choice on dropdown open', () => {
|
it('highlights first choice on dropdown open', () => {
|
||||||
cy.get('[data-test-hook=scrolling-dropdown]')
|
cy.get('[data-test-hook=scrolling-dropdown]')
|
||||||
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal('Choice 1');
|
expect($choice.text().trim()).to.equal('Choice 1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -593,7 +595,7 @@ describe('Choices - select multiple', () => {
|
||||||
|
|
||||||
cy.get('[data-test-hook=scrolling-dropdown]')
|
cy.get('[data-test-hook=scrolling-dropdown]')
|
||||||
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal(`Choice ${index + 1}`);
|
expect($choice.text().trim()).to.equal(`Choice ${index + 1}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -617,7 +619,7 @@ describe('Choices - select multiple', () => {
|
||||||
|
|
||||||
cy.get('[data-test-hook=scrolling-dropdown]')
|
cy.get('[data-test-hook=scrolling-dropdown]')
|
||||||
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal(`Choice ${index}`);
|
expect($choice.text().trim()).to.equal(`Choice ${index}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -636,7 +638,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=groups]')
|
cy.get('[data-test-hook=groups]')
|
||||||
.find('.choices__list--dropdown .choices__list .choices__group')
|
.find('.choices__list--dropdown .choices__list .choices__group')
|
||||||
.first()
|
.first()
|
||||||
.then($group => {
|
.then(($group) => {
|
||||||
groupValue = $group.text().trim();
|
groupValue = $group.text().trim();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -657,7 +659,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=groups]')
|
cy.get('[data-test-hook=groups]')
|
||||||
.find('.choices__list--dropdown .choices__list .choices__group')
|
.find('.choices__list--dropdown .choices__list .choices__group')
|
||||||
.first()
|
.first()
|
||||||
.should($group => {
|
.should(($group) => {
|
||||||
expect($group.text().trim()).to.not.equal(groupValue);
|
expect($group.text().trim()).to.not.equal(groupValue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -688,7 +690,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=groups]')
|
cy.get('[data-test-hook=groups]')
|
||||||
.find('.choices__list--dropdown .choices__list .choices__group')
|
.find('.choices__list--dropdown .choices__list .choices__group')
|
||||||
.first()
|
.first()
|
||||||
.should($group => {
|
.should(($group) => {
|
||||||
expect($group.text().trim()).to.equal(groupValue);
|
expect($group.text().trim()).to.equal(groupValue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -728,7 +730,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal(city);
|
expect($choice.text().trim()).to.equal(city);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -742,9 +744,7 @@ describe('Choices - select multiple', () => {
|
||||||
|
|
||||||
describe('non-string values', () => {
|
describe('non-string values', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.get('[data-test-hook=non-string-values]')
|
cy.get('[data-test-hook=non-string-values]').find('.choices').click();
|
||||||
.find('.choices')
|
|
||||||
.click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays expected amount of choices in dropdown', () => {
|
it('displays expected amount of choices in dropdown', () => {
|
||||||
|
@ -760,7 +760,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.then($choice => {
|
.then(($choice) => {
|
||||||
$selectedChoice = $choice;
|
$selectedChoice = $choice;
|
||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
@ -768,7 +768,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=non-string-values]')
|
cy.get('[data-test-hook=non-string-values]')
|
||||||
.find('.choices__list--single .choices__item')
|
.find('.choices__list--single .choices__item')
|
||||||
.last()
|
.last()
|
||||||
.should($item => {
|
.should(($item) => {
|
||||||
expect($item.text().trim()).to.equal($selectedChoice.text().trim());
|
expect($item.text().trim()).to.equal($selectedChoice.text().trim());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -778,7 +778,7 @@ describe('Choices - select multiple', () => {
|
||||||
describe('selecting choice', () => {
|
describe('selecting choice', () => {
|
||||||
describe('on enter key', () => {
|
describe('on enter key', () => {
|
||||||
it('selects choice', () => {
|
it('selects choice', () => {
|
||||||
cy.get('[data-test-hook=within-form] form').then($form => {
|
cy.get('[data-test-hook=within-form] form').then(($form) => {
|
||||||
$form.submit(() => {
|
$form.submit(() => {
|
||||||
// this will fail the test if the form submits
|
// this will fail the test if the form submits
|
||||||
throw new Error('Form submitted');
|
throw new Error('Form submitted');
|
||||||
|
@ -793,7 +793,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=within-form]')
|
cy.get('[data-test-hook=within-form]')
|
||||||
.find('.choices__list--multiple .choices__item')
|
.find('.choices__list--multiple .choices__item')
|
||||||
.last()
|
.last()
|
||||||
.should($item => {
|
.should(($item) => {
|
||||||
expect($item).to.contain('Choice 1');
|
expect($item).to.contain('Choice 1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -808,7 +808,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=set-choice-by-value]')
|
cy.get('[data-test-hook=set-choice-by-value]')
|
||||||
.find('.choices__list--multiple .choices__item')
|
.find('.choices__list--multiple .choices__item')
|
||||||
.last()
|
.last()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal(
|
expect($choice.text().trim()).to.equal(
|
||||||
dynamicallySelectedChoiceValue,
|
dynamicallySelectedChoiceValue,
|
||||||
);
|
);
|
||||||
|
@ -819,7 +819,7 @@ describe('Choices - select multiple', () => {
|
||||||
cy.get('[data-test-hook=set-choice-by-value]')
|
cy.get('[data-test-hook=set-choice-by-value]')
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.each($choice => {
|
.each(($choice) => {
|
||||||
expect($choice.text().trim()).to.not.equal(
|
expect($choice.text().trim()).to.not.equal(
|
||||||
dynamicallySelectedChoiceValue,
|
dynamicallySelectedChoiceValue,
|
||||||
);
|
);
|
||||||
|
@ -829,7 +829,7 @@ describe('Choices - select multiple', () => {
|
||||||
it('updates the value of the original input', () => {
|
it('updates the value of the original input', () => {
|
||||||
cy.get('[data-test-hook=set-choice-by-value]')
|
cy.get('[data-test-hook=set-choice-by-value]')
|
||||||
.find('.choices__input[hidden]')
|
.find('.choices__input[hidden]')
|
||||||
.should($select => {
|
.should(($select) => {
|
||||||
const val = $select.val() || [];
|
const val = $select.val() || [];
|
||||||
expect(val).to.contain(dynamicallySelectedChoiceValue);
|
expect(val).to.contain(dynamicallySelectedChoiceValue);
|
||||||
});
|
});
|
||||||
|
@ -846,7 +846,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal('No results found');
|
expect($choice.text().trim()).to.equal('No results found');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -860,7 +860,7 @@ describe('Choices - select multiple', () => {
|
||||||
.find('.choices__list--dropdown .choices__list')
|
.find('.choices__list--dropdown .choices__list')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.should($choice => {
|
.should(($choice) => {
|
||||||
expect($choice.text().trim()).to.equal('label1');
|
expect($choice.text().trim()).to.equal('label1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
31093
package-lock.json
generated
31093
package-lock.json
generated
File diff suppressed because it is too large
Load diff
93
package.json
93
package.json
|
@ -13,7 +13,7 @@
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"cypress:ci": "cypress run --record --group $GITHUB_REF --ci-build-id $GITHUB_SHA",
|
"cypress:ci": "cypress run --record --group $GITHUB_REF --ci-build-id $GITHUB_SHA",
|
||||||
"test": "run-s test:unit test:e2e",
|
"test": "run-s test:unit test:e2e",
|
||||||
"test:unit": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha",
|
"test:unit": "cross-env TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha",
|
||||||
"test:unit:watch": "npm run test:unit -- --watch --inspect=5556",
|
"test:unit:watch": "npm run test:unit -- --watch --inspect=5556",
|
||||||
"test:unit:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text --reporter=text-summary mocha",
|
"test:unit:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text --reporter=text-summary mocha",
|
||||||
"test:e2e": "run-p --race start cypress:run",
|
"test:e2e": "run-p --race start cypress:run",
|
||||||
|
@ -53,56 +53,57 @@
|
||||||
"js"
|
"js"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.6.4",
|
"@babel/core": "^7.16.5",
|
||||||
"@babel/preset-env": "^7.6.3",
|
"@babel/preset-env": "^7.16.5",
|
||||||
"@babel/register": "^7.6.2",
|
"@babel/register": "^7.16.5",
|
||||||
"@types/chai": "^4.2.7",
|
"@types/chai": "^4.3.0",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^9.0.0",
|
||||||
"@types/sinon": "^7.5.1",
|
"@types/sinon": "^10.0.6",
|
||||||
"@types/sinon-chai": "^3.2.3",
|
"@types/sinon-chai": "^3.2.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.11.0",
|
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||||
"@typescript-eslint/parser": "^2.11.0",
|
"@typescript-eslint/parser": "^5.7.0",
|
||||||
"autoprefixer": "^9.6.5",
|
"autoprefixer": "^10.4.0",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.2.3",
|
||||||
"bundlesize": "^0.18.0",
|
"bundlesize": "^0.18.1",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.3.4",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"csso-cli": "^3.0.0",
|
"csso-cli": "^3.0.0",
|
||||||
"cypress": "3.6.0",
|
"cypress": "9.1.1",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.4.1",
|
||||||
"eslint-config-airbnb-base": "^14.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^6.5.0",
|
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-loader": "^3.0.2",
|
"eslint-loader": "^3.0.2",
|
||||||
"eslint-plugin-compat": "3.3.0",
|
"eslint-plugin-compat": "4.0.0",
|
||||||
"eslint-plugin-cypress": "^2.8.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-prettier": "^3.1.1",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-sort-class-members": "^1.6.0",
|
"eslint-plugin-sort-class-members": "^1.14.1",
|
||||||
"express": "^4.16.4",
|
"express": "^4.17.1",
|
||||||
"husky": "^3.0.9",
|
"husky": "^7.0.4",
|
||||||
"jsdom": "^15.2.0",
|
"jsdom": "^19.0.0",
|
||||||
"lint-staged": "^9.4.2",
|
"lint-staged": "^12.1.2",
|
||||||
"mocha": "^6.2.2",
|
"mocha": "^9.1.3",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^7.0.0",
|
||||||
"nodemon": "^1.18.10",
|
"nodemon": "^2.0.15",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"nyc": "^14.1.1",
|
"nyc": "^15.1.0",
|
||||||
"postcss-cli": "^6.1.3",
|
"postcss-cli": "^9.1.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^2.5.1",
|
||||||
"sinon": "^7.5.0",
|
"sinon": "^12.0.1",
|
||||||
"sinon-chai": "^3.3.0",
|
"sinon-chai": "^3.7.0",
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^9.2.6",
|
||||||
"ts-node": "^8.5.4",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^3.7.3",
|
"typescript": "^4.5.4",
|
||||||
"webpack": "^4.41.2",
|
"webpack": "^5.65.0",
|
||||||
"webpack-cli": "^3.3.9",
|
"webpack-cli": "^4.9.1",
|
||||||
"webpack-dev-middleware": "^3.7.2",
|
"webpack-dev-middleware": "^5.2.2",
|
||||||
"webpack-hot-middleware": "^2.25.0"
|
"webpack-hot-middleware": "^2.25.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepmerge": "^4.2.0",
|
"deepmerge": "^4.2.2",
|
||||||
"fuse.js": "^3.4.6",
|
"fuse.js": "^3.4.6",
|
||||||
"redux": "^4.0.4"
|
"redux": "^4.1.2"
|
||||||
},
|
},
|
||||||
"npmName": "choices.js",
|
"npmName": "choices.js",
|
||||||
"npmFileMap": [
|
"npmFileMap": [
|
||||||
|
@ -125,7 +126,7 @@
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
{
|
{
|
||||||
"path": "public/assets/scripts/choices.min.js",
|
"path": "public/assets/scripts/choices.min.js",
|
||||||
"maxSize": "20 kB"
|
"maxSize": "25 kB"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "public/assets/styles/choices.min.css",
|
"path": "public/assets/styles/choices.min.css",
|
||||||
|
|
|
@ -78,8 +78,7 @@ a:focus {
|
||||||
border-radius: 2.5px;
|
border-radius: 2.5px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
appearance: none;
|
||||||
appearance: none;
|
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
public/assets/styles/base.min.css
vendored
2
public/assets/styles/base.min.css
vendored
|
@ -1 +1 @@
|
||||||
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:'Helvetica Neue',Helvetica,Arial,'Lucida Grande',sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label,p{margin-bottom:8px}label{font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}label+p{margin-top:-4px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none}.push-bottom{margin-bottom:24px}.zero-bottom{margin-bottom:0}.zero-top{margin-top:0}.text-center{text-align:center}[data-test-hook]{margin-bottom:24px}
|
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:'Helvetica Neue',Helvetica,Arial,'Lucida Grande',sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label,p{margin-bottom:8px}label{font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}label+p{margin-top:-4px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none}.push-bottom{margin-bottom:24px}.zero-bottom{margin-bottom:0}.zero-top{margin-top:0}.text-center{text-align:center}[data-test-hook]{margin-bottom:24px}
|
|
@ -25,7 +25,6 @@
|
||||||
background-color: #eaeaea;
|
background-color: #eaeaea;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,7 +319,6 @@
|
||||||
.choices__item--disabled {
|
.choices__item--disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
@ -336,8 +334,7 @@
|
||||||
.choices__button {
|
.choices__button {
|
||||||
text-indent: -9999px;
|
text-indent: -9999px;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
appearance: none;
|
||||||
appearance: none;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
2
public/assets/styles/choices.min.css
vendored
2
public/assets/styles/choices.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
||||||
import { ACTION_TYPES } from '../constants';
|
import { ACTION_TYPES } from '../constants';
|
||||||
import { Choice } from '../interfaces';
|
import { Choice } from '../interfaces/choice';
|
||||||
|
|
||||||
export interface AddChoiceAction {
|
export interface AddChoiceAction {
|
||||||
type: typeof ACTION_TYPES.ADD_CHOICE;
|
type: typeof ACTION_TYPES.ADD_CHOICE;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import { State } from '../interfaces/state';
|
||||||
import * as actions from './misc';
|
import * as actions from './misc';
|
||||||
import { State } from '../interfaces';
|
|
||||||
|
|
||||||
describe('actions/misc', () => {
|
describe('actions/misc', () => {
|
||||||
describe('clearAll action', () => {
|
describe('clearAll action', () => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { State } from '../interfaces';
|
|
||||||
import { ACTION_TYPES } from '../constants';
|
import { ACTION_TYPES } from '../constants';
|
||||||
|
import { State } from '../interfaces/state';
|
||||||
|
|
||||||
export interface ClearAllAction {
|
export interface ClearAllAction {
|
||||||
type: typeof ACTION_TYPES.CLEAR_ALL;
|
type: typeof ACTION_TYPES.CLEAR_ALL;
|
||||||
|
|
|
@ -4,11 +4,14 @@ import sinonChai from 'sinon-chai';
|
||||||
|
|
||||||
import Choices from './choices';
|
import Choices from './choices';
|
||||||
|
|
||||||
import { EVENTS, ACTION_TYPES, DEFAULT_CONFIG, KEY_CODES } from './constants';
|
import { EVENTS, ACTION_TYPES, KEY_CODES } from './constants';
|
||||||
import { WrappedSelect, WrappedInput } from './components/index';
|
import { WrappedSelect, WrappedInput } from './components/index';
|
||||||
import { removeItem } from './actions/items';
|
import { removeItem } from './actions/items';
|
||||||
import { Item, Choice, Group } from './interfaces';
|
|
||||||
import templates from './templates';
|
import templates from './templates';
|
||||||
|
import { Choice } from './interfaces/choice';
|
||||||
|
import { Group } from './interfaces/group';
|
||||||
|
import { Item } from './interfaces/item';
|
||||||
|
import { DEFAULT_CONFIG } from './defaults';
|
||||||
|
|
||||||
chai.use(sinonChai);
|
chai.use(sinonChai);
|
||||||
|
|
||||||
|
@ -563,21 +566,21 @@ describe('choices', () => {
|
||||||
expect(output).to.eql(instance);
|
expect(output).to.eql(instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens containerOuter', done => {
|
it('opens containerOuter', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(containerOuterOpenSpy.called).to.equal(true);
|
expect(containerOuterOpenSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows dropdown with blurInput flag', done => {
|
it('shows dropdown with blurInput flag', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(dropdownShowSpy.called).to.equal(true);
|
expect(dropdownShowSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers event on passedElement', done => {
|
it('triggers event on passedElement', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(passedElementTriggerEventStub.called).to.equal(true);
|
expect(passedElementTriggerEventStub.called).to.equal(true);
|
||||||
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(
|
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(
|
||||||
|
@ -595,7 +598,7 @@ describe('choices', () => {
|
||||||
output = instance.showDropdown(true);
|
output = instance.showDropdown(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('focuses input', done => {
|
it('focuses input', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(inputFocusSpy.called).to.equal(true);
|
expect(inputFocusSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
|
@ -661,21 +664,21 @@ describe('choices', () => {
|
||||||
expect(output).to.eql(instance);
|
expect(output).to.eql(instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes containerOuter', done => {
|
it('closes containerOuter', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(containerOuterCloseSpy.called).to.equal(true);
|
expect(containerOuterCloseSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides dropdown with blurInput flag', done => {
|
it('hides dropdown with blurInput flag', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(dropdownHideSpy.called).to.equal(true);
|
expect(dropdownHideSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers event on passedElement', done => {
|
it('triggers event on passedElement', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(passedElementTriggerEventStub.called).to.equal(true);
|
expect(passedElementTriggerEventStub.called).to.equal(true);
|
||||||
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(
|
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(
|
||||||
|
@ -693,14 +696,14 @@ describe('choices', () => {
|
||||||
output = instance.hideDropdown(true);
|
output = instance.hideDropdown(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes active descendants', done => {
|
it('removes active descendants', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(inputRemoveActiveDescendantSpy.called).to.equal(true);
|
expect(inputRemoveActiveDescendantSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('blurs input', done => {
|
it('blurs input', (done) => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(inputBlurSpy.called).to.equal(true);
|
expect(inputBlurSpy.called).to.equal(true);
|
||||||
done();
|
done();
|
||||||
|
@ -1192,7 +1195,8 @@ describe('choices', () => {
|
||||||
const fetcher = async (inst): Promise<Choice[]> => {
|
const fetcher = async (inst): Promise<Choice[]> => {
|
||||||
expect(inst).to.eq(choice);
|
expect(inst).to.eq(choice);
|
||||||
fetcherCalled = true;
|
fetcherCalled = true;
|
||||||
await new Promise(resolve => setTimeout(resolve, 800));
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ label: 'l1', value: 'v1', customProperties: { prop1: true } },
|
{ label: 'l1', value: 'v1', customProperties: { prop1: true } },
|
||||||
|
@ -1381,7 +1385,7 @@ describe('choices', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns all active item values', () => {
|
it('returns all active item values', () => {
|
||||||
expect(output).to.eql(items.map(item => item.value));
|
expect(output).to.eql(items.map((item) => item.value));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1612,7 +1616,8 @@ describe('choices', () => {
|
||||||
instance.clearChoices = clearChoicesStub;
|
instance.clearChoices = clearChoicesStub;
|
||||||
instance._addGroup = addGroupStub;
|
instance._addGroup = addGroupStub;
|
||||||
instance._addChoice = addChoiceStub;
|
instance._addChoice = addChoiceStub;
|
||||||
instance.containerOuter.removeLoadingState = containerOuterRemoveLoadingStateStub;
|
instance.containerOuter.removeLoadingState =
|
||||||
|
containerOuterRemoveLoadingStateStub;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -2093,7 +2098,7 @@ describe('choices', () => {
|
||||||
KEY_CODES.PAGE_DOWN_KEY,
|
KEY_CODES.PAGE_DOWN_KEY,
|
||||||
];
|
];
|
||||||
|
|
||||||
keyCodes.forEach(keyCode => {
|
keyCodes.forEach((keyCode) => {
|
||||||
it(`calls _onDirectionKey with the expected arguments`, () => {
|
it(`calls _onDirectionKey with the expected arguments`, () => {
|
||||||
const event = {
|
const event = {
|
||||||
keyCode,
|
keyCode,
|
||||||
|
@ -2143,7 +2148,7 @@ describe('choices', () => {
|
||||||
describe('delete key', () => {
|
describe('delete key', () => {
|
||||||
const keyCodes = [KEY_CODES.DELETE_KEY, KEY_CODES.BACK_KEY];
|
const keyCodes = [KEY_CODES.DELETE_KEY, KEY_CODES.BACK_KEY];
|
||||||
|
|
||||||
keyCodes.forEach(keyCode => {
|
keyCodes.forEach((keyCode) => {
|
||||||
it(`calls _onDeleteKey with the expected arguments`, () => {
|
it(`calls _onDeleteKey with the expected arguments`, () => {
|
||||||
const event = {
|
const event = {
|
||||||
keyCode,
|
keyCode,
|
||||||
|
@ -2188,10 +2193,10 @@ describe('choices', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers a REMOVE_ITEM event on the passed element', done => {
|
it('triggers a REMOVE_ITEM event on the passed element', (done) => {
|
||||||
passedElement.addEventListener(
|
passedElement.addEventListener(
|
||||||
'removeItem',
|
'removeItem',
|
||||||
event => {
|
(event) => {
|
||||||
expect(event.detail).to.eql({
|
expect(event.detail).to.eql({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
|
@ -2226,10 +2231,10 @@ describe('choices', () => {
|
||||||
instance._store.getGroupById.reset();
|
instance._store.getGroupById.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes the group's value in the triggered event", done => {
|
it("includes the group's value in the triggered event", (done) => {
|
||||||
passedElement.addEventListener(
|
passedElement.addEventListener(
|
||||||
'removeItem',
|
'removeItem',
|
||||||
event => {
|
(event) => {
|
||||||
expect(event.detail).to.eql({
|
expect(event.detail).to.eql({
|
||||||
id: itemWithGroup.id,
|
id: itemWithGroup.id,
|
||||||
value: itemWithGroup.value,
|
value: itemWithGroup.value,
|
||||||
|
|
|
@ -1,56 +1,55 @@
|
||||||
|
import merge from 'deepmerge';
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import merge from 'deepmerge';
|
|
||||||
|
|
||||||
import Store from './store/store';
|
|
||||||
import {
|
import {
|
||||||
Dropdown,
|
activateChoices,
|
||||||
|
addChoice,
|
||||||
|
clearChoices,
|
||||||
|
filterChoices,
|
||||||
|
Result,
|
||||||
|
} from './actions/choices';
|
||||||
|
import { addGroup } from './actions/groups';
|
||||||
|
import { addItem, highlightItem, removeItem } from './actions/items';
|
||||||
|
import { clearAll, resetTo, setIsLoading } from './actions/misc';
|
||||||
|
import {
|
||||||
Container,
|
Container,
|
||||||
|
Dropdown,
|
||||||
Input,
|
Input,
|
||||||
List,
|
List,
|
||||||
WrappedInput,
|
WrappedInput,
|
||||||
WrappedSelect,
|
WrappedSelect,
|
||||||
} from './components';
|
} from './components';
|
||||||
import {
|
import {
|
||||||
DEFAULT_CONFIG,
|
|
||||||
EVENTS,
|
EVENTS,
|
||||||
KEY_CODES,
|
KEY_CODES,
|
||||||
TEXT_TYPE,
|
|
||||||
SELECT_ONE_TYPE,
|
|
||||||
SELECT_MULTIPLE_TYPE,
|
SELECT_MULTIPLE_TYPE,
|
||||||
|
SELECT_ONE_TYPE,
|
||||||
|
TEXT_TYPE,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import templates from './templates';
|
import { DEFAULT_CONFIG } from './defaults';
|
||||||
|
import { Choice } from './interfaces/choice';
|
||||||
|
import { Group } from './interfaces/group';
|
||||||
|
import { Item } from './interfaces/item';
|
||||||
|
import { Notice } from './interfaces/notice';
|
||||||
|
import { Options } from './interfaces/options';
|
||||||
|
import { PassedElement } from './interfaces/passed-element';
|
||||||
|
import { State } from './interfaces/state';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addChoice,
|
diff,
|
||||||
filterChoices,
|
existsInArray,
|
||||||
activateChoices,
|
generateId,
|
||||||
clearChoices,
|
|
||||||
Result,
|
|
||||||
} from './actions/choices';
|
|
||||||
import { addItem, removeItem, highlightItem } from './actions/items';
|
|
||||||
import { addGroup } from './actions/groups';
|
|
||||||
import { clearAll, resetTo, setIsLoading } from './actions/misc';
|
|
||||||
import {
|
|
||||||
isScrolledIntoView,
|
|
||||||
getAdjacentEl,
|
getAdjacentEl,
|
||||||
getType,
|
getType,
|
||||||
|
isScrolledIntoView,
|
||||||
isType,
|
isType,
|
||||||
strToEl,
|
|
||||||
sortByScore,
|
sortByScore,
|
||||||
generateId,
|
strToEl,
|
||||||
existsInArray,
|
|
||||||
diff,
|
|
||||||
} from './lib/utils';
|
} from './lib/utils';
|
||||||
import {
|
|
||||||
Options,
|
|
||||||
Choice,
|
|
||||||
Item,
|
|
||||||
Group,
|
|
||||||
Notice,
|
|
||||||
State,
|
|
||||||
PassedElement,
|
|
||||||
} from './interfaces';
|
|
||||||
import { defaultState } from './reducers';
|
import { defaultState } from './reducers';
|
||||||
|
import Store from './store/store';
|
||||||
|
import templates from './templates';
|
||||||
|
|
||||||
/** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */
|
/** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */
|
||||||
const IS_IE11 =
|
const IS_IE11 =
|
||||||
|
@ -63,7 +62,7 @@ const USER_DEFAULTS: Partial<Options> = {};
|
||||||
* Choices
|
* Choices
|
||||||
* @author Josh Johnson<josh@joshuajohnson.co.uk>
|
* @author Josh Johnson<josh@joshuajohnson.co.uk>
|
||||||
*/
|
*/
|
||||||
class Choices {
|
class Choices implements Choices {
|
||||||
static get defaults(): {
|
static get defaults(): {
|
||||||
options: Partial<Options>;
|
options: Partial<Options>;
|
||||||
templates: typeof templates;
|
templates: typeof templates;
|
||||||
|
@ -79,39 +78,69 @@ class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
initialised: boolean;
|
initialised: boolean;
|
||||||
|
|
||||||
config: Options;
|
config: Options;
|
||||||
|
|
||||||
passedElement: WrappedInput | WrappedSelect;
|
passedElement: WrappedInput | WrappedSelect;
|
||||||
|
|
||||||
containerOuter: Container;
|
containerOuter: Container;
|
||||||
|
|
||||||
containerInner: Container;
|
containerInner: Container;
|
||||||
|
|
||||||
choiceList: List;
|
choiceList: List;
|
||||||
|
|
||||||
itemList: List;
|
itemList: List;
|
||||||
|
|
||||||
input: Input;
|
input: Input;
|
||||||
|
|
||||||
dropdown: Dropdown;
|
dropdown: Dropdown;
|
||||||
|
|
||||||
_isTextElement: boolean;
|
_isTextElement: boolean;
|
||||||
|
|
||||||
_isSelectOneElement: boolean;
|
_isSelectOneElement: boolean;
|
||||||
|
|
||||||
_isSelectMultipleElement: boolean;
|
_isSelectMultipleElement: boolean;
|
||||||
|
|
||||||
_isSelectElement: boolean;
|
_isSelectElement: boolean;
|
||||||
|
|
||||||
_store: Store;
|
_store: Store;
|
||||||
|
|
||||||
_templates: typeof templates;
|
_templates: typeof templates;
|
||||||
|
|
||||||
_initialState: State;
|
_initialState: State;
|
||||||
|
|
||||||
_currentState: State;
|
_currentState: State;
|
||||||
|
|
||||||
_prevState: State;
|
_prevState: State;
|
||||||
|
|
||||||
_currentValue: string;
|
_currentValue: string;
|
||||||
|
|
||||||
_canSearch: boolean;
|
_canSearch: boolean;
|
||||||
|
|
||||||
_isScrollingOnIe: boolean;
|
_isScrollingOnIe: boolean;
|
||||||
|
|
||||||
_highlightPosition: number;
|
_highlightPosition: number;
|
||||||
|
|
||||||
_wasTap: boolean;
|
_wasTap: boolean;
|
||||||
|
|
||||||
_isSearching: boolean;
|
_isSearching: boolean;
|
||||||
|
|
||||||
_placeholderValue: string | null;
|
_placeholderValue: string | null;
|
||||||
|
|
||||||
_baseId: string;
|
_baseId: string;
|
||||||
|
|
||||||
_direction: HTMLElement['dir'];
|
_direction: HTMLElement['dir'];
|
||||||
|
|
||||||
_idNames: {
|
_idNames: {
|
||||||
itemChoice: string;
|
itemChoice: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
_presetGroups: Group[] | HTMLOptGroupElement[] | Element[];
|
_presetGroups: Group[] | HTMLOptGroupElement[] | Element[];
|
||||||
|
|
||||||
_presetOptions: Item[] | HTMLOptionElement[];
|
_presetOptions: Item[] | HTMLOptionElement[];
|
||||||
|
|
||||||
_presetChoices: Partial<Choice>[];
|
_presetChoices: Partial<Choice>[];
|
||||||
|
|
||||||
_presetItems: Item[] | string[];
|
_presetItems: Item[] | string[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -247,7 +276,7 @@ class Choices {
|
||||||
}
|
}
|
||||||
// Create array of choices from option elements
|
// Create array of choices from option elements
|
||||||
if ((this.passedElement as WrappedSelect).options) {
|
if ((this.passedElement as WrappedSelect).options) {
|
||||||
(this.passedElement as WrappedSelect).options.forEach(option => {
|
(this.passedElement as WrappedSelect).options.forEach((option) => {
|
||||||
this._presetChoices.push({
|
this._presetChoices.push({
|
||||||
value: option.value,
|
value: option.value,
|
||||||
label: option.innerHTML,
|
label: option.innerHTML,
|
||||||
|
@ -415,21 +444,21 @@ class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightAll(): this {
|
highlightAll(): this {
|
||||||
this._store.items.forEach(item => this.highlightItem(item));
|
this._store.items.forEach((item) => this.highlightItem(item));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
unhighlightAll(): this {
|
unhighlightAll(): this {
|
||||||
this._store.items.forEach(item => this.unhighlightItem(item));
|
this._store.items.forEach((item) => this.unhighlightItem(item));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeActiveItemsByValue(value: string): this {
|
removeActiveItemsByValue(value: string): this {
|
||||||
this._store.activeItems
|
this._store.activeItems
|
||||||
.filter(item => item.value === value)
|
.filter((item) => item.value === value)
|
||||||
.forEach(item => this._removeItem(item));
|
.forEach((item) => this._removeItem(item));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -437,13 +466,13 @@ class Choices {
|
||||||
removeActiveItems(excludedId: number): this {
|
removeActiveItems(excludedId: number): this {
|
||||||
this._store.activeItems
|
this._store.activeItems
|
||||||
.filter(({ id }) => id !== excludedId)
|
.filter(({ id }) => id !== excludedId)
|
||||||
.forEach(item => this._removeItem(item));
|
.forEach((item) => this._removeItem(item));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeHighlightedItems(runEvent = false): this {
|
removeHighlightedItems(runEvent = false): this {
|
||||||
this._store.highlightedActiveItems.forEach(item => {
|
this._store.highlightedActiveItems.forEach((item) => {
|
||||||
this._removeItem(item);
|
this._removeItem(item);
|
||||||
// If this action was performed by the user
|
// If this action was performed by the user
|
||||||
// trigger the event
|
// trigger the event
|
||||||
|
@ -513,7 +542,7 @@ class Choices {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.forEach(value => this._setChoiceOrItem(value));
|
items.forEach((value) => this._setChoiceOrItem(value));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -527,7 +556,7 @@ class Choices {
|
||||||
const choiceValue = Array.isArray(value) ? value : [value];
|
const choiceValue = Array.isArray(value) ? value : [value];
|
||||||
|
|
||||||
// Loop through each value and
|
// Loop through each value and
|
||||||
choiceValue.forEach(val => this._findAndSelectChoiceByValue(val));
|
choiceValue.forEach((val) => this._findAndSelectChoiceByValue(val));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -630,14 +659,14 @@ class Choices {
|
||||||
|
|
||||||
if (typeof Promise === 'function' && fetcher instanceof Promise) {
|
if (typeof Promise === 'function' && fetcher instanceof Promise) {
|
||||||
// that's a promise
|
// that's a promise
|
||||||
// eslint-disable-next-line compat/compat
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
return new Promise(resolve => requestAnimationFrame(resolve)) // eslint-disable-line compat/compat
|
return new Promise((resolve) => requestAnimationFrame(resolve))
|
||||||
.then(() => this._handleLoadingState(true))
|
.then(() => this._handleLoadingState(true))
|
||||||
.then(() => fetcher)
|
.then(() => fetcher)
|
||||||
.then((data: Choice[]) =>
|
.then((data: Choice[]) =>
|
||||||
this.setChoices(data, value, label, replaceChoices),
|
this.setChoices(data, value, label, replaceChoices),
|
||||||
)
|
)
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
if (!this.config.silent) {
|
if (!this.config.silent) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
@ -766,7 +795,7 @@ class Choices {
|
||||||
if (activeGroups.length >= 1 && !this._isSearching) {
|
if (activeGroups.length >= 1 && !this._isSearching) {
|
||||||
// If we have a placeholder choice along with groups
|
// If we have a placeholder choice along with groups
|
||||||
const activePlaceholders = activeChoices.filter(
|
const activePlaceholders = activeChoices.filter(
|
||||||
activeChoice =>
|
(activeChoice) =>
|
||||||
activeChoice.placeholder === true && activeChoice.groupId === -1,
|
activeChoice.placeholder === true && activeChoice.groupId === -1,
|
||||||
);
|
);
|
||||||
if (activePlaceholders.length >= 1) {
|
if (activePlaceholders.length >= 1) {
|
||||||
|
@ -849,7 +878,7 @@ class Choices {
|
||||||
fragment: DocumentFragment = document.createDocumentFragment(),
|
fragment: DocumentFragment = document.createDocumentFragment(),
|
||||||
): DocumentFragment {
|
): DocumentFragment {
|
||||||
const getGroupChoices = (group): Choice[] =>
|
const getGroupChoices = (group): Choice[] =>
|
||||||
choices.filter(choice => {
|
choices.filter((choice) => {
|
||||||
if (this._isSelectOneElement) {
|
if (this._isSelectOneElement) {
|
||||||
return choice.groupId === group.id;
|
return choice.groupId === group.id;
|
||||||
}
|
}
|
||||||
|
@ -865,7 +894,7 @@ class Choices {
|
||||||
groups.sort(this.config.sorter);
|
groups.sort(this.config.sorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
groups.forEach(group => {
|
groups.forEach((group) => {
|
||||||
const groupChoices = getGroupChoices(group);
|
const groupChoices = getGroupChoices(group);
|
||||||
if (groupChoices.length >= 1) {
|
if (groupChoices.length >= 1) {
|
||||||
const dropdownGroup = this._getTemplate('choiceGroup', group);
|
const dropdownGroup = this._getTemplate('choiceGroup', group);
|
||||||
|
@ -883,11 +912,8 @@ class Choices {
|
||||||
withinGroup = false,
|
withinGroup = false,
|
||||||
): DocumentFragment {
|
): DocumentFragment {
|
||||||
// Create a fragment to store our list items (so we don't have to update the DOM for each item)
|
// Create a fragment to store our list items (so we don't have to update the DOM for each item)
|
||||||
const {
|
const { renderSelectedChoices, searchResultLimit, renderChoiceLimit } =
|
||||||
renderSelectedChoices,
|
this.config;
|
||||||
searchResultLimit,
|
|
||||||
renderChoiceLimit,
|
|
||||||
} = this.config;
|
|
||||||
const filter = this._isSearching ? sortByScore : this.config.sorter;
|
const filter = this._isSearching ? sortByScore : this.config.sorter;
|
||||||
const appendChoice = (choice: Choice): void => {
|
const appendChoice = (choice: Choice): void => {
|
||||||
const shouldRender =
|
const shouldRender =
|
||||||
|
@ -909,7 +935,7 @@ class Choices {
|
||||||
let rendererableChoices = choices;
|
let rendererableChoices = choices;
|
||||||
|
|
||||||
if (renderSelectedChoices === 'auto' && !this._isSelectOneElement) {
|
if (renderSelectedChoices === 'auto' && !this._isSelectOneElement) {
|
||||||
rendererableChoices = choices.filter(choice => !choice.selected);
|
rendererableChoices = choices.filter((choice) => !choice.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split array into placeholders and "normal" choices
|
// Split array into placeholders and "normal" choices
|
||||||
|
@ -1027,7 +1053,7 @@ class Choices {
|
||||||
const itemId =
|
const itemId =
|
||||||
element.parentNode && (element.parentNode as HTMLElement).dataset.id;
|
element.parentNode && (element.parentNode as HTMLElement).dataset.id;
|
||||||
const itemToRemove =
|
const itemToRemove =
|
||||||
itemId && activeItems.find(item => item.id === parseInt(itemId, 10));
|
itemId && activeItems.find((item) => item.id === parseInt(itemId, 10));
|
||||||
|
|
||||||
if (!itemToRemove) {
|
if (!itemToRemove) {
|
||||||
return;
|
return;
|
||||||
|
@ -1061,7 +1087,7 @@ class Choices {
|
||||||
// We only want to select one item with a click
|
// We only want to select one item with a click
|
||||||
// so we deselect any items that aren't the target
|
// so we deselect any items that aren't the target
|
||||||
// unless shift is being pressed
|
// unless shift is being pressed
|
||||||
activeItems.forEach(item => {
|
activeItems.forEach((item) => {
|
||||||
if (item.id === parseInt(`${passedId}`, 10) && !item.highlighted) {
|
if (item.id === parseInt(`${passedId}`, 10) && !item.highlighted) {
|
||||||
this.highlightItem(item);
|
this.highlightItem(item);
|
||||||
} else if (!hasShiftKey && item.highlighted) {
|
} else if (!hasShiftKey && item.highlighted) {
|
||||||
|
@ -1132,7 +1158,7 @@ class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastItem = activeItems[activeItems.length - 1];
|
const lastItem = activeItems[activeItems.length - 1];
|
||||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
const hasHighlightedItems = activeItems.some((item) => item.highlighted);
|
||||||
|
|
||||||
// If editing the last item is allowed and there are not other selected items,
|
// If editing the last item is allowed and there are not other selected items,
|
||||||
// we can edit the item value. Otherwise if we can remove items, remove all selected items
|
// we can edit the item value. Otherwise if we can remove items, remove all selected items
|
||||||
|
@ -1204,7 +1230,7 @@ class Choices {
|
||||||
|
|
||||||
const { choices } = this._store;
|
const { choices } = this._store;
|
||||||
const { searchFloor, searchChoices } = this.config;
|
const { searchFloor, searchChoices } = this.config;
|
||||||
const hasUnactiveChoices = choices.some(option => !option.active);
|
const hasUnactiveChoices = choices.some((option) => !option.active);
|
||||||
|
|
||||||
// Check that we have a value to search and the input was an alphanumeric character
|
// Check that we have a value to search and the input was an alphanumeric character
|
||||||
if (value && value.length >= searchFloor) {
|
if (value && value.length >= searchFloor) {
|
||||||
|
@ -1800,7 +1826,7 @@ class Choices {
|
||||||
|
|
||||||
if (blurWasWithinContainer && !this._isScrollingOnIe) {
|
if (blurWasWithinContainer && !this._isScrollingOnIe) {
|
||||||
const { activeItems } = this._store;
|
const { activeItems } = this._store;
|
||||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
const hasHighlightedItems = activeItems.some((item) => item.highlighted);
|
||||||
const blurActions = {
|
const blurActions = {
|
||||||
[TEXT_TYPE]: (): void => {
|
[TEXT_TYPE]: (): void => {
|
||||||
if (target === this.input.element) {
|
if (target === this.input.element) {
|
||||||
|
@ -1862,7 +1888,7 @@ class Choices {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove any highlighted choices
|
// Remove any highlighted choices
|
||||||
highlightedChoices.forEach(choice => {
|
highlightedChoices.forEach((choice) => {
|
||||||
choice.classList.remove(this.config.classNames.highlightedState);
|
choice.classList.remove(this.config.classNames.highlightedState);
|
||||||
choice.setAttribute('aria-selected', 'false');
|
choice.setAttribute('aria-selected', 'false');
|
||||||
});
|
});
|
||||||
|
@ -2214,7 +2240,7 @@ class Choices {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
groups.forEach(group =>
|
groups.forEach((group) =>
|
||||||
this._addGroup({
|
this._addGroup({
|
||||||
group,
|
group,
|
||||||
id: group.id || null,
|
id: group.id || null,
|
||||||
|
@ -2228,9 +2254,9 @@ class Choices {
|
||||||
choices.sort(this.config.sorter);
|
choices.sort(this.config.sorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasSelectedChoice = choices.some(choice => choice.selected);
|
const hasSelectedChoice = choices.some((choice) => choice.selected);
|
||||||
const firstEnabledChoiceIndex = choices.findIndex(
|
const firstEnabledChoiceIndex = choices.findIndex(
|
||||||
choice => choice.disabled === undefined || !choice.disabled,
|
(choice) => choice.disabled === undefined || !choice.disabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
choices.forEach((choice, index) => {
|
choices.forEach((choice, index) => {
|
||||||
|
@ -2258,8 +2284,6 @@ class Choices {
|
||||||
const isSelected = shouldPreselect ? true : choice.selected;
|
const isSelected = shouldPreselect ? true : choice.selected;
|
||||||
const isDisabled = choice.disabled;
|
const isDisabled = choice.disabled;
|
||||||
|
|
||||||
console.log(isDisabled, choice);
|
|
||||||
|
|
||||||
this._addChoice({
|
this._addChoice({
|
||||||
value,
|
value,
|
||||||
label,
|
label,
|
||||||
|
@ -2283,7 +2307,7 @@ class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
_addPredefinedItems(items: Item[] | string[]): void {
|
_addPredefinedItems(items: Item[] | string[]): void {
|
||||||
items.forEach(item => {
|
items.forEach((item) => {
|
||||||
if (typeof item === 'object' && item.value) {
|
if (typeof item === 'object' && item.value) {
|
||||||
this._addItem({
|
this._addItem({
|
||||||
value: item.value,
|
value: item.value,
|
||||||
|
@ -2353,7 +2377,7 @@ class Choices {
|
||||||
_findAndSelectChoiceByValue(value: string): void {
|
_findAndSelectChoiceByValue(value: string): void {
|
||||||
const { choices } = this._store;
|
const { choices } = this._store;
|
||||||
// Check 'value' property exists and the choice isn't already selected
|
// Check 'value' property exists and the choice isn't already selected
|
||||||
const foundChoice = choices.find(choice =>
|
const foundChoice = choices.find((choice) =>
|
||||||
this.config.valueComparer(choice.value, value),
|
this.config.valueComparer(choice.value, value),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
import { DEFAULT_CLASSNAMES } from '../defaults';
|
||||||
import Container from './container';
|
import Container from './container';
|
||||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
|
||||||
|
|
||||||
describe('components/container', () => {
|
describe('components/container', () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
import { wrap } from '../lib/utils';
|
import { wrap } from '../lib/utils';
|
||||||
import { SELECT_ONE_TYPE } from '../constants';
|
import { SELECT_ONE_TYPE } from '../constants';
|
||||||
import { PassedElement, ClassNames, Options } from '../interfaces';
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
|
import { PositionOptionsType } from '../interfaces/position-options-type';
|
||||||
|
import { PassedElementType } from '../interfaces/passed-element-type';
|
||||||
|
|
||||||
export default class Container {
|
export default class Container {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
type: PassedElement['type'];
|
|
||||||
|
type: PassedElementType;
|
||||||
|
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
position: Options['position'];
|
|
||||||
|
position: PositionOptionsType;
|
||||||
|
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
||||||
isFlipped: boolean;
|
isFlipped: boolean;
|
||||||
|
|
||||||
isFocussed: boolean;
|
isFocussed: boolean;
|
||||||
|
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
@ -20,9 +30,9 @@ export default class Container {
|
||||||
position,
|
position,
|
||||||
}: {
|
}: {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
type: PassedElement['type'];
|
type: PassedElementType;
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
position: Options['position'];
|
position: PositionOptionsType;
|
||||||
}) {
|
}) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.classNames = classNames;
|
this.classNames = classNames;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
import { DEFAULT_CLASSNAMES } from '../defaults';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
|
||||||
|
|
||||||
describe('components/dropdown', () => {
|
describe('components/dropdown', () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { PassedElement, ClassNames } from '../interfaces';
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
|
import { PassedElementType } from '../interfaces/passed-element-type';
|
||||||
|
|
||||||
export default class Dropdown {
|
export default class Dropdown {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
type: PassedElement['type'];
|
|
||||||
|
type: PassedElementType;
|
||||||
|
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
|
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
@ -12,7 +16,7 @@ export default class Dropdown {
|
||||||
classNames,
|
classNames,
|
||||||
}: {
|
}: {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
type: PassedElement['type'];
|
type: PassedElementType;
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
}) {
|
}) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
import { DEFAULT_CLASSNAMES } from '../defaults';
|
||||||
import Input from './input';
|
import Input from './input';
|
||||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
|
||||||
|
|
||||||
describe('components/input', () => {
|
describe('components/input', () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
import { sanitise } from '../lib/utils';
|
import { sanitise } from '../lib/utils';
|
||||||
import { SELECT_ONE_TYPE } from '../constants';
|
import { SELECT_ONE_TYPE } from '../constants';
|
||||||
import { PassedElement, ClassNames } from '../interfaces';
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
|
import { PassedElementType } from '../interfaces/passed-element-type';
|
||||||
|
|
||||||
export default class Input {
|
export default class Input {
|
||||||
element: HTMLInputElement;
|
element: HTMLInputElement;
|
||||||
type: PassedElement['type'];
|
|
||||||
|
type: PassedElementType;
|
||||||
|
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
|
|
||||||
preventPaste: boolean;
|
preventPaste: boolean;
|
||||||
|
|
||||||
isFocussed: boolean;
|
isFocussed: boolean;
|
||||||
|
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
@ -17,7 +23,7 @@ export default class Input {
|
||||||
preventPaste,
|
preventPaste,
|
||||||
}: {
|
}: {
|
||||||
element: HTMLInputElement;
|
element: HTMLInputElement;
|
||||||
type: PassedElement['type'];
|
type: PassedElementType;
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
preventPaste: boolean;
|
preventPaste: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { SCROLLING_SPEED } from '../constants';
|
||||||
|
|
||||||
export default class List {
|
export default class List {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
|
|
||||||
scrollPos: number;
|
scrollPos: number;
|
||||||
|
|
||||||
height: number;
|
height: number;
|
||||||
|
|
||||||
constructor({ element }: { element: HTMLElement }) {
|
constructor({ element }: { element: HTMLElement }) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import { DEFAULT_CLASSNAMES } from '../defaults';
|
||||||
import WrappedElement from './wrapped-element';
|
import WrappedElement from './wrapped-element';
|
||||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
|
||||||
|
|
||||||
describe('components/wrappedElement', () => {
|
describe('components/wrappedElement', () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
@ -163,7 +163,7 @@ describe('components/wrappedElement', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('triggerEvent', () => {
|
describe('triggerEvent', () => {
|
||||||
it('fires event on element using passed eventType and data', done => {
|
it('fires event on element using passed eventType and data', (done) => {
|
||||||
const data = {
|
const data = {
|
||||||
test: true,
|
test: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
|
import { EventType } from '../interfaces/event-type';
|
||||||
import { dispatchEvent } from '../lib/utils';
|
import { dispatchEvent } from '../lib/utils';
|
||||||
import { ClassNames, EventMap } from '../interfaces';
|
|
||||||
|
|
||||||
export default class WrappedElement {
|
export default class WrappedElement {
|
||||||
element: HTMLInputElement | HTMLSelectElement;
|
element: HTMLInputElement | HTMLSelectElement;
|
||||||
|
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
|
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
|
||||||
constructor({ element, classNames }) {
|
constructor({ element, classNames }) {
|
||||||
|
@ -89,7 +92,7 @@ export default class WrappedElement {
|
||||||
this.isDisabled = true;
|
this.isDisabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerEvent<K extends keyof EventMap>(eventType: K, data?: object): void {
|
triggerEvent(eventType: EventType, data?: object): void {
|
||||||
dispatchEvent(this.element, eventType, data);
|
dispatchEvent(this.element, eventType, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
import { DEFAULT_CLASSNAMES } from '../defaults';
|
||||||
import WrappedElement from './wrapped-element';
|
import WrappedElement from './wrapped-element';
|
||||||
import WrappedInput from './wrapped-input';
|
import WrappedInput from './wrapped-input';
|
||||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
|
||||||
|
|
||||||
describe('components/wrappedInput', () => {
|
describe('components/wrappedInput', () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
@ -36,7 +36,7 @@ describe('components/wrappedInput', () => {
|
||||||
describe('inherited methods', () => {
|
describe('inherited methods', () => {
|
||||||
const methods: string[] = ['conceal', 'reveal', 'enable', 'disable'];
|
const methods: string[] = ['conceal', 'reveal', 'enable', 'disable'];
|
||||||
|
|
||||||
methods.forEach(method => {
|
methods.forEach((method) => {
|
||||||
describe(method, () => {
|
describe(method, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub(WrappedElement.prototype, method as keyof WrappedElement);
|
stub(WrappedElement.prototype, method as keyof WrappedElement);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
import WrappedElement from './wrapped-element';
|
import WrappedElement from './wrapped-element';
|
||||||
import { ClassNames } from '../interfaces';
|
|
||||||
|
|
||||||
export default class WrappedInput extends WrappedElement {
|
export default class WrappedInput extends WrappedElement {
|
||||||
element: HTMLInputElement;
|
element: HTMLInputElement;
|
||||||
|
|
||||||
delimiter: string;
|
delimiter: string;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { expect } from 'chai';
|
||||||
import { stub, spy } from 'sinon';
|
import { stub, spy } from 'sinon';
|
||||||
import WrappedElement from './wrapped-element';
|
import WrappedElement from './wrapped-element';
|
||||||
import WrappedSelect from './wrapped-select';
|
import WrappedSelect from './wrapped-select';
|
||||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
|
||||||
import Templates from '../templates';
|
import Templates from '../templates';
|
||||||
|
import { DEFAULT_CLASSNAMES } from '../defaults';
|
||||||
|
|
||||||
describe('components/wrappedSelect', () => {
|
describe('components/wrappedSelect', () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
@ -56,7 +56,7 @@ describe('components/wrappedSelect', () => {
|
||||||
describe('inherited methods', () => {
|
describe('inherited methods', () => {
|
||||||
const methods: string[] = ['conceal', 'reveal', 'enable', 'disable'];
|
const methods: string[] = ['conceal', 'reveal', 'enable', 'disable'];
|
||||||
|
|
||||||
methods.forEach(method => {
|
methods.forEach((method) => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stub(WrappedElement.prototype, method as keyof WrappedElement);
|
stub(WrappedElement.prototype, method as keyof WrappedElement);
|
||||||
});
|
});
|
||||||
|
@ -93,7 +93,7 @@ describe('components/wrappedSelect', () => {
|
||||||
it('returns all option elements', () => {
|
it('returns all option elements', () => {
|
||||||
const { options } = instance;
|
const { options } = instance;
|
||||||
expect(options).to.be.an('array');
|
expect(options).to.be.an('array');
|
||||||
options.forEach(option => {
|
options.forEach((option) => {
|
||||||
expect(option).to.be.instanceOf(HTMLOptionElement);
|
expect(option).to.be.instanceOf(HTMLOptionElement);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -108,7 +108,7 @@ describe('components/wrappedSelect', () => {
|
||||||
|
|
||||||
const { optionGroups } = instance;
|
const { optionGroups } = instance;
|
||||||
expect(optionGroups.length).to.equal(3);
|
expect(optionGroups.length).to.equal(3);
|
||||||
optionGroups.forEach(option => {
|
optionGroups.forEach((option) => {
|
||||||
expect(option).to.be.instanceOf(HTMLOptGroupElement);
|
expect(option).to.be.instanceOf(HTMLOptGroupElement);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
|
import { Item } from '../interfaces/item';
|
||||||
import WrappedElement from './wrapped-element';
|
import WrappedElement from './wrapped-element';
|
||||||
import { ClassNames, Item } from '../interfaces';
|
|
||||||
|
|
||||||
export default class WrappedSelect extends WrappedElement {
|
export default class WrappedSelect extends WrappedElement {
|
||||||
element: HTMLSelectElement;
|
element: HTMLSelectElement;
|
||||||
|
|
||||||
classNames: ClassNames;
|
classNames: ClassNames;
|
||||||
|
|
||||||
template: (data: object) => HTMLOptionElement;
|
template: (data: object) => HTMLOptionElement;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
|
@ -45,7 +48,7 @@ export default class WrappedSelect extends WrappedElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add each list item to list
|
// Add each list item to list
|
||||||
options.forEach(optionData => addOptionToFragment(optionData));
|
options.forEach((optionData) => addOptionToFragment(optionData));
|
||||||
|
|
||||||
this.appendDocFragment(fragment);
|
this.appendDocFragment(fragment);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import {
|
import { EVENTS, ACTION_TYPES, KEY_CODES, SCROLLING_SPEED } from './constants';
|
||||||
DEFAULT_CLASSNAMES,
|
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from './defaults';
|
||||||
DEFAULT_CONFIG,
|
|
||||||
EVENTS,
|
|
||||||
ACTION_TYPES,
|
|
||||||
KEY_CODES,
|
|
||||||
SCROLLING_SPEED,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
describe('constants', () => {
|
describe('constants', () => {
|
||||||
describe('type checks', () => {
|
describe('type checks', () => {
|
||||||
|
@ -145,7 +139,7 @@ describe('constants', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exports each value as a number', () => {
|
it('exports each value as a number', () => {
|
||||||
Object.keys(KEY_CODES).forEach(key => {
|
Object.keys(KEY_CODES).forEach((key) => {
|
||||||
expect(KEY_CODES[key]).to.be.a('number');
|
expect(KEY_CODES[key]).to.be.a('number');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,89 +1,8 @@
|
||||||
import { sanitise, sortByAlpha } from './lib/utils';
|
import { ActionType } from './interfaces/action-type';
|
||||||
import {
|
import { EventType } from './interfaces/event-type';
|
||||||
Options,
|
import { KeyCodeMap } from './interfaces/keycode-map';
|
||||||
ClassNames,
|
|
||||||
EventMap,
|
|
||||||
ActionType,
|
|
||||||
KeyCodeMap,
|
|
||||||
} from './interfaces';
|
|
||||||
|
|
||||||
export const DEFAULT_CLASSNAMES: ClassNames = {
|
export const EVENTS: Record<EventType, EventType> = {
|
||||||
containerOuter: 'choices',
|
|
||||||
containerInner: 'choices__inner',
|
|
||||||
input: 'choices__input',
|
|
||||||
inputCloned: 'choices__input--cloned',
|
|
||||||
list: 'choices__list',
|
|
||||||
listItems: 'choices__list--multiple',
|
|
||||||
listSingle: 'choices__list--single',
|
|
||||||
listDropdown: 'choices__list--dropdown',
|
|
||||||
item: 'choices__item',
|
|
||||||
itemSelectable: 'choices__item--selectable',
|
|
||||||
itemDisabled: 'choices__item--disabled',
|
|
||||||
itemChoice: 'choices__item--choice',
|
|
||||||
placeholder: 'choices__placeholder',
|
|
||||||
group: 'choices__group',
|
|
||||||
groupHeading: 'choices__heading',
|
|
||||||
button: 'choices__button',
|
|
||||||
activeState: 'is-active',
|
|
||||||
focusState: 'is-focused',
|
|
||||||
openState: 'is-open',
|
|
||||||
disabledState: 'is-disabled',
|
|
||||||
highlightedState: 'is-highlighted',
|
|
||||||
selectedState: 'is-selected',
|
|
||||||
flippedState: 'is-flipped',
|
|
||||||
loadingState: 'is-loading',
|
|
||||||
noResults: 'has-no-results',
|
|
||||||
noChoices: 'has-no-choices',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DEFAULT_CONFIG: Options = {
|
|
||||||
items: [],
|
|
||||||
choices: [],
|
|
||||||
silent: false,
|
|
||||||
renderChoiceLimit: -1,
|
|
||||||
maxItemCount: -1,
|
|
||||||
addItems: true,
|
|
||||||
addItemFilter: null,
|
|
||||||
removeItems: true,
|
|
||||||
removeItemButton: false,
|
|
||||||
editItems: false,
|
|
||||||
duplicateItemsAllowed: true,
|
|
||||||
delimiter: ',',
|
|
||||||
paste: true,
|
|
||||||
searchEnabled: true,
|
|
||||||
searchChoices: true,
|
|
||||||
searchFloor: 1,
|
|
||||||
searchResultLimit: 4,
|
|
||||||
searchFields: ['label', 'value'],
|
|
||||||
position: 'auto',
|
|
||||||
resetScrollPosition: true,
|
|
||||||
shouldSort: true,
|
|
||||||
shouldSortItems: false,
|
|
||||||
sorter: sortByAlpha,
|
|
||||||
placeholder: true,
|
|
||||||
placeholderValue: null,
|
|
||||||
searchPlaceholderValue: null,
|
|
||||||
prependValue: null,
|
|
||||||
appendValue: null,
|
|
||||||
renderSelectedChoices: 'auto',
|
|
||||||
loadingText: 'Loading...',
|
|
||||||
noResultsText: 'No results found',
|
|
||||||
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',
|
|
||||||
addItemText: value => `Press Enter to add <b>"${sanitise(value)}"</b>`,
|
|
||||||
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added`,
|
|
||||||
valueComparer: (value1, value2) => value1 === value2,
|
|
||||||
fuseOptions: {
|
|
||||||
includeScore: true,
|
|
||||||
},
|
|
||||||
callbackOnInit: null,
|
|
||||||
callbackOnCreateTemplates: null,
|
|
||||||
classNames: DEFAULT_CLASSNAMES,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EVENTS: Record<keyof EventMap, keyof EventMap> = {
|
|
||||||
showDropdown: 'showDropdown',
|
showDropdown: 'showDropdown',
|
||||||
hideDropdown: 'hideDropdown',
|
hideDropdown: 'hideDropdown',
|
||||||
change: 'change',
|
change: 'change',
|
||||||
|
|
79
src/scripts/defaults.ts
Normal file
79
src/scripts/defaults.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { ClassNames } from './interfaces/class-names';
|
||||||
|
import { Options } from './interfaces/options';
|
||||||
|
import { sortByAlpha, sanitise } from './lib/utils';
|
||||||
|
|
||||||
|
export const DEFAULT_CLASSNAMES: ClassNames = {
|
||||||
|
containerOuter: 'choices',
|
||||||
|
containerInner: 'choices__inner',
|
||||||
|
input: 'choices__input',
|
||||||
|
inputCloned: 'choices__input--cloned',
|
||||||
|
list: 'choices__list',
|
||||||
|
listItems: 'choices__list--multiple',
|
||||||
|
listSingle: 'choices__list--single',
|
||||||
|
listDropdown: 'choices__list--dropdown',
|
||||||
|
item: 'choices__item',
|
||||||
|
itemSelectable: 'choices__item--selectable',
|
||||||
|
itemDisabled: 'choices__item--disabled',
|
||||||
|
itemChoice: 'choices__item--choice',
|
||||||
|
placeholder: 'choices__placeholder',
|
||||||
|
group: 'choices__group',
|
||||||
|
groupHeading: 'choices__heading',
|
||||||
|
button: 'choices__button',
|
||||||
|
activeState: 'is-active',
|
||||||
|
focusState: 'is-focused',
|
||||||
|
openState: 'is-open',
|
||||||
|
disabledState: 'is-disabled',
|
||||||
|
highlightedState: 'is-highlighted',
|
||||||
|
selectedState: 'is-selected',
|
||||||
|
flippedState: 'is-flipped',
|
||||||
|
loadingState: 'is-loading',
|
||||||
|
noResults: 'has-no-results',
|
||||||
|
noChoices: 'has-no-choices',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_CONFIG: Options = {
|
||||||
|
items: [],
|
||||||
|
choices: [],
|
||||||
|
silent: false,
|
||||||
|
renderChoiceLimit: -1,
|
||||||
|
maxItemCount: -1,
|
||||||
|
addItems: true,
|
||||||
|
addItemFilter: null,
|
||||||
|
removeItems: true,
|
||||||
|
removeItemButton: false,
|
||||||
|
editItems: false,
|
||||||
|
duplicateItemsAllowed: true,
|
||||||
|
delimiter: ',',
|
||||||
|
paste: true,
|
||||||
|
searchEnabled: true,
|
||||||
|
searchChoices: true,
|
||||||
|
searchFloor: 1,
|
||||||
|
searchResultLimit: 4,
|
||||||
|
searchFields: ['label', 'value'],
|
||||||
|
position: 'auto',
|
||||||
|
resetScrollPosition: true,
|
||||||
|
shouldSort: true,
|
||||||
|
shouldSortItems: false,
|
||||||
|
sorter: sortByAlpha,
|
||||||
|
placeholder: true,
|
||||||
|
placeholderValue: null,
|
||||||
|
searchPlaceholderValue: null,
|
||||||
|
prependValue: null,
|
||||||
|
appendValue: null,
|
||||||
|
renderSelectedChoices: 'auto',
|
||||||
|
loadingText: 'Loading...',
|
||||||
|
noResultsText: 'No results found',
|
||||||
|
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',
|
||||||
|
addItemText: (value) => `Press Enter to add <b>"${sanitise(value)}"</b>`,
|
||||||
|
maxItemText: (maxItemCount) => `Only ${maxItemCount} values can be added`,
|
||||||
|
valueComparer: (value1, value2) => value1 === value2,
|
||||||
|
fuseOptions: {
|
||||||
|
includeScore: true,
|
||||||
|
},
|
||||||
|
callbackOnInit: null,
|
||||||
|
callbackOnCreateTemplates: null,
|
||||||
|
classNames: DEFAULT_CLASSNAMES,
|
||||||
|
};
|
12
src/scripts/interfaces/action-type.ts
Normal file
12
src/scripts/interfaces/action-type.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export type ActionType =
|
||||||
|
| 'ADD_CHOICE'
|
||||||
|
| 'FILTER_CHOICES'
|
||||||
|
| 'ACTIVATE_CHOICES'
|
||||||
|
| 'CLEAR_CHOICES'
|
||||||
|
| 'ADD_GROUP'
|
||||||
|
| 'ADD_ITEM'
|
||||||
|
| 'REMOVE_ITEM'
|
||||||
|
| 'HIGHLIGHT_ITEM'
|
||||||
|
| 'CLEAR_ALL'
|
||||||
|
| 'RESET_TO'
|
||||||
|
| 'SET_IS_LOADING';
|
17
src/scripts/interfaces/choice.ts
Normal file
17
src/scripts/interfaces/choice.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export interface Choice {
|
||||||
|
id?: number;
|
||||||
|
customProperties?: Record<string, any>;
|
||||||
|
disabled?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
elementId?: number;
|
||||||
|
groupId?: number;
|
||||||
|
keyCode?: number;
|
||||||
|
label: string;
|
||||||
|
placeholder?: boolean;
|
||||||
|
selected?: boolean;
|
||||||
|
value: string;
|
||||||
|
score?: number;
|
||||||
|
choices?: Choice[];
|
||||||
|
}
|
87
src/scripts/interfaces/choices.ts
Normal file
87
src/scripts/interfaces/choices.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import { Options } from 'deepmerge';
|
||||||
|
import { Store } from 'redux';
|
||||||
|
import {
|
||||||
|
WrappedInput,
|
||||||
|
WrappedSelect,
|
||||||
|
Container,
|
||||||
|
List,
|
||||||
|
Input,
|
||||||
|
Dropdown,
|
||||||
|
} from '../components';
|
||||||
|
import { Choice } from './choice';
|
||||||
|
import { Group } from './group';
|
||||||
|
import { Item } from './item';
|
||||||
|
import { State } from './state';
|
||||||
|
import templates from '../templates';
|
||||||
|
|
||||||
|
export interface Choices {
|
||||||
|
initialised: boolean;
|
||||||
|
config: Options;
|
||||||
|
|
||||||
|
passedElement: WrappedInput | WrappedSelect;
|
||||||
|
|
||||||
|
containerOuter: Container;
|
||||||
|
|
||||||
|
containerInner: Container;
|
||||||
|
|
||||||
|
choiceList: List;
|
||||||
|
|
||||||
|
itemList: List;
|
||||||
|
|
||||||
|
input: Input;
|
||||||
|
|
||||||
|
dropdown: Dropdown;
|
||||||
|
|
||||||
|
_isTextElement: boolean;
|
||||||
|
|
||||||
|
_isSelectOneElement: boolean;
|
||||||
|
|
||||||
|
_isSelectMultipleElement: boolean;
|
||||||
|
|
||||||
|
_isSelectElement: boolean;
|
||||||
|
|
||||||
|
_store: Store;
|
||||||
|
|
||||||
|
_templates: typeof templates;
|
||||||
|
|
||||||
|
_initialState: State;
|
||||||
|
|
||||||
|
_currentState: State;
|
||||||
|
|
||||||
|
_prevState: State;
|
||||||
|
|
||||||
|
_currentValue: string;
|
||||||
|
|
||||||
|
_canSearch: boolean;
|
||||||
|
|
||||||
|
_isScrollingOnIe: boolean;
|
||||||
|
|
||||||
|
_highlightPosition: number;
|
||||||
|
|
||||||
|
_wasTap: boolean;
|
||||||
|
|
||||||
|
_isSearching: boolean;
|
||||||
|
|
||||||
|
_placeholderValue: string | null;
|
||||||
|
|
||||||
|
_baseId: string;
|
||||||
|
|
||||||
|
_direction: HTMLElement['dir'];
|
||||||
|
|
||||||
|
_idNames: {
|
||||||
|
itemChoice: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
_presetGroups: Group[] | HTMLOptGroupElement[] | Element[];
|
||||||
|
|
||||||
|
_presetOptions: Item[] | HTMLOptionElement[];
|
||||||
|
|
||||||
|
_presetChoices: Partial<Choice>[];
|
||||||
|
|
||||||
|
_presetItems: Item[] | string[];
|
||||||
|
|
||||||
|
new (
|
||||||
|
element: string | Element | HTMLInputElement | HTMLSelectElement,
|
||||||
|
userConfig: Partial<Options>,
|
||||||
|
);
|
||||||
|
}
|
55
src/scripts/interfaces/class-names.ts
Normal file
55
src/scripts/interfaces/class-names.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/** Classes added to HTML generated by By default classnames follow the BEM notation. */
|
||||||
|
export interface ClassNames {
|
||||||
|
/** @default 'choices' */
|
||||||
|
containerOuter: string;
|
||||||
|
/** @default 'choices__inner' */
|
||||||
|
containerInner: string;
|
||||||
|
/** @default 'choices__input' */
|
||||||
|
input: string;
|
||||||
|
/** @default 'choices__input--cloned' */
|
||||||
|
inputCloned: string;
|
||||||
|
/** @default 'choices__list' */
|
||||||
|
list: string;
|
||||||
|
/** @default 'choices__list--multiple' */
|
||||||
|
listItems: string;
|
||||||
|
/** @default 'choices__list--single' */
|
||||||
|
listSingle: string;
|
||||||
|
/** @default 'choices__list--dropdown' */
|
||||||
|
listDropdown: string;
|
||||||
|
/** @default 'choices__item' */
|
||||||
|
item: string;
|
||||||
|
/** @default 'choices__item--selectable' */
|
||||||
|
itemSelectable: string;
|
||||||
|
/** @default 'choices__item--disabled' */
|
||||||
|
itemDisabled: string;
|
||||||
|
/** @default 'choices__item--choice' */
|
||||||
|
itemChoice: string;
|
||||||
|
/** @default 'choices__placeholder' */
|
||||||
|
placeholder: string;
|
||||||
|
/** @default 'choices__group' */
|
||||||
|
group: string;
|
||||||
|
/** @default 'choices__heading' */
|
||||||
|
groupHeading: string;
|
||||||
|
/** @default 'choices__button' */
|
||||||
|
button: string;
|
||||||
|
/** @default 'is-active' */
|
||||||
|
activeState: string;
|
||||||
|
/** @default 'is-focused' */
|
||||||
|
focusState: string;
|
||||||
|
/** @default 'is-open' */
|
||||||
|
openState: string;
|
||||||
|
/** @default 'is-disabled' */
|
||||||
|
disabledState: string;
|
||||||
|
/** @default 'is-highlighted' */
|
||||||
|
highlightedState: string;
|
||||||
|
/** @default 'is-selected' */
|
||||||
|
selectedState: string;
|
||||||
|
/** @default 'is-flipped' */
|
||||||
|
flippedState: string;
|
||||||
|
/** @default 'is-loading' */
|
||||||
|
loadingState: string;
|
||||||
|
/** @default 'has-no-results' */
|
||||||
|
noResults: string;
|
||||||
|
/** @default 'has-no-choices' */
|
||||||
|
noChoices: string;
|
||||||
|
}
|
11
src/scripts/interfaces/event-type.ts
Normal file
11
src/scripts/interfaces/event-type.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export type EventType =
|
||||||
|
| 'addItem'
|
||||||
|
| 'removeItem'
|
||||||
|
| 'highlightItem'
|
||||||
|
| 'unhighlightItem'
|
||||||
|
| 'choice'
|
||||||
|
| 'change'
|
||||||
|
| 'search'
|
||||||
|
| 'showDropdown'
|
||||||
|
| 'hideDropdown'
|
||||||
|
| 'highlightChoice';
|
8
src/scripts/interfaces/group.ts
Normal file
8
src/scripts/interfaces/group.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export interface Group {
|
||||||
|
id?: number;
|
||||||
|
active?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
value: any;
|
||||||
|
}
|
6
src/scripts/interfaces/item.ts
Normal file
6
src/scripts/interfaces/item.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { Choice } from './choice';
|
||||||
|
|
||||||
|
export interface Item extends Choice {
|
||||||
|
choiceId?: number;
|
||||||
|
highlighted?: boolean;
|
||||||
|
}
|
11
src/scripts/interfaces/keycode-map.ts
Normal file
11
src/scripts/interfaces/keycode-map.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export interface KeyCodeMap {
|
||||||
|
BACK_KEY: 46;
|
||||||
|
DELETE_KEY: 8;
|
||||||
|
ENTER_KEY: 13;
|
||||||
|
A_KEY: 65;
|
||||||
|
ESC_KEY: 27;
|
||||||
|
UP_KEY: 38;
|
||||||
|
DOWN_KEY: 40;
|
||||||
|
PAGE_UP_KEY: 33;
|
||||||
|
PAGE_DOWN_KEY: 34;
|
||||||
|
}
|
5
src/scripts/interfaces/notice.ts
Normal file
5
src/scripts/interfaces/notice.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// @todo rename
|
||||||
|
export interface Notice {
|
||||||
|
response: boolean;
|
||||||
|
notice: string;
|
||||||
|
}
|
|
@ -1,261 +1,9 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { FuseOptions } from 'fuse.js';
|
import { FuseOptions } from 'fuse.js';
|
||||||
import Choices from './choices';
|
import { Choices } from './choices';
|
||||||
|
import { Choice } from './choice';
|
||||||
export namespace Types {
|
import { ClassNames } from './class-names';
|
||||||
export type strToEl = (
|
import { PositionOptionsType } from './position-options-type';
|
||||||
str: string,
|
import { Types } from './types';
|
||||||
) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
|
||||||
export type stringFunction = () => string;
|
|
||||||
export type noticeStringFunction = (value: string) => string;
|
|
||||||
export type noticeLimitFunction = (maxItemCount: number) => string;
|
|
||||||
export type filterFunction = (value: string) => boolean;
|
|
||||||
export type valueCompareFunction = (
|
|
||||||
value1: string,
|
|
||||||
value2: string,
|
|
||||||
) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Choice {
|
|
||||||
id?: number;
|
|
||||||
customProperties?: Record<string, any>;
|
|
||||||
disabled?: boolean;
|
|
||||||
active?: boolean;
|
|
||||||
elementId?: number;
|
|
||||||
groupId?: number;
|
|
||||||
keyCode?: number;
|
|
||||||
label: string;
|
|
||||||
placeholder?: boolean;
|
|
||||||
selected?: boolean;
|
|
||||||
value: string;
|
|
||||||
score?: number;
|
|
||||||
choices?: Choice[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Group {
|
|
||||||
id?: number;
|
|
||||||
active?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
value: any;
|
|
||||||
}
|
|
||||||
export interface Item extends Choice {
|
|
||||||
choiceId?: number;
|
|
||||||
highlighted?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object.
|
|
||||||
*/
|
|
||||||
export interface EventMap {
|
|
||||||
/**
|
|
||||||
* Triggered each time an item is added (programmatically or by the user).
|
|
||||||
*
|
|
||||||
* **Input types affected:** text, select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: id, value, label, groupValue, keyCode
|
|
||||||
*/
|
|
||||||
addItem: CustomEvent<{
|
|
||||||
id: number;
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
groupValue: string;
|
|
||||||
keyCode: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered each time an item is removed (programmatically or by the user).
|
|
||||||
*
|
|
||||||
* **Input types affected:** text, select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: id, value, label, groupValue
|
|
||||||
*/
|
|
||||||
removeItem: CustomEvent<{
|
|
||||||
id: number;
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
groupValue: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered each time an item is highlighted.
|
|
||||||
*
|
|
||||||
* **Input types affected:** text, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: id, value, label, groupValue
|
|
||||||
*/
|
|
||||||
highlightItem: CustomEvent<{
|
|
||||||
id: number;
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
groupValue: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered each time an item is unhighlighted.
|
|
||||||
*
|
|
||||||
* **Input types affected:** text, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: id, value, label, groupValue
|
|
||||||
*/
|
|
||||||
unhighlightItem: CustomEvent<{
|
|
||||||
id: number;
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
groupValue: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input.
|
|
||||||
*
|
|
||||||
* **Input types affected:** select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: choice: Choice
|
|
||||||
*/
|
|
||||||
choice: CustomEvent<{ choice: Choice }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered each time an item is added/removed **by a user**.
|
|
||||||
*
|
|
||||||
* **Input types affected:** text, select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: value
|
|
||||||
*/
|
|
||||||
change: CustomEvent<{ value: string }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when a user types into an input to search choices.
|
|
||||||
*
|
|
||||||
* **Input types affected:** select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: value, resultCount
|
|
||||||
*/
|
|
||||||
search: CustomEvent<{ value: string; resultCount: number }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when the dropdown is shown.
|
|
||||||
*
|
|
||||||
* **Input types affected:** select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: -
|
|
||||||
*/
|
|
||||||
showDropdown: CustomEvent<undefined>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when the dropdown is hidden.
|
|
||||||
*
|
|
||||||
* **Input types affected:** select-one, select-multiple
|
|
||||||
*
|
|
||||||
* Arguments: -
|
|
||||||
*/
|
|
||||||
hideDropdown: CustomEvent<undefined>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when a choice from the dropdown is highlighted.
|
|
||||||
*
|
|
||||||
* Input types affected: select-one, select-multiple
|
|
||||||
* Arguments: el is the choice.passedElement that was affected.
|
|
||||||
*/
|
|
||||||
highlightChoice: CustomEvent<{ el: PassedElement }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface KeyCodeMap {
|
|
||||||
BACK_KEY: 46;
|
|
||||||
DELETE_KEY: 8;
|
|
||||||
ENTER_KEY: 13;
|
|
||||||
A_KEY: 65;
|
|
||||||
ESC_KEY: 27;
|
|
||||||
UP_KEY: 38;
|
|
||||||
DOWN_KEY: 40;
|
|
||||||
PAGE_UP_KEY: 33;
|
|
||||||
PAGE_DOWN_KEY: 34;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ActionType =
|
|
||||||
| 'ADD_CHOICE'
|
|
||||||
| 'FILTER_CHOICES'
|
|
||||||
| 'ACTIVATE_CHOICES'
|
|
||||||
| 'CLEAR_CHOICES'
|
|
||||||
| 'ADD_GROUP'
|
|
||||||
| 'ADD_ITEM'
|
|
||||||
| 'REMOVE_ITEM'
|
|
||||||
| 'HIGHLIGHT_ITEM'
|
|
||||||
| 'CLEAR_ALL'
|
|
||||||
| 'RESET_TO'
|
|
||||||
| 'SET_IS_LOADING';
|
|
||||||
|
|
||||||
/** Classes added to HTML generated by By default classnames follow the BEM notation. */
|
|
||||||
export interface ClassNames {
|
|
||||||
/** @default 'choices' */
|
|
||||||
containerOuter: string;
|
|
||||||
/** @default 'choices__inner' */
|
|
||||||
containerInner: string;
|
|
||||||
/** @default 'choices__input' */
|
|
||||||
input: string;
|
|
||||||
/** @default 'choices__input--cloned' */
|
|
||||||
inputCloned: string;
|
|
||||||
/** @default 'choices__list' */
|
|
||||||
list: string;
|
|
||||||
/** @default 'choices__list--multiple' */
|
|
||||||
listItems: string;
|
|
||||||
/** @default 'choices__list--single' */
|
|
||||||
listSingle: string;
|
|
||||||
/** @default 'choices__list--dropdown' */
|
|
||||||
listDropdown: string;
|
|
||||||
/** @default 'choices__item' */
|
|
||||||
item: string;
|
|
||||||
/** @default 'choices__item--selectable' */
|
|
||||||
itemSelectable: string;
|
|
||||||
/** @default 'choices__item--disabled' */
|
|
||||||
itemDisabled: string;
|
|
||||||
/** @default 'choices__item--choice' */
|
|
||||||
itemChoice: string;
|
|
||||||
/** @default 'choices__placeholder' */
|
|
||||||
placeholder: string;
|
|
||||||
/** @default 'choices__group' */
|
|
||||||
group: string;
|
|
||||||
/** @default 'choices__heading' */
|
|
||||||
groupHeading: string;
|
|
||||||
/** @default 'choices__button' */
|
|
||||||
button: string;
|
|
||||||
/** @default 'is-active' */
|
|
||||||
activeState: string;
|
|
||||||
/** @default 'is-focused' */
|
|
||||||
focusState: string;
|
|
||||||
/** @default 'is-open' */
|
|
||||||
openState: string;
|
|
||||||
/** @default 'is-disabled' */
|
|
||||||
disabledState: string;
|
|
||||||
/** @default 'is-highlighted' */
|
|
||||||
highlightedState: string;
|
|
||||||
/** @default 'is-selected' */
|
|
||||||
selectedState: string;
|
|
||||||
/** @default 'is-flipped' */
|
|
||||||
flippedState: string;
|
|
||||||
/** @default 'is-loading' */
|
|
||||||
loadingState: string;
|
|
||||||
/** @default 'has-no-results' */
|
|
||||||
noResults: string;
|
|
||||||
/** @default 'has-no-choices' */
|
|
||||||
noChoices: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PassedElement extends HTMLElement {
|
|
||||||
classNames: ClassNames;
|
|
||||||
element: (HTMLInputElement | HTMLSelectElement) & {
|
|
||||||
// Extends HTMLElement addEventListener with Choices events
|
|
||||||
addEventListener<K extends keyof EventMap>(
|
|
||||||
type: K,
|
|
||||||
listener: (
|
|
||||||
this: HTMLInputElement | HTMLSelectElement,
|
|
||||||
ev: EventMap[K],
|
|
||||||
) => void,
|
|
||||||
options?: boolean | AddEventListenerOptions,
|
|
||||||
): void;
|
|
||||||
};
|
|
||||||
type: 'text' | 'select-one' | 'select-multiple';
|
|
||||||
isDisabled: boolean;
|
|
||||||
parentInstance: Choices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choices options interface
|
* Choices options interface
|
||||||
|
@ -370,7 +118,7 @@ export interface Options {
|
||||||
*
|
*
|
||||||
* @default null
|
* @default null
|
||||||
*/
|
*/
|
||||||
addItemFilter: string | RegExp | Types.filterFunction | null;
|
addItemFilter: string | RegExp | Types.FilterFunction | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string.
|
* The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string.
|
||||||
|
@ -382,7 +130,7 @@ export interface Options {
|
||||||
* (value) => `Press Enter to add <b>"${value}"</b>`;
|
* (value) => `Press Enter to add <b>"${value}"</b>`;
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
addItemText: string | Types.noticeStringFunction;
|
addItemText: string | Types.NoticeStringFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a user can remove items.
|
* Whether a user can remove items.
|
||||||
|
@ -492,7 +240,7 @@ export interface Options {
|
||||||
*
|
*
|
||||||
* @default 'auto'
|
* @default 'auto'
|
||||||
*/
|
*/
|
||||||
position: 'auto' | 'top' | 'bottom';
|
position: PositionOptionsType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the scroll position should reset after adding an item.
|
* Whether the scroll position should reset after adding an item.
|
||||||
|
@ -620,7 +368,7 @@ export interface Options {
|
||||||
*
|
*
|
||||||
* @default 'No results found'
|
* @default 'No results found'
|
||||||
*/
|
*/
|
||||||
noResultsText: string | Types.stringFunction;
|
noResultsText: string | Types.StringFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text that is shown when a user has selected all possible choices. Optionally pass a function returning a string.
|
* The text that is shown when a user has selected all possible choices. Optionally pass a function returning a string.
|
||||||
|
@ -629,7 +377,7 @@ export interface Options {
|
||||||
*
|
*
|
||||||
* @default 'No choices to choose from'
|
* @default 'No choices to choose from'
|
||||||
*/
|
*/
|
||||||
noChoicesText: string | Types.stringFunction;
|
noChoicesText: string | Types.StringFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text that is shown when a user hovers over a selectable choice.
|
* The text that is shown when a user hovers over a selectable choice.
|
||||||
|
@ -650,14 +398,14 @@ export interface Options {
|
||||||
* (maxItemCount) => `Only ${maxItemCount} values can be added.`;
|
* (maxItemCount) => `Only ${maxItemCount} values can be added.`;
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
maxItemText: string | Types.noticeLimitFunction;
|
maxItemText: string | Types.NoticeLimitFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If no duplicates are allowed, and the value already exists in the array.
|
* If no duplicates are allowed, and the value already exists in the array.
|
||||||
*
|
*
|
||||||
* @default 'Only unique values can be added'
|
* @default 'Only unique values can be added'
|
||||||
*/
|
*/
|
||||||
uniqueItemText: string | Types.noticeStringFunction;
|
uniqueItemText: string | Types.NoticeStringFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text that is shown when addItemFilter is passed and it returns false
|
* The text that is shown when addItemFilter is passed and it returns false
|
||||||
|
@ -666,7 +414,7 @@ export interface Options {
|
||||||
*
|
*
|
||||||
* @default 'Only values matching specific conditions can be added'
|
* @default 'Only values matching specific conditions can be added'
|
||||||
*/
|
*/
|
||||||
customAddItemText: string | Types.noticeStringFunction;
|
customAddItemText: string | Types.NoticeStringFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `valueComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example).
|
* Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `valueComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example).
|
||||||
|
@ -678,7 +426,7 @@ export interface Options {
|
||||||
* (choice, item) => choice === item;
|
* (choice, item) => choice === item;
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
valueComparer: Types.valueCompareFunction;
|
valueComparer: Types.ValueCompareFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classes added to HTML generated by By default classnames follow the BEM notation.
|
* Classes added to HTML generated by By default classnames follow the BEM notation.
|
||||||
|
@ -737,18 +485,5 @@ export interface Options {
|
||||||
*
|
*
|
||||||
* @default null
|
* @default null
|
||||||
*/
|
*/
|
||||||
callbackOnCreateTemplates: ((template: Types.strToEl) => void) | null;
|
callbackOnCreateTemplates: ((template: Types.StrToEl) => void) | null;
|
||||||
}
|
|
||||||
|
|
||||||
// @todo rename
|
|
||||||
export interface Notice {
|
|
||||||
response: boolean;
|
|
||||||
notice: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
choices: Choice[];
|
|
||||||
groups: Group[];
|
|
||||||
items: Item[];
|
|
||||||
loading: boolean;
|
|
||||||
}
|
}
|
1
src/scripts/interfaces/passed-element-type.ts
Normal file
1
src/scripts/interfaces/passed-element-type.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type PassedElementType = 'text' | 'select-one' | 'select-multiple';
|
138
src/scripts/interfaces/passed-element.ts
Normal file
138
src/scripts/interfaces/passed-element.ts
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import { Choices } from './choices';
|
||||||
|
import { Choice } from './choice';
|
||||||
|
import { ClassNames } from './class-names';
|
||||||
|
import { EventType } from './event-type';
|
||||||
|
import { PassedElementType } from './passed-element-type';
|
||||||
|
|
||||||
|
export interface PassedElement extends HTMLElement {
|
||||||
|
classNames: ClassNames;
|
||||||
|
element: (HTMLInputElement | HTMLSelectElement) & {
|
||||||
|
// Extends HTMLElement addEventListener with Choices events
|
||||||
|
addEventListener<K extends EventType>(
|
||||||
|
type: K,
|
||||||
|
listener: (
|
||||||
|
this: HTMLInputElement | HTMLSelectElement,
|
||||||
|
ev: EventMap[K],
|
||||||
|
) => void,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): void;
|
||||||
|
};
|
||||||
|
type: PassedElementType;
|
||||||
|
isDisabled: boolean;
|
||||||
|
parentInstance: Choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object.
|
||||||
|
*/
|
||||||
|
export interface EventMap {
|
||||||
|
/**
|
||||||
|
* Triggered each time an item is added (programmatically or by the user).
|
||||||
|
*
|
||||||
|
* **Input types affected:** text, select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: id, value, label, groupValue, keyCode
|
||||||
|
*/
|
||||||
|
addItem: CustomEvent<{
|
||||||
|
id: number;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
groupValue: string;
|
||||||
|
keyCode: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered each time an item is removed (programmatically or by the user).
|
||||||
|
*
|
||||||
|
* **Input types affected:** text, select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: id, value, label, groupValue
|
||||||
|
*/
|
||||||
|
removeItem: CustomEvent<{
|
||||||
|
id: number;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
groupValue: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered each time an item is highlighted.
|
||||||
|
*
|
||||||
|
* **Input types affected:** text, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: id, value, label, groupValue
|
||||||
|
*/
|
||||||
|
highlightItem: CustomEvent<{
|
||||||
|
id: number;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
groupValue: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered each time an item is unhighlighted.
|
||||||
|
*
|
||||||
|
* **Input types affected:** text, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: id, value, label, groupValue
|
||||||
|
*/
|
||||||
|
unhighlightItem: CustomEvent<{
|
||||||
|
id: number;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
groupValue: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input.
|
||||||
|
*
|
||||||
|
* **Input types affected:** select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: choice: Choice
|
||||||
|
*/
|
||||||
|
choice: CustomEvent<{ choice: Choice }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered each time an item is added/removed **by a user**.
|
||||||
|
*
|
||||||
|
* **Input types affected:** text, select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: value
|
||||||
|
*/
|
||||||
|
change: CustomEvent<{ value: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when a user types into an input to search choices.
|
||||||
|
*
|
||||||
|
* **Input types affected:** select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: value, resultCount
|
||||||
|
*/
|
||||||
|
search: CustomEvent<{ value: string; resultCount: number }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when the dropdown is shown.
|
||||||
|
*
|
||||||
|
* **Input types affected:** select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: -
|
||||||
|
*/
|
||||||
|
showDropdown: CustomEvent<undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when the dropdown is hidden.
|
||||||
|
*
|
||||||
|
* **Input types affected:** select-one, select-multiple
|
||||||
|
*
|
||||||
|
* Arguments: -
|
||||||
|
*/
|
||||||
|
hideDropdown: CustomEvent<undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when a choice from the dropdown is highlighted.
|
||||||
|
*
|
||||||
|
* Input types affected: select-one, select-multiple
|
||||||
|
* Arguments: el is the choice.passedElement that was affected.
|
||||||
|
*/
|
||||||
|
highlightChoice: CustomEvent<{ el: PassedElement }>;
|
||||||
|
}
|
1
src/scripts/interfaces/position-options-type.ts
Normal file
1
src/scripts/interfaces/position-options-type.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type PositionOptionsType = 'auto' | 'top' | 'bottom';
|
10
src/scripts/interfaces/state.ts
Normal file
10
src/scripts/interfaces/state.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Choice } from './choice';
|
||||||
|
import { Group } from './group';
|
||||||
|
import { Item } from './item';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
choices: Choice[];
|
||||||
|
groups: Group[];
|
||||||
|
items: Item[];
|
||||||
|
loading: boolean;
|
||||||
|
}
|
13
src/scripts/interfaces/types.ts
Normal file
13
src/scripts/interfaces/types.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export namespace Types {
|
||||||
|
export type StrToEl = (
|
||||||
|
str: string,
|
||||||
|
) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
||||||
|
export type StringFunction = () => string;
|
||||||
|
export type NoticeStringFunction = (value: string) => string;
|
||||||
|
export type NoticeLimitFunction = (maxItemCount: number) => string;
|
||||||
|
export type FilterFunction = (value: string) => boolean;
|
||||||
|
export type ValueCompareFunction = (
|
||||||
|
value1: string,
|
||||||
|
value2: string,
|
||||||
|
) => boolean;
|
||||||
|
}
|
|
@ -84,7 +84,7 @@ describe('utils', () => {
|
||||||
expect(getType([])).to.equal('Array');
|
expect(getType([])).to.equal('Array');
|
||||||
expect(getType(() => {})).to.equal('Function');
|
expect(getType(() => {})).to.equal('Function');
|
||||||
expect(getType(new Error())).to.equal('Error');
|
expect(getType(new Error())).to.equal('Error');
|
||||||
expect(getType(new RegExp(/''/g))).to.equal('RegExp');
|
expect(getType(/''/g)).to.equal('RegExp');
|
||||||
expect(getType(new String())).to.equal('String'); // eslint-disable-line
|
expect(getType(new String())).to.equal('String'); // eslint-disable-line
|
||||||
expect(getType('')).to.equal('String');
|
expect(getType('')).to.equal('String');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { EventMap, Choice } from '../interfaces';
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { Choice } from '../interfaces/choice';
|
||||||
|
import { EventType } from '../interfaces/event-type';
|
||||||
|
|
||||||
export const getRandomNumber = (min: number, max: number): number =>
|
export const getRandomNumber = (min: number, max: number): number =>
|
||||||
Math.floor(Math.random() * (max - min) + min);
|
Math.floor(Math.random() * (max - min) + min);
|
||||||
|
|
||||||
|
@ -32,11 +33,12 @@ export const wrap = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
wrapper: HTMLElement = document.createElement('div'),
|
wrapper: HTMLElement = document.createElement('div'),
|
||||||
): HTMLElement => {
|
): HTMLElement => {
|
||||||
if (element.nextSibling) {
|
if (element.parentNode) {
|
||||||
element.parentNode &&
|
if (element.nextSibling) {
|
||||||
element.parentNode.insertBefore(wrapper, element.nextSibling);
|
element.parentNode.insertBefore(wrapper, element.nextSibling);
|
||||||
} else {
|
} else {
|
||||||
element.parentNode && element.parentNode.appendChild(wrapper);
|
element.parentNode.appendChild(wrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper.appendChild(element);
|
return wrapper.appendChild(element);
|
||||||
|
@ -138,7 +140,7 @@ export const sortByScore = (
|
||||||
|
|
||||||
export const dispatchEvent = (
|
export const dispatchEvent = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
type: keyof EventMap,
|
type: EventType,
|
||||||
customArgs: object | null = null,
|
customArgs: object | null = null,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const event = new CustomEvent(type, {
|
const event = new CustomEvent(type, {
|
||||||
|
@ -155,7 +157,7 @@ export const existsInArray = (
|
||||||
value: string,
|
value: string,
|
||||||
key = 'value',
|
key = 'value',
|
||||||
): boolean =>
|
): boolean =>
|
||||||
array.some(item => {
|
array.some((item) => {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return item[key] === value.trim();
|
return item[key] === value.trim();
|
||||||
}
|
}
|
||||||
|
@ -176,5 +178,5 @@ export const diff = (
|
||||||
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 aKeys.filter(i => bKeys.indexOf(i) < 0);
|
return aKeys.filter((i) => bKeys.indexOf(i) < 0);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import { Choice } from '../interfaces/choice';
|
||||||
import choices, { defaultState } from './choices';
|
import choices, { defaultState } from './choices';
|
||||||
import { Choice } from '../interfaces';
|
|
||||||
|
|
||||||
describe('reducers/choices', () => {
|
describe('reducers/choices', () => {
|
||||||
it('should return same state when no action matches', () => {
|
it('should return same state when no action matches', () => {
|
||||||
|
@ -178,7 +178,7 @@ describe('reducers/choices', () => {
|
||||||
score,
|
score,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}).find(choice => choice.id === id);
|
}).find((choice) => choice.id === id);
|
||||||
|
|
||||||
expect(actualResponse).to.eql(expectedResponse);
|
expect(actualResponse).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Choice } from '../interfaces';
|
|
||||||
import {
|
import {
|
||||||
AddChoiceAction,
|
AddChoiceAction,
|
||||||
FilterChoicesAction,
|
FilterChoicesAction,
|
||||||
|
@ -6,6 +5,7 @@ import {
|
||||||
ClearChoicesAction,
|
ClearChoicesAction,
|
||||||
} from '../actions/choices';
|
} from '../actions/choices';
|
||||||
import { AddItemAction, RemoveItemAction } from '../actions/items';
|
import { AddItemAction, RemoveItemAction } from '../actions/items';
|
||||||
|
import { Choice } from '../interfaces/choice';
|
||||||
|
|
||||||
export const defaultState = [];
|
export const defaultState = [];
|
||||||
|
|
||||||
|
@ -15,11 +15,12 @@ type ActionTypes =
|
||||||
| ActivateChoicesAction
|
| ActivateChoicesAction
|
||||||
| ClearChoicesAction
|
| ClearChoicesAction
|
||||||
| AddItemAction
|
| AddItemAction
|
||||||
| RemoveItemAction;
|
| RemoveItemAction
|
||||||
|
| Record<string, never>;
|
||||||
|
|
||||||
export default function choices(
|
export default function choices(
|
||||||
state: Choice[] = defaultState,
|
state: Choice[] = defaultState,
|
||||||
action: ActionTypes,
|
action: ActionTypes = {},
|
||||||
): Choice[] {
|
): Choice[] {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ADD_CHOICE': {
|
case 'ADD_CHOICE': {
|
||||||
|
@ -52,7 +53,7 @@ export default function choices(
|
||||||
// When an item is added and it has an associated choice,
|
// When an item is added and it has an associated choice,
|
||||||
// we want to disable it so it can't be chosen again
|
// we want to disable it so it can't be chosen again
|
||||||
if (addItemAction.choiceId > -1) {
|
if (addItemAction.choiceId > -1) {
|
||||||
return state.map(obj => {
|
return state.map((obj) => {
|
||||||
const choice = obj;
|
const choice = obj;
|
||||||
if (choice.id === parseInt(`${addItemAction.choiceId}`, 10)) {
|
if (choice.id === parseInt(`${addItemAction.choiceId}`, 10)) {
|
||||||
choice.selected = true;
|
choice.selected = true;
|
||||||
|
@ -71,7 +72,7 @@ export default function choices(
|
||||||
// When an item is removed and it has an associated choice,
|
// When an item is removed and it has an associated choice,
|
||||||
// we want to re-enable it so it can be chosen again
|
// we want to re-enable it so it can be chosen again
|
||||||
if (removeItemAction.choiceId && removeItemAction.choiceId > -1) {
|
if (removeItemAction.choiceId && removeItemAction.choiceId > -1) {
|
||||||
return state.map(obj => {
|
return state.map((obj) => {
|
||||||
const choice = obj;
|
const choice = obj;
|
||||||
if (choice.id === parseInt(`${removeItemAction.choiceId}`, 10)) {
|
if (choice.id === parseInt(`${removeItemAction.choiceId}`, 10)) {
|
||||||
choice.selected = false;
|
choice.selected = false;
|
||||||
|
@ -87,7 +88,7 @@ export default function choices(
|
||||||
case 'FILTER_CHOICES': {
|
case 'FILTER_CHOICES': {
|
||||||
const filterChoicesAction = action as FilterChoicesAction;
|
const filterChoicesAction = action as FilterChoicesAction;
|
||||||
|
|
||||||
return state.map(obj => {
|
return state.map((obj) => {
|
||||||
const choice = obj;
|
const choice = obj;
|
||||||
// Set active state based on whether choice is
|
// Set active state based on whether choice is
|
||||||
// within filtered results
|
// within filtered results
|
||||||
|
@ -108,7 +109,7 @@ export default function choices(
|
||||||
case 'ACTIVATE_CHOICES': {
|
case 'ACTIVATE_CHOICES': {
|
||||||
const activateChoicesAction = action as ActivateChoicesAction;
|
const activateChoicesAction = action as ActivateChoicesAction;
|
||||||
|
|
||||||
return state.map(obj => {
|
return state.map((obj) => {
|
||||||
const choice = obj;
|
const choice = obj;
|
||||||
choice.active = activateChoicesAction.active;
|
choice.active = activateChoicesAction.active;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { Group, State } from '../interfaces';
|
|
||||||
import { AddGroupAction } from '../actions/groups';
|
import { AddGroupAction } from '../actions/groups';
|
||||||
import { ClearChoicesAction } from '../actions/choices';
|
import { ClearChoicesAction } from '../actions/choices';
|
||||||
|
import { Group } from '../interfaces/group';
|
||||||
|
import { State } from '../interfaces/state';
|
||||||
|
|
||||||
export const defaultState = [];
|
export const defaultState = [];
|
||||||
|
|
||||||
type ActionTypes = AddGroupAction | ClearChoicesAction;
|
type ActionTypes = AddGroupAction | ClearChoicesAction | Record<string, never>;
|
||||||
|
|
||||||
export default function groups(
|
export default function groups(
|
||||||
state: Group[] = defaultState,
|
state: Group[] = defaultState,
|
||||||
action: ActionTypes,
|
action: ActionTypes = {},
|
||||||
): State['groups'] {
|
): State['groups'] {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ADD_GROUP': {
|
case 'ADD_GROUP': {
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe('reducers/items', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unhighlights all highlighted items', () => {
|
it('unhighlights all highlighted items', () => {
|
||||||
actualResponse.forEach(item => {
|
actualResponse.forEach((item) => {
|
||||||
expect(item.highlighted).to.equal(false);
|
expect(item.highlighted).to.equal(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
import { Item, State } from '../interfaces';
|
|
||||||
import {
|
import {
|
||||||
AddItemAction,
|
AddItemAction,
|
||||||
RemoveItemAction,
|
RemoveItemAction,
|
||||||
HighlightItemAction,
|
HighlightItemAction,
|
||||||
} from '../actions/items';
|
} from '../actions/items';
|
||||||
|
import { Item } from '../interfaces/item';
|
||||||
|
import { State } from '../interfaces/state';
|
||||||
|
|
||||||
export const defaultState = [];
|
export const defaultState = [];
|
||||||
|
|
||||||
type ActionTypes = AddItemAction | RemoveItemAction | HighlightItemAction;
|
type ActionTypes =
|
||||||
|
| AddItemAction
|
||||||
|
| RemoveItemAction
|
||||||
|
| HighlightItemAction
|
||||||
|
| Record<string, never>;
|
||||||
|
|
||||||
export default function items(
|
export default function items(
|
||||||
state: Item[] = defaultState,
|
state: Item[] = defaultState,
|
||||||
action: ActionTypes,
|
action: ActionTypes = {},
|
||||||
): State['items'] {
|
): State['items'] {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ADD_ITEM': {
|
case 'ADD_ITEM': {
|
||||||
|
@ -43,7 +48,7 @@ export default function items(
|
||||||
|
|
||||||
case 'REMOVE_ITEM': {
|
case 'REMOVE_ITEM': {
|
||||||
// Set item to inactive
|
// Set item to inactive
|
||||||
return state.map(obj => {
|
return state.map((obj) => {
|
||||||
const item = obj;
|
const item = obj;
|
||||||
if (item.id === action.id) {
|
if (item.id === action.id) {
|
||||||
item.active = false;
|
item.active = false;
|
||||||
|
@ -56,7 +61,7 @@ export default function items(
|
||||||
case 'HIGHLIGHT_ITEM': {
|
case 'HIGHLIGHT_ITEM': {
|
||||||
const highlightItemAction = action as HighlightItemAction;
|
const highlightItemAction = action as HighlightItemAction;
|
||||||
|
|
||||||
return state.map(obj => {
|
return state.map((obj) => {
|
||||||
const item = obj;
|
const item = obj;
|
||||||
if (item.id === highlightItemAction.id) {
|
if (item.id === highlightItemAction.id) {
|
||||||
item.highlighted = highlightItemAction.highlighted;
|
item.highlighted = highlightItemAction.highlighted;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { SetIsLoadingAction } from '../actions/misc';
|
import { SetIsLoadingAction } from '../actions/misc';
|
||||||
import { State } from '../interfaces';
|
import { State } from '../interfaces/state';
|
||||||
|
|
||||||
export const defaultState = false;
|
export const defaultState = false;
|
||||||
|
|
||||||
type ActionTypes = SetIsLoadingAction;
|
type ActionTypes = SetIsLoadingAction | Record<string, never>;
|
||||||
|
|
||||||
const general = (
|
const general = (
|
||||||
state = defaultState,
|
state = defaultState,
|
||||||
action: ActionTypes,
|
action: ActionTypes = {},
|
||||||
): State['loading'] => {
|
): State['loading'] => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_IS_LOADING': {
|
case 'SET_IS_LOADING': {
|
||||||
|
|
|
@ -161,7 +161,7 @@ describe('reducers/store', () => {
|
||||||
|
|
||||||
describe('activeItems getter', () => {
|
describe('activeItems getter', () => {
|
||||||
it('returns items that are active', () => {
|
it('returns items that are active', () => {
|
||||||
const expectedResponse = state.items.filter(item => item.active);
|
const expectedResponse = state.items.filter((item) => item.active);
|
||||||
expect(instance.activeItems).to.eql(expectedResponse);
|
expect(instance.activeItems).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -169,7 +169,7 @@ describe('reducers/store', () => {
|
||||||
describe('highlightedActiveItems getter', () => {
|
describe('highlightedActiveItems getter', () => {
|
||||||
it('returns items that are active and highlighted', () => {
|
it('returns items that are active and highlighted', () => {
|
||||||
const expectedResponse = state.items.filter(
|
const expectedResponse = state.items.filter(
|
||||||
item => item.highlighted && item.active,
|
(item) => item.highlighted && item.active,
|
||||||
);
|
);
|
||||||
expect(instance.highlightedActiveItems).to.eql(expectedResponse);
|
expect(instance.highlightedActiveItems).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
|
@ -184,7 +184,9 @@ describe('reducers/store', () => {
|
||||||
|
|
||||||
describe('activeChoices getter', () => {
|
describe('activeChoices getter', () => {
|
||||||
it('returns choices that are active', () => {
|
it('returns choices that are active', () => {
|
||||||
const expectedResponse = state.choices.filter(choice => choice.active);
|
const expectedResponse = state.choices.filter(
|
||||||
|
(choice) => choice.active,
|
||||||
|
);
|
||||||
expect(instance.activeChoices).to.eql(expectedResponse);
|
expect(instance.activeChoices).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -192,7 +194,7 @@ describe('reducers/store', () => {
|
||||||
describe('selectableChoices getter', () => {
|
describe('selectableChoices getter', () => {
|
||||||
it('returns choices that are not disabled', () => {
|
it('returns choices that are not disabled', () => {
|
||||||
const expectedResponse = state.choices.filter(
|
const expectedResponse = state.choices.filter(
|
||||||
choice => !choice.disabled,
|
(choice) => !choice.disabled,
|
||||||
);
|
);
|
||||||
expect(instance.selectableChoices).to.eql(expectedResponse);
|
expect(instance.selectableChoices).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
|
@ -201,7 +203,7 @@ describe('reducers/store', () => {
|
||||||
describe('searchableChoices getter', () => {
|
describe('searchableChoices getter', () => {
|
||||||
it('returns choices that are not placeholders and are selectable', () => {
|
it('returns choices that are not placeholders and are selectable', () => {
|
||||||
const expectedResponse = state.choices.filter(
|
const expectedResponse = state.choices.filter(
|
||||||
choice => !choice.disabled && !choice.placeholder,
|
(choice) => !choice.disabled && !choice.placeholder,
|
||||||
);
|
);
|
||||||
expect(instance.searchableChoices).to.eql(expectedResponse);
|
expect(instance.searchableChoices).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
|
@ -212,7 +214,7 @@ describe('reducers/store', () => {
|
||||||
it('returns active choice by passed id', () => {
|
it('returns active choice by passed id', () => {
|
||||||
const id = '1';
|
const id = '1';
|
||||||
const expectedResponse = state.choices.find(
|
const expectedResponse = state.choices.find(
|
||||||
choice => choice.id === parseInt(id, 10),
|
(choice) => choice.id === parseInt(id, 10),
|
||||||
);
|
);
|
||||||
const actualResponse = instance.getChoiceById(id);
|
const actualResponse = instance.getChoiceById(id);
|
||||||
expect(actualResponse).to.eql(expectedResponse);
|
expect(actualResponse).to.eql(expectedResponse);
|
||||||
|
@ -224,7 +226,7 @@ describe('reducers/store', () => {
|
||||||
it('returns placeholder choice', () => {
|
it('returns placeholder choice', () => {
|
||||||
const expectedResponse = state.choices
|
const expectedResponse = state.choices
|
||||||
.reverse()
|
.reverse()
|
||||||
.find(choice => choice.placeholder);
|
.find((choice) => choice.placeholder);
|
||||||
expect(instance.getPlaceholderChoice).to.eql(expectedResponse);
|
expect(instance.getPlaceholderChoice).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -238,7 +240,7 @@ describe('reducers/store', () => {
|
||||||
|
|
||||||
describe('activeGroups getter', () => {
|
describe('activeGroups getter', () => {
|
||||||
it('returns active groups', () => {
|
it('returns active groups', () => {
|
||||||
const expectedResponse = state.groups.filter(group => group.active);
|
const expectedResponse = state.groups.filter((group) => group.active);
|
||||||
expect(instance.activeGroups).to.eql(expectedResponse);
|
expect(instance.activeGroups).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -246,7 +248,7 @@ describe('reducers/store', () => {
|
||||||
describe('getGroupById', () => {
|
describe('getGroupById', () => {
|
||||||
it('returns group by id', () => {
|
it('returns group by id', () => {
|
||||||
const id = 1;
|
const id = 1;
|
||||||
const expectedResponse = state.groups.find(group => group.id === id);
|
const expectedResponse = state.groups.find((group) => group.id === id);
|
||||||
const actualResponse = instance.getGroupById(id);
|
const actualResponse = instance.getGroupById(id);
|
||||||
expect(actualResponse).to.eql(expectedResponse);
|
expect(actualResponse).to.eql(expectedResponse);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { createStore, Store as IStore, AnyAction } from 'redux';
|
import { createStore, Store as IStore, AnyAction } from 'redux';
|
||||||
|
import { Choice } from '../interfaces/choice';
|
||||||
|
import { Group } from '../interfaces/group';
|
||||||
|
import { Item } from '../interfaces/item';
|
||||||
|
import { State } from '../interfaces/state';
|
||||||
import rootReducer from '../reducers/index';
|
import rootReducer from '../reducers/index';
|
||||||
import { Choice, Group, Item, State } from '../interfaces';
|
|
||||||
|
|
||||||
export default class Store {
|
export default class Store {
|
||||||
_store: IStore;
|
_store: IStore;
|
||||||
|
@ -46,14 +49,14 @@ export default class Store {
|
||||||
* Get active items from store
|
* Get active items from store
|
||||||
*/
|
*/
|
||||||
get activeItems(): Item[] {
|
get activeItems(): Item[] {
|
||||||
return this.items.filter(item => item.active === true);
|
return this.items.filter((item) => item.active === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get highlighted items from store
|
* Get highlighted items from store
|
||||||
*/
|
*/
|
||||||
get highlightedActiveItems(): Item[] {
|
get highlightedActiveItems(): Item[] {
|
||||||
return this.items.filter(item => item.active && item.highlighted);
|
return this.items.filter((item) => item.active && item.highlighted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,21 +70,23 @@ export default class Store {
|
||||||
* Get active choices from store
|
* Get active choices from store
|
||||||
*/
|
*/
|
||||||
get activeChoices(): Choice[] {
|
get activeChoices(): Choice[] {
|
||||||
return this.choices.filter(choice => choice.active === true);
|
return this.choices.filter((choice) => choice.active === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get selectable choices from store
|
* Get selectable choices from store
|
||||||
*/
|
*/
|
||||||
get selectableChoices(): Choice[] {
|
get selectableChoices(): Choice[] {
|
||||||
return this.choices.filter(choice => choice.disabled !== true);
|
return this.choices.filter((choice) => choice.disabled !== true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get choices that can be searched (excluding placeholders)
|
* Get choices that can be searched (excluding placeholders)
|
||||||
*/
|
*/
|
||||||
get searchableChoices(): Choice[] {
|
get searchableChoices(): Choice[] {
|
||||||
return this.selectableChoices.filter(choice => choice.placeholder !== true);
|
return this.selectableChoices.filter(
|
||||||
|
(choice) => choice.placeholder !== true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +95,7 @@ export default class Store {
|
||||||
get placeholderChoice(): Choice | undefined {
|
get placeholderChoice(): Choice | undefined {
|
||||||
return [...this.choices]
|
return [...this.choices]
|
||||||
.reverse()
|
.reverse()
|
||||||
.find(choice => choice.placeholder === true);
|
.find((choice) => choice.placeholder === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,10 +111,10 @@ export default class Store {
|
||||||
get activeGroups(): Group[] {
|
get activeGroups(): Group[] {
|
||||||
const { groups, choices } = this;
|
const { groups, choices } = this;
|
||||||
|
|
||||||
return groups.filter(group => {
|
return groups.filter((group) => {
|
||||||
const isActive = group.active === true && group.disabled === false;
|
const isActive = group.active === true && group.disabled === false;
|
||||||
const hasActiveOptions = choices.some(
|
const hasActiveOptions = choices.some(
|
||||||
choice => choice.active === true && choice.disabled === false,
|
(choice) => choice.active === true && choice.disabled === false,
|
||||||
);
|
);
|
||||||
|
|
||||||
return isActive && hasActiveOptions;
|
return isActive && hasActiveOptions;
|
||||||
|
@ -127,13 +132,13 @@ export default class Store {
|
||||||
* Get single choice by it's ID
|
* Get single choice by it's ID
|
||||||
*/
|
*/
|
||||||
getChoiceById(id: string): Choice | undefined {
|
getChoiceById(id: string): Choice | undefined {
|
||||||
return this.activeChoices.find(choice => choice.id === parseInt(id, 10));
|
return this.activeChoices.find((choice) => choice.id === parseInt(id, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get group by group id
|
* Get group by group id
|
||||||
*/
|
*/
|
||||||
getGroupById(id: number): Group | undefined {
|
getGroupById(id: number): Group | undefined {
|
||||||
return this.groups.find(group => group.id === id);
|
return this.groups.find((group) => group.id === id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import { ClassNames, Item, Choice, Group, PassedElement } from './interfaces';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helpers to create HTML elements used by Choices
|
* Helpers to create HTML elements used by Choices
|
||||||
* Can be overridden by providing `callbackOnCreateTemplates` option
|
* Can be overridden by providing `callbackOnCreateTemplates` option
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Choice } from './interfaces/choice';
|
||||||
|
import { ClassNames } from './interfaces/class-names';
|
||||||
|
import { Group } from './interfaces/group';
|
||||||
|
import { Item } from './interfaces/item';
|
||||||
|
import { PassedElementType } from './interfaces/passed-element-type';
|
||||||
|
|
||||||
const templates = {
|
const templates = {
|
||||||
containerOuter(
|
containerOuter(
|
||||||
{ containerOuter }: Pick<ClassNames, 'containerOuter'>,
|
{ containerOuter }: Pick<ClassNames, 'containerOuter'>,
|
||||||
|
@ -12,7 +16,7 @@ const templates = {
|
||||||
isSelectElement: boolean,
|
isSelectElement: boolean,
|
||||||
isSelectOneElement: boolean,
|
isSelectOneElement: boolean,
|
||||||
searchEnabled: boolean,
|
searchEnabled: boolean,
|
||||||
passedElementType: PassedElement['type'],
|
passedElementType: PassedElementType,
|
||||||
): HTMLDivElement {
|
): HTMLDivElement {
|
||||||
const div = Object.assign(document.createElement('div'), {
|
const div = Object.assign(document.createElement('div'), {
|
||||||
className: containerOuter,
|
className: containerOuter,
|
||||||
|
|
Loading…
Reference in a new issue