@@ -13,6 +13,8 @@ import {
1313 useRef ,
1414 useState ,
1515} from 'react' ;
16+ import { resolvedOrEmpty } from '../utils/localized-strings' ;
17+ import { glossedSuggestionEntries } from '../utils/suggestion-engine' ;
1618import {
1719 useAnalysisLanguage ,
1820 useApproveAnalysisDispatch ,
@@ -27,8 +29,6 @@ import {
2729} from './AnalysisStore' ;
2830import { MorphemeBreakdownPopover , MorphemeGlossInput } from './MorphemeEditor' ;
2931import SuggestionDropdown from './SuggestionDropdown' ;
30- import { resolvedOrEmpty } from '../utils/localized-strings' ;
31- import { glossedSuggestionEntries } from '../utils/suggestion-engine' ;
3232
3333const STRING_KEYS = [
3434 '%interlinearizer_tokenChip_editMorphemes%' ,
@@ -174,58 +174,50 @@ export function TokenChip({
174174
175175 const hasMorphemes = morphemes . length > 0 ;
176176
177- // The pool entries to offer via the suggestion dropdown, with their accept/promote status. The
178- // engine flattens the resolved read into ranked, status-tagged rows: for a suggested token its
179- // pick (green "accept") plus candidates (blue "promote"); for an approved token the pool
180- // alternatives only (all blue "promote", the already-approved payload excluded). Blank-in-active-
181- // language entries are dropped individually rather than shown as empty rows (see
182- // `user-questions.md` display #3). Memoized on the (reference-stable) resolved read and active
183- // language so typing a gloss — which only changes local draft state — never re-runs the flatten/
184- // filter; it recomputes only when the resolved read or language actually changes.
177+ // Pool entries for the suggestion dropdown: for a suggested token, the top pick (green "accept")
178+ // plus candidates (blue "promote"); for an approved token, the pool alternatives only (the
179+ // already-approved payload excluded). Blank-in-active-language entries are dropped rather than
180+ // shown as empty rows. Memoized on the (reference-stable) resolved read and active language so
181+ // typing a gloss — which only changes local draft state — never re-runs the flatten/filter.
185182 const glossedRanked = useMemo (
186183 ( ) => glossedSuggestionEntries ( resolved , analysisLanguage ) ,
187184 [ resolved , analysisLanguage ] ,
188185 ) ;
189186 // Whether this token has anything to suggest: gated on the demo toggle (via the resolve short-
190187 // circuit) and editability. The chevron and dropdown only ever appear when this is true.
191188 const hasSuggestions = showSuggestions && ! disabled && glossedRanked . length > 0 ;
192- // The engine's top pick (row 0, the green "suggested"), shown as ghost placeholder text in the
193- // empty input so the row reveals at a glance which tokens already have a suggestion — without
194- // needing focus or hover. Only meaningful while the draft is empty; once the user types, the
195- // typed value replaces it. Undefined when this token has no suggestion to offer.
189+ // Top pick (row 0, the green "suggested") shown as ghost placeholder text so the row reveals
190+ // which tokens have a suggestion at a glance — without focus or hover. Once the user types, the
191+ // typed value replaces it.
196192 const suggestedGloss = hasSuggestions ? glossedRanked [ 0 ] . gloss : undefined ;
197193 const showSuggestedPlaceholder = suggestedGloss !== undefined && draft === '' ;
198194
199195 /**
200- * Builds the listbox option element id for a row, kept in sync with the input's
201- * `aria-activedescendant` so assistive tech can follow the keyboard-highlighted row.
196+ * Returns the listbox option id for `index`; kept in sync with `aria-activedescendant` so
197+ * assistive tech follows the keyboard-highlighted row.
202198 *
203199 * @param index - The row's index in {@link glossedRanked}.
204200 * @returns The option element id.
205201 */
206- const optionId = ( index : number ) => `${ listboxId } -opt-${ index } ` ;
202+ const optionId = useCallback ( ( index : number ) => `${ listboxId } -opt-${ index } ` , [ listboxId ] ) ;
207203
208204 /** Closes the suggestion dropdown and clears the keyboard highlight, leaving focus untouched. */
209205 const closeSuggestions = useCallback ( ( ) => {
210206 setSuggestionsOpen ( false ) ;
211207 setActiveIndex ( - 1 ) ;
212208 } , [ ] ) ;
213209
214- /**
215- * Commits the current draft gloss the same way blur does: only when it differs from the committed
216- * value. Only called from the (disabled-gated) blur and key-down handlers, so it needs no
217- * disabled check.
218- */
210+ /** Commits the draft gloss only when it differs from the committed value. */
219211 const commitDraft = ( ) => {
220212 if ( draft !== committedGloss ) {
221213 onGlossChange ( token . ref , token . surfaceText , draft ) ;
222214 }
223215 } ;
224216
225217 /**
226- * Approves the chosen suggestion payload for this token and closes the dropdown. The typed draft
227- * (if any) is discarded: approving updates committedGloss, which the sync effect mirrors back
228- * into the input, so the selection wins over the abandoned draft .
218+ * Approves the chosen suggestion payload for this token and closes the dropdown. Any typed draft
219+ * is discarded: the approval updates ` committedGloss` , which the sync effect mirrors back into
220+ * the input so the selection wins.
229221 *
230222 * @param id - The chosen payload's id (the suggested pick or a promoted candidate).
231223 */
@@ -236,12 +228,10 @@ export function TokenChip({
236228
237229 /**
238230 * Drives the gloss input as a combobox. While the dropdown is open, arrow keys move the highlight
239- * (stopping at the ends, with Up returning to the no-highlight state), Enter commits the
240- * highlight or the top row, and Escape closes without committing. While it is closed, ArrowDown
241- * opens the dropdown (the keyboard equivalent of the chevron) when this token has suggestions —
242- * so an approved token whose dropdown does not auto-open on focus can still summon its pool
243- * alternatives from the keyboard — and Enter commits the typed draft. Other keys fall through to
244- * the input.
231+ * (stopping at the ends; Up returns to the no-highlight state), Enter commits the highlighted row
232+ * or the top row, and Escape closes without committing. While closed, ArrowDown opens the
233+ * dropdown (keyboard equivalent of the chevron — lets an approved token summon pool alternatives
234+ * without focusing away) and Enter commits the typed draft.
245235 *
246236 * @param e - The gloss input's key-down event.
247237 */
0 commit comments