-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Prioritize prefix matches when searching in select/multiselect dropdown #1904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Co-authored-by: NickCrews <[email protected]>
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR enhances the search functionality in select/multiselect dropdowns by prioritizing exact matches and prefix matches in the search results ordering, addressing issue #1903.
- Implements a sorting algorithm that prioritizes exact matches first, then prefix matches, before other substring matches
- Replaces the simple filter-only approach with a filter-then-sort strategy for better user experience
- Maintains original ordering for items with the same match priority level
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| const filtered = options.filter((v) => v.label.toLowerCase().includes(searchLower)); | ||
|
|
||
| // Sort to prioritize exact matches and prefix matches | ||
| return filtered.sort((a, b) => { | ||
| const aLabelLower = a.label.toLowerCase(); | ||
| const bLabelLower = b.label.toLowerCase(); | ||
|
|
||
| // Check for exact matches first | ||
| if (aLabelLower === searchLower && bLabelLower !== searchLower) return -1; | ||
| if (bLabelLower === searchLower && aLabelLower !== searchLower) return 1; | ||
|
|
||
| // Check for prefix matches | ||
| const aStartsWith = aLabelLower.startsWith(searchLower); | ||
| const bStartsWith = bLabelLower.startsWith(searchLower); | ||
|
|
||
| if (aStartsWith && !bStartsWith) return -1; | ||
| if (bStartsWith && !aStartsWith) return 1; | ||
|
|
||
| // If both start with search or neither does, maintain original order | ||
| return 0; | ||
| }); |
Copilot
AI
Oct 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting each option's label to lowercase multiple times is inefficient. Consider preprocessing the lowercase labels once before filtering and sorting to avoid repeated string operations.
| const filtered = options.filter((v) => v.label.toLowerCase().includes(searchLower)); | |
| // Sort to prioritize exact matches and prefix matches | |
| return filtered.sort((a, b) => { | |
| const aLabelLower = a.label.toLowerCase(); | |
| const bLabelLower = b.label.toLowerCase(); | |
| // Check for exact matches first | |
| if (aLabelLower === searchLower && bLabelLower !== searchLower) return -1; | |
| if (bLabelLower === searchLower && aLabelLower !== searchLower) return 1; | |
| // Check for prefix matches | |
| const aStartsWith = aLabelLower.startsWith(searchLower); | |
| const bStartsWith = bLabelLower.startsWith(searchLower); | |
| if (aStartsWith && !bStartsWith) return -1; | |
| if (bStartsWith && !aStartsWith) return 1; | |
| // If both start with search or neither does, maintain original order | |
| return 0; | |
| }); | |
| // Preprocess options to include lowercase label | |
| const optionsWithLower = options.map((option) => ({ | |
| option, | |
| labelLower: option.label.toLowerCase(), | |
| })); | |
| const filtered = optionsWithLower.filter((v) => v.labelLower.includes(searchLower)); | |
| // Sort to prioritize exact matches and prefix matches | |
| filtered.sort((a, b) => { | |
| // Check for exact matches first | |
| if (a.labelLower === searchLower && b.labelLower !== searchLower) return -1; | |
| if (b.labelLower === searchLower && a.labelLower !== searchLower) return 1; | |
| // Check for prefix matches | |
| const aStartsWith = a.labelLower.startsWith(searchLower); | |
| const bStartsWith = b.labelLower.startsWith(searchLower); | |
| if (aStartsWith && !bStartsWith) return -1; | |
| if (bStartsWith && !aStartsWith) return 1; | |
| // If both start with search or neither does, maintain original order | |
| return 0; | |
| }); | |
| // Return the original option objects | |
| return filtered.map((v) => v.option); |
Fixes #1903
This was generated by copilot.
If you want I can add a test for this.
I also am just blindly trusting that this effects multiselect in addition to single select, but it might not. If not, let me know and I can fix it up.