feat(core-ui): Allow searchMatcher to return a score for result ordering#108719
feat(core-ui): Allow searchMatcher to return a score for result ordering#108719
Conversation
Allow callers to provide a custom match function via the new searchMatcher prop. When provided, it replaces the built-in case-insensitive substring matching so callers can implement arbitrary logic (e.g. suffix matching, fuzzy search, matching on non-label fields). The function receives the full option object and the current search string, returning true when the option should be visible. Co-Authored-By: Claude <noreply@anthropic.com>
| // Return the values of options that were removed. | ||
| return hiddenOptionsSet; |
There was a problem hiding this comment.
wildest API decision I've ever seen...
Add tests verifying that: - clearing the search input restores the full option list - closing and reopening the menu resets the search query and shows all options Both ListBox and GridList variants are covered. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Extend the searchMatcher prop so it can return a SearchMatchResult object instead of a plain boolean. The result's score field is used to sort matching options — higher scores appear first. Returning a SearchMatchResult always means the option is a match. Returning false still hides the option. Returning true shows it with no ordering influence. Sorting is applied within sections for sectioned lists, and globally for flat lists. Options without a score maintain their original relative order. Co-Authored-By: Claude <noreply@anthropic.com>
Remove the boolean return path from searchMatcher. The function must now
always return a SearchMatchResult. A score > 0 means the option matches;
score <= 0 hides it. Higher scores sort first.
The default implementation returns {score: 1} for a substring match and
{score: 0} for no match, preserving the existing behaviour. Sorting is
only triggered when a custom searchMatcher is provided, so the default
path pays no sorting cost.
Co-Authored-By: Claude <noreply@anthropic.com>
…rn type
getHiddenOptions was changed to return {hidden, scores} instead of a
plain Set, but two callers in searchQueryBuilder and tokenizedInput
were not updated, causing TypeScript errors.
Co-Authored-By: Claude <noreply@anthropic.com>
0d7131d to
7474bcf
Compare
…nt options visible When both sizeLimit and a custom searchMatcher with varying scores are active, the size limit was applied based on original item order before score sorting. Lower-scored items appearing early in the list would stay visible while higher-scored items beyond the limit were hidden, defeating the purpose of score-based ranking. Sort remaining items by score before applying the size limit so the most relevant results are always the ones kept visible. Co-Authored-By: Claude <noreply@anthropic.com>
natemoo-re
left a comment
There was a problem hiding this comment.
Some small nits but overall LGTM
| items: Array<SelectOptionOrSectionWithKey<Value>>, | ||
| search: string, | ||
| limit = Infinity, | ||
| searchMatcher?: (option: SelectOptionWithKey<Value>, search: string) => boolean | ||
| ): Set<SelectKey> { | ||
| searchMatcher?: ( | ||
| option: SelectOptionWithKey<Value>, | ||
| search: string | ||
| ) => SearchMatchResult |
There was a problem hiding this comment.
While we're refactoring this, can we move to a single options object or (items, { search, limit, searchMatcher })? More than two params starts to get extremely messy
There was a problem hiding this comment.
Let me do this in a followup because it will require a lot of instance changes
| * matches; options with higher scores are sorted first. If not provided, defaults to | ||
| * case-insensitive substring matching on `textValue` or `label` with a static score |
There was a problem hiding this comment.
Took me a few times reading this to realize that "If not provided..." refers to the entire matcher and not the scores. Might want to add a newline or two above that point.
| * The result of a custom `searchMatcher` function. Returning this (instead of a plain | ||
| * boolean) allows callers to influence how matching options are sorted: options with a | ||
| * higher `score` are shown first. To hide an option, return `false` instead. | ||
| */ |
There was a problem hiding this comment.
Still references boolean format. Also doesn't seem relevant to the type as much as the actual searchMatcher implementation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
…ing (#108719) Stacked on #108714. Extends the `searchMatcher` prop introduced in the base PR so it can optionally return a `SearchMatchResult` object in addition to a plain `boolean`. ```ts interface SearchMatchResult { score: number; } ``` When a matcher returns `SearchMatchResult`, the option is shown and its `score` is used to sort matching options — higher scores appear first. Returning `false` still hides the option. Returning `true` (the existing behaviour) shows it with no ordering influence, so the change is fully additive. --- **Update**: `searchMatcher` now always returns `SearchMatchResult` — the `boolean` return path is removed. A score > 0 means the option matches; score <= 0 hides it. The default implementation returns `{score: 1}` for a substring match and `{score: 0}` otherwise. Sorting is only triggered when a custom `searchMatcher` is provided, so the default path pays no extra cost. Sorting is scoped: within each section for sectioned lists, and globally for flat lists. Options with equal scores maintain their original relative order (stable sort). The `SearchMatchResult` type and the new `getSortedItems` utility are exported from the package for callers building custom composite selects. --------- Co-authored-by: Claude <noreply@anthropic.com>
…ing (#108719) Stacked on #108714. Extends the `searchMatcher` prop introduced in the base PR so it can optionally return a `SearchMatchResult` object in addition to a plain `boolean`. ```ts interface SearchMatchResult { score: number; } ``` When a matcher returns `SearchMatchResult`, the option is shown and its `score` is used to sort matching options — higher scores appear first. Returning `false` still hides the option. Returning `true` (the existing behaviour) shows it with no ordering influence, so the change is fully additive. --- **Update**: `searchMatcher` now always returns `SearchMatchResult` — the `boolean` return path is removed. A score > 0 means the option matches; score <= 0 hides it. The default implementation returns `{score: 1}` for a substring match and `{score: 0}` otherwise. Sorting is only triggered when a custom `searchMatcher` is provided, so the default path pays no extra cost. Sorting is scoped: within each section for sectioned lists, and globally for flat lists. Options with equal scores maintain their original relative order (stable sort). The `SearchMatchResult` type and the new `getSortedItems` utility are exported from the package for callers building custom composite selects. --------- Co-authored-by: Claude <noreply@anthropic.com>
Stacked on #108714.
Extends the
searchMatcherprop introduced in the base PR so it can optionally return aSearchMatchResultobject in addition to a plainboolean.When a matcher returns
SearchMatchResult, the option is shown and itsscoreis used to sort matching options — higher scores appear first. Returningfalsestill hides the option. Returningtrue(the existing behaviour) shows it with no ordering influence, so the change is fully additive.Update:
searchMatchernow always returnsSearchMatchResult— thebooleanreturn path is removed. A score > 0 means the option matches; score <= 0 hides it. The default implementation returns{score: 1}for a substring match and{score: 0}otherwise. Sorting is only triggered when a customsearchMatcheris provided, so the default path pays no extra cost.Sorting is scoped: within each section for sectioned lists, and globally for flat lists. Options with equal scores maintain their original relative order (stable sort).
The
SearchMatchResulttype and the newgetSortedItemsutility are exported from the package for callers building custom composite selects.