Skip to content

Commit

Permalink
Merge remote-tracking branch 'joeworkman-forks/addchoices' into main
Browse files Browse the repository at this point in the history
ref: #1117
  • Loading branch information
Xon committed Jul 23, 2024
2 parents 9359f5c + 61e48f8 commit a88f4f8
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 12 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Or include Choices directly:
choices: [],
renderChoiceLimit: -1,
maxItemCount: -1,
addChoices: false,
addItems: true,
addItemFilter: null,
removeItems: true,
Expand Down Expand Up @@ -304,6 +305,15 @@ Pass an array of objects:

**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

**Type:** `Boolean` **Default:** `true`
Expand Down
18 changes: 18 additions & 0 deletions cypress/e2e/select-multiple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,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);
});
});
});
});
});
});
21 changes: 21 additions & 0 deletions cypress/e2e/select-one.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1145,5 +1145,26 @@ describe('Choices - select one', () => {
.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);
});
});
});
});
});
11 changes: 11 additions & 0 deletions public/test/select-multiple/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,15 @@ <h2>Select multiple inputs</h2>
multiple
></select>
</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>
<script>
Expand Down Expand Up @@ -643,6 +652,8 @@ <h2>Select multiple inputs</h2>
},
],
});

new Choices('#choices-add-choices', { addChoices: true });
});
</script>
</body>
Expand Down
12 changes: 12 additions & 0 deletions public/test/select-one/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ <h2>Select one inputs</h2>
<button class="destroy">Destroy</button>
<button class="init">Init</button>
</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>
<script>
Expand Down Expand Up @@ -676,6 +686,8 @@ <h2>Select one inputs</h2>
document.querySelector('button.init').addEventListener('click', () => {
newDestroyInitChoices.init();
});

new Choices('#choices-add-choices', { addChoices: true });
});
</script>
</body>
Expand Down
1 change: 1 addition & 0 deletions public/types/src/scripts/choices.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ declare class Choices implements Choices {
_stopLoading(): void;
_handleLoadingState(setLoading?: boolean): void;
_handleSearch(value: string): void;
_canAddChoice(activeItems: Item[], value: string): Notice;
_canAddItem(activeItems: Item[], value: string): Notice;
_searchChoices(value: string): number;
_addEventListeners(): void;
Expand Down
8 changes: 8 additions & 0 deletions public/types/src/scripts/interfaces/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ export interface Options {
* @default false
*/
pseudoMultiSelectForSingle: boolean;
/**
* Whether a user can add choices dynamically.
*
* **Input types affected:** select-one, select-multiple
*
* @default false
*/
addChoices: boolean;
/**
* Whether a user can add items.
*
Expand Down
45 changes: 33 additions & 12 deletions src/scripts/choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,12 +827,12 @@ class Choices implements Choices {
);
}

// If we have choices to show
const { activeItems } = this._store; // If we have choices to show

if (
choiceListFragment.childNodes &&
choiceListFragment.childNodes.length > 0
) {
const { activeItems } = this._store;
const canAddItem = this._canAddItem(activeItems, this.input.value);

// ...and we can select them
Expand All @@ -846,18 +846,21 @@ class Choices implements Choices {
}
} else {
// Otherwise show a notice
const canAddChoice = this._canAddChoice(activeItems, this.input.value);

let dropdownItem;
let notice;

if (this._isSearching) {
notice =
if (canAddChoice.response) {
dropdownItem = this._getTemplate('notice', canAddChoice.notice);
} else if (this._isSearching) {
const notice =
typeof this.config.noResultsText === 'function'
? this.config.noResultsText()
: this.config.noResultsText;

dropdownItem = this._getTemplate('notice', notice, 'no-results');
} else {
notice =
const notice =
typeof this.config.noChoicesText === 'function'
? this.config.noChoicesText()
: this.config.noChoicesText;
Expand Down Expand Up @@ -1290,6 +1293,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 {
let canAddItem = true;
let notice =
Expand Down Expand Up @@ -1617,16 +1628,22 @@ class Choices implements Choices {
const { ENTER_KEY: enterKey } = KEY_CODES;
const targetWasButton =
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 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._addItem({ value });
this._triggerChange(value);
this.clearInput();
addedItem = true;
}
}

Expand All @@ -1641,11 +1658,15 @@ class Choices implements Choices {
);

if (highlightedChoice) {
// add enter keyCode value
if (activeItems[0]) {
activeItems[0].keyCode = enterKey; // eslint-disable-line no-param-reassign
if (addedItem) {
this.unhighlightAll();
} 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();
Expand Down
1 change: 1 addition & 0 deletions src/scripts/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const DEFAULT_CONFIG: Options = {
renderChoiceLimit: -1,
maxItemCount: -1,
pseudoMultiSelectForSingle: false,
addChoices: false,
addItems: true,
addItemFilter: null,
removeItems: true,
Expand Down
9 changes: 9 additions & 0 deletions src/scripts/interfaces/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ export interface Options {
*/
pseudoMultiSelectForSingle: boolean

/**
* Whether a user can add choices dynamically.
*
* **Input types affected:** select-one, select-multiple
*
* @default false
*/
addChoices: boolean;

/**
* Whether a user can add items.
*
Expand Down

0 comments on commit a88f4f8

Please sign in to comment.