From 7cf89390d9bafdc0b3e3c4e14cdfee36086ee9bb Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Thu, 7 May 2026 13:43:04 -0700 Subject: [PATCH 1/2] optimize dropdown search --- .../src/utils/dropdownSearch.ts | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/components/dash-core-components/src/utils/dropdownSearch.ts b/components/dash-core-components/src/utils/dropdownSearch.ts index 7b88014a4f..079bba0290 100644 --- a/components/dash-core-components/src/utils/dropdownSearch.ts +++ b/components/dash-core-components/src/utils/dropdownSearch.ts @@ -23,6 +23,7 @@ export interface SanitizedOptions { options: DetailedOption[]; indexes: string[]; valueSet: Set; + search: Search; } // Single-pass sanitization via sanitizeOptions, plus detection of @@ -55,38 +56,48 @@ export function sanitizeDropdownOptions( indexes.push('search'); } - return {options: sanitized, indexes, valueSet}; + // Build the search index ONCE during sanitization + const search = new Search('value'); + search.searchIndex = new UnorderedSearchIndex(); + search.indexStrategy = new AllSubstringsIndexStrategy(); + search.tokenizer = TOKENIZER; + + indexes.forEach(index => { + search.addIndex(index); + }); + + if (sanitized.length > 0) { + search.addDocuments(sanitized); + } + + return { + options: sanitized, + indexes, + valueSet, + search, + }; } export function filterOptions( options: SanitizedOptions, searchValue?: string, - search_order?: 'index' | 'original' + searchOrder?: string ): DetailedOption[] { if (!searchValue) { return options.options; } - const search = new Search('value'); - search.searchIndex = new UnorderedSearchIndex(); - search.indexStrategy = new AllSubstringsIndexStrategy(); - search.tokenizer = TOKENIZER; - - options.indexes.forEach(index => { - search.addIndex(index); - }); + const results = + (options.search.search(searchValue) as DetailedOption[]) || []; - if (options.options.length > 0) { - search.addDocuments(options.options); - } + // Preserve original option order + if (searchOrder === 'original') { + const resultSet = new Set(results.map(option => option.value)); - const searchResults = - (search.search(searchValue) as DetailedOption[]) || []; - - if (search_order === 'original') { - const resultSet = new Set(searchResults); - return options.options.filter(option => resultSet.has(option)); + return options.options.filter(option => + resultSet.has(option.value) + ); } - return searchResults; + return results; } From 99a1a2ae37c8e87c0f1aa6189a334f938e5d9fde Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Thu, 7 May 2026 15:13:46 -0700 Subject: [PATCH 2/2] added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bfcf40c7e..405eee0f49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#3723](https://github.com/plotly/dash/pull/3723) Fix misaligned `dcc.Slider` marks when some labels are empty strings - [#3740](https://github.com/plotly/dash/pull/3740) Fix cannot tab into dropdowns in Safari - [#2462](https://github.com/plotly/dash/issues/2462) Allow `MATCH` in `Input`/`State` when the callback's `Output` has no wildcards (fixed-id Output, no Output, or `ALL`-only wildcard Output). `ALLSMALLER` still requires a corresponding `MATCH` in an Output. +- [#3768](https://github.com/plotly/dash/pull/3768) Improved `Dropdown` search performance for large options lists ## [4.1.0] - 2026-03-23