Merge pull request #1358 from samuelhwilliams/search-render-selected-choices

Allow selected options to be hidden when searching
This commit is contained in:
Xon 2026-01-01 02:42:08 +08:00 committed by GitHub
commit 51f77558a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 118 additions and 1 deletions

View file

@ -1,5 +1,10 @@
# Changelog
## [Unreleased]
### Features
- Add `searchRenderSelectedChoices` configuration option to control whether selected choices appear in search results for select-multiple inputs. Defaults to `true` (backward compatible behavior). Set to `false` to hide selected choices from search results.
## [11.2.0]
### Features

View file

@ -177,6 +177,7 @@ import "choices.js/public/assets/styles/choices.css";
prependValue: null,
appendValue: null,
renderSelectedChoices: 'auto',
searchRenderSelectedChoices: true,
loadingText: 'Loading...',
noResultsText: 'No results found',
noChoicesText: 'No choices to choose from',
@ -671,6 +672,23 @@ For backward compatibility, `<option value="">This is a placeholder</option>` an
**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`.
### searchRenderSelectedChoices
**Type:** `Boolean` **Default:** `true'`
**Input types affected:** `select-multiple`
**Usage:** Whether selected choices should be removed from the list during search.
**Example:**
```js
// Hide selected choices from search results
const example = new Choices(element, {
searchRenderSelectedChoices: false,
});
```
### loadingText
**Type:** `String` **Default:** `Loading...`

View file

@ -500,6 +500,29 @@
</script>
</div>
<div data-test-hook="search-hide-selected">
<label for="choices-search-hide-selected">Search hide selected</label>
<select
class="form-control"
name="choices-search-hide-selected"
id="choices-search-hide-selected"
multiple
>
<option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option>
<option value="Choice 4">Choice 4</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', function() {
new Choices('#choices-search-hide-selected', {
allowHTML: false,
searchRenderSelectedChoices: false,
});
});
</script>
</div>
<div data-test-hook="placeholder-via-option-value">
<label for="choices-placeholder-via-option-value"
>Placeholder via empty option value</label

View file

@ -970,7 +970,10 @@ class Choices {
const renderableChoices = (choices: ChoiceFull[]): ChoiceFull[] =>
choices.filter(
(choice) =>
!choice.placeholder && (isSearching ? !!choice.rank : config.renderSelectedChoices || !choice.selected),
!choice.placeholder &&
(isSearching
? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank
: config.renderSelectedChoices || !choice.selected),
);
const showLabel = config.appendGroupInSearch && isSearching;

View file

@ -74,6 +74,7 @@ export const DEFAULT_CONFIG: Options = {
prependValue: null,
appendValue: null,
renderSelectedChoices: 'auto',
searchRenderSelectedChoices: true,
loadingText: 'Loading...',
noResultsText: 'No results found',
noChoicesText: 'No choices to choose from',

View file

@ -480,6 +480,15 @@ export interface Options {
*/
renderSelectedChoices: 'auto' | 'always' | boolean;
/**
* Whether selected choices should be removed from the list during search.
*
* **Input types affected:** select-multiple
*
* @default false;
*/
searchRenderSelectedChoices: boolean;
/**
* The text that is shown whilst choices are being populated via AJAX.
*

View file

@ -651,6 +651,47 @@ describe(`Choices - select multiple`, () => {
});
});
describe('search hide selected', () => {
const testId = 'search-hide-selected';
describe('selecting choices and searching', () => {
test('selected choices do not appear in search results', async ({ page, bundle }) => {
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
await suite.startWithClick();
await suite.getChoiceWithText('Choice 1').click();
await suite.expectedItemCount(1);
await suite.typeText('Choice');
await suite.expectVisibleDropdown();
await expect(suite.choices.filter({ hasText: 'Choice 1' })).toHaveCount(0);
await expect(suite.choices.filter({ hasText: 'Choice 2' })).toHaveCount(1);
await expect(suite.choices.filter({ hasText: 'Choice 3' })).toHaveCount(1);
await expect(suite.choices.filter({ hasText: 'Choice 4' })).toHaveCount(1);
});
test('selecting multiple choices hides all from search', async ({ page, bundle }) => {
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
await suite.startWithClick();
await suite.getChoiceWithText('Choice 1').click();
await suite.getChoiceWithText('Choice 2').click();
await suite.expectedItemCount(2);
await suite.typeText('Choice');
await suite.expectVisibleDropdown();
await expect(suite.choices.filter({ hasText: 'Choice 1' })).toHaveCount(0);
await expect(suite.choices.filter({ hasText: 'Choice 2' })).toHaveCount(0);
await expect(suite.choices.filter({ hasText: 'Choice 3' })).toHaveCount(1);
await expect(suite.choices.filter({ hasText: 'Choice 4' })).toHaveCount(1);
});
});
});
[
{
name: 'empty option value',

View file

@ -47,6 +47,7 @@ describe('choices', () => {
searchEnabled: false,
closeDropdownOnSelect: true,
renderSelectedChoices: false,
searchRenderSelectedChoices: true,
});
});
});
@ -68,6 +69,7 @@ describe('choices', () => {
searchEnabled: false,
closeDropdownOnSelect: true,
renderSelectedChoices: false,
searchRenderSelectedChoices: true,
...config,
});
});
@ -115,6 +117,21 @@ describe('choices', () => {
expect(instance.config.renderSelectedChoices).to.equal(false);
});
});
describe('passing the searchRenderSelectedChoices config option with false', () => {
it('keeps searchRenderSelectedChoices as false', () => {
document.body.innerHTML = `
<select data-choice multiple></select>
`;
instance = new Choices('[data-choice]', {
allowHTML: true,
searchRenderSelectedChoices: false,
});
expect(instance.config.searchRenderSelectedChoices).to.equal(false);
});
});
});
});