Autocomplete and filterable Select used to filter with a case-insensitive
"contains" check and render whatever survived in the order you passed it.
They now score each item and list the closest match first.
Nothing to change unless you pass your own @filter. The results are ordered
differently, which is the point.
The old default could only answer "does this item match?", never "how well?":
// before
function defaultFilter(itemValue: string, filterValue: string): boolean {
return itemValue.toLowerCase().includes(filterValue.toLowerCase());
}
A predicate filters but cannot reorder, so an exact match sat wherever @items
happened to put it. Typing button into a list ordered alphabetically returned
ButtonGroup above Button — both "match", and source order decided the rest.
Typing butt:
| Before | After | |
|---|---|---|
| 1 | ButtonGroup | Button |
| 2 | Button Group | ButtonGroup |
| 3 | Button | Button Group |
Acronyms also match now, which "contains" could never do:
bg → ButtonGroupnz → New Zealandprog → ProgressBarNothing that matched before stops matching. The threshold is calibrated so
every result the old includes() filter returned is still returned; the change
is additive plus reordering.
@filter accepts a scorefilter?: (itemValue: string, inputValue: string) => boolean | number;
Return a number to rank — higher sorts first, 0 means no match. Return a
boolean to filter only, preserving the order of @items.
Existing boolean filters are unaffected:
{{! still works exactly as before, including source ordering }}
<Autocomplete @filter={{this.startsWith}} @items={{this.items}} />
startsWith = (itemValue: string, inputValue: string) =>
itemValue.toLowerCase().startsWith(inputValue.toLowerCase());
Pass the previous implementation explicitly:
const containsFilter = (itemValue: string, inputValue: string) =>
itemValue.toLowerCase().includes(inputValue.toLowerCase());
<Select @isFilterable={{true}} @filter={{containsFilter}} @items={{this.items}} />
Fuzzy matching is looser than "contains", so a threshold keeps out noise. It
cannot distinguish a useful abbreviation from a coincidence — btn → Button
and sa → Spain are the same shape, both scattered mid-word subsequences —
so the default favors precision and matches neither.
To trade precision for recall, build your own:
import { createFuzzyFilter } from 'frontile/utils/filter';
// matches btn -> Button, at the cost of also matching sa -> Spain
const looseFilter = createFuzzyFilter({ threshold: 0 });
<Autocomplete @filter={{looseFilter}} @items={{this.items}} />
DEFAULT_MATCH_THRESHOLD is 0.4. Raise it for stricter matching, lower it for
looser.