import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode, } from 'react'; import { useI18n } from '../i18n'; import type { Move } from './GridCell'; export interface LinkOption { value: string; label: string; } /** * The foreign-key editor: a search field in the cell, the rows the column may * point at in a panel under it. * * A custom element rather than a ` { touched.current = true; setSearch(e.target.value); setHighlight(0); }} onBlur={() => { // Nothing here commits on blur: a search is not a value, and every // way of picking one has already written it. if (!settled.current) onCancel(false); }} onKeyDown={(e) => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); e.stopPropagation(); touched.current = true; const step = e.key === 'ArrowDown' ? 1 : -1; setHighlight((h) => Math.max(0, Math.min(entries.length - 1, h + step))); } else if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); commit(entries[highlight]?.value ?? draft, 'down'); } else if (e.key === 'Tab') { e.preventDefault(); e.stopPropagation(); const keep = touched.current ? (entries[highlight]?.value ?? draft) : draft; commit(keep, e.shiftKey ? 'left' : 'right'); } else if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); settled.current = true; onCancel(true); } else { e.stopPropagation(); } }} /> {pos && (
e.preventDefault()} > {entries.map((entry, i) => ( ))} {matches.length === 0 && (
{t('grid.noLinkMatches')}
)} {capped && (
{t('grid.linkCapped', options.length, locale)}
)}
)} ); }