From 910ad894433443821bbe66d19acdba71629e7ed0 Mon Sep 17 00:00:00 2001 From: Adam Firestone Date: Mon, 17 Aug 2026 16:48:08 -0500 Subject: [PATCH] fix(web): font picker highlights and Enter-selects the first match after typing Typing in an Appearance font picker did not visibly highlight the first match, arrows misbehaved, and Enter did nothing, so the only way to apply a font was to click it. LegendList only re-renders a row when its item or extraData changes, so a font that stayed visible while the filter shifted its position kept a stale index. Base UI's combobox highlights and Enter-selects by index, so it targeted the wrong or an unmounted row. Pass the full collection as items and the filtered list as filteredItems, and give LegendList the filtered list as extraData so every filter change re-renders the visible rows with fresh indices. Base UI's own highlight, arrow navigation, hover, and Enter path then work as-is. Claude Fable 5 via Claude Code (initial implementation by GPT-5.6 Sol via Codex CLI) --- .../components/settings/FontFamilyPicker.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/settings/FontFamilyPicker.tsx b/apps/web/src/components/settings/FontFamilyPicker.tsx index 9bdccf0167ca..303cdfc30f68 100644 --- a/apps/web/src/components/settings/FontFamilyPicker.tsx +++ b/apps/web/src/components/settings/FontFamilyPicker.tsx @@ -190,7 +190,14 @@ export function FontFamilyPicker({ return candidate; }, [families, query, requireMonospace]); - const items = useMemo(() => { + const collectionItems = useMemo(() => { + const result = [DEFAULT_FONT_VALUE]; + if (manualFamily !== null) result.push(manualFamily); + result.push(...families); + return result; + }, [families, manualFamily]); + + const filteredItems = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); const result: string[] = []; if (normalizedQuery.length === 0) result.push(DEFAULT_FONT_VALUE); @@ -239,8 +246,8 @@ export function FontFamilyPicker({ return ( ref={listRef} - data={items} + data={filteredItems} + // LegendList only re-renders a row when its item or extraData + // changes, so a font that stays visible while the filter shifts + // its position would keep a stale `index`. Base UI highlights and + // Enter-selects by index, so every filter change must re-render. + extraData={filteredItems} keyExtractor={(item) => item} renderItem={({ item, index }) => renderItem(item, index)} estimatedItemSize={30} drawDistance={360} - style={{ height: Math.min(items.length * 30, 288) }} + style={{ height: Math.min(filteredItems.length * 30, 288) }} />