replicated PR 525 from main repo

This commit is contained in:
Joe Workman 2023-04-28 13:12:48 -07:00
parent 5dbea2825a
commit 04977bae97
No known key found for this signature in database
GPG key ID: 52D9F1902936CAFE
8 changed files with 116 additions and 13 deletions

View file

@ -122,6 +122,7 @@ Or include Choices directly:
choices: [], choices: [],
renderChoiceLimit: -1, renderChoiceLimit: -1,
maxItemCount: -1, maxItemCount: -1,
addChoices: false,
addItems: true, addItems: true,
addItemFilter: null, addItemFilter: null,
removeItems: true, removeItems: true,
@ -304,6 +305,15 @@ Pass an array of objects:
**Usage:** The amount of items a user can input/select ("-1" indicates no limit). **Usage:** The amount of items a user can input/select ("-1" indicates no limit).
### addChoices
**Type**: `Boolean` **Default:** `false`
**Input types affected:** `select-multiple`, `select-one`
**Usage:** Whether a user can add choices
**Note:** `addItems` must also be `true`
### addItems ### addItems
**Type:** `Boolean` **Default:** `true` **Type:** `Boolean` **Default:** `true`

View file

@ -1006,6 +1006,24 @@ describe('Choices - select multiple', () => {
}); });
}); });
}); });
describe('adding user-created choices', () => {
it('allows the user to add choices', () => {
const newChoice = 'New Choice';
cy.get('[data-test-hook=add-choices]')
.find('.choices__input--cloned')
.type(newChoice)
.type('{enter}');
cy.get('[data-test-hook=add-choices]')
.find('.choices__list--multiple')
.last()
.should($el => {
expect($el).to.contain(newChoice);
});
});
});
}); });
}); });
}); });

View file

@ -1145,5 +1145,26 @@ describe('Choices - select one', () => {
.should('have.length', 3); .should('have.length', 3);
}); });
}); });
describe('adding user-created choices', () => {
beforeEach(() => {
cy.get('[data-test-hook=add-choices]').find('.choices').click();
});
it('allows the user to add choices', () => {
const newChoice = 'New Choice';
cy.get('[data-test-hook=add-choices]')
.find('.choices__input--cloned')
.type(newChoice)
.type('{enter}');
cy.get('[data-test-hook=add-choices]')
.find('.choices__list--single .choices__item')
.should(($el) => {
expect($el).to.contain(newChoice);
});
});
});
}); });
}); });

View file

@ -397,6 +397,15 @@
multiple multiple
></select> ></select>
</div> </div>
<div data-test-hook="add-choices">
<label for="choices-add-choices">Add choices</label>
<select class="form-control" name="choices-add-choices" id="choices-add-choices" multiple>
<option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option>
</select>
</div>
</div> </div>
</div> </div>
<script> <script>
@ -622,6 +631,8 @@
}, },
], ],
}); });
new Choices('#choices-add-choices', { addChoices: true });
}); });
</script> </script>
</body> </body>

View file

@ -417,6 +417,16 @@
<button class="destroy">Destroy</button> <button class="destroy">Destroy</button>
<button class="init">Init</button> <button class="init">Init</button>
</div> </div>
<div data-test-hook="add-choices">
<label for="choices-add-choices">Add choices</label>
<select class="form-control" name="choices-add-choices" id="choices-add-choices">
<option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option>
</select>
</div>
</div> </div>
</div> </div>
<script> <script>
@ -676,6 +686,8 @@
document.querySelector('button.init').addEventListener('click', () => { document.querySelector('button.init').addEventListener('click', () => {
newDestroyInitChoices.init(); newDestroyInitChoices.init();
}); });
new Choices('#choices-add-choices', { addChoices: true });
}); });
</script> </script>
</body> </body>

View file

@ -825,12 +825,12 @@ class Choices implements Choices {
); );
} }
// If we have choices to show const { activeItems } = this._store; // If we have choices to show
if ( if (
choiceListFragment.childNodes && choiceListFragment.childNodes &&
choiceListFragment.childNodes.length > 0 choiceListFragment.childNodes.length > 0
) { ) {
const { activeItems } = this._store;
const canAddItem = this._canAddItem(activeItems, this.input.value); const canAddItem = this._canAddItem(activeItems, this.input.value);
// ...and we can select them // ...and we can select them
@ -844,18 +844,21 @@ class Choices implements Choices {
} }
} else { } else {
// Otherwise show a notice // Otherwise show a notice
let dropdownItem; const canAddChoice = this._canAddChoice(activeItems, this.input.value);
let notice;
if (this._isSearching) { let dropdownItem;
notice =
if (canAddChoice.response) {
dropdownItem = this._getTemplate('notice', canAddChoice.notice);
} else if (this._isSearching) {
const notice =
typeof this.config.noResultsText === 'function' typeof this.config.noResultsText === 'function'
? this.config.noResultsText() ? this.config.noResultsText()
: this.config.noResultsText; : this.config.noResultsText;
dropdownItem = this._getTemplate('notice', notice, 'no-results'); dropdownItem = this._getTemplate('notice', notice, 'no-results');
} else { } else {
notice = const notice =
typeof this.config.noChoicesText === 'function' typeof this.config.noChoicesText === 'function'
? this.config.noChoicesText() ? this.config.noChoicesText()
: this.config.noChoicesText; : this.config.noChoicesText;
@ -1260,6 +1263,14 @@ class Choices implements Choices {
} }
} }
_canAddChoice(activeItems: Item[], value: string): Notice {
const canAddItem = this._canAddItem(activeItems, value);
canAddItem.response = this.config.addChoices && canAddItem.response;
return canAddItem;
}
_canAddItem(activeItems: Item[], value: string): Notice { _canAddItem(activeItems: Item[], value: string): Notice {
let canAddItem = true; let canAddItem = true;
let notice = let notice =
@ -1555,16 +1566,22 @@ class Choices implements Choices {
const { ENTER_KEY: enterKey } = KEY_CODES; const { ENTER_KEY: enterKey } = KEY_CODES;
const targetWasButton = const targetWasButton =
target && (target as HTMLElement).hasAttribute('data-button'); target && (target as HTMLElement).hasAttribute('data-button');
let addedItem = false;
if (this._isTextElement && target && (target as HTMLInputElement).value) { if (target && (target as HTMLInputElement).value) {
const { value } = this.input; const { value } = this.input;
const canAddItem = this._canAddItem(activeItems, value); const canAddItem = this._canAddItem(activeItems, value);
const canAddChoice = this._canAddChoice(activeItems, value);
if (canAddItem.response) { if (
(this._isTextElement && canAddItem.response) ||
(!this._isTextElement && canAddChoice.response)
) {
this.hideDropdown(true); this.hideDropdown(true);
this._addItem({ value }); this._addItem({ value });
this._triggerChange(value); this._triggerChange(value);
this.clearInput(); this.clearInput();
addedItem = true;
} }
} }
@ -1579,11 +1596,15 @@ class Choices implements Choices {
); );
if (highlightedChoice) { if (highlightedChoice) {
// add enter keyCode value if (addedItem) {
if (activeItems[0]) { this.unhighlightAll();
activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign } else {
if (activeItems[0]) {
// add enter keyCode value
activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign
}
this._handleChoiceAction(activeItems, highlightedChoice);
} }
this._handleChoiceAction(activeItems, highlightedChoice);
} }
event.preventDefault(); event.preventDefault();

View file

@ -37,6 +37,7 @@ export const DEFAULT_CONFIG: Options = {
silent: false, silent: false,
renderChoiceLimit: -1, renderChoiceLimit: -1,
maxItemCount: -1, maxItemCount: -1,
addChoices: false,
addItems: true, addItems: true,
addItemFilter: null, addItemFilter: null,
removeItems: true, removeItems: true,

View file

@ -102,6 +102,15 @@ export interface Options {
*/ */
maxItemCount: number; maxItemCount: number;
/**
* Whether a user can add choices dynamically.
*
* **Input types affected:** select-one, select-multiple
*
* @default false
*/
addChoices: boolean;
/** /**
* Whether a user can add items. * Whether a user can add items.
* *