Skip to content

Commit b40c7fc

Browse files
Allow selected options to be hidden when searching
1 parent cddbe21 commit b40c7fc

File tree

8 files changed

+178
-1
lines changed

8 files changed

+178
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Features
6+
- Add `searchRenderSelectedChoices` configuration option to control whether selected choices appear in search results for select-multiple inputs. Defaults to `'auto'` (backward compatible behavior). Set to `false` to hide selected choices from search results.
7+
38
## [11.2.0]
49

510
### Features

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ import "choices.js/public/assets/styles/choices.css";
176176
prependValue: null,
177177
appendValue: null,
178178
renderSelectedChoices: 'auto',
179+
searchRenderSelectedChoices: 'auto',
179180
loadingText: 'Loading...',
180181
noResultsText: 'No results found',
181182
noChoicesText: 'No choices to choose from',
@@ -662,6 +663,23 @@ For backward compatibility, `<option value="">This is a placeholder</option>` an
662663
663664
**Usage:** Whether selected choices should be removed from the list. By default choices are removed when they are selected in multiple select box. To always render choices pass `always`.
664665
666+
### searchRenderSelectedChoices
667+
668+
**Type:** `'auto' | 'always' | Boolean` **Default:** `'auto'`
669+
670+
**Input types affected:** `select-multiple`
671+
672+
**Usage:** Whether selected choices should be removed from the list during search. By default (`'auto'`), selected choices appear in search results if they match the search query. Set to `false` to hide selected choices from search results, or `'always'` to always show them in search results.
673+
674+
**Example:**
675+
676+
```js
677+
// Hide selected choices from search results
678+
const example = new Choices(element, {
679+
searchRenderSelectedChoices: false,
680+
});
681+
```
682+
665683
### loadingText
666684
667685
**Type:** `String` **Default:** `Loading...`

public/test/select-multiple/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,29 @@ <h2>Select multiple inputs</h2>
500500
</script>
501501
</div>
502502

503+
<div data-test-hook="search-hide-selected">
504+
<label for="choices-search-hide-selected">Search hide selected</label>
505+
<select
506+
class="form-control"
507+
name="choices-search-hide-selected"
508+
id="choices-search-hide-selected"
509+
multiple
510+
>
511+
<option value="Choice 1">Choice 1</option>
512+
<option value="Choice 2">Choice 2</option>
513+
<option value="Choice 3">Choice 3</option>
514+
<option value="Choice 4">Choice 4</option>
515+
</select>
516+
<script>
517+
document.addEventListener('DOMContentLoaded', function() {
518+
new Choices('#choices-search-hide-selected', {
519+
allowHTML: false,
520+
searchRenderSelectedChoices: false,
521+
});
522+
});
523+
</script>
524+
</div>
525+
503526
<div data-test-hook="placeholder-via-option-value">
504527
<label for="choices-placeholder-via-option-value"
505528
>Placeholder via empty option value</label

src/scripts/choices.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ class Choices {
219219
config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne;
220220
}
221221

222+
if (typeof config.searchRenderSelectedChoices !== 'boolean') {
223+
config.searchRenderSelectedChoices = true;
224+
}
225+
222226
if (config.closeDropdownOnSelect === 'auto') {
223227
config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect;
224228
} else {
@@ -959,7 +963,10 @@ class Choices {
959963
const renderableChoices = (choices: ChoiceFull[]): ChoiceFull[] =>
960964
choices.filter(
961965
(choice) =>
962-
!choice.placeholder && (isSearching ? !!choice.rank : config.renderSelectedChoices || !choice.selected),
966+
!choice.placeholder &&
967+
(isSearching
968+
? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank
969+
: config.renderSelectedChoices || !choice.selected),
963970
);
964971

965972
const showLabel = config.appendGroupInSearch && isSearching;

src/scripts/defaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const DEFAULT_CONFIG: Options = {
7373
prependValue: null,
7474
appendValue: null,
7575
renderSelectedChoices: 'auto',
76+
searchRenderSelectedChoices: 'auto',
7677
loadingText: 'Loading...',
7778
noResultsText: 'No results found',
7879
noChoicesText: 'No choices to choose from',

src/scripts/interfaces/options.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,15 @@ export interface Options {
471471
*/
472472
renderSelectedChoices: 'auto' | 'always' | boolean;
473473

474+
/**
475+
* Whether selected choices should be removed from the list during search. By default selected choices appear in search results if they match the search query. Set to `false` to hide selected choices from search results.
476+
*
477+
* **Input types affected:** select-multiple
478+
*
479+
* @default 'auto';
480+
*/
481+
searchRenderSelectedChoices: 'auto' | 'always' | boolean;
482+
474483
/**
475484
* The text that is shown whilst choices are being populated via AJAX.
476485
*

test-e2e/tests/select-multiple.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,47 @@ describe(`Choices - select multiple`, () => {
651651
});
652652
});
653653

654+
describe('search hide selected', () => {
655+
const testId = 'search-hide-selected';
656+
657+
describe('selecting choices and searching', () => {
658+
test('selected choices do not appear in search results', async ({ page, bundle }) => {
659+
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
660+
await suite.startWithClick();
661+
662+
await suite.getChoiceWithText('Choice 1').click();
663+
await suite.expectedItemCount(1);
664+
665+
await suite.typeText('Choice');
666+
await suite.expectVisibleDropdown();
667+
668+
await expect(suite.choices.filter({ hasText: 'Choice 1' })).toHaveCount(0);
669+
670+
await expect(suite.choices.filter({ hasText: 'Choice 2' })).toHaveCount(1);
671+
await expect(suite.choices.filter({ hasText: 'Choice 3' })).toHaveCount(1);
672+
await expect(suite.choices.filter({ hasText: 'Choice 4' })).toHaveCount(1);
673+
});
674+
675+
test('selecting multiple choices hides all from search', async ({ page, bundle }) => {
676+
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
677+
await suite.startWithClick();
678+
679+
await suite.getChoiceWithText('Choice 1').click();
680+
await suite.getChoiceWithText('Choice 2').click();
681+
await suite.expectedItemCount(2);
682+
683+
await suite.typeText('Choice');
684+
await suite.expectVisibleDropdown();
685+
686+
await expect(suite.choices.filter({ hasText: 'Choice 1' })).toHaveCount(0);
687+
await expect(suite.choices.filter({ hasText: 'Choice 2' })).toHaveCount(0);
688+
689+
await expect(suite.choices.filter({ hasText: 'Choice 3' })).toHaveCount(1);
690+
await expect(suite.choices.filter({ hasText: 'Choice 4' })).toHaveCount(1);
691+
});
692+
});
693+
});
694+
654695
[
655696
{
656697
name: 'empty option value',

test/scripts/choices.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe('choices', () => {
4747
searchEnabled: false,
4848
closeDropdownOnSelect: true,
4949
renderSelectedChoices: false,
50+
searchRenderSelectedChoices: true,
5051
});
5152
});
5253
});
@@ -68,6 +69,7 @@ describe('choices', () => {
6869
searchEnabled: false,
6970
closeDropdownOnSelect: true,
7071
renderSelectedChoices: false,
72+
searchRenderSelectedChoices: true,
7173
...config,
7274
});
7375
});
@@ -115,6 +117,77 @@ describe('choices', () => {
115117
expect(instance.config.renderSelectedChoices).to.equal(false);
116118
});
117119
});
120+
121+
describe('passing the searchRenderSelectedChoices config option with an unexpected value', () => {
122+
it('sets searchRenderSelectedChoices to true for select-multiple', () => {
123+
document.body.innerHTML = `
124+
<select data-choice multiple></select>
125+
`;
126+
127+
instance = new Choices('[data-choice]', {
128+
allowHTML: true,
129+
searchRenderSelectedChoices: 'test' as any,
130+
});
131+
132+
expect(instance.config.searchRenderSelectedChoices).to.equal(true);
133+
});
134+
135+
it('sets searchRenderSelectedChoices to true for select-one', () => {
136+
document.body.innerHTML = `
137+
<select data-choice></select>
138+
`;
139+
140+
instance = new Choices('[data-choice]', {
141+
allowHTML: true,
142+
searchRenderSelectedChoices: 'test' as any,
143+
});
144+
145+
expect(instance.config.searchRenderSelectedChoices).to.equal(true);
146+
});
147+
148+
it('sets searchRenderSelectedChoices to true for select-multiple', () => {
149+
document.body.innerHTML = `
150+
<select data-choice multiple></select>
151+
`;
152+
153+
instance = new Choices('[data-choice]', {
154+
allowHTML: true,
155+
searchRenderSelectedChoices: 'test' as any,
156+
});
157+
158+
expect(instance.config.searchRenderSelectedChoices).to.equal(true);
159+
});
160+
});
161+
162+
describe('passing the searchRenderSelectedChoices config option with "always"', () => {
163+
it('sets searchRenderSelectedChoices to true', () => {
164+
document.body.innerHTML = `
165+
<select data-choice multiple></select>
166+
`;
167+
168+
instance = new Choices('[data-choice]', {
169+
allowHTML: true,
170+
searchRenderSelectedChoices: 'always',
171+
});
172+
173+
expect(instance.config.searchRenderSelectedChoices).to.equal(true);
174+
});
175+
});
176+
177+
describe('passing the searchRenderSelectedChoices config option with false', () => {
178+
it('keeps searchRenderSelectedChoices as false', () => {
179+
document.body.innerHTML = `
180+
<select data-choice multiple></select>
181+
`;
182+
183+
instance = new Choices('[data-choice]', {
184+
allowHTML: true,
185+
searchRenderSelectedChoices: false,
186+
});
187+
188+
expect(instance.config.searchRenderSelectedChoices).to.equal(false);
189+
});
190+
});
118191
});
119192
});
120193

0 commit comments

Comments
 (0)