Skip to content

feat(core-ui): Allow searchMatcher to return a score for result ordering#108719

Merged
JonasBa merged 11 commits intomasterfrom
jb/compactselect/search-result
Feb 23, 2026
Merged

feat(core-ui): Allow searchMatcher to return a score for result ordering#108719
JonasBa merged 11 commits intomasterfrom
jb/compactselect/search-result

Conversation

@JonasBa
Copy link
Member

@JonasBa JonasBa commented Feb 20, 2026

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.

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.

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>
@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Feb 20, 2026
@JonasBa JonasBa marked this pull request as ready for review February 20, 2026 19:20
@JonasBa JonasBa requested a review from a team as a code owner February 20, 2026 19:20
Comment on lines -189 to -190
// Return the values of options that were removed.
return hiddenOptionsSet;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wildest API decision I've ever seen...

JonasBa and others added 7 commits February 20, 2026 12:04
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>
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>
…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>
Copy link
Member

@natemoo-re natemoo-re left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small nits but overall LGTM

Comment on lines 112 to +135
items: Array<SelectOptionOrSectionWithKey<Value>>,
search: string,
limit = Infinity,
searchMatcher?: (option: SelectOptionWithKey<Value>, search: string) => boolean
): Set<SelectKey> {
searchMatcher?: (
option: SelectOptionWithKey<Value>,
search: string
) => SearchMatchResult
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me do this in a followup because it will require a lot of instance changes

Comment on lines +192 to +193
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +42 to +45
* 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.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still references boolean format. Also doesn't seem relevant to the type as much as the actual searchMatcher implementation

Base automatically changed from jb/compactselect/search-fn to master February 23, 2026 20:58
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@JonasBa JonasBa enabled auto-merge (squash) February 23, 2026 21:12
@JonasBa JonasBa merged commit d00d279 into master Feb 23, 2026
63 checks passed
@JonasBa JonasBa deleted the jb/compactselect/search-result branch February 23, 2026 21:15
mchen-sentry pushed a commit that referenced this pull request Feb 24, 2026
…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>
wedamija pushed a commit that referenced this pull request Feb 24, 2026
…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>
@github-actions github-actions bot locked and limited conversation to collaborators Mar 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants