fuzzy target search

This commit is contained in:
Ben Allfree
2026-04-10 16:34:58 -07:00
parent 33dbffeb8f
commit ff67199e82
2 changed files with 21 additions and 6 deletions
+20 -6
View File
@@ -16,16 +16,29 @@ type ComboboxFieldProps = {
layout?: 'stacked' | 'inline'
/** When set and `value` is non-empty, first row clears selection (`onChange('')`). */
clearSelectionLabel?: string
/** Match by letters/digits only (case-insensitive); ignores spaces and punctuation in query and options. */
filterNormalize?: boolean
}
function normalizeForFilter(s: string): string {
return s.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '')
}
function buildRows(
options: readonly string[],
filter: string,
clearSelectionLabel: string | undefined,
value: string
value: string,
filterNormalize: boolean
): Row[] {
const q = filter.trim().toLowerCase()
const filtered = !q ? [...options] : options.filter(o => o.toLowerCase().includes(q))
let filtered: readonly string[]
if (filterNormalize) {
const nq = normalizeForFilter(filter)
filtered = !nq ? [...options] : options.filter(o => normalizeForFilter(o).includes(nq))
} else {
const q = filter.trim().toLowerCase()
filtered = !q ? [...options] : options.filter(o => o.toLowerCase().includes(q))
}
const r: Row[] = []
if (clearSelectionLabel && value) r.push({ kind: 'clear' })
for (const o of filtered) r.push({ kind: 'opt', value: o })
@@ -46,6 +59,7 @@ export function ComboboxField({
placeholder = 'Choose…',
layout = 'stacked',
clearSelectionLabel,
filterNormalize = false,
}: ComboboxFieldProps) {
const rid = useId().replace(/:/g, '')
const triggerId = id ?? `cb-${rid}`
@@ -57,14 +71,14 @@ export function ComboboxField({
const listRef = useRef<HTMLUListElement>(null)
const rows = useMemo(
() => buildRows(options, filter, clearSelectionLabel, value),
[options, filter, clearSelectionLabel, value]
() => buildRows(options, filter, clearSelectionLabel, value, filterNormalize),
[options, filter, clearSelectionLabel, value, filterNormalize]
)
const handleOpenChange = (next: boolean) => {
if (next) {
setFilter('')
const initial = buildRows(options, '', clearSelectionLabel, value)
const initial = buildRows(options, '', clearSelectionLabel, value, filterNormalize)
const idx = initial.findIndex(row => row.kind === 'opt' && row.value === value)
setHighlighted(idx >= 0 ? idx : 0)
} else {
+1
View File
@@ -581,6 +581,7 @@ export default function RepoPage() {
id="mesh-forge-target"
options={envNames}
value={envDraft}
filterNormalize
placeholder="--target--"
clearSelectionLabel="Clear target"
onChange={v => {