Prioritize prefix matches when searching in select/multiselect dropdown#1904
Prioritize prefix matches when searching in select/multiselect dropdown#1904NickCrews wants to merge 2 commits intoteableio:developfrom
Conversation
Co-authored-by: NickCrews <10820686+NickCrews@users.noreply.github.com>
|
|
There was a problem hiding this comment.
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; | ||
| }); |
There was a problem hiding this comment.
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.