@ai-matrx/design-system 0.48.6 → 0.48.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/data-table/copy-helpers.d.cts +1 -1
  3. package/dist/data-table/copy-helpers.d.ts +1 -1
  4. package/dist/data-table/filter-engine.d.cts +1 -1
  5. package/dist/data-table/filter-engine.d.ts +1 -1
  6. package/dist/data-table/host.d.cts +1 -1
  7. package/dist/data-table/host.d.ts +1 -1
  8. package/dist/data-table/index.cjs +22 -4
  9. package/dist/data-table/index.cjs.map +1 -1
  10. package/dist/data-table/index.d.cts +2 -2
  11. package/dist/data-table/index.d.ts +2 -2
  12. package/dist/data-table/index.js +22 -4
  13. package/dist/data-table/index.js.map +1 -1
  14. package/dist/data-table/infer-filter.d.cts +1 -1
  15. package/dist/data-table/infer-filter.d.ts +1 -1
  16. package/dist/data-table/layered-filters.d.cts +1 -1
  17. package/dist/data-table/layered-filters.d.ts +1 -1
  18. package/dist/data-table/pagination.cjs +1 -1
  19. package/dist/data-table/pagination.cjs.map +1 -1
  20. package/dist/data-table/pagination.js +1 -1
  21. package/dist/data-table/pagination.js.map +1 -1
  22. package/dist/data-table/query-control.d.cts +1 -1
  23. package/dist/data-table/query-control.d.ts +1 -1
  24. package/dist/data-table/spreadsheet.cjs +18 -0
  25. package/dist/data-table/spreadsheet.cjs.map +1 -1
  26. package/dist/data-table/spreadsheet.js +18 -0
  27. package/dist/data-table/spreadsheet.js.map +1 -1
  28. package/dist/data-table/types.cjs.map +1 -1
  29. package/dist/data-table/types.d.cts +1 -1
  30. package/dist/data-table/types.d.ts +1 -1
  31. package/dist/data-table/url-state.d.cts +1 -1
  32. package/dist/data-table/url-state.d.ts +1 -1
  33. package/dist/data-table/xlsx.d.cts +1 -1
  34. package/dist/data-table/xlsx.d.ts +1 -1
  35. package/dist/{types-hMqC2Fwn.d.ts → types-BqLsZ2o1.d.ts} +3 -3
  36. package/dist/{types-Dh3wRc0N.d.cts → types-ufKpwnVR.d.cts} +3 -3
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/useSpreadsheetGrid.ts","../../src/data-table/grid-selection.ts","../../src/data-table/grid-clipboard.ts","../../src/data-table/fill.ts","../../src/data-table/cell-undo.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/data-table — THE SPREADSHEET GESTURES, in the shared\n * table.\n *\n * A range you can drag out, arrow keys and Tab that mean something, Cmd-C /\n * Cmd-X / Cmd-V against real Excel and Sheets blocks, Cmd-D to fill down, a\n * drag handle at the corner of a selection, and an undo stack. All of it\n * existed, and all of it lived inside ONE route's hand-rolled grid where no\n * other table in the product could reach it.\n *\n * WHAT THIS HOOK OWNS AND WHAT IT DOES NOT\n *\n * It owns the SELECTION and the GESTURES. It does not own the data: every\n * mutation leaves as a list of `CellPatch` through `onPatch`, so the surface\n * applies it through the door it already has and this table never invents a\n * persistence path. That is also why undo is honest — the stack holds the\n * before-values the caller gave us, so an undo is just another patch list.\n *\n * ADDRESSES ARE (rowId, columnId), NEVER (rowIndex, colIndex). Rows reload,\n * realtime reorders them, a filter changes underneath a person's hand — an\n * index-based selection silently jumps to a DIFFERENT cell when that happens,\n * and the next keystroke then edits the wrong row. Indexes appear here only as\n * a derived rectangle, recomputed from ids on every render.\n *\n * THE CLICK LAW is inherited from `./grid-selection`: a single click may SELECT\n * a cell; it may never drop a person into a free-text buffer. Selecting is not\n * editing, and this hook deliberately does not open editors — the table's own\n * `editable` path still owns that.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n allRange,\n classifyGridKey,\n columnRange,\n moveSelection,\n rangeBounds,\n rangeRows,\n rowRange,\n sameCell,\n type CellAddress,\n type CellRange,\n type RangeBounds,\n} from \"./grid-selection\";\nimport { gridToTsv, parseClipboardGrid } from \"./grid-clipboard\";\nimport { fillDown, fillRange, fillTargetFor, type CellPatch, type FillRect } from \"./fill\";\nimport {\n createUndoStack,\n popRedo,\n popUndo,\n pushUndo,\n undoEntryFor,\n type UndoStack,\n} from \"./cell-undo\";\n\n/** What a set of cell patches was for, so a consumer can label its own toast. */\nexport type SpreadsheetIntent = \"paste\" | \"fill\" | \"fill-down\" | \"undo\" | \"redo\" | \"clear\";\n\n/** One cell a paste did not write, with the sentence that says why. */\nexport interface PasteRefusal {\n /** Null when the cell belongs to no row on the grid. */\n rowId: string | null;\n columnId: string;\n reason: string;\n /**\n * WHAT WAS REFUSED, kept rather than dropped.\n *\n * Without it a consumer showing a person their fifty pasted rows can say a\n * cell was skipped but not WHICH one or what was in it — measured on Rincon\n * Plumbing's September billing run, 2026-09-21: every refusal on an existing\n * record read \"Row 0, amount_due\" because the value never reached the code\n * that knew the line and the column's name. A refusal nobody can locate is\n * only half a refusal.\n */\n value?: unknown;\n}\n\n/**\n * What a paste WOULD do, handed over before any of it happens.\n *\n * Addresses are (rowId, columnId) like everywhere else in the grid, never\n * indices — a row filtered out between the copy and the paste is not a row.\n */\nexport interface SpreadsheetPastePlan {\n /** The cells that would be written, already screened by `validateCell`. */\n accepted: CellPatch[];\n /** Cells `validateCell` or `writableColumnIds` turned away, each with its sentence. */\n refusals: PasteRefusal[];\n /**\n * Rows of the pasted block that fell past the last row of the grid, in the\n * grid's current column order (so a hidden or reordered column is honoured).\n * Each entry maps a columnId to the raw clipboard text for that cell.\n */\n overflow: Array<Record<string, string>>;\n /** Cells that fell past the RIGHT edge — there is no column to hold them. */\n pastRightEdge: number;\n /** Write `accepted` through `onPatch`, exactly as an unconfirmed paste would. */\n apply: () => Promise<void>;\n}\n\nexport interface SpreadsheetGridOptions {\n /** Row ids currently rendered, in visual order. */\n rowIds: readonly string[];\n /** Visible column ids, in visual order. */\n columnIds: readonly string[];\n /** Reads a cell's current value, for copy and for fill. */\n valueAt: (rowId: string, columnId: string) => unknown;\n /** Whether a column can take a written value at all. */\n isColumnWritable: (columnId: string) => boolean;\n /** Why a column refuses, as a sentence. Used in the skip list. */\n columnRefusal?: ((columnId: string) => string | undefined) | undefined;\n /**\n * Applies a batch of writes. The consumer owns the door. Optional only for a\n * `readOnly` grid, which never writes; a writable grid without it refuses\n * every write out loud rather than dropping it.\n */\n onPatch?: ((patches: CellPatch[], intent: SpreadsheetIntent) => void | Promise<void>) | undefined;\n /**\n * SELECT AND COPY, NEVER CHANGE. A reader gets the whole range cursor — click,\n * drag, shift-click, arrows, select-all, Cmd-C — and no gesture that writes:\n * no fill handle, and cut, paste, clear, fill-down, undo and redo each say\n * `readOnlyReason` instead of doing anything. Separate from editing on\n * purpose: whether a person may CHANGE a table is not whether they may COPY\n * a range out of it.\n */\n readOnly?: boolean | undefined;\n /** The sentence a refused write says on a read-only grid. */\n readOnlyReason?: string | undefined;\n /** 0 turns undo off. */\n undoDepth?: number | undefined;\n /** The little square at the corner of a selection. */\n fillHandle?: boolean | undefined;\n /** Excel/Sheets TSV paste. */\n paste?: boolean | undefined;\n /** Says what just happened, and why something did not. */\n notify?: ((message: string) => void) | undefined;\n /**\n * Hand the whole paste over instead of applying it. See\n * `MatrxDataTableSpreadsheetConfig.onPastePlan` for why this exists.\n */\n onPastePlan?: ((plan: SpreadsheetPastePlan) => void) | undefined;\n /**\n * OPEN THE EDITOR on the focused cell. Return true when the editor actually\n * opened, so the keystroke is consumed; return false and the key is left to\n * whatever else wants it.\n *\n * `seed` is the character the person typed when they typed instead of\n * pressing Enter — type-to-replace, which every spreadsheet has and which is\n * how most values are actually entered. Null means Enter/F2: open the editor\n * on the value that is already there.\n *\n * THE CLICK LAW still holds: this hook never opens an editor on a CLICK. A\n * deliberate keystroke aimed at the focused cell is a different gesture from\n * a click that might have been aimed at the row.\n */\n onEditRequest?: ((address: CellAddress, seed: string | null) => boolean) | undefined;\n /**\n * Judge ONE value before a paste, a fill or a clear writes it. Return a\n * sentence to refuse that cell; return undefined to accept. A refused cell is\n * left alone and the person is told which rule refused it — a rule a paste\n * can walk around is not a rule.\n */\n validateCell?: ((args: { rowId: string; columnId: string; value: unknown }) => string | undefined) | undefined;\n /**\n * SPACE OPENS THE ROW (Airtable's expand). Return true when the row opened, so the keystroke is\n * consumed; absent or false and Space keeps its older meaning (open the cell's editor).\n */\n onOpenRow?: ((rowId: string) => boolean) | undefined;\n}\n\n/**\n * WHERE THE CURSOR GOES WHEN AN EDITOR CLOSES. Google Sheets and Airtable: Enter moves down, Tab\n * moves right (Shift-Tab left), Escape stays — and in every case the GRID has the keyboard again.\n */\nexport type SpreadsheetEditExit = \"down\" | \"up\" | \"right\" | \"left\" | \"stay\";\n\n/**\n * THE GRID'S HANDS, for a consumer whose cells carry their own editors (`controllerRef`).\n *\n * An editor owns the keyboard while it is open; when it closes, focus has to come BACK to the grid\n * or the next arrow, Enter and Cmd-Z reach the page body and the grid feels dead (merged-grid review\n * 2026-09-26, A1). The table's own editors are covered by the grid's key net; an editor the\n * consumer draws says when it closed, and how, through `endEdit`.\n */\nexport interface SpreadsheetController {\n /**\n * An editor closed. `from` is the cell it was editing; the cursor moves from there by `exit`.\n * `how: \"keyboard\"` (Enter / Tab / Escape) always takes the keyboard back; `\"pointer\"` (a pick in\n * a list, a click away) takes it back only when nothing else has it — a click on a toolbar\n * button keeps its focus.\n */\n endEdit: (\n exit?: SpreadsheetEditExit,\n from?: { rowId: string; columnId: string } | null,\n how?: \"keyboard\" | \"pointer\",\n ) => void;\n /** Put the cursor on a cell, or a rectangle from `anchor` to `focus`. */\n select: (anchor: { rowId: string; columnId: string }, focus?: { rowId: string; columnId: string }) => void;\n /** Give the grid the keyboard. */\n focusGrid: () => void;\n}\n\nexport interface SpreadsheetCellProps {\n \"data-matrx-cell-row\": string;\n \"data-matrx-cell-col\": string;\n \"data-matrx-cell-selected\"?: \"true\";\n \"data-matrx-cell-active\"?: \"true\";\n \"data-matrx-cell-fill-preview\"?: \"true\";\n onMouseDown: (event: {\n shiftKey: boolean;\n button: number;\n preventDefault: () => void;\n /** The cell element, used to give the grid keyboard focus. */\n currentTarget?: unknown;\n /**\n * What was actually pressed. A press inside an overlay this cell portalled out\n * of the table is NOT a press on the cell — see `camePastAPortal`.\n */\n target?: unknown;\n }) => void;\n onMouseEnter: () => void;\n}\n\nexport interface SpreadsheetGrid {\n /** The current range, or null when nothing is selected. */\n range: CellRange | null;\n /** The rectangle the range spans, recomputed from ids every render. */\n bounds: RangeBounds | null;\n /** True when this hook is doing anything at all. */\n enabled: boolean;\n /** Props for the hidden textarea the grid's focus lives in (type-to-edit). */\n typeCatcherProps: SpreadsheetTypeCatcherProps;\n /** Props for the scroll container that owns the keyboard and the clipboard. */\n containerProps: {\n /** Focus landing on the container itself (Tab into the grid) moves on to the type catcher. */\n onFocus: (event: { target: unknown; currentTarget: unknown }) => void;\n tabIndex: number;\n onKeyDown: (event: SpreadsheetKeyEvent) => void;\n /**\n * THE KEY NET: Enter, Tab or Escape pressed INSIDE an editor (the table's own or a portalled\n * picker) that leaves focus on the page body gets the keyboard back to the grid, with the\n * cursor moved the way Sheets moves it. Capture phase, because an editor stops propagation.\n */\n onKeyDownCapture: (event: SpreadsheetKeyEvent) => void;\n onCopy: (event: SpreadsheetClipboardEvent) => void;\n onCut: (event: SpreadsheetClipboardEvent) => void;\n onPaste: (event: SpreadsheetClipboardEvent) => void;\n };\n /** Props for one data cell. */\n cellProps: (rowId: string, columnId: string) => SpreadsheetCellProps;\n /** True for the one cell that carries the drag handle. */\n isFillHandleCell: (rowId: string, columnId: string) => boolean;\n /** Starts a fill-handle drag. */\n startFillDrag: (event: { preventDefault: () => void; stopPropagation: () => void }) => void;\n canUndo: boolean;\n canRedo: boolean;\n /** The selected rectangle as an Excel/Sheets-compatible TSV block. */\n selectionTsv: () => string;\n /** The ids the current rectangle covers, in visual order. */\n selectionAddresses: () => { rowIds: string[]; columnIds: string[] };\n /** Focus return, cursor moves and selection, for a consumer that draws its own editors. */\n controller: SpreadsheetController;\n}\n\nexport interface SpreadsheetKeyEvent {\n key: string;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n target: EventTarget | null;\n preventDefault: () => void;\n}\n\nexport interface SpreadsheetClipboardEvent {\n clipboardData: { getData: (type: string) => string; setData: (type: string, value: string) => void } | null;\n target: EventTarget | null;\n preventDefault: () => void;\n}\n\n/** A keystroke aimed at a text box is the text box's, never the grid's. */\nfunction targetsAnEditor(target: unknown): boolean {\n const element = target instanceof Element ? target : null;\n if (!element) return false;\n // The TYPE CATCHER is a textarea only so it can take text; it is the grid's own focus, never\n // an editor, and every grid key must still reach the grid through it.\n if (element.closest(`[${TYPE_CATCHER_ATTR}]`)) return false;\n return element.closest(\"input, textarea, select, [contenteditable='true']\") !== null;\n}\n\n/** Marks the grid's hidden type catcher (type-to-edit). */\nexport const TYPE_CATCHER_ATTR = \"data-matrx-type-catcher\";\n\n/**\n * Invisible and out of the layout, yet focusable (never `display: none` or `visibility:\n * hidden`, which would make it unfocusable). `fixed` at the viewport's corner so focusing it\n * never scrolls the page, 1px so a browser still treats it as a real text field for IME.\n */\nconst TYPE_CATCHER_STYLE = {\n position: \"fixed\",\n top: 0,\n left: 0,\n width: 1,\n height: 1,\n padding: 0,\n border: 0,\n opacity: 0,\n overflow: \"hidden\",\n resize: \"none\",\n pointerEvents: \"none\",\n} as const;\n\n/** The hidden textarea the grid's keyboard focus lives in. */\nexport interface SpreadsheetTypeCatcherProps {\n ref: (element: HTMLTextAreaElement | null) => void;\n tabIndex: -1;\n \"aria-label\": string;\n inputMode: \"none\";\n autoComplete: \"off\";\n autoCorrect: \"off\";\n autoCapitalize: \"off\";\n spellCheck: false;\n rows: 1;\n [TYPE_CATCHER_ATTR]: \"\";\n style: typeof TYPE_CATCHER_STYLE;\n onInput: (event: { currentTarget: { value: string }; nativeEvent?: unknown }) => void;\n onCompositionStart: () => void;\n onCompositionEnd: (event: { data?: string; currentTarget: { value: string } }) => void;\n}\n\n/**\n * 🚨 A PORTALLED OVERLAY IS A REACT DESCENDANT OF THE CELL THAT OPENED IT, AND ITS\n * EVENTS ARRIVE HERE (lane FIX-15, 2026-09-23).\n *\n * React synthetic events propagate through the REACT tree, not the DOM tree. A cell that\n * renders a Radix Popover renders the popover's content through a portal into\n * `document.body` — nowhere near the table in the DOM, but still this cell's child in\n * React. So every mousedown, keystroke and clipboard event inside that popover was\n * delivered to the grid's own handlers as if the person had clicked the cell.\n *\n * WHAT THAT DID ON A LIVE SCREEN: one real mouse click in the relation picker's own\n * `Search` box ran `cellProps.onMouseDown`, whose `preventDefault()` (correct, for a\n * cell) stopped the input from taking focus and whose `focus()` then moved focus to the\n * scroll container. Radix saw focus leave its layer and dismissed the picker. The control\n * was unreachable: the box could not be typed into at all, so the Create offer behind it\n * could never be seen. Measured on production 2026-09-22 (build 93970125f9) and\n * reproduced on this package's own harness.\n *\n * THE TEST IS DOM CONTAINMENT, not a list of known overlays: an event whose real target\n * is not inside the element the handler is bound to was re-parented by a portal and is\n * not the grid's. That covers the relation picker, the person picker, a select, a date\n * picker, a menu — every overlay any cell renders, today and tomorrow.\n */\nfunction camePastAPortal(target: unknown, host: unknown): boolean {\n if (typeof Element === \"undefined\") return false;\n const element = target instanceof Element ? target : null;\n const owner = host instanceof Element ? host : null;\n if (!element || !owner) return false;\n return !owner.contains(element);\n}\n\n/**\n * The same question for the container's own handlers, which have no per-cell element to\n * measure against: an event that did not come from inside A spreadsheet scroll container\n * came past a portal.\n */\nfunction camePastAPortalIntoTheContainer(target: EventTarget | null): boolean {\n if (typeof Element === \"undefined\") return false;\n const element = target instanceof Element ? target : null;\n if (!element) return false;\n return element.closest('[data-matrx-table-spreadsheet=\"true\"]') === null;\n}\n\nconst NOTHING: CellPatch[] = [];\n\n/** What a refused write says on a read-only grid when the consumer names nothing better. */\nexport const READ_ONLY_REASON = \"This table is view-only — you can select and copy, but not change cells.\";\n\nexport function useSpreadsheetGrid(\n options: SpreadsheetGridOptions | null,\n): SpreadsheetGrid {\n const [range, setRange] = useState<CellRange | null>(null);\n const [fillTarget, setFillTarget] = useState<FillRect | null>(null);\n const [stack, setStack] = useState<UndoStack>(() => createUndoStack(options?.undoDepth ?? 100));\n const draggingRef = useRef<\"select\" | \"fill\" | null>(null);\n const catcherRef = useRef<HTMLTextAreaElement | null>(null);\n const composingRef = useRef(false);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const rowIds = options?.rowIds ?? [];\n const columnIds = useMemo(\n () => (options ? options.columnIds.filter((id) => id !== \"\") : []),\n [options],\n );\n\n // A selection whose row or column is gone is a selection pointing at nothing.\n // Drop it rather than letting the next keystroke land somewhere arbitrary.\n useEffect(() => {\n if (!range) return;\n const rowSet = new Set(rowIds);\n const colSet = new Set(columnIds);\n const alive =\n rowSet.has(range.anchor.rowId) &&\n rowSet.has(range.focus.rowId) &&\n colSet.has(range.anchor.fieldName) &&\n colSet.has(range.focus.fieldName);\n if (!alive) setRange(null);\n }, [range, rowIds, columnIds]);\n\n const bounds = useMemo(\n () => (range ? rangeBounds(range, rowIds, columnIds) : null),\n [range, rowIds, columnIds],\n );\n\n /**\n * 🚨 INDEXES, NOT `indexOf`.\n *\n * `cellProps` runs for every rendered cell on every render and needs the\n * row's and the column's position. `rowIds.indexOf(rowId)` there is O(rows)\n * per cell: on a hundred-thousand-row table with forty rows and six columns\n * in the window, twenty-four million comparisons PER FRAME. Nothing in jsdom\n * could show it — the DOM was right, it was just slow.\n */\n const rowIndexById = useMemo(() => {\n const map = new Map<string, number>();\n for (let i = 0; i < rowIds.length; i += 1) map.set(rowIds[i]!, i);\n return map;\n }, [rowIds]);\n const columnIndexById = useMemo(() => {\n const map = new Map<string, number>();\n for (let i = 0; i < columnIds.length; i += 1) map.set(columnIds[i]!, i);\n return map;\n }, [columnIds]);\n\n /**\n * THE ONE GATE every write passes through.\n *\n * A column rule that only the inline editor enforced would be a rule a paste\n * walks around — so the same judgement runs here, on the patches a paste, a\n * fill or a clear produced, and a refused cell is dropped and NAMED rather\n * than written or silently skipped.\n */\n const screenPatches = useCallback(\n (patches: CellPatch[]): { accepted: CellPatch[]; refusals: PasteRefusal[] } => {\n const validate = optionsRef.current?.validateCell;\n if (!validate) return { accepted: patches, refusals: [] };\n const accepted: CellPatch[] = [];\n const refusals: PasteRefusal[] = [];\n for (const patch of patches) {\n let reason: string | undefined;\n try {\n reason = validate({ rowId: patch.rowId, columnId: patch.columnId, value: patch.value });\n } catch {\n reason = undefined;\n }\n if (reason) refusals.push({ rowId: patch.rowId, columnId: patch.columnId, reason, value: patch.value });\n else accepted.push(patch);\n }\n return { accepted, refusals };\n },\n [],\n );\n\n const applyPatches = useCallback(\n async (patches: CellPatch[], intent: SpreadsheetIntent, label: string) => {\n const current = optionsRef.current;\n if (!current || patches.length === 0) return;\n const depth = current.undoDepth ?? 100;\n if (depth > 0 && intent !== \"undo\" && intent !== \"redo\") {\n const before: CellPatch[] = patches.map((patch) => ({\n rowId: patch.rowId,\n columnId: patch.columnId,\n value: current.valueAt(patch.rowId, patch.columnId) ?? null,\n }));\n setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));\n }\n if (!current.onPatch) return;\n await current.onPatch(patches, intent);\n },\n [],\n );\n\n /**\n * THE READ-ONLY GATE. Every gesture that would write asks this first; on a\n * reader's grid it says why and the gesture stops — never a silent no-op.\n */\n const refuseWrite = useCallback((): boolean => {\n const current = optionsRef.current;\n if (!current) return true;\n if (!current.readOnly && current.onPatch) return false;\n current.notify?.(current.readOnlyReason ?? READ_ONLY_REASON);\n return true;\n }, []);\n\n const readValueAt = useCallback((rowIndex: number, columnIndex: number) => {\n const current = optionsRef.current;\n const rowId = current?.rowIds[rowIndex];\n const columnId = current?.columnIds[columnIndex];\n if (!current || rowId === undefined || columnId === undefined) return null;\n return current.valueAt(rowId, columnId);\n }, []);\n\n const fillRequestBase = useCallback(() => {\n const current = optionsRef.current;\n if (!current) return null;\n return {\n valueAt: readValueAt,\n columnIds: [...current.columnIds],\n rowIds: [...current.rowIds],\n isColumnWritable: current.isColumnWritable,\n };\n }, [readValueAt]);\n\n const announceSkips = useCallback(\n (skipped: PasteRefusal[], summary: string) => {\n const current = optionsRef.current;\n if (!current?.notify) return;\n if (skipped.length === 0) {\n current.notify(summary);\n return;\n }\n // Never a silent drop: the first refusal is named, and the rest counted.\n const first = skipped[0];\n const reason = current.columnRefusal?.(first!.columnId) ?? first!.reason;\n const rest = skipped.length > 1 ? ` ${skipped.length - 1} more were left alone.` : \"\";\n current.notify(`${summary} ${reason}${rest}`);\n },\n [],\n );\n\n const doFillDown = useCallback(async () => {\n if (refuseWrite()) return;\n const base = fillRequestBase();\n if (!base || !bounds) return;\n const result = fillDown({\n ...base,\n range: { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },\n });\n const screened = screenPatches(result.patches);\n await applyPatches(screened.accepted, \"fill-down\", \"Filled down\");\n announceSkips([...result.skipped, ...screened.refusals], result.summary);\n }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);\n\n const doClear = useCallback(async () => {\n if (refuseWrite()) return;\n const current = optionsRef.current;\n if (!current || !bounds) return;\n const patches: CellPatch[] = [];\n const skipped: PasteRefusal[] = [];\n for (let r = bounds.r0; r <= bounds.r1; r += 1) {\n for (let c = bounds.c0; c <= bounds.c1; c += 1) {\n const rowId = current.rowIds[r];\n const columnId = current.columnIds[c];\n if (rowId === undefined || columnId === undefined) continue;\n if (!current.isColumnWritable(columnId)) {\n skipped.push({\n rowId,\n columnId,\n reason: current.columnRefusal?.(columnId) ?? `${columnId} cannot be cleared.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: null });\n }\n }\n const screened = screenPatches(patches);\n await applyPatches(screened.accepted, \"clear\", \"Cleared\");\n announceSkips(\n [...skipped, ...screened.refusals],\n `Cleared ${screened.accepted.length} cells.`,\n );\n }, [bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);\n\n const doUndo = useCallback(async () => {\n if (refuseWrite()) return;\n const taken = popUndo(stack);\n if (!taken.entry) return;\n setStack(taken.stack);\n await applyPatches(taken.entry.undo, \"undo\", taken.entry.label);\n optionsRef.current?.notify?.(taken.entry.label);\n }, [stack, applyPatches, refuseWrite]);\n\n const doRedo = useCallback(async () => {\n if (refuseWrite()) return;\n const taken = popRedo(stack);\n if (!taken.entry) return;\n setStack(taken.stack);\n await applyPatches(taken.entry.redo, \"redo\", taken.entry.label);\n }, [stack, applyPatches, refuseWrite]);\n\n const move = useCallback(\n (direction: Parameters<typeof moveSelection>[1], extend: boolean) => {\n setRange((previous) => {\n const from = previous ? previous.focus : null;\n const next = moveSelection(from, direction, rowIds, columnIds);\n if (!next) return previous;\n if (extend && previous) return { anchor: previous.anchor, focus: next };\n return { anchor: next, focus: next };\n });\n },\n [rowIds, columnIds],\n );\n\n /*\n * ═══ THE KEYBOARD COMES BACK (merged-grid review 2026-09-26, A1) ══════════════════════════════\n *\n * An open editor owns focus; when it closes (Enter, Tab, Escape, a pick in a list) the element\n * that had focus is unmounted and the browser drops focus on <body>. From there no arrow, no\n * Enter and no Cmd-Z reaches the grid until somebody clicks — the old /data grid's\n * `refocusGrid` handoff, which this primitive never had. Sheets and Airtable: the cursor moves\n * (down on Enter, right on Tab) and the grid has the keyboard, every time.\n */\n const rowIdsRef = useRef<readonly string[]>(rowIds);\n rowIdsRef.current = rowIds;\n const columnIdsRef = useRef<readonly string[]>(columnIds);\n columnIdsRef.current = columnIds;\n const rangeRef = useRef<CellRange | null>(range);\n rangeRef.current = range;\n\n const focusGrid = useCallback(() => {\n const catcher = catcherRef.current;\n if (catcher && catcher.isConnected) {\n catcher.focus({ preventScroll: true });\n return;\n }\n }, []);\n\n /** Nobody has the keyboard: it fell to the page body (or the element holding it is gone). */\n const focusIsLost = useCallback((): boolean => {\n if (typeof document === \"undefined\") return false;\n const active = document.activeElement;\n return !active || active === document.body || active === document.documentElement || !active.isConnected;\n }, []);\n\n /** Lost, or still inside this grid (an editor's own leftover, a cell's inner control). */\n const focusIsLostOrOurs = useCallback((): boolean => {\n if (focusIsLost()) return true;\n const container = catcherRef.current?.closest('[data-matrx-table-spreadsheet=\"true\"]');\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n return Boolean(container && active && container.contains(active));\n }, [focusIsLost]);\n\n const endEdit = useCallback(\n (\n exit: SpreadsheetEditExit = \"stay\",\n from: { rowId: string; columnId: string } | null = null,\n how: \"keyboard\" | \"pointer\" = \"keyboard\",\n ) => {\n const move: Parameters<typeof moveSelection>[1] | null =\n exit === \"down\" ? \"down\" : exit === \"up\" ? \"up\" : exit === \"right\" ? \"nextCell\" : exit === \"left\" ? \"prevCell\" : null;\n const at: CellAddress | null = from ? { rowId: from.rowId, fieldName: from.columnId } : null;\n setRange((previous) => {\n // A POINTER EXIT NEVER MOVES A CURSOR THE PERSON HAS SINCE MOVED: the click that closed a\n // picker by landing on another cell already put the cursor there.\n if (how === \"pointer\" && previous && at && !sameCell(previous.focus, at)) return previous;\n if (move === null) return previous ?? (at ? { anchor: at, focus: at } : previous);\n const start = at ?? previous?.focus ?? null;\n if (!start) return previous;\n const next = moveSelection(start, move, rowIdsRef.current, columnIdsRef.current);\n return next ? { anchor: next, focus: next } : previous;\n });\n if (how === \"keyboard\") {\n focusGrid();\n return;\n }\n // A pick in a list: the list's own element may still hold focus for this tick, and is gone\n // on the next. Take the keyboard back only if nobody else took it.\n if (focusIsLostOrOurs()) focusGrid();\n setTimeout(() => {\n if (focusIsLost()) focusGrid();\n }, 0);\n },\n [focusGrid, focusIsLost, focusIsLostOrOurs],\n );\n\n const select = useCallback(\n (anchor: { rowId: string; columnId: string }, focus?: { rowId: string; columnId: string }) => {\n const a: CellAddress = { rowId: anchor.rowId, fieldName: anchor.columnId };\n const f: CellAddress = focus ? { rowId: focus.rowId, fieldName: focus.columnId } : a;\n setRange({ anchor: a, focus: f });\n },\n [],\n );\n\n const onKeyDownCapture = useCallback(\n (event: SpreadsheetKeyEvent) => {\n const key = event.key;\n if (key !== \"Enter\" && key !== \"Tab\" && key !== \"Escape\") return;\n // Only keys pressed INSIDE an editor: the table's own inputs, or a portalled picker's list.\n const element = event.target instanceof Element ? event.target : null;\n if (!element || element.closest(`[${TYPE_CATCHER_ATTR}]`)) return;\n const inEditor =\n targetsAnEditor(event.target) ||\n (camePastAPortalIntoTheContainer(event.target) &&\n element.closest('input, textarea, select, [contenteditable=\"true\"], [role=\"listbox\"], [role=\"option\"], [role=\"combobox\"]') !== null);\n if (!inEditor) return;\n // Shift-Enter / Alt-Enter is the newline in a multi-line editor, never an exit.\n if (key === \"Enter\" && (event.shiftKey || event.altKey)) return;\n const exit: SpreadsheetEditExit = key === \"Enter\" ? \"down\" : key === \"Tab\" ? (event.shiftKey ? \"left\" : \"right\") : \"stay\";\n const at = rangeRef.current?.focus ?? null;\n setTimeout(() => {\n // The consumer already handed focus back (`endEdit`), or the editor kept it (a refused\n // value stays open): nothing to do.\n if (!focusIsLost()) return;\n endEdit(exit, at ? { rowId: at.rowId, columnId: at.fieldName } : null, \"keyboard\");\n }, 0);\n },\n [endEdit, focusIsLost],\n );\n\n const controller = useMemo<SpreadsheetController>(() => ({ endEdit, select, focusGrid }), [endEdit, select, focusGrid]);\n\n const onKeyDown = useCallback(\n (event: SpreadsheetKeyEvent) => {\n const current = optionsRef.current;\n if (!current) return;\n if (targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n // Undo and redo are not in the grid key vocabulary, because in the app\n // they were the editor's. Here the stack belongs to the GRID, so the\n // keystrokes do too — and they are recognised before anything else so a\n // Cmd-Z can never be read as a move.\n const accel = event.metaKey || event.ctrlKey;\n if (accel && (event.key === \"z\" || event.key === \"Z\")) {\n event.preventDefault();\n if (event.shiftKey) void doRedo();\n else void doUndo();\n return;\n }\n if (accel && (event.key === \"y\" || event.key === \"Y\")) {\n event.preventDefault();\n void doRedo();\n return;\n }\n const action = classifyGridKey(event);\n if (!action) return;\n switch (action.kind) {\n case \"move\":\n event.preventDefault();\n move(action.move, false);\n return;\n case \"extend\":\n event.preventDefault();\n move(action.move, true);\n return;\n case \"selectAll\": {\n event.preventDefault();\n const all = allRange(rowIds, columnIds);\n if (all) setRange(all);\n return;\n }\n case \"selectRow\": {\n event.preventDefault();\n const focused = range?.focus;\n const next = focused ? rowRange(focused.rowId, columnIds) : null;\n if (next) setRange(next);\n return;\n }\n case \"selectColumn\": {\n event.preventDefault();\n const focused = range?.focus;\n const next = focused ? columnRange(focused.fieldName, rowIds) : null;\n if (next) setRange(next);\n return;\n }\n case \"clearCell\":\n event.preventDefault();\n void doClear();\n return;\n case \"fillDown\":\n event.preventDefault();\n void doFillDown();\n return;\n case \"escape\":\n setRange(null);\n return;\n case \"edit\":\n case \"editSeeded\": {\n // ENTER OPENS THE CELL, and so does simply typing into it. The\n // gesture is the grid's to RECOGNISE and the table's to PERFORM: the\n // hook asks, and only consumes the keystroke when an editor actually\n // opened. Answering false leaves the key to whatever else wants it,\n // which is why a read-only column still types nothing instead of\n // swallowing the character.\n const focused = range?.focus;\n // SPACE EXPANDS THE ROW (Airtable): the record opens beside the grid, and the grid keeps\n // the keyboard so the arrows walk the panel from row to row.\n if (focused && event.key === \" \" && current.onOpenRow) {\n if (current.onOpenRow(focused.rowId)) {\n event.preventDefault();\n return;\n }\n }\n if (!focused || !current.onEditRequest || current.readOnly) return;\n const seed = action.kind === \"editSeeded\" ? action.seed : null;\n if (current.onEditRequest(focused, seed)) event.preventDefault();\n return;\n }\n default:\n // `copy`, `cut` and `paste` belong to the clipboard handlers below.\n // Claiming them here would take the gesture away from the thing\n // that actually performs it.\n return;\n }\n },\n [move, range, rowIds, columnIds, doClear, doFillDown, doUndo, doRedo],\n );\n\n const selectionAsTsv = useCallback(() => {\n const current = optionsRef.current;\n if (!current || !range) return \"\";\n const grid = rangeRows(range, rowIds, columnIds).map((row) =>\n row.map((address) => current.valueAt(address.rowId, address.fieldName)),\n );\n return gridToTsv(grid);\n }, [range, rowIds, columnIds]);\n\n const selectionAddresses = useCallback(() => {\n if (!range || !bounds) return { rowIds: [] as string[], columnIds: [] as string[] };\n return {\n rowIds: rowIds.slice(bounds.r0, bounds.r1 + 1),\n columnIds: columnIds.slice(bounds.c0, bounds.c1 + 1),\n };\n }, [range, bounds, rowIds, columnIds]);\n\n const onCopy = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n const text = selectionAsTsv();\n if (!text) return;\n event.preventDefault();\n event.clipboardData?.setData(\"text/plain\", text);\n // SAID, NOT SILENT (review B2: \"No Copied toast\"). A copy leaves no mark on the grid, so the\n // person is told it happened and how much went.\n const { rowIds: copiedRows, columnIds: copiedColumns } = selectionAddresses();\n const cells = Math.max(1, copiedRows.length * copiedColumns.length);\n optionsRef.current?.notify?.(`Copied ${cells} ${cells === 1 ? \"cell\" : \"cells\"}.`);\n },\n [range, selectionAsTsv, selectionAddresses],\n );\n\n const onCut = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n const text = selectionAsTsv();\n if (!text) return;\n event.preventDefault();\n event.clipboardData?.setData(\"text/plain\", text);\n void doClear();\n },\n [range, selectionAsTsv, doClear],\n );\n\n const onPaste = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n const current = optionsRef.current;\n if (!current || current.paste === false || !range || targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) return;\n event.preventDefault();\n if (refuseWrite()) return;\n let block = parseClipboardGrid(text);\n let startRow = rowIds.indexOf(range.focus.rowId);\n let startCol = columnIds.indexOf(range.focus.fieldName);\n if (startRow === -1 || startCol === -1) return;\n /*\n * ONE VALUE OVER A SELECTED RANGE FILLS THE RANGE (review B2: \"pasting one value over a\n * 3-cell range writes only 1 cell\"). Google Sheets and Excel: a copied block whose size\n * divides the selected rectangle is repeated across it — one value fills every cell, a\n * 1×2 fills a 3×2 row by row. A block that does not divide it pastes once, at the top-left.\n */\n if (bounds) {\n const blockRows = block.length;\n const blockCols = block.reduce((widest, line) => Math.max(widest, line.length), 0);\n const height = bounds.r1 - bounds.r0 + 1;\n const width = bounds.c1 - bounds.c0 + 1;\n startRow = bounds.r0;\n startCol = bounds.c0;\n if (\n blockRows > 0 &&\n blockCols > 0 &&\n (height > blockRows || width > blockCols) &&\n height % blockRows === 0 &&\n width % blockCols === 0\n ) {\n const tiled: string[][] = [];\n for (let r = 0; r < height; r += 1) {\n const line: string[] = [];\n for (let c = 0; c < width; c += 1) line.push(block[r % blockRows]?.[c % blockCols] ?? \"\");\n tiled.push(line);\n }\n block = tiled;\n }\n }\n // THE PASTED BLOCK IS WHAT IS SELECTED AFTERWARDS, as in Sheets: the next Cmd-C, Delete or\n // fill acts on what just landed, and the grid keeps the keyboard.\n {\n const lastRow = Math.min(startRow + Math.max(block.length, 1) - 1, rowIds.length - 1);\n const widest = block.reduce((w, line) => Math.max(w, line.length), 1);\n const lastCol = Math.min(startCol + widest - 1, columnIds.length - 1);\n const anchor: CellAddress = { rowId: rowIds[startRow]!, fieldName: columnIds[startCol]! };\n const focus: CellAddress = { rowId: rowIds[lastRow]!, fieldName: columnIds[lastCol]! };\n setRange({ anchor, focus });\n }\n const patches: CellPatch[] = [];\n const skipped: PasteRefusal[] = [];\n // PAST THE RIGHT EDGE AND PAST THE BOTTOM ARE DIFFERENT ANSWERS. There is\n // no column to hold a cell past the right edge and there never will be\n // one on this grid, so it is counted. A row past the BOTTOM is a row the\n // consumer can make — it is collected whole, in the grid's column order,\n // and handed over. Until 2026-09-21 both were counted together and\n // dropped, which is how a paste of fifty spreadsheet rows into a\n // three-row table wrote three rows and announced that forty-seven \"landed\n // past the edge of the table and were ignored\".\n let pastRightEdge = 0;\n const overflow: Array<Record<string, string>> = [];\n for (let r = 0; r < block.length; r += 1) {\n const line = block[r] ?? [];\n const rowId = rowIds[startRow + r];\n if (rowId === undefined) {\n const beyond: Record<string, string> = {};\n for (let c = 0; c < line.length; c += 1) {\n const columnId = columnIds[startCol + c];\n if (columnId === undefined) {\n pastRightEdge += 1;\n continue;\n }\n beyond[columnId] = line[c] ?? \"\";\n }\n overflow.push(beyond);\n continue;\n }\n for (let c = 0; c < line.length; c += 1) {\n const columnId = columnIds[startCol + c];\n if (columnId === undefined) {\n pastRightEdge += 1;\n continue;\n }\n if (!current.isColumnWritable(columnId)) {\n skipped.push({\n rowId,\n columnId,\n reason:\n current.columnRefusal?.(columnId) ??\n `${columnId} cannot take a pasted value.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: line[c] ?? null });\n }\n }\n const screened = screenPatches(patches);\n const refusals = [...skipped, ...screened.refusals];\n // THE CONSUMER MAY WANT TO SHOW THIS BEFORE ANY OF IT HAPPENS. When it\n // does, NOTHING is applied here and nothing is announced: the plan is the\n // whole answer, and `apply()` is the only way it becomes a write.\n if (current.onPastePlan) {\n const accepted = screened.accepted;\n current.onPastePlan({\n accepted,\n refusals,\n overflow,\n pastRightEdge,\n apply: async () => {\n await applyPatches(accepted, \"paste\", \"Pasted\");\n },\n });\n return;\n }\n void applyPatches(screened.accepted, \"paste\", \"Pasted\");\n const outside = pastRightEdge + overflow.reduce((n, row) => n + Object.keys(row).length, 0);\n const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : \"\";\n announceSkips(refusals, `Pasted ${screened.accepted.length} cells.${tail}`);\n },\n [range, bounds, rowIds, columnIds, applyPatches, announceSkips, screenPatches, refuseWrite],\n );\n\n const cellProps = useCallback(\n (rowId: string, columnId: string): SpreadsheetCellProps => {\n const address: CellAddress = { rowId, fieldName: columnId };\n const rowIndex = rowIndexById.get(rowId) ?? -1;\n const colIndex = columnIndexById.get(columnId) ?? -1;\n const selected =\n bounds !== null &&\n rowIndex >= bounds.r0 &&\n rowIndex <= bounds.r1 &&\n colIndex >= bounds.c0 &&\n colIndex <= bounds.c1;\n const active = range !== null && sameCell(range.focus, address);\n const inFillPreview =\n fillTarget !== null &&\n rowIndex >= fillTarget.top &&\n rowIndex <= fillTarget.bottom &&\n colIndex >= fillTarget.left &&\n colIndex <= fillTarget.right;\n return {\n \"data-matrx-cell-row\": rowId,\n \"data-matrx-cell-col\": columnId,\n ...(selected ? { \"data-matrx-cell-selected\": \"true\" as const } : {}),\n ...(active ? { \"data-matrx-cell-active\": \"true\" as const } : {}),\n ...(inFillPreview ? { \"data-matrx-cell-fill-preview\": \"true\" as const } : {}),\n onMouseDown: (event) => {\n if (event.button !== 0) return;\n // A press inside a popover, menu or dialog this cell rendered through a\n // portal is not a press on the cell. See `camePastAPortal`: without this,\n // the `preventDefault()` below stole focus from the control the person was\n // actually pressing and the overlay dismissed itself.\n if (camePastAPortal(event.target ?? null, event.currentTarget)) return;\n // A press inside the cell's OWN open editor (its text box) is the text box's: the person\n // is placing the caret or fixing a refused value, and taking focus to the type catcher\n // left the editor open with no way to type into it or Escape out of it (merged-grid\n // review lane A, 2026-09-26: a refused typed date could not be corrected by mouse).\n if (targetsAnEditor(event.target ?? null)) return;\n // 🚨 `preventDefault` ON A MOUSEDOWN ALSO PREVENTS FOCUS. Without the\n // line below, clicking a cell selected it and then every keystroke —\n // Enter, a typed character, an arrow, Cmd-Z — went to the document\n // body instead of the grid. jsdom could never show it, because a\n // jsdom test dispatches the keydown ON the container by hand; a real\n // browser routes it to whatever actually has focus, and nothing did.\n event.preventDefault();\n const origin = event.currentTarget;\n if (typeof HTMLElement !== \"undefined\" && origin instanceof HTMLElement) {\n const container = origin.closest<HTMLElement>('[data-matrx-table-spreadsheet=\"true\"]');\n // The grid's focus is its TYPE CATCHER, so typed text has somewhere to land.\n const catcher = container?.querySelector<HTMLTextAreaElement>(`[${TYPE_CATCHER_ATTR}]`);\n (catcher ?? container)?.focus({ preventScroll: true });\n }\n setRange((previous) =>\n event.shiftKey && previous\n ? { anchor: previous.anchor, focus: address }\n : { anchor: address, focus: address },\n );\n draggingRef.current = \"select\";\n },\n onMouseEnter: () => {\n if (draggingRef.current === \"select\") {\n setRange((previous) => (previous ? { anchor: previous.anchor, focus: address } : previous));\n return;\n }\n if (draggingRef.current === \"fill\" && bounds) {\n setFillTarget(\n fillTargetFor(\n { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },\n rowIndex,\n colIndex,\n ),\n );\n }\n },\n };\n },\n [rowIndexById, columnIndexById, bounds, range, fillTarget],\n );\n\n const isFillHandleCell = useCallback(\n (rowId: string, columnId: string) => {\n const current = optionsRef.current;\n if (!current || current.fillHandle === false || current.readOnly || !bounds) return false;\n return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId;\n },\n [bounds, rowIds, columnIds],\n );\n\n const startFillDrag = useCallback(\n (event: { preventDefault: () => void; stopPropagation: () => void }) => {\n event.preventDefault();\n event.stopPropagation();\n draggingRef.current = \"fill\";\n },\n [],\n );\n\n // The drag ends on the window, wherever the pointer is — so the commit has\n // to live where the target last was, not where the mouse came up.\n const fillTargetRef = useRef<FillRect | null>(null);\n fillTargetRef.current = fillTarget;\n const boundsRef = useRef<RangeBounds | null>(null);\n boundsRef.current = bounds;\n useEffect(() => {\n if (!options) return;\n // 🚨 ONE mouseup handler, not two. There used to be a second listener whose\n // only job was to clear `draggingRef` — registered FIRST, so on every real\n // mouse-up it ran first, nulled the ref, and this commit then saw no drag\n // in progress and did nothing. The fill handle dragged, previewed, and\n // silently filled nothing. jsdom never fires a window mouse-up, so only a\n // real pointer could find it.\n const commit = () => {\n const wasDragging = draggingRef.current;\n draggingRef.current = null;\n if (wasDragging !== \"fill\") return;\n const target = fillTargetRef.current;\n const source = boundsRef.current;\n setFillTarget(null);\n if (!target || !source) return;\n if (refuseWrite()) return;\n const base = fillRequestBase();\n if (!base) return;\n const result = fillRange({\n ...base,\n source: { top: source.r0, left: source.c0, bottom: source.r1, right: source.c1 },\n target,\n });\n const screened = screenPatches(result.patches);\n void applyPatches(screened.accepted, \"fill\", \"Filled\");\n announceSkips([...result.skipped, ...screened.refusals], result.summary);\n };\n window.addEventListener(\"mouseup\", commit);\n return () => window.removeEventListener(\"mouseup\", commit);\n }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches, refuseWrite]);\n\n /*\n * ═══ THE TYPE CATCHER — TYPING HAS TWO DOORS ═══════════════════════════════════════════════\n *\n * `keydown` catches a plain letter (`editSeeded` above). A lot of real typing never produces a\n * one-character keydown: an IME composition (key \"Process\"), an Option / AltGr character\n * (refused by `isTypingKey` as a possible shortcut), a dead-key accent, dictation, the\n * browser's own insertText. A focused <div> cannot take text, so all of that went nowhere and\n * the next Enter opened the OLD value. The grid's focus therefore lives in a hidden textarea\n * inside the container (`inputMode=\"none\"`, so a phone raises no keyboard for a tap): keys the\n * grid knows still bubble to `onKeyDown` and are prevented there exactly as before; any TEXT\n * that lands in the catcher opens the selected cell's editor seeded with it. A paste or a drop\n * is the clipboard's, never a seed; a space alone opens nothing; a read-only grid takes nothing.\n */\n const takeTypedText = useCallback(\n (text: string) => {\n if (catcherRef.current) catcherRef.current.value = \"\";\n const current = optionsRef.current;\n const focused = range?.focus;\n if (!current || !focused || current.readOnly || !current.onEditRequest) return;\n if (text.trim() === \"\") return;\n current.onEditRequest(focused, text);\n },\n [range],\n );\n\n const typeCatcherProps: SpreadsheetTypeCatcherProps = {\n ref: (element) => {\n catcherRef.current = element;\n },\n tabIndex: -1,\n \"aria-label\": \"Type to edit the selected cell\",\n inputMode: \"none\",\n autoComplete: \"off\",\n autoCorrect: \"off\",\n autoCapitalize: \"off\",\n spellCheck: false,\n rows: 1,\n [TYPE_CATCHER_ATTR]: \"\",\n style: TYPE_CATCHER_STYLE,\n onInput: (event) => {\n const kind = (event.nativeEvent as { inputType?: unknown } | undefined)?.inputType;\n if (typeof kind === \"string\" && (kind.startsWith(\"insertFromPaste\") || kind.startsWith(\"insertFromDrop\"))) {\n event.currentTarget.value = \"\";\n return;\n }\n // Mid-composition the IME owns the text; the composed result arrives once, on compositionend.\n if (composingRef.current) return;\n takeTypedText(event.currentTarget.value);\n },\n onCompositionStart: () => {\n composingRef.current = true;\n },\n onCompositionEnd: (event) => {\n composingRef.current = false;\n takeTypedText(event.data || event.currentTarget.value);\n },\n };\n\n return {\n range,\n bounds,\n enabled: options !== null,\n typeCatcherProps,\n containerProps: {\n onFocus: (event) => {\n if (event.target !== event.currentTarget) return;\n catcherRef.current?.focus({ preventScroll: true });\n },\n tabIndex: options ? 0 : -1,\n onKeyDown,\n onKeyDownCapture,\n onCopy,\n onCut,\n onPaste,\n },\n cellProps,\n isFillHandleCell,\n startFillDrag,\n canUndo: stack.past.length > 0,\n canRedo: stack.future.length > 0,\n selectionTsv: selectionAsTsv,\n selectionAddresses,\n controller,\n };\n}\n\nexport { NOTHING as NO_PATCHES };\n","/**\n * Grid selection model for user data tables.\n * @ai-matrx/design-system/data-table/grid-selection\n *\n * This moved here from the app so every table in the product shares one\n * range-selection model.\n *\n * THE THREE STATES. A spreadsheet grid has three, not two, and collapsing them\n * to two is what made this grid feel like a web page instead of a tool:\n *\n * 1. NOTHING SELECTED — the grid at rest.\n * 2. SELECTED — one cell is current. Nothing has changed. You can\n * look, copy, and move with the keyboard.\n * 3. EDITING — a value is being changed.\n *\n * State 2 is the one that was missing, and its absence is why every edit needed\n * a double-click and why arrow keys, Tab, copy and fill-down were all\n * impossible: none of them mean anything without a notion of \"the current cell\".\n *\n * 🚨 THE CLICK LAW — a single click may SELECT a cell, TOGGLE a two-state\n * value, or OPEN a chooser. It may NEVER drop the user into a free-text buffer.\n * Opening a menu is not a mutation and toggling a checkbox is instantly\n * reversible, but landing in a text buffer turns every attempt to select-and-\n * copy into an accidental edit. `directClickKinds()` below is the whole list of\n * what a single click may act on; adding a free-text editor to it is a defect.\n *\n * A CHOOSER OPENS ON THE SECOND CLICK. A choice cell's first click SELECTS it\n * (so Cmd-C, Cmd-X, the arrow keys and the right-click menu all have a current\n * cell); a click on the cell that is ALREADY selected opens the option list —\n * the Airtable gesture. Opening the chooser on the very first click moved\n * focus into its search box, and every keyboard shortcut then went to that box\n * instead of the grid: a choice column could not be copied at all (2026-09-14).\n * Checkboxes and ratings stay one-click: a mis-click on those is instantly\n * undone in place and steals no focus.\n *\n * CLIPBOARD KEYS. Cmd-C / Cmd-X / Cmd-V on a selected cell act on THAT CELL —\n * copy its value, cut it (copy + clear), paste over it. A paste carrying a\n * spreadsheet block (tabs / line breaks) lands as a block from the selected\n * cell downward and rightward (`grid-clipboard.ts`). None of these need an\n * editor open, which is the whole point: a normal table copies what you point\n * at, not only what you are typing into.\n *\n * Addresses are (rowId, fieldName), never (rowIndex, colIndex): the grid\n * reloads after every write, and realtime reorders rows underneath the user. An\n * index-based selection silently jumps to a DIFFERENT cell when that happens —\n * and the next keystroke then edits the wrong row.\n *\n * Pure module: no React, no DOM. Everything here is a function of the current\n * address and the row/field lists, which is what makes the navigation testable\n * without mounting a grid.\n */\n\nexport type CellAddress = { rowId: string; fieldName: string };\n\nexport type GridMove =\n | \"up\"\n | \"down\"\n | \"left\"\n | \"right\"\n | \"nextCell\"\n | \"prevCell\"\n | \"rowStart\"\n | \"rowEnd\"\n | \"gridStart\"\n | \"gridEnd\";\n\n/** What a keystroke means when a cell is selected but NOT being edited. */\nexport type GridKeyAction =\n | { kind: \"move\"; move: GridMove }\n /** Shift+arrow: grow the range toward `move`, anchor fixed. */\n | { kind: \"extend\"; move: GridMove }\n | { kind: \"selectAll\" }\n | { kind: \"selectRow\" }\n | { kind: \"selectColumn\" }\n | { kind: \"fillDown\" }\n | { kind: \"edit\" }\n | { kind: \"editSeeded\"; seed: string }\n | { kind: \"clearCell\" }\n | { kind: \"copy\" }\n | { kind: \"cut\" }\n | { kind: \"paste\" }\n | { kind: \"escape\" }\n | null;\n\n// ─── Ranges ─────────────────────────────────────────────────────────────────\n//\n// A RANGE is an anchor (where the selection started — the cell with the ring)\n// and a focus (where it was extended to), both by ADDRESS. The rectangle they\n// span is resolved against the grid's current row and column order at the\n// moment it is needed, so a range survives a re-sort or a hidden column the\n// same way a single selection does: by naming cells, not positions.\n\nexport type CellRange = { anchor: CellAddress; focus: CellAddress };\n\n/** The rectangle a range spans, as inclusive indexes into the grid's order. */\nexport type RangeBounds = { r0: number; r1: number; c0: number; c1: number };\n\nexport function rangeBounds(\n range: CellRange,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): RangeBounds | null {\n const ra = rowIds.indexOf(range.anchor.rowId);\n const rf = rowIds.indexOf(range.focus.rowId);\n const ca = fieldNames.indexOf(range.anchor.fieldName);\n const cf = fieldNames.indexOf(range.focus.fieldName);\n if (ra === -1 || rf === -1 || ca === -1 || cf === -1) return null;\n return {\n r0: Math.min(ra, rf),\n r1: Math.max(ra, rf),\n c0: Math.min(ca, cf),\n c1: Math.max(ca, cf),\n };\n}\n\nexport function rangeSize(bounds: RangeBounds): {\n rows: number;\n cols: number;\n cells: number;\n} {\n const rows = bounds.r1 - bounds.r0 + 1;\n const cols = bounds.c1 - bounds.c0 + 1;\n return { rows, cols, cells: rows * cols };\n}\n\nexport function boundsContain(\n bounds: RangeBounds,\n rowIndex: number,\n colIndex: number,\n): boolean {\n return (\n rowIndex >= bounds.r0 &&\n rowIndex <= bounds.r1 &&\n colIndex >= bounds.c0 &&\n colIndex <= bounds.c1\n );\n}\n\n/** Every cell of the range in reading order (row by row, left to right). */\nexport function cellsInRange(\n range: CellRange,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellAddress[] {\n const b = rangeBounds(range, rowIds, fieldNames);\n if (!b) return [];\n const out: CellAddress[] = [];\n // `rangeBounds` derives every index from `indexOf`, so both lookups below are\n // in range; the guards make that explicit instead of asserting it.\n for (let r = b.r0; r <= b.r1; r += 1) {\n const rowId = rowIds[r];\n if (rowId === undefined) continue;\n for (let c = b.c0; c <= b.c1; c += 1) {\n const fieldName = fieldNames[c];\n if (fieldName === undefined) continue;\n out.push({ rowId, fieldName });\n }\n }\n return out;\n}\n\n/** The range as a 2-D grid of addresses — what a block copy serialises. */\nexport function rangeRows(\n range: CellRange,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellAddress[][] {\n const b = rangeBounds(range, rowIds, fieldNames);\n if (!b) return [];\n const rows: CellAddress[][] = [];\n // Same in-range guarantee as `cellsInRange`.\n for (let r = b.r0; r <= b.r1; r += 1) {\n const rowId = rowIds[r];\n if (rowId === undefined) continue;\n const row: CellAddress[] = [];\n for (let c = b.c0; c <= b.c1; c += 1) {\n const fieldName = fieldNames[c];\n if (fieldName === undefined) continue;\n row.push({ rowId, fieldName });\n }\n rows.push(row);\n }\n return rows;\n}\n\nexport function isSingleCellRange(range: CellRange): boolean {\n return sameCell(range.anchor, range.focus);\n}\n\n/** The whole row, first column to last — what clicking a row's edge selects. */\nexport function rowRange(\n rowId: string,\n fieldNames: readonly string[],\n): CellRange | null {\n const firstField = fieldNames[0];\n const lastField = fieldNames[fieldNames.length - 1];\n // A non-empty list always has both ends; the null here is the same \"no range\n // to make\" answer the empty-list case gives.\n if (firstField === undefined || lastField === undefined) return null;\n return {\n anchor: { rowId, fieldName: firstField },\n focus: { rowId, fieldName: lastField },\n };\n}\n\n/** The whole column across the page — what clicking a header's edge selects. */\nexport function columnRange(\n fieldName: string,\n rowIds: readonly string[],\n): CellRange | null {\n const firstRow = rowIds[0];\n const lastRow = rowIds[rowIds.length - 1];\n if (firstRow === undefined || lastRow === undefined) return null;\n return {\n anchor: { rowId: firstRow, fieldName },\n focus: { rowId: lastRow, fieldName },\n };\n}\n\n/** Every cell on the page. */\nexport function allRange(\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellRange | null {\n const firstRow = rowIds[0];\n const lastRow = rowIds[rowIds.length - 1];\n const firstField = fieldNames[0];\n const lastField = fieldNames[fieldNames.length - 1];\n if (\n firstRow === undefined ||\n lastRow === undefined ||\n firstField === undefined ||\n lastField === undefined\n ) {\n return null;\n }\n return {\n anchor: { rowId: firstRow, fieldName: firstField },\n focus: { rowId: lastRow, fieldName: lastField },\n };\n}\n\nexport function sameCell(\n a: CellAddress | null,\n b: CellAddress | null,\n): boolean {\n if (!a || !b) return a === b;\n return a.rowId === b.rowId && a.fieldName === b.fieldName;\n}\n\n/**\n * Where does `move` land from `current`?\n *\n * Returns null when the move is impossible (no selection, empty grid). Vertical\n * moves CLAMP at the edges — running into the top of the grid should park you\n * on the first row, not clear the selection, because a selection that vanishes\n * when you overshoot loses your place. Horizontal Tab moves WRAP to the next or\n * previous row, which is what makes Tab a usable data-entry gesture.\n */\nexport function moveSelection(\n current: CellAddress | null,\n move: GridMove,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellAddress | null {\n if (rowIds.length === 0 || fieldNames.length === 0) return null;\n\n const firstRowId = rowIds[0];\n const firstFieldName = fieldNames[0];\n // Both lists are non-empty by the check above; `null` here is the same \"no\n // move is possible\" answer the empty-grid case gives.\n if (firstRowId === undefined || firstFieldName === undefined) return null;\n const first: CellAddress = { rowId: firstRowId, fieldName: firstFieldName };\n if (!current) return first;\n\n let r = rowIds.indexOf(current.rowId);\n let c = fieldNames.indexOf(current.fieldName);\n // The selected row or column disappeared (deleted, filtered out, re-sorted\n // off the page). Fall back to the start rather than computing from -1, which\n // would quietly resolve to the last element.\n if (r === -1 || c === -1) return first;\n\n const lastRow = rowIds.length - 1;\n const lastCol = fieldNames.length - 1;\n const clamp = (n: number, max: number) => Math.min(Math.max(n, 0), max);\n\n switch (move) {\n case \"up\":\n r = clamp(r - 1, lastRow);\n break;\n case \"down\":\n r = clamp(r + 1, lastRow);\n break;\n case \"left\":\n c = clamp(c - 1, lastCol);\n break;\n case \"right\":\n c = clamp(c + 1, lastCol);\n break;\n case \"rowStart\":\n c = 0;\n break;\n case \"rowEnd\":\n c = lastCol;\n break;\n case \"gridStart\":\n r = 0;\n c = 0;\n break;\n case \"gridEnd\":\n r = lastRow;\n c = lastCol;\n break;\n case \"nextCell\":\n if (c < lastCol) c += 1;\n else if (r < lastRow) {\n r += 1;\n c = 0;\n }\n break;\n case \"prevCell\":\n if (c > 0) c -= 1;\n else if (r > 0) {\n r -= 1;\n c = lastCol;\n }\n break;\n }\n\n // Every branch above clamps into range or leaves r/c at their `indexOf`\n // values, so both reads land; falling back to `first` matches what this\n // function already does when an index cannot be resolved.\n const rowId = rowIds[r];\n const fieldName = fieldNames[c];\n if (rowId === undefined || fieldName === undefined) return first;\n return { rowId, fieldName };\n}\n\n/**\n * A single printable character typed on a selected cell starts an edit seeded\n * with it — the spreadsheet reflex of \"just start typing to replace\".\n *\n * Modifier combinations are excluded so Cmd-C / Ctrl-R never get mistaken for\n * typing, and only genuinely printable single characters qualify: `key` is the\n * character itself for those, and a multi-character name (\"ArrowUp\", \"F3\") for\n * everything else.\n */\nexport function isTypingKey(e: {\n key: string;\n ctrlKey: boolean;\n metaKey: boolean;\n altKey: boolean;\n}): boolean {\n if (e.ctrlKey || e.metaKey || e.altKey) return false;\n return e.key.length === 1 && e.key !== \" \";\n}\n\n/**\n * Translate a keystroke into a grid action. Called ONLY when a cell is selected\n * and not being edited — an editor owns its own keys while it is open.\n */\nexport function classifyGridKey(e: {\n key: string;\n shiftKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n altKey: boolean;\n}): GridKeyAction {\n const mod = e.ctrlKey || e.metaKey;\n\n // Shift+arrow GROWS the selection instead of moving it — the spreadsheet\n // gesture for \"these cells\". Shift+Tab stays a move (previous cell).\n const arrowMove: Record<string, GridMove> = {\n ArrowUp: mod ? \"gridStart\" : \"up\",\n ArrowDown: mod ? \"gridEnd\" : \"down\",\n ArrowLeft: mod ? \"rowStart\" : \"left\",\n ArrowRight: mod ? \"rowEnd\" : \"right\",\n Home: mod ? \"gridStart\" : \"rowStart\",\n End: mod ? \"gridEnd\" : \"rowEnd\",\n };\n // Read the entry rather than testing `in`: `in` also answers true for\n // inherited Object.prototype names (\"constructor\", \"toString\"), which used to\n // produce a move action carrying an undefined move.\n const arrow = arrowMove[e.key];\n if (arrow !== undefined) {\n return { kind: e.shiftKey ? \"extend\" : \"move\", move: arrow };\n }\n\n switch (e.key) {\n case \"Tab\":\n return { kind: \"move\", move: e.shiftKey ? \"prevCell\" : \"nextCell\" };\n case \"Enter\":\n case \"F2\":\n return { kind: \"edit\" };\n case \" \":\n // Shift+Space selects the row and Ctrl/Cmd+Space the column (Excel);\n // plain Space opens the editor without seeding it, so a boolean or\n // choice cell can be operated entirely from the keyboard.\n if (e.shiftKey) return { kind: \"selectRow\" };\n if (mod) return { kind: \"selectColumn\" };\n return { kind: \"edit\" };\n case \"a\":\n case \"A\":\n if (mod) return { kind: \"selectAll\" };\n break;\n case \"d\":\n case \"D\":\n if (mod) return { kind: \"fillDown\" };\n break;\n case \"Escape\":\n return { kind: \"escape\" };\n case \"Delete\":\n case \"Backspace\":\n return { kind: \"clearCell\" };\n case \"c\":\n case \"C\":\n if (mod) return { kind: \"copy\" };\n break;\n case \"x\":\n case \"X\":\n if (mod) return { kind: \"cut\" };\n break;\n case \"v\":\n case \"V\":\n if (mod) return { kind: \"paste\" };\n break;\n }\n\n if (isTypingKey(e)) return { kind: \"editSeeded\", seed: e.key };\n return null;\n}\n\n/**\n * Editor kinds a SINGLE click may operate directly, without first entering edit\n * mode. See THE CLICK LAW at the top of this file — this list is closed sets\n * and two-state values only, where a mis-click is obvious and instantly undone.\n */\nexport function directClickKinds(): readonly string[] {\n return [\"checkbox\", \"rating\", \"select\", \"multiselect\"] as const;\n}\n\nexport function isDirectClickEditor(editorKind: string | undefined): boolean {\n return editorKind !== undefined && directClickKinds().includes(editorKind);\n}\n\n/** Stable DOM key for a cell, so selection can scroll itself into view. */\nexport function cellDomKey(address: CellAddress): string {\n return `${address.rowId}::${address.fieldName}`;\n}\n","/**\n * Clipboard model for the user-data-table grid.\n * @ai-matrx/design-system/data-table/grid-clipboard\n *\n * This moved here from the app so every table in the product shares one\n * range-selection model.\n *\n * WHAT A SPREADSHEET PUTS ON THE CLIPBOARD. Excel, Google Sheets and Numbers\n * all copy a range as TAB-separated rows, one line per row, and quote any cell\n * whose text contains a tab, a line break or a double quote (doubling the\n * quotes inside). That is also what they ACCEPT on paste. Speaking exactly\n * that dialect is what makes \"copy three columns in Sheets, click a cell here,\n * Cmd-V\" land as a block instead of as one cell holding a wall of text — and\n * what makes the reverse trip (copy here, paste into Sheets) split into cells.\n *\n * WHY THE PLAN IS A PURE FUNCTION. Pasting a block is a write to many cells at\n * once, and \"which cells\" is the part that goes wrong: off by one column, the\n * anchor row filtered out, a block wider than the grid. Computing the landing\n * cells here, with no React and no DOM, is what lets that mapping be tested\n * cell by cell — and lets the caller decide separately what to do with the\n * rows that did not fit.\n *\n * Addresses are (rowId, fieldName), like the rest of the grid — never indices.\n */\n\nimport type { CellAddress } from \"./grid-selection\";\n\n/** One cell as text for the clipboard. Objects go out as JSON. */\nexport function cellClipboardText(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/** Quote a cell the way Excel does, only when it has to be. */\nfunction tsvCell(text: string): string {\n return /[\\t\\r\\n\"]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\n/** A rectangular block of cells as spreadsheet TSV. */\nexport function gridToTsv(block: readonly (readonly unknown[])[]): string {\n return block\n .map((row) => row.map((cell) => tsvCell(cellClipboardText(cell))).join(\"\\t\"))\n .join(\"\\n\");\n}\n\n/**\n * Parse clipboard text as a spreadsheet block.\n *\n * Handles the Excel quoting dialect (a quoted cell may span lines and carries\n * `\"\"` for a literal quote), both `\\n` and `\\r\\n` line endings, and the single\n * trailing line break Excel and Sheets append to every copy — without that\n * strip, every paste would be one empty row taller than what was copied.\n *\n * Plain text with no tabs and no quotes still parses: one column, one row per\n * line, which is exactly what Sheets does with the same text. A single value\n * with no line break is a 1×1 block.\n */\nexport function parseClipboardGrid(text: string): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let cell = \"\";\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const ch = text[i];\n\n // A quote opens a quoted cell only at the START of a cell.\n if (cell === \"\" && ch === '\"') {\n // Quoted cell: read to the closing quote, honouring doubled quotes.\n let j = i + 1;\n let out = \"\";\n let closed = false;\n while (j < n) {\n const c = text[j];\n if (c === '\"') {\n if (text[j + 1] === '\"') {\n out += '\"';\n j += 2;\n continue;\n }\n closed = true;\n j += 1;\n break;\n }\n out += c;\n j += 1;\n }\n if (closed && (j >= n || text[j] === \"\\t\" || text[j] === \"\\n\" || text[j] === \"\\r\")) {\n cell = out;\n i = j;\n // Fall through to the delimiter handling below with `cell` complete.\n if (i >= n) break;\n const d = text[i];\n if (d === \"\\t\") {\n row.push(cell);\n cell = \"\";\n i += 1;\n continue;\n }\n // line break\n row.push(cell);\n rows.push(row);\n row = [];\n cell = \"\";\n i += d === \"\\r\" && text[i + 1] === \"\\n\" ? 2 : 1;\n continue;\n }\n // Not a well-formed quoted cell (a value that merely starts with a\n // quote). Treat the quote literally and keep going.\n cell += ch;\n i += 1;\n continue;\n }\n\n if (ch === \"\\t\") {\n row.push(cell);\n cell = \"\";\n i += 1;\n continue;\n }\n if (ch === \"\\n\" || ch === \"\\r\") {\n row.push(cell);\n rows.push(row);\n row = [];\n cell = \"\";\n i += ch === \"\\r\" && text[i + 1] === \"\\n\" ? 2 : 1;\n continue;\n }\n cell += ch;\n i += 1;\n }\n\n // Flush the last cell — unless the text ended on a line break, in which\n // case that break was the trailing one every spreadsheet appends.\n const endedOnBreak = n > 0 && (text[n - 1] === \"\\n\" || text[n - 1] === \"\\r\");\n if (!endedOnBreak) {\n row.push(cell);\n rows.push(row);\n } else if (row.length > 0) {\n row.push(cell);\n rows.push(row);\n }\n\n return rows.length === 0 ? [[\"\"]] : rows;\n}\n\n/** One landing cell of a paste, with the raw text that goes into it. */\nexport type PasteCell = CellAddress & { raw: string };\n\nexport type PastePlan = {\n /** Cells inside the grid, in reading order (row by row). */\n cells: PasteCell[];\n /**\n * Block rows below the last grid row, trimmed to the columns that fit from\n * the anchor column rightward. The caller decides whether these become new\n * rows — the plan only reports them so nothing is dropped silently.\n */\n overflowRows: string[][];\n /** How many block columns fell off the right edge (per row, at most). */\n clippedColumns: number;\n /** The columns (field names) the block spans, anchor first. */\n fieldNames: string[];\n};\n\n/**\n * Where does `block` land when pasted with its top-left cell at `anchor`?\n *\n * Rows extend downward from the anchor row and columns rightward from the\n * anchor column, in the GRID's current order (so a hidden or re-ordered column\n * is honoured — you paste into what you see). A block wider than the remaining\n * columns is clipped on the right; a block taller than the remaining rows is\n * reported as `overflowRows` rather than lost.\n *\n * Returns null when the anchor is not on the grid — the row was filtered out\n * or the column hidden since the selection was made. Writing \"somewhere near\"\n * is worse than refusing.\n */\nexport function planPaste(\n anchor: CellAddress,\n block: readonly (readonly string[])[],\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): PastePlan | null {\n const r0 = rowIds.indexOf(anchor.rowId);\n const c0 = fieldNames.indexOf(anchor.fieldName);\n if (r0 === -1 || c0 === -1) return null;\n\n const width = block.reduce((w, row) => Math.max(w, row.length), 0);\n const colsAvailable = fieldNames.length - c0;\n const colsUsed = Math.min(width, colsAvailable);\n const targetFields = fieldNames.slice(c0, c0 + colsUsed);\n\n const cells: PasteCell[] = [];\n const overflowRows: string[][] = [];\n\n block.forEach((blockRow, dr) => {\n const rowId = rowIds[r0 + dr];\n if (rowId === undefined) {\n overflowRows.push(targetFields.map((_, dc) => blockRow[dc] ?? \"\"));\n return;\n }\n targetFields.forEach((fieldName, dc) => {\n // A ragged block (some rows shorter) leaves the cells it does not\n // reach alone rather than blanking them — matches Sheets.\n if (dc >= blockRow.length) return;\n const raw = blockRow[dc];\n // The bound check above puts this index inside the row; a hole would be\n // \"not reached\" too, and is left alone the same way.\n if (raw === undefined) return;\n cells.push({ rowId, fieldName, raw });\n });\n });\n\n return {\n cells,\n overflowRows,\n clippedColumns: Math.max(0, width - colsAvailable),\n fieldNames: targetFields,\n };\n}\n\n/** True when the parsed block is a single value — the common Cmd-V case. */\nexport function isSingleCell(block: readonly (readonly string[])[]): boolean {\n // A one-row block always has that row; `?.` keeps the read total.\n return block.length === 1 && block[0]?.length === 1;\n}\n\n/**\n * Would writing `next` over `prior` change what is stored? Objects compare by\n * their JSON, the same test the inline editor uses to skip a no-op write.\n */\nexport function storedValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || a === undefined || b === undefined) {\n return (a ?? null) === (b ?? null);\n }\n if (typeof a === \"object\" || typeof b === \"object\") {\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n }\n return false;\n}\n","/**\n * THE FILL MODEL — what a fill-down and a drag of the fill handle produce.\n *\n * WHY THE LITTLE SQUARE EXISTS. Cmd-D fills down, and a keyboard user is happy.\n * But the first thing a spreadsheet person reaches for is the small square in\n * the corner of the selection: they grab it, drag, and the column fills. A grid\n * without that square is a grid they have to be taught to use. This module is\n * the whole answer to \"what does that drag mean\", expressed as data so the same\n * rules hold for the keyboard shortcut, the handle, and anything later.\n *\n * PURE MODULE: no React, no DOM, no network. A fill is a pure function of the\n * rectangles, the current values, and the row/column order — which is what makes\n * every spreadsheet convention below testable without mounting a grid.\n *\n * THE CONVENTIONS THIS ENCODES, AND WHY EACH ONE IS NOT NEGOTIABLE:\n *\n * TILING. A two-cell pattern filling five cells lays down `a b a b a`. Users\n * select a pattern precisely so it repeats; writing the first value five times\n * is the behaviour of a grid that has never been used for data entry.\n *\n * A SINGLE NUMBER IS NOT A SERIES. One cell holding `5` dragged down gives\n * five 5s, not 5, 6, 7, 8, 9. This is the classic bug: it looks clever, and it\n * silently corrupts every column of ids, quantities and prices a user drags.\n * A run needs at least two values agreeing on a step before it is a run, and\n * `1, 2, 4` — inconsistent — is not one either.\n *\n * DIRECTION. A fill upward continues the run upward: `3, 2` dragged up gives\n * `1, 0, -1`. The series is continued, never mirrored.\n *\n * ONE AXIS. A diagonal drag resolves to whichever axis moved further, exactly\n * as every spreadsheet does; a drag that stays inside the selection fills\n * nothing rather than guessing.\n *\n * NOTHING FAILS SILENTLY. A column that cannot take a written value produces\n * no patch AND one `skipped` entry per cell carrying a plain sentence, so the\n * grid can tell the user what did not happen. Nothing here throws: a\n * `valueAt` that blows up is read as blank and its dependent cell is skipped\n * with a reason.\n *\n * The rectangles are index-based because a drag is a geometric gesture over the\n * grid's CURRENT visual order; the RESULT is addressed by `rowId`/`columnId`, so\n * the patches survive the re-sort that follows a write (the same reason\n * `grid-selection.ts` addresses cells by id).\n */\n\nimport { boundsContain, type RangeBounds } from \"./grid-selection\";\n\n/** A rectangle of the grid, as inclusive indexes into the visual order. */\nexport interface FillRect {\n top: number;\n left: number;\n bottom: number;\n right: number;\n}\n\nexport interface FillRequest {\n /** The cells the user had selected — the PATTERN. */\n source: { top: number; left: number; bottom: number; right: number };\n /** The rectangle the handle was dragged to. Must share an edge with source. */\n target: { top: number; left: number; bottom: number; right: number };\n /** Reads the current value of a cell. Column is addressed by index into `columnIds`. */\n valueAt: (rowIndex: number, columnIndex: number) => unknown;\n /** Column ids in visual order, for the returned patches. */\n columnIds: string[];\n /** Row ids in visual order, for the returned patches. */\n rowIds: string[];\n /** Per-column opt-out: a column that cannot take a written value (a formula column, a read-only column). */\n isColumnWritable?: (columnId: string) => boolean;\n /** \"copy\" repeats the pattern; \"series\" continues a detected run. Default \"series\". */\n mode?: \"copy\" | \"series\";\n}\n\nexport interface CellPatch {\n rowId: string;\n columnId: string;\n value: unknown;\n}\n\nexport interface FillSkip {\n rowId: string;\n columnId: string;\n reason: string;\n}\n\nexport interface FillResult {\n patches: CellPatch[];\n /** Cells inside the target that were skipped, and the plain-English reason. */\n skipped: FillSkip[];\n /** What the fill did, for a status line: e.g. \"Filled 24 cells down 4 columns.\" */\n summary: string;\n}\n\nexport type SeriesKind = \"number\" | \"date\" | \"none\";\n\nexport interface SeriesDetection {\n kind: SeriesKind;\n step: number;\n}\n\ntype FillDirection = \"down\" | \"up\" | \"right\" | \"left\";\n\nconst NO_SERIES: SeriesDetection = { kind: \"none\", step: 0 };\nconst MS_PER_DAY = 86_400_000;\n\n// ─── Small English helpers ──────────────────────────────────────────────────\n//\n// A status line a person reads is part of the feature, not decoration: \"Filled\n// 24 cells down 4 columns.\" is the difference between a grid that reports what\n// it did and one that changes 24 values behind your back.\n\nfunction plural(count: number, one: string, many: string): string {\n return `${count} ${count === 1 ? one : many}`;\n}\n\n// ─── Geometry ───────────────────────────────────────────────────────────────\n\nfunction isValidRect(rect: FillRect): boolean {\n return (\n Number.isInteger(rect.top) &&\n Number.isInteger(rect.left) &&\n Number.isInteger(rect.bottom) &&\n Number.isInteger(rect.right) &&\n rect.top <= rect.bottom &&\n rect.left <= rect.right\n );\n}\n\nfunction asBounds(rect: FillRect): RangeBounds {\n return { r0: rect.top, r1: rect.bottom, c0: rect.left, c1: rect.right };\n}\n\n/**\n * Which way does this fill run? Null when the target does not share an edge\n * with the source, which is the only shape a handle drag can produce and the\n * only shape whose tiling is defined.\n */\nfunction directionOf(source: FillRect, target: FillRect): FillDirection | null {\n if (!isValidRect(source) || !isValidRect(target)) return null;\n if (target.top === source.bottom + 1) return \"down\";\n if (target.bottom === source.top - 1) return \"up\";\n if (target.left === source.right + 1) return \"right\";\n if (target.right === source.left - 1) return \"left\";\n return null;\n}\n\n/**\n * Where does index `i` read from, given a pattern occupying `a..b`?\n *\n * This is the tiling rule, per axis: past the end the pattern repeats forward,\n * before the start it repeats backward, so `a b` extended in either direction\n * stays `… a b [a b] a b …`.\n */\nexport function tileIndex(i: number, a: number, b: number): number {\n const n = b - a + 1;\n if (n <= 0) return a;\n if (i >= a && i <= b) return i;\n if (i > b) return a + ((i - a) % n);\n return b - ((a - 1 - i) % n);\n}\n\n// ─── Series detection ───────────────────────────────────────────────────────\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DATE_ISH = /^\\d{4}-\\d{2}-\\d{2}([T ]|$)/;\n\nfunction dateMs(v: unknown): number | null {\n if (v instanceof Date) {\n const t = v.getTime();\n return Number.isFinite(t) ? t : null;\n }\n if (typeof v === \"string\" && DATE_ISH.test(v)) {\n const t = Date.parse(v);\n return Number.isFinite(t) ? t : null;\n }\n return null;\n}\n\n/**\n * Detects a numeric or date run in a list of values. Exported because it is the\n * interesting part — and because the single-value case is where grids get this\n * wrong, so it deserves its own name and its own tests.\n */\nexport function detectSeries(values: unknown[]): SeriesDetection {\n // ONE VALUE IS NEVER A RUN. Two values are the minimum evidence of intent.\n if (!Array.isArray(values) || values.length < 2) return NO_SERIES;\n\n if (values.every(isFiniteNumber)) {\n const nums = values as number[];\n const first = nums[0];\n const second = nums[1];\n if (first === undefined || second === undefined) return NO_SERIES;\n const step = second - first;\n for (let i = 1; i < nums.length; i += 1) {\n const prev = nums[i - 1];\n const cur = nums[i];\n if (prev === undefined || cur === undefined) return NO_SERIES;\n if (cur - prev !== step) return NO_SERIES;\n }\n return { kind: \"number\", step };\n }\n\n const stamps = values.map(dateMs);\n if (stamps.every((t): t is number => t !== null)) {\n const first = stamps[0];\n const second = stamps[1];\n if (first === undefined || second === undefined) return NO_SERIES;\n const deltaMs = second - first;\n // Whole days only, in UTC. A run of month-ends is a different feature and\n // guessing at it would silently write the wrong dates.\n if (deltaMs % MS_PER_DAY !== 0) return NO_SERIES;\n for (let i = 1; i < stamps.length; i += 1) {\n const prev = stamps[i - 1];\n const cur = stamps[i];\n if (prev === undefined || cur === undefined) return NO_SERIES;\n if (cur - prev !== deltaMs) return NO_SERIES;\n }\n return { kind: \"date\", step: deltaMs / MS_PER_DAY };\n }\n\n return NO_SERIES;\n}\n\nfunction formatDateLike(ms: number, sample: unknown): string {\n const iso = new Date(ms).toISOString();\n if (typeof sample === \"string\" && DATE_ONLY.test(sample)) return iso.slice(0, 10);\n return iso;\n}\n\n/**\n * Continue `line` by `k` steps past its END, in reading order. Filling upward\n * passes the same positive `k`: the run is continued, never reflected, which is\n * why `3, 2` filled upward reads 1, 0, -1 away from the selection.\n */\nfunction extrapolate(line: unknown[], series: SeriesDetection, k: number): unknown {\n const last = line[line.length - 1];\n if (series.kind === \"number\" && isFiniteNumber(last)) {\n return last + k * series.step;\n }\n if (series.kind === \"date\") {\n const base = dateMs(last);\n if (base === null) return jsonable(last);\n return formatDateLike(base + k * series.step * MS_PER_DAY, last);\n }\n return jsonable(last);\n}\n\n/** Every value we hand back is plain data — a Date becomes its ISO string. */\nfunction jsonable(value: unknown): unknown {\n if (value instanceof Date) {\n const t = value.getTime();\n return Number.isFinite(t) ? value.toISOString() : null;\n }\n if (value === undefined) return null;\n return value;\n}\n\n// ─── The fill ───────────────────────────────────────────────────────────────\n\nfunction emptyResult(summary: string): FillResult {\n return { patches: [], skipped: [], summary };\n}\n\nexport function fillRange(request: FillRequest): FillResult {\n const { source, target, columnIds, rowIds } = request;\n const mode = request.mode ?? \"series\";\n const isWritable = request.isColumnWritable ?? (() => true);\n\n const direction = directionOf(source, target);\n if (!direction) {\n return emptyResult(\n \"Filled 0 cells because the fill area does not touch the selection.\",\n );\n }\n if (rowIds.length === 0 || columnIds.length === 0) {\n return emptyResult(\"Filled 0 cells because the table is empty.\");\n }\n\n // A read that throws is a blank, never an exception escaping the model.\n const read = (r: number, c: number): { ok: boolean; value: unknown } => {\n try {\n return { ok: true, value: request.valueAt(r, c) };\n } catch {\n return { ok: false, value: undefined };\n }\n };\n\n const vertical = direction === \"down\" || direction === \"up\";\n const forward = direction === \"down\" || direction === \"right\";\n\n const fillStart = vertical ? source.top : source.left;\n const fillEnd = vertical ? source.bottom : source.right;\n const crossStart = vertical ? source.left : source.top;\n const crossEnd = vertical ? source.right : source.bottom;\n\n const maxFill = (vertical ? rowIds.length : columnIds.length) - 1;\n const maxCross = (vertical ? columnIds.length : rowIds.length) - 1;\n\n const rawFillFrom = vertical ? target.top : target.left;\n const rawFillTo = vertical ? target.bottom : target.right;\n const rawCrossFrom = vertical ? target.left : target.top;\n const rawCrossTo = vertical ? target.right : target.bottom;\n\n const fillFrom = Math.max(rawFillFrom, 0);\n const fillTo = Math.min(rawFillTo, maxFill);\n const crossFrom = Math.max(rawCrossFrom, 0);\n const crossTo = Math.min(rawCrossTo, maxCross);\n\n const requested =\n Math.max(rawFillTo - rawFillFrom + 1, 0) *\n Math.max(rawCrossTo - rawCrossFrom + 1, 0);\n const inside =\n Math.max(fillTo - fillFrom + 1, 0) * Math.max(crossTo - crossFrom + 1, 0);\n const outside = Math.max(requested - inside, 0);\n\n const sourceBounds = asBounds(source);\n const patches: CellPatch[] = [];\n const skipped: FillSkip[] = [];\n const touchedColumns = new Set<string>();\n\n // The pattern line for each cross position, read ONCE. Reading it per target\n // cell would call the host's `valueAt` thousands of times for a long drag.\n const lines = new Map<number, { values: unknown[]; reads: boolean[]; series: SeriesDetection }>();\n const lineFor = (crossIndex: number) => {\n const srcCross = tileIndex(crossIndex, crossStart, crossEnd);\n const cached = lines.get(srcCross);\n if (cached) return cached;\n const values: unknown[] = [];\n const reads: boolean[] = [];\n for (let f = fillStart; f <= fillEnd; f += 1) {\n const r = vertical ? f : srcCross;\n const c = vertical ? srcCross : f;\n const got = read(r, c);\n values.push(got.value);\n reads.push(got.ok);\n }\n const series = mode === \"series\" ? detectSeries(values) : NO_SERIES;\n const line = { values, reads, series };\n lines.set(srcCross, line);\n return line;\n };\n\n for (let f = fillFrom; f <= fillTo; f += 1) {\n for (let x = crossFrom; x <= crossTo; x += 1) {\n const rowIndex = vertical ? f : x;\n const colIndex = vertical ? x : f;\n const rowId = rowIds[rowIndex];\n const columnId = columnIds[colIndex];\n if (rowId === undefined || columnId === undefined) continue;\n\n // The pattern is never rewritten with itself.\n if (boundsContain(sourceBounds, rowIndex, colIndex)) continue;\n\n if (!isWritable(columnId)) {\n skipped.push({\n rowId,\n columnId,\n reason: `${columnId} cannot be filled because that column is read-only.`,\n });\n continue;\n }\n\n const line = lineFor(x);\n if (line.series.kind !== \"none\") {\n const k = forward ? f - fillEnd : fillStart - f;\n patches.push({ rowId, columnId, value: jsonable(extrapolate(line.values, line.series, k)) });\n touchedColumns.add(columnId);\n continue;\n }\n\n const srcFill = tileIndex(f, fillStart, fillEnd);\n const patternIndex = srcFill - fillStart;\n if (line.reads[patternIndex] !== true) {\n skipped.push({\n rowId,\n columnId,\n reason: `The value to copy into ${columnId} could not be read, so that cell was left alone.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: jsonable(line.values[patternIndex]) });\n touchedColumns.add(columnId);\n }\n }\n\n return { patches, skipped, summary: summarize(direction, patches, skipped, touchedColumns, outside) };\n}\n\nfunction summarize(\n direction: FillDirection,\n patches: CellPatch[],\n skipped: FillSkip[],\n touchedColumns: Set<string>,\n outside: number,\n): string {\n const parts: string[] = [];\n if (patches.length === 0) {\n parts.push(\"Filled 0 cells because there was nothing to fill.\");\n } else if (direction === \"down\" || direction === \"up\") {\n const word = direction === \"down\" ? \"down\" : \"up\";\n parts.push(\n `Filled ${plural(patches.length, \"cell\", \"cells\")} ${word} ${plural(touchedColumns.size, \"column\", \"columns\")}.`,\n );\n } else {\n const word = direction === \"right\" ? \"right\" : \"left\";\n parts.push(\n `Filled ${plural(patches.length, \"cell\", \"cells\")} ${word} across ${plural(touchedColumns.size, \"column\", \"columns\")}.`,\n );\n }\n if (skipped.length > 0) {\n parts.push(`Skipped ${plural(skipped.length, \"cell\", \"cells\")} that could not be filled.`);\n }\n if (outside > 0) {\n parts.push(`Ignored ${plural(outside, \"cell\", \"cells\")} outside the table.`);\n }\n return parts.join(\" \");\n}\n\n/** Cmd-D: fill the top row of the selection into the rest of it. */\nexport function fillDown(\n request: Omit<FillRequest, \"source\" | \"target\"> & { range: FillRequest[\"source\"] },\n): FillResult {\n const { range, ...rest } = request;\n if (!isValidRect(range)) {\n return emptyResult(\"Filled 0 cells because the selection is not a rectangle.\");\n }\n if (range.top === range.bottom) {\n return emptyResult(\"Filled 0 cells because the selection is a single row.\");\n }\n return fillRange({\n ...rest,\n source: { top: range.top, left: range.left, bottom: range.top, right: range.right },\n target: { top: range.top + 1, left: range.left, bottom: range.bottom, right: range.right },\n });\n}\n\n/**\n * The rectangle a drag from a handle at the selection's corner to (row, column)\n * produces — or null when the drag stayed inside the selection and there is\n * nothing to fill.\n *\n * A DIAGONAL DRAG RESOLVES TO ONE AXIS: whichever moved further, with a tie\n * going to the vertical, because filling a column is the gesture people mean.\n */\nexport function fillTargetFor(\n source: FillRequest[\"source\"],\n toRow: number,\n toColumn: number,\n): FillRequest[\"target\"] | null {\n if (!isValidRect(source)) return null;\n if (!Number.isInteger(toRow) || !Number.isInteger(toColumn)) return null;\n\n const below = toRow - source.bottom;\n const above = source.top - toRow;\n const rightOf = toColumn - source.right;\n const leftOf = source.left - toColumn;\n\n const vertical = Math.max(below, above, 0);\n const horizontal = Math.max(rightOf, leftOf, 0);\n if (vertical === 0 && horizontal === 0) return null;\n\n if (vertical >= horizontal) {\n if (below > 0) {\n return { top: source.bottom + 1, bottom: toRow, left: source.left, right: source.right };\n }\n return { top: toRow, bottom: source.top - 1, left: source.left, right: source.right };\n }\n if (rightOf > 0) {\n return { top: source.top, bottom: source.bottom, left: source.right + 1, right: toColumn };\n }\n return { top: source.top, bottom: source.bottom, left: toColumn, right: source.left - 1 };\n}\n","/**\n * THE CELL UNDO STACK — the model behind Cmd-Z in a grid.\n *\n * WHY IT SHIPS WITH EASY EDITING, NOT AFTER IT. Lowering the friction to change\n * data raises the rate of accidental change: a click toggles a boolean, a bare\n * keystroke starts an edit, Delete empties a cell, and one drag of the fill\n * handle rewrites a column. Every one of those is a good affordance ONLY if the\n * floor underneath it is recoverable.\n *\n * WHAT THIS FILE IS. A bounded, PURE stack of reversible edits: no React, no\n * network, no toasts. The app's `useCellUndo` hook owned all three plus the\n * stack, which meant the stack's rules — depth, the redo contract, what an undo\n * of an added row means — could only be exercised by mounting a component and\n * mocking a database. Those rules are spreadsheet law, not app trivia, so they\n * belong in a module anyone can test in a millisecond and any client can reuse.\n *\n * WHY IT IS PATCHES, NOT CELL COORDINATES. An entry carries the patches that\n * undo it and the patches that redo it, captured BEFORE the write and never\n * derived after it: re-reading a cell later to discover what to restore races\n * with realtime and with agent writes, and would happily \"undo\" to a value\n * somebody else just set. Patches also make the entry shape indifferent to WHAT\n * changed — one cell, a filled column, or the cells of a row that was added or\n * deleted — which is how row and column operations join the same Cmd-Z the user\n * already trusts instead of living outside it.\n *\n * PURITY IS THE POINT. Every function returns a NEW stack and never mutates the\n * one it was handed, so the stack can live in React state, in a store, or in a\n * ref without the \"my undo history changed under me\" class of bug.\n */\n\nimport type { CellPatch } from \"./fill\";\n\nexport type { CellPatch };\n\nexport interface UndoEntry {\n /** What the user did, in plain English, for the toast: \"Undid the change to Day rate.\" */\n label: string;\n /** The patches that UNDO it. */\n undo: CellPatch[];\n /** The patches that REDO it. */\n redo: CellPatch[];\n}\n\nexport interface UndoStack {\n past: UndoEntry[];\n future: UndoEntry[];\n depth: number;\n}\n\n/**\n * How many steps back we remember. Deliberately generous — the cost is a few\n * kilobytes of session memory, and the failure mode of a too-short stack is a\n * user discovering their mistake is unrecoverable.\n */\nexport const DEFAULT_UNDO_DEPTH = 100;\n\nexport function createUndoStack(depth: number = DEFAULT_UNDO_DEPTH): UndoStack {\n const bounded = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 0;\n return { past: [], future: [], depth: bounded };\n}\n\n/**\n * Record an edit that already happened.\n *\n * Pushing CLEARS the redo future — the standard editor contract: you cannot\n * redo into a future you have just diverged from. Past `depth`, the OLDEST\n * entry is dropped, so the stack is bounded memory rather than a leak.\n */\nexport function pushUndo(stack: UndoStack, entry: UndoEntry): UndoStack {\n if (stack.depth <= 0) {\n // Undo is off. Pushing keeps the stack empty rather than silently growing\n // a history nothing will ever read.\n return { past: [], future: [], depth: stack.depth };\n }\n const past = [...stack.past, entry];\n while (past.length > stack.depth) past.shift();\n return { past, future: [], depth: stack.depth };\n}\n\nexport function popUndo(stack: UndoStack): { stack: UndoStack; entry: UndoEntry | null } {\n const entry = stack.past[stack.past.length - 1];\n if (entry === undefined) return { stack, entry: null };\n return {\n stack: {\n past: stack.past.slice(0, -1),\n future: [...stack.future, entry],\n depth: stack.depth,\n },\n entry,\n };\n}\n\nexport function popRedo(stack: UndoStack): { stack: UndoStack; entry: UndoEntry | null } {\n const entry = stack.future[stack.future.length - 1];\n if (entry === undefined) return { stack, entry: null };\n const past = [...stack.past, entry];\n while (past.length > stack.depth) past.shift();\n return {\n stack: { past, future: stack.future.slice(0, -1), depth: stack.depth },\n entry,\n };\n}\n\nfunction keyOf(patch: CellPatch): string {\n // JSON, not a separator character: a row id or column id containing whatever\n // separator we picked would collide two different cells into one key, and the\n // undo would restore the wrong one.\n return JSON.stringify([patch.rowId, patch.columnId]);\n}\n\n/**\n * Build an entry from a before/after pair, so a caller never assembles one by\n * hand — hand-assembled inverses are how an undo ends up restoring the wrong\n * cell.\n *\n * An after-patch with NO matching before is still undoable: its undo sets the\n * value back to `null`. That is the added-row case — a cell that did not exist\n * before the change has no prior value, and \"undo\" for it means empty, not\n * \"leave whatever is there\".\n */\nexport function undoEntryFor(\n before: CellPatch[],\n after: CellPatch[],\n label: string,\n): UndoEntry {\n const priors = new Map<string, CellPatch>();\n for (const patch of before) priors.set(keyOf(patch), patch);\n\n const undo: CellPatch[] = after.map((patch) => {\n const prior = priors.get(keyOf(patch));\n return {\n rowId: patch.rowId,\n columnId: patch.columnId,\n value: prior ? prior.value : null,\n };\n });\n\n return {\n label,\n undo,\n redo: after.map((patch) => ({\n rowId: patch.rowId,\n columnId: patch.columnId,\n value: patch.value,\n })),\n };\n}\n\n/** True when there is a step to take back. */\nexport function canUndo(stack: UndoStack): boolean {\n return stack.depth > 0 && stack.past.length > 0;\n}\n\n/** True when there is a step to reapply. */\nexport function canRedo(stack: UndoStack): boolean {\n return stack.depth > 0 && stack.future.length > 0;\n}\n"],"mappings":";;;AA8BA,SAAS,aAAa,WAAW,SAAS,QAAQ,gBAAgB;;;ACmE3D,SAAS,YACd,OACA,QACA,YACoB;AACpB,QAAM,KAAK,OAAO,QAAQ,MAAM,OAAO,KAAK;AAC5C,QAAM,KAAK,OAAO,QAAQ,MAAM,MAAM,KAAK;AAC3C,QAAM,KAAK,WAAW,QAAQ,MAAM,OAAO,SAAS;AACpD,QAAM,KAAK,WAAW,QAAQ,MAAM,MAAM,SAAS;AACnD,MAAI,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,GAAI,QAAO;AAC7D,SAAO;AAAA,IACL,IAAI,KAAK,IAAI,IAAI,EAAE;AAAA,IACnB,IAAI,KAAK,IAAI,IAAI,EAAE;AAAA,IACnB,IAAI,KAAK,IAAI,IAAI,EAAE;AAAA,IACnB,IAAI,KAAK,IAAI,IAAI,EAAE;AAAA,EACrB;AACF;AAYO,SAAS,cACd,QACA,UACA,UACS;AACT,SACE,YAAY,OAAO,MACnB,YAAY,OAAO,MACnB,YAAY,OAAO,MACnB,YAAY,OAAO;AAEvB;AA0BO,SAAS,UACd,OACA,QACA,YACiB;AACjB,QAAM,IAAI,YAAY,OAAO,QAAQ,UAAU;AAC/C,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,QAAM,OAAwB,CAAC;AAE/B,WAAS,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI,KAAK,GAAG;AACpC,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,UAAU,OAAW;AACzB,UAAM,MAAqB,CAAC;AAC5B,aAAS,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI,KAAK,GAAG;AACpC,YAAM,YAAY,WAAW,CAAC;AAC9B,UAAI,cAAc,OAAW;AAC7B,UAAI,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,IAC/B;AACA,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO;AACT;AAOO,SAAS,SACd,OACA,YACkB;AAClB,QAAM,aAAa,WAAW,CAAC;AAC/B,QAAM,YAAY,WAAW,WAAW,SAAS,CAAC;AAGlD,MAAI,eAAe,UAAa,cAAc,OAAW,QAAO;AAChE,SAAO;AAAA,IACL,QAAQ,EAAE,OAAO,WAAW,WAAW;AAAA,IACvC,OAAO,EAAE,OAAO,WAAW,UAAU;AAAA,EACvC;AACF;AAGO,SAAS,YACd,WACA,QACkB;AAClB,QAAM,WAAW,OAAO,CAAC;AACzB,QAAM,UAAU,OAAO,OAAO,SAAS,CAAC;AACxC,MAAI,aAAa,UAAa,YAAY,OAAW,QAAO;AAC5D,SAAO;AAAA,IACL,QAAQ,EAAE,OAAO,UAAU,UAAU;AAAA,IACrC,OAAO,EAAE,OAAO,SAAS,UAAU;AAAA,EACrC;AACF;AAGO,SAAS,SACd,QACA,YACkB;AAClB,QAAM,WAAW,OAAO,CAAC;AACzB,QAAM,UAAU,OAAO,OAAO,SAAS,CAAC;AACxC,QAAM,aAAa,WAAW,CAAC;AAC/B,QAAM,YAAY,WAAW,WAAW,SAAS,CAAC;AAClD,MACE,aAAa,UACb,YAAY,UACZ,eAAe,UACf,cAAc,QACd;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,QAAQ,EAAE,OAAO,UAAU,WAAW,WAAW;AAAA,IACjD,OAAO,EAAE,OAAO,SAAS,WAAW,UAAU;AAAA,EAChD;AACF;AAEO,SAAS,SACd,GACA,GACS;AACT,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO,MAAM;AAC3B,SAAO,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE;AAClD;AAWO,SAAS,cACd,SACA,MACA,QACA,YACoB;AACpB,MAAI,OAAO,WAAW,KAAK,WAAW,WAAW,EAAG,QAAO;AAE3D,QAAM,aAAa,OAAO,CAAC;AAC3B,QAAM,iBAAiB,WAAW,CAAC;AAGnC,MAAI,eAAe,UAAa,mBAAmB,OAAW,QAAO;AACrE,QAAM,QAAqB,EAAE,OAAO,YAAY,WAAW,eAAe;AAC1E,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,IAAI,OAAO,QAAQ,QAAQ,KAAK;AACpC,MAAI,IAAI,WAAW,QAAQ,QAAQ,SAAS;AAI5C,MAAI,MAAM,MAAM,MAAM,GAAI,QAAO;AAEjC,QAAM,UAAU,OAAO,SAAS;AAChC,QAAM,UAAU,WAAW,SAAS;AACpC,QAAM,QAAQ,CAAC,GAAW,QAAgB,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,GAAG;AAEtE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,UAAI,MAAM,IAAI,GAAG,OAAO;AACxB;AAAA,IACF,KAAK;AACH,UAAI,MAAM,IAAI,GAAG,OAAO;AACxB;AAAA,IACF,KAAK;AACH,UAAI,MAAM,IAAI,GAAG,OAAO;AACxB;AAAA,IACF,KAAK;AACH,UAAI,MAAM,IAAI,GAAG,OAAO;AACxB;AAAA,IACF,KAAK;AACH,UAAI;AACJ;AAAA,IACF,KAAK;AACH,UAAI;AACJ;AAAA,IACF,KAAK;AACH,UAAI;AACJ,UAAI;AACJ;AAAA,IACF,KAAK;AACH,UAAI;AACJ,UAAI;AACJ;AAAA,IACF,KAAK;AACH,UAAI,IAAI,QAAS,MAAK;AAAA,eACb,IAAI,SAAS;AACpB,aAAK;AACL,YAAI;AAAA,MACN;AACA;AAAA,IACF,KAAK;AACH,UAAI,IAAI,EAAG,MAAK;AAAA,eACP,IAAI,GAAG;AACd,aAAK;AACL,YAAI;AAAA,MACN;AACA;AAAA,EACJ;AAKA,QAAM,QAAQ,OAAO,CAAC;AACtB,QAAM,YAAY,WAAW,CAAC;AAC9B,MAAI,UAAU,UAAa,cAAc,OAAW,QAAO;AAC3D,SAAO,EAAE,OAAO,UAAU;AAC5B;AAWO,SAAS,YAAY,GAKhB;AACV,MAAI,EAAE,WAAW,EAAE,WAAW,EAAE,OAAQ,QAAO;AAC/C,SAAO,EAAE,IAAI,WAAW,KAAK,EAAE,QAAQ;AACzC;AAMO,SAAS,gBAAgB,GAMd;AAChB,QAAM,MAAM,EAAE,WAAW,EAAE;AAI3B,QAAM,YAAsC;AAAA,IAC1C,SAAS,MAAM,cAAc;AAAA,IAC7B,WAAW,MAAM,YAAY;AAAA,IAC7B,WAAW,MAAM,aAAa;AAAA,IAC9B,YAAY,MAAM,WAAW;AAAA,IAC7B,MAAM,MAAM,cAAc;AAAA,IAC1B,KAAK,MAAM,YAAY;AAAA,EACzB;AAIA,QAAM,QAAQ,UAAU,EAAE,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,WAAO,EAAE,MAAM,EAAE,WAAW,WAAW,QAAQ,MAAM,MAAM;AAAA,EAC7D;AAEA,UAAQ,EAAE,KAAK;AAAA,IACb,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,EAAE,WAAW,aAAa,WAAW;AAAA,IACpE,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB,KAAK;AAIH,UAAI,EAAE,SAAU,QAAO,EAAE,MAAM,YAAY;AAC3C,UAAI,IAAK,QAAO,EAAE,MAAM,eAAe;AACvC,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB,KAAK;AAAA,IACL,KAAK;AACH,UAAI,IAAK,QAAO,EAAE,MAAM,YAAY;AACpC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,UAAI,IAAK,QAAO,EAAE,MAAM,WAAW;AACnC;AAAA,IACF,KAAK;AACH,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,MAAM,YAAY;AAAA,IAC7B,KAAK;AAAA,IACL,KAAK;AACH,UAAI,IAAK,QAAO,EAAE,MAAM,OAAO;AAC/B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,UAAI,IAAK,QAAO,EAAE,MAAM,MAAM;AAC9B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,UAAI,IAAK,QAAO,EAAE,MAAM,QAAQ;AAChC;AAAA,EACJ;AAEA,MAAI,YAAY,CAAC,EAAG,QAAO,EAAE,MAAM,cAAc,MAAM,EAAE,IAAI;AAC7D,SAAO;AACT;;;AClZO,SAAS,kBAAkB,OAAwB;AACxD,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC1D,SAAO,OAAO,KAAK;AACrB;AAGA,SAAS,QAAQ,MAAsB;AACrC,SAAO,YAAY,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC,MAAM;AACpE;AAGO,SAAS,UAAU,OAAgD;AACxE,SAAO,MACJ,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,QAAQ,kBAAkB,IAAI,CAAC,CAAC,EAAE,KAAK,GAAI,CAAC,EAC3E,KAAK,IAAI;AACd;AAcO,SAAS,mBAAmB,MAA0B;AAC3D,QAAM,OAAmB,CAAC;AAC1B,MAAI,MAAgB,CAAC;AACrB,MAAI,OAAO;AACX,MAAI,IAAI;AACR,QAAM,IAAI,KAAK;AAEf,SAAO,IAAI,GAAG;AACZ,UAAM,KAAK,KAAK,CAAC;AAGjB,QAAI,SAAS,MAAM,OAAO,KAAK;AAE7B,UAAI,IAAI,IAAI;AACZ,UAAI,MAAM;AACV,UAAI,SAAS;AACb,aAAO,IAAI,GAAG;AACZ,cAAM,IAAI,KAAK,CAAC;AAChB,YAAI,MAAM,KAAK;AACb,cAAI,KAAK,IAAI,CAAC,MAAM,KAAK;AACvB,mBAAO;AACP,iBAAK;AACL;AAAA,UACF;AACA,mBAAS;AACT,eAAK;AACL;AAAA,QACF;AACA,eAAO;AACP,aAAK;AAAA,MACP;AACA,UAAI,WAAW,KAAK,KAAK,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,OAAO;AAClF,eAAO;AACP,YAAI;AAEJ,YAAI,KAAK,EAAG;AACZ,cAAM,IAAI,KAAK,CAAC;AAChB,YAAI,MAAM,KAAM;AACd,cAAI,KAAK,IAAI;AACb,iBAAO;AACP,eAAK;AACL;AAAA,QACF;AAEA,YAAI,KAAK,IAAI;AACb,aAAK,KAAK,GAAG;AACb,cAAM,CAAC;AACP,eAAO;AACP,aAAK,MAAM,QAAQ,KAAK,IAAI,CAAC,MAAM,OAAO,IAAI;AAC9C;AAAA,MACF;AAGA,cAAQ;AACR,WAAK;AACL;AAAA,IACF;AAEA,QAAI,OAAO,KAAM;AACf,UAAI,KAAK,IAAI;AACb,aAAO;AACP,WAAK;AACL;AAAA,IACF;AACA,QAAI,OAAO,QAAQ,OAAO,MAAM;AAC9B,UAAI,KAAK,IAAI;AACb,WAAK,KAAK,GAAG;AACb,YAAM,CAAC;AACP,aAAO;AACP,WAAK,OAAO,QAAQ,KAAK,IAAI,CAAC,MAAM,OAAO,IAAI;AAC/C;AAAA,IACF;AACA,YAAQ;AACR,SAAK;AAAA,EACP;AAIA,QAAM,eAAe,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,CAAC,MAAM;AACvE,MAAI,CAAC,cAAc;AACjB,QAAI,KAAK,IAAI;AACb,SAAK,KAAK,GAAG;AAAA,EACf,WAAW,IAAI,SAAS,GAAG;AACzB,QAAI,KAAK,IAAI;AACb,SAAK,KAAK,GAAG;AAAA,EACf;AAEA,SAAO,KAAK,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI;AACtC;;;AC7CA,IAAM,YAA6B,EAAE,MAAM,QAAQ,MAAM,EAAE;AAC3D,IAAM,aAAa;AAQnB,SAAS,OAAO,OAAe,KAAa,MAAsB;AAChE,SAAO,GAAG,KAAK,IAAI,UAAU,IAAI,MAAM,IAAI;AAC7C;AAIA,SAAS,YAAY,MAAyB;AAC5C,SACE,OAAO,UAAU,KAAK,GAAG,KACzB,OAAO,UAAU,KAAK,IAAI,KAC1B,OAAO,UAAU,KAAK,MAAM,KAC5B,OAAO,UAAU,KAAK,KAAK,KAC3B,KAAK,OAAO,KAAK,UACjB,KAAK,QAAQ,KAAK;AAEtB;AAEA,SAAS,SAAS,MAA6B;AAC7C,SAAO,EAAE,IAAI,KAAK,KAAK,IAAI,KAAK,QAAQ,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM;AACxE;AAOA,SAAS,YAAY,QAAkB,QAAwC;AAC7E,MAAI,CAAC,YAAY,MAAM,KAAK,CAAC,YAAY,MAAM,EAAG,QAAO;AACzD,MAAI,OAAO,QAAQ,OAAO,SAAS,EAAG,QAAO;AAC7C,MAAI,OAAO,WAAW,OAAO,MAAM,EAAG,QAAO;AAC7C,MAAI,OAAO,SAAS,OAAO,QAAQ,EAAG,QAAO;AAC7C,MAAI,OAAO,UAAU,OAAO,OAAO,EAAG,QAAO;AAC7C,SAAO;AACT;AASO,SAAS,UAAU,GAAW,GAAW,GAAmB;AACjE,QAAM,IAAI,IAAI,IAAI;AAClB,MAAI,KAAK,EAAG,QAAO;AACnB,MAAI,KAAK,KAAK,KAAK,EAAG,QAAO;AAC7B,MAAI,IAAI,EAAG,QAAO,KAAM,IAAI,KAAK;AACjC,SAAO,KAAM,IAAI,IAAI,KAAK;AAC5B;AAIA,SAAS,eAAe,GAAyB;AAC/C,SAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACnD;AAEA,IAAM,YAAY;AAClB,IAAM,WAAW;AAEjB,SAAS,OAAO,GAA2B;AACzC,MAAI,aAAa,MAAM;AACrB,UAAM,IAAI,EAAE,QAAQ;AACpB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,MAAI,OAAO,MAAM,YAAY,SAAS,KAAK,CAAC,GAAG;AAC7C,UAAM,IAAI,KAAK,MAAM,CAAC;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,QAAoC;AAE/D,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,EAAG,QAAO;AAExD,MAAI,OAAO,MAAM,cAAc,GAAG;AAChC,UAAM,OAAO;AACb,UAAM,QAAQ,KAAK,CAAC;AACpB,UAAM,SAAS,KAAK,CAAC;AACrB,QAAI,UAAU,UAAa,WAAW,OAAW,QAAO;AACxD,UAAM,OAAO,SAAS;AACtB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,YAAM,OAAO,KAAK,IAAI,CAAC;AACvB,YAAM,MAAM,KAAK,CAAC;AAClB,UAAI,SAAS,UAAa,QAAQ,OAAW,QAAO;AACpD,UAAI,MAAM,SAAS,KAAM,QAAO;AAAA,IAClC;AACA,WAAO,EAAE,MAAM,UAAU,KAAK;AAAA,EAChC;AAEA,QAAM,SAAS,OAAO,IAAI,MAAM;AAChC,MAAI,OAAO,MAAM,CAAC,MAAmB,MAAM,IAAI,GAAG;AAChD,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,SAAS,OAAO,CAAC;AACvB,QAAI,UAAU,UAAa,WAAW,OAAW,QAAO;AACxD,UAAM,UAAU,SAAS;AAGzB,QAAI,UAAU,eAAe,EAAG,QAAO;AACvC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,YAAM,OAAO,OAAO,IAAI,CAAC;AACzB,YAAM,MAAM,OAAO,CAAC;AACpB,UAAI,SAAS,UAAa,QAAQ,OAAW,QAAO;AACpD,UAAI,MAAM,SAAS,QAAS,QAAO;AAAA,IACrC;AACA,WAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,WAAW;AAAA,EACpD;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,IAAY,QAAyB;AAC3D,QAAM,MAAM,IAAI,KAAK,EAAE,EAAE,YAAY;AACrC,MAAI,OAAO,WAAW,YAAY,UAAU,KAAK,MAAM,EAAG,QAAO,IAAI,MAAM,GAAG,EAAE;AAChF,SAAO;AACT;AAOA,SAAS,YAAY,MAAiB,QAAyB,GAAoB;AACjF,QAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,MAAI,OAAO,SAAS,YAAY,eAAe,IAAI,GAAG;AACpD,WAAO,OAAO,IAAI,OAAO;AAAA,EAC3B;AACA,MAAI,OAAO,SAAS,QAAQ;AAC1B,UAAM,OAAO,OAAO,IAAI;AACxB,QAAI,SAAS,KAAM,QAAO,SAAS,IAAI;AACvC,WAAO,eAAe,OAAO,IAAI,OAAO,OAAO,YAAY,IAAI;AAAA,EACjE;AACA,SAAO,SAAS,IAAI;AACtB;AAGA,SAAS,SAAS,OAAyB;AACzC,MAAI,iBAAiB,MAAM;AACzB,UAAM,IAAI,MAAM,QAAQ;AACxB,WAAO,OAAO,SAAS,CAAC,IAAI,MAAM,YAAY,IAAI;AAAA,EACpD;AACA,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO;AACT;AAIA,SAAS,YAAY,SAA6B;AAChD,SAAO,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ;AAC7C;AAEO,SAAS,UAAU,SAAkC;AAC1D,QAAM,EAAE,QAAQ,QAAQ,WAAW,OAAO,IAAI;AAC9C,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,QAAQ,qBAAqB,MAAM;AAEtD,QAAM,YAAY,YAAY,QAAQ,MAAM;AAC5C,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,WAAW,KAAK,UAAU,WAAW,GAAG;AACjD,WAAO,YAAY,4CAA4C;AAAA,EACjE;AAGA,QAAM,OAAO,CAAC,GAAW,MAA+C;AACtE,QAAI;AACF,aAAO,EAAE,IAAI,MAAM,OAAO,QAAQ,QAAQ,GAAG,CAAC,EAAE;AAAA,IAClD,QAAQ;AACN,aAAO,EAAE,IAAI,OAAO,OAAO,OAAU;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,WAAW,cAAc,UAAU,cAAc;AACvD,QAAM,UAAU,cAAc,UAAU,cAAc;AAEtD,QAAM,YAAY,WAAW,OAAO,MAAM,OAAO;AACjD,QAAM,UAAU,WAAW,OAAO,SAAS,OAAO;AAClD,QAAM,aAAa,WAAW,OAAO,OAAO,OAAO;AACnD,QAAM,WAAW,WAAW,OAAO,QAAQ,OAAO;AAElD,QAAM,WAAW,WAAW,OAAO,SAAS,UAAU,UAAU;AAChE,QAAM,YAAY,WAAW,UAAU,SAAS,OAAO,UAAU;AAEjE,QAAM,cAAc,WAAW,OAAO,MAAM,OAAO;AACnD,QAAM,YAAY,WAAW,OAAO,SAAS,OAAO;AACpD,QAAM,eAAe,WAAW,OAAO,OAAO,OAAO;AACrD,QAAM,aAAa,WAAW,OAAO,QAAQ,OAAO;AAEpD,QAAM,WAAW,KAAK,IAAI,aAAa,CAAC;AACxC,QAAM,SAAS,KAAK,IAAI,WAAW,OAAO;AAC1C,QAAM,YAAY,KAAK,IAAI,cAAc,CAAC;AAC1C,QAAM,UAAU,KAAK,IAAI,YAAY,QAAQ;AAE7C,QAAM,YACJ,KAAK,IAAI,YAAY,cAAc,GAAG,CAAC,IACvC,KAAK,IAAI,aAAa,eAAe,GAAG,CAAC;AAC3C,QAAM,SACJ,KAAK,IAAI,SAAS,WAAW,GAAG,CAAC,IAAI,KAAK,IAAI,UAAU,YAAY,GAAG,CAAC;AAC1E,QAAM,UAAU,KAAK,IAAI,YAAY,QAAQ,CAAC;AAE9C,QAAM,eAAe,SAAS,MAAM;AACpC,QAAM,UAAuB,CAAC;AAC9B,QAAM,UAAsB,CAAC;AAC7B,QAAM,iBAAiB,oBAAI,IAAY;AAIvC,QAAM,QAAQ,oBAAI,IAA8E;AAChG,QAAM,UAAU,CAAC,eAAuB;AACtC,UAAM,WAAW,UAAU,YAAY,YAAY,QAAQ;AAC3D,UAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,QAAI,OAAQ,QAAO;AACnB,UAAM,SAAoB,CAAC;AAC3B,UAAM,QAAmB,CAAC;AAC1B,aAAS,IAAI,WAAW,KAAK,SAAS,KAAK,GAAG;AAC5C,YAAM,IAAI,WAAW,IAAI;AACzB,YAAM,IAAI,WAAW,WAAW;AAChC,YAAM,MAAM,KAAK,GAAG,CAAC;AACrB,aAAO,KAAK,IAAI,KAAK;AACrB,YAAM,KAAK,IAAI,EAAE;AAAA,IACnB;AACA,UAAM,SAAS,SAAS,WAAW,aAAa,MAAM,IAAI;AAC1D,UAAM,OAAO,EAAE,QAAQ,OAAO,OAAO;AACrC,UAAM,IAAI,UAAU,IAAI;AACxB,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,UAAU,KAAK,QAAQ,KAAK,GAAG;AAC1C,aAAS,IAAI,WAAW,KAAK,SAAS,KAAK,GAAG;AAC5C,YAAM,WAAW,WAAW,IAAI;AAChC,YAAM,WAAW,WAAW,IAAI;AAChC,YAAM,QAAQ,OAAO,QAAQ;AAC7B,YAAM,WAAW,UAAU,QAAQ;AACnC,UAAI,UAAU,UAAa,aAAa,OAAW;AAGnD,UAAI,cAAc,cAAc,UAAU,QAAQ,EAAG;AAErD,UAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA;AAAA,UACA,QAAQ,GAAG,QAAQ;AAAA,QACrB,CAAC;AACD;AAAA,MACF;AAEA,YAAM,OAAO,QAAQ,CAAC;AACtB,UAAI,KAAK,OAAO,SAAS,QAAQ;AAC/B,cAAM,IAAI,UAAU,IAAI,UAAU,YAAY;AAC9C,gBAAQ,KAAK,EAAE,OAAO,UAAU,OAAO,SAAS,YAAY,KAAK,QAAQ,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC;AAC3F,uBAAe,IAAI,QAAQ;AAC3B;AAAA,MACF;AAEA,YAAM,UAAU,UAAU,GAAG,WAAW,OAAO;AAC/C,YAAM,eAAe,UAAU;AAC/B,UAAI,KAAK,MAAM,YAAY,MAAM,MAAM;AACrC,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA;AAAA,UACA,QAAQ,0BAA0B,QAAQ;AAAA,QAC5C,CAAC;AACD;AAAA,MACF;AACA,cAAQ,KAAK,EAAE,OAAO,UAAU,OAAO,SAAS,KAAK,OAAO,YAAY,CAAC,EAAE,CAAC;AAC5E,qBAAe,IAAI,QAAQ;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,SAAS,SAAS,UAAU,WAAW,SAAS,SAAS,gBAAgB,OAAO,EAAE;AACtG;AAEA,SAAS,UACP,WACA,SACA,SACA,gBACA,SACQ;AACR,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,KAAK,mDAAmD;AAAA,EAChE,WAAW,cAAc,UAAU,cAAc,MAAM;AACrD,UAAM,OAAO,cAAc,SAAS,SAAS;AAC7C,UAAM;AAAA,MACJ,UAAU,OAAO,QAAQ,QAAQ,QAAQ,OAAO,CAAC,IAAI,IAAI,IAAI,OAAO,eAAe,MAAM,UAAU,SAAS,CAAC;AAAA,IAC/G;AAAA,EACF,OAAO;AACL,UAAM,OAAO,cAAc,UAAU,UAAU;AAC/C,UAAM;AAAA,MACJ,UAAU,OAAO,QAAQ,QAAQ,QAAQ,OAAO,CAAC,IAAI,IAAI,WAAW,OAAO,eAAe,MAAM,UAAU,SAAS,CAAC;AAAA,IACtH;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,KAAK,WAAW,OAAO,QAAQ,QAAQ,QAAQ,OAAO,CAAC,4BAA4B;AAAA,EAC3F;AACA,MAAI,UAAU,GAAG;AACf,UAAM,KAAK,WAAW,OAAO,SAAS,QAAQ,OAAO,CAAC,qBAAqB;AAAA,EAC7E;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAGO,SAAS,SACd,SACY;AACZ,QAAM,EAAE,OAAO,GAAG,KAAK,IAAI;AAC3B,MAAI,CAAC,YAAY,KAAK,GAAG;AACvB,WAAO,YAAY,0DAA0D;AAAA,EAC/E;AACA,MAAI,MAAM,QAAQ,MAAM,QAAQ;AAC9B,WAAO,YAAY,uDAAuD;AAAA,EAC5E;AACA,SAAO,UAAU;AAAA,IACf,GAAG;AAAA,IACH,QAAQ,EAAE,KAAK,MAAM,KAAK,MAAM,MAAM,MAAM,QAAQ,MAAM,KAAK,OAAO,MAAM,MAAM;AAAA,IAClF,QAAQ,EAAE,KAAK,MAAM,MAAM,GAAG,MAAM,MAAM,MAAM,QAAQ,MAAM,QAAQ,OAAO,MAAM,MAAM;AAAA,EAC3F,CAAC;AACH;AAUO,SAAS,cACd,QACA,OACA,UAC8B;AAC9B,MAAI,CAAC,YAAY,MAAM,EAAG,QAAO;AACjC,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,CAAC,OAAO,UAAU,QAAQ,EAAG,QAAO;AAEpE,QAAM,QAAQ,QAAQ,OAAO;AAC7B,QAAM,QAAQ,OAAO,MAAM;AAC3B,QAAM,UAAU,WAAW,OAAO;AAClC,QAAM,SAAS,OAAO,OAAO;AAE7B,QAAM,WAAW,KAAK,IAAI,OAAO,OAAO,CAAC;AACzC,QAAM,aAAa,KAAK,IAAI,SAAS,QAAQ,CAAC;AAC9C,MAAI,aAAa,KAAK,eAAe,EAAG,QAAO;AAE/C,MAAI,YAAY,YAAY;AAC1B,QAAI,QAAQ,GAAG;AACb,aAAO,EAAE,KAAK,OAAO,SAAS,GAAG,QAAQ,OAAO,MAAM,OAAO,MAAM,OAAO,OAAO,MAAM;AAAA,IACzF;AACA,WAAO,EAAE,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG,MAAM,OAAO,MAAM,OAAO,OAAO,MAAM;AAAA,EACtF;AACA,MAAI,UAAU,GAAG;AACf,WAAO,EAAE,KAAK,OAAO,KAAK,QAAQ,OAAO,QAAQ,MAAM,OAAO,QAAQ,GAAG,OAAO,SAAS;AAAA,EAC3F;AACA,SAAO,EAAE,KAAK,OAAO,KAAK,QAAQ,OAAO,QAAQ,MAAM,UAAU,OAAO,OAAO,OAAO,EAAE;AAC1F;;;ACpaO,IAAM,qBAAqB;AAE3B,SAAS,gBAAgB,QAAgB,oBAA+B;AAC7E,QAAM,UAAU,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI;AAC1E,SAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,OAAO,QAAQ;AAChD;AASO,SAAS,SAAS,OAAkB,OAA6B;AACtE,MAAI,MAAM,SAAS,GAAG;AAGpB,WAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,OAAO,MAAM,MAAM;AAAA,EACpD;AACA,QAAM,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK;AAClC,SAAO,KAAK,SAAS,MAAM,MAAO,MAAK,MAAM;AAC7C,SAAO,EAAE,MAAM,QAAQ,CAAC,GAAG,OAAO,MAAM,MAAM;AAChD;AAEO,SAAS,QAAQ,OAAiE;AACvF,QAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AAC9C,MAAI,UAAU,OAAW,QAAO,EAAE,OAAO,OAAO,KAAK;AACrD,SAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,MAC5B,QAAQ,CAAC,GAAG,MAAM,QAAQ,KAAK;AAAA,MAC/B,OAAO,MAAM;AAAA,IACf;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,QAAQ,OAAiE;AACvF,QAAM,QAAQ,MAAM,OAAO,MAAM,OAAO,SAAS,CAAC;AAClD,MAAI,UAAU,OAAW,QAAO,EAAE,OAAO,OAAO,KAAK;AACrD,QAAM,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK;AAClC,SAAO,KAAK,SAAS,MAAM,MAAO,MAAK,MAAM;AAC7C,SAAO;AAAA,IACL,OAAO,EAAE,MAAM,QAAQ,MAAM,OAAO,MAAM,GAAG,EAAE,GAAG,OAAO,MAAM,MAAM;AAAA,IACrE;AAAA,EACF;AACF;AAEA,SAAS,MAAM,OAA0B;AAIvC,SAAO,KAAK,UAAU,CAAC,MAAM,OAAO,MAAM,QAAQ,CAAC;AACrD;AAYO,SAAS,aACd,QACA,OACA,OACW;AACX,QAAM,SAAS,oBAAI,IAAuB;AAC1C,aAAW,SAAS,OAAQ,QAAO,IAAI,MAAM,KAAK,GAAG,KAAK;AAE1D,QAAM,OAAoB,MAAM,IAAI,CAAC,UAAU;AAC7C,UAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,CAAC;AACrC,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,UAAU,MAAM;AAAA,MAChB,OAAO,QAAQ,MAAM,QAAQ;AAAA,IAC/B;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,MAAM,IAAI,CAAC,WAAW;AAAA,MAC1B,OAAO,MAAM;AAAA,MACb,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,IACf,EAAE;AAAA,EACJ;AACF;;;AJwIA,SAAS,gBAAgB,QAA0B;AACjD,QAAM,UAAU,kBAAkB,UAAU,SAAS;AACrD,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,QAAQ,QAAQ,IAAI,iBAAiB,GAAG,EAAG,QAAO;AACtD,SAAO,QAAQ,QAAQ,mDAAmD,MAAM;AAClF;AAGO,IAAM,oBAAoB;AAOjC,IAAM,qBAAqB;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,eAAe;AACjB;AA2CA,SAAS,gBAAgB,QAAiB,MAAwB;AAChE,MAAI,OAAO,YAAY,YAAa,QAAO;AAC3C,QAAM,UAAU,kBAAkB,UAAU,SAAS;AACrD,QAAM,QAAQ,gBAAgB,UAAU,OAAO;AAC/C,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAC/B,SAAO,CAAC,MAAM,SAAS,OAAO;AAChC;AAOA,SAAS,gCAAgC,QAAqC;AAC5E,MAAI,OAAO,YAAY,YAAa,QAAO;AAC3C,QAAM,UAAU,kBAAkB,UAAU,SAAS;AACrD,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,QAAQ,uCAAuC,MAAM;AACtE;AAEA,IAAM,UAAuB,CAAC;AAGvB,IAAM,mBAAmB;AAEzB,SAAS,mBACd,SACiB;AACjB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA2B,IAAI;AACzD,QAAM,CAAC,YAAY,aAAa,IAAI,SAA0B,IAAI;AAClE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB,MAAM,gBAAgB,SAAS,aAAa,GAAG,CAAC;AAC9F,QAAM,cAAc,OAAiC,IAAI;AACzD,QAAM,aAAa,OAAmC,IAAI;AAC1D,QAAM,eAAe,OAAO,KAAK;AACjC,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,SAAS,SAAS,UAAU,CAAC;AACnC,QAAM,YAAY;AAAA,IAChB,MAAO,UAAU,QAAQ,UAAU,OAAO,CAAC,OAAO,OAAO,EAAE,IAAI,CAAC;AAAA,IAChE,CAAC,OAAO;AAAA,EACV;AAIA,YAAU,MAAM;AACd,QAAI,CAAC,MAAO;AACZ,UAAM,SAAS,IAAI,IAAI,MAAM;AAC7B,UAAM,SAAS,IAAI,IAAI,SAAS;AAChC,UAAM,QACJ,OAAO,IAAI,MAAM,OAAO,KAAK,KAC7B,OAAO,IAAI,MAAM,MAAM,KAAK,KAC5B,OAAO,IAAI,MAAM,OAAO,SAAS,KACjC,OAAO,IAAI,MAAM,MAAM,SAAS;AAClC,QAAI,CAAC,MAAO,UAAS,IAAI;AAAA,EAC3B,GAAG,CAAC,OAAO,QAAQ,SAAS,CAAC;AAE7B,QAAM,SAAS;AAAA,IACb,MAAO,QAAQ,YAAY,OAAO,QAAQ,SAAS,IAAI;AAAA,IACvD,CAAC,OAAO,QAAQ,SAAS;AAAA,EAC3B;AAWA,QAAM,eAAe,QAAQ,MAAM;AACjC,UAAM,MAAM,oBAAI,IAAoB;AACpC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,EAAG,KAAI,IAAI,OAAO,CAAC,GAAI,CAAC;AAChE,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AACX,QAAM,kBAAkB,QAAQ,MAAM;AACpC,UAAM,MAAM,oBAAI,IAAoB;AACpC,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,EAAG,KAAI,IAAI,UAAU,CAAC,GAAI,CAAC;AACtE,WAAO;AAAA,EACT,GAAG,CAAC,SAAS,CAAC;AAUd,QAAM,gBAAgB;AAAA,IACpB,CAAC,YAA8E;AAC7E,YAAM,WAAW,WAAW,SAAS;AACrC,UAAI,CAAC,SAAU,QAAO,EAAE,UAAU,SAAS,UAAU,CAAC,EAAE;AACxD,YAAM,WAAwB,CAAC;AAC/B,YAAM,WAA2B,CAAC;AAClC,iBAAW,SAAS,SAAS;AAC3B,YAAI;AACJ,YAAI;AACF,mBAAS,SAAS,EAAE,OAAO,MAAM,OAAO,UAAU,MAAM,UAAU,OAAO,MAAM,MAAM,CAAC;AAAA,QACxF,QAAQ;AACN,mBAAS;AAAA,QACX;AACA,YAAI,OAAQ,UAAS,KAAK,EAAE,OAAO,MAAM,OAAO,UAAU,MAAM,UAAU,QAAQ,OAAO,MAAM,MAAM,CAAC;AAAA,YACjG,UAAS,KAAK,KAAK;AAAA,MAC1B;AACA,aAAO,EAAE,UAAU,SAAS;AAAA,IAC9B;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,eAAe;AAAA,IACnB,OAAO,SAAsB,QAA2B,UAAkB;AACxE,YAAM,UAAU,WAAW;AAC3B,UAAI,CAAC,WAAW,QAAQ,WAAW,EAAG;AACtC,YAAM,QAAQ,QAAQ,aAAa;AACnC,UAAI,QAAQ,KAAK,WAAW,UAAU,WAAW,QAAQ;AACvD,cAAM,SAAsB,QAAQ,IAAI,CAAC,WAAW;AAAA,UAClD,OAAO,MAAM;AAAA,UACb,UAAU,MAAM;AAAA,UAChB,OAAO,QAAQ,QAAQ,MAAM,OAAO,MAAM,QAAQ,KAAK;AAAA,QACzD,EAAE;AACF,iBAAS,CAAC,aAAa,SAAS,UAAU,aAAa,QAAQ,SAAS,KAAK,CAAC,CAAC;AAAA,MACjF;AACA,UAAI,CAAC,QAAQ,QAAS;AACtB,YAAM,QAAQ,QAAQ,SAAS,MAAM;AAAA,IACvC;AAAA,IACA,CAAC;AAAA,EACH;AAMA,QAAM,cAAc,YAAY,MAAe;AAC7C,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI,CAAC,QAAQ,YAAY,QAAQ,QAAS,QAAO;AACjD,YAAQ,SAAS,QAAQ,kBAAkB,gBAAgB;AAC3D,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,YAAY,CAAC,UAAkB,gBAAwB;AACzE,UAAM,UAAU,WAAW;AAC3B,UAAM,QAAQ,SAAS,OAAO,QAAQ;AACtC,UAAM,WAAW,SAAS,UAAU,WAAW;AAC/C,QAAI,CAAC,WAAW,UAAU,UAAa,aAAa,OAAW,QAAO;AACtE,WAAO,QAAQ,QAAQ,OAAO,QAAQ;AAAA,EACxC,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,YAAY,MAAM;AACxC,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW,CAAC,GAAG,QAAQ,SAAS;AAAA,MAChC,QAAQ,CAAC,GAAG,QAAQ,MAAM;AAAA,MAC1B,kBAAkB,QAAQ;AAAA,IAC5B;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,gBAAgB;AAAA,IACpB,CAAC,SAAyB,YAAoB;AAC5C,YAAM,UAAU,WAAW;AAC3B,UAAI,CAAC,SAAS,OAAQ;AACtB,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,OAAO,OAAO;AACtB;AAAA,MACF;AAEA,YAAM,QAAQ,QAAQ,CAAC;AACvB,YAAM,SAAS,QAAQ,gBAAgB,MAAO,QAAQ,KAAK,MAAO;AAClE,YAAM,OAAO,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,CAAC,2BAA2B;AACnF,cAAQ,OAAO,GAAG,OAAO,IAAI,MAAM,GAAG,IAAI,EAAE;AAAA,IAC9C;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,aAAa,YAAY,YAAY;AACzC,QAAI,YAAY,EAAG;AACnB,UAAM,OAAO,gBAAgB;AAC7B,QAAI,CAAC,QAAQ,CAAC,OAAQ;AACtB,UAAM,SAAS,SAAS;AAAA,MACtB,GAAG;AAAA,MACH,OAAO,EAAE,KAAK,OAAO,IAAI,MAAM,OAAO,IAAI,QAAQ,OAAO,IAAI,OAAO,OAAO,GAAG;AAAA,IAChF,CAAC;AACD,UAAM,WAAW,cAAc,OAAO,OAAO;AAC7C,UAAM,aAAa,SAAS,UAAU,aAAa,aAAa;AAChE,kBAAc,CAAC,GAAG,OAAO,SAAS,GAAG,SAAS,QAAQ,GAAG,OAAO,OAAO;AAAA,EACzE,GAAG,CAAC,iBAAiB,QAAQ,cAAc,eAAe,eAAe,WAAW,CAAC;AAErF,QAAM,UAAU,YAAY,YAAY;AACtC,QAAI,YAAY,EAAG;AACnB,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,WAAW,CAAC,OAAQ;AACzB,UAAM,UAAuB,CAAC;AAC9B,UAAM,UAA0B,CAAC;AACjC,aAAS,IAAI,OAAO,IAAI,KAAK,OAAO,IAAI,KAAK,GAAG;AAC9C,eAAS,IAAI,OAAO,IAAI,KAAK,OAAO,IAAI,KAAK,GAAG;AAC9C,cAAM,QAAQ,QAAQ,OAAO,CAAC;AAC9B,cAAM,WAAW,QAAQ,UAAU,CAAC;AACpC,YAAI,UAAU,UAAa,aAAa,OAAW;AACnD,YAAI,CAAC,QAAQ,iBAAiB,QAAQ,GAAG;AACvC,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA;AAAA,YACA,QAAQ,QAAQ,gBAAgB,QAAQ,KAAK,GAAG,QAAQ;AAAA,UAC1D,CAAC;AACD;AAAA,QACF;AACA,gBAAQ,KAAK,EAAE,OAAO,UAAU,OAAO,KAAK,CAAC;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,WAAW,cAAc,OAAO;AACtC,UAAM,aAAa,SAAS,UAAU,SAAS,SAAS;AACxD;AAAA,MACE,CAAC,GAAG,SAAS,GAAG,SAAS,QAAQ;AAAA,MACjC,WAAW,SAAS,SAAS,MAAM;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,eAAe,eAAe,WAAW,CAAC;AAEpE,QAAM,SAAS,YAAY,YAAY;AACrC,QAAI,YAAY,EAAG;AACnB,UAAM,QAAQ,QAAQ,KAAK;AAC3B,QAAI,CAAC,MAAM,MAAO;AAClB,aAAS,MAAM,KAAK;AACpB,UAAM,aAAa,MAAM,MAAM,MAAM,QAAQ,MAAM,MAAM,KAAK;AAC9D,eAAW,SAAS,SAAS,MAAM,MAAM,KAAK;AAAA,EAChD,GAAG,CAAC,OAAO,cAAc,WAAW,CAAC;AAErC,QAAM,SAAS,YAAY,YAAY;AACrC,QAAI,YAAY,EAAG;AACnB,UAAM,QAAQ,QAAQ,KAAK;AAC3B,QAAI,CAAC,MAAM,MAAO;AAClB,aAAS,MAAM,KAAK;AACpB,UAAM,aAAa,MAAM,MAAM,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAChE,GAAG,CAAC,OAAO,cAAc,WAAW,CAAC;AAErC,QAAM,OAAO;AAAA,IACX,CAAC,WAAgD,WAAoB;AACnE,eAAS,CAAC,aAAa;AACrB,cAAM,OAAO,WAAW,SAAS,QAAQ;AACzC,cAAM,OAAO,cAAc,MAAM,WAAW,QAAQ,SAAS;AAC7D,YAAI,CAAC,KAAM,QAAO;AAClB,YAAI,UAAU,SAAU,QAAO,EAAE,QAAQ,SAAS,QAAQ,OAAO,KAAK;AACtE,eAAO,EAAE,QAAQ,MAAM,OAAO,KAAK;AAAA,MACrC,CAAC;AAAA,IACH;AAAA,IACA,CAAC,QAAQ,SAAS;AAAA,EACpB;AAWA,QAAM,YAAY,OAA0B,MAAM;AAClD,YAAU,UAAU;AACpB,QAAM,eAAe,OAA0B,SAAS;AACxD,eAAa,UAAU;AACvB,QAAM,WAAW,OAAyB,KAAK;AAC/C,WAAS,UAAU;AAEnB,QAAM,YAAY,YAAY,MAAM;AAClC,UAAM,UAAU,WAAW;AAC3B,QAAI,WAAW,QAAQ,aAAa;AAClC,cAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AACrC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,cAAc,YAAY,MAAe;AAC7C,QAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,UAAM,SAAS,SAAS;AACxB,WAAO,CAAC,UAAU,WAAW,SAAS,QAAQ,WAAW,SAAS,mBAAmB,CAAC,OAAO;AAAA,EAC/F,GAAG,CAAC,CAAC;AAGL,QAAM,oBAAoB,YAAY,MAAe;AACnD,QAAI,YAAY,EAAG,QAAO;AAC1B,UAAM,YAAY,WAAW,SAAS,QAAQ,uCAAuC;AACrF,UAAM,SAAS,OAAO,aAAa,cAAc,OAAO,SAAS;AACjE,WAAO,QAAQ,aAAa,UAAU,UAAU,SAAS,MAAM,CAAC;AAAA,EAClE,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,UAAU;AAAA,IACd,CACE,OAA4B,QAC5B,OAAmD,MACnD,MAA8B,eAC3B;AACH,YAAMA,QACJ,SAAS,SAAS,SAAS,SAAS,OAAO,OAAO,SAAS,UAAU,aAAa,SAAS,SAAS,aAAa;AACnH,YAAM,KAAyB,OAAO,EAAE,OAAO,KAAK,OAAO,WAAW,KAAK,SAAS,IAAI;AACxF,eAAS,CAAC,aAAa;AAGrB,YAAI,QAAQ,aAAa,YAAY,MAAM,CAAC,SAAS,SAAS,OAAO,EAAE,EAAG,QAAO;AACjF,YAAIA,UAAS,KAAM,QAAO,aAAa,KAAK,EAAE,QAAQ,IAAI,OAAO,GAAG,IAAI;AACxE,cAAM,QAAQ,MAAM,UAAU,SAAS;AACvC,YAAI,CAAC,MAAO,QAAO;AACnB,cAAM,OAAO,cAAc,OAAOA,OAAM,UAAU,SAAS,aAAa,OAAO;AAC/E,eAAO,OAAO,EAAE,QAAQ,MAAM,OAAO,KAAK,IAAI;AAAA,MAChD,CAAC;AACD,UAAI,QAAQ,YAAY;AACtB,kBAAU;AACV;AAAA,MACF;AAGA,UAAI,kBAAkB,EAAG,WAAU;AACnC,iBAAW,MAAM;AACf,YAAI,YAAY,EAAG,WAAU;AAAA,MAC/B,GAAG,CAAC;AAAA,IACN;AAAA,IACA,CAAC,WAAW,aAAa,iBAAiB;AAAA,EAC5C;AAEA,QAAM,SAAS;AAAA,IACb,CAAC,QAA6C,UAAgD;AAC5F,YAAM,IAAiB,EAAE,OAAO,OAAO,OAAO,WAAW,OAAO,SAAS;AACzE,YAAM,IAAiB,QAAQ,EAAE,OAAO,MAAM,OAAO,WAAW,MAAM,SAAS,IAAI;AACnF,eAAS,EAAE,QAAQ,GAAG,OAAO,EAAE,CAAC;AAAA,IAClC;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,mBAAmB;AAAA,IACvB,CAAC,UAA+B;AAC9B,YAAM,MAAM,MAAM;AAClB,UAAI,QAAQ,WAAW,QAAQ,SAAS,QAAQ,SAAU;AAE1D,YAAM,UAAU,MAAM,kBAAkB,UAAU,MAAM,SAAS;AACjE,UAAI,CAAC,WAAW,QAAQ,QAAQ,IAAI,iBAAiB,GAAG,EAAG;AAC3D,YAAM,WACJ,gBAAgB,MAAM,MAAM,KAC3B,gCAAgC,MAAM,MAAM,KAC3C,QAAQ,QAAQ,yGAAyG,MAAM;AACnI,UAAI,CAAC,SAAU;AAEf,UAAI,QAAQ,YAAY,MAAM,YAAY,MAAM,QAAS;AACzD,YAAM,OAA4B,QAAQ,UAAU,SAAS,QAAQ,QAAS,MAAM,WAAW,SAAS,UAAW;AACnH,YAAM,KAAK,SAAS,SAAS,SAAS;AACtC,iBAAW,MAAM;AAGf,YAAI,CAAC,YAAY,EAAG;AACpB,gBAAQ,MAAM,KAAK,EAAE,OAAO,GAAG,OAAO,UAAU,GAAG,UAAU,IAAI,MAAM,UAAU;AAAA,MACnF,GAAG,CAAC;AAAA,IACN;AAAA,IACA,CAAC,SAAS,WAAW;AAAA,EACvB;AAEA,QAAM,aAAa,QAA+B,OAAO,EAAE,SAAS,QAAQ,UAAU,IAAI,CAAC,SAAS,QAAQ,SAAS,CAAC;AAEtH,QAAM,YAAY;AAAA,IAChB,CAAC,UAA+B;AAC9B,YAAM,UAAU,WAAW;AAC3B,UAAI,CAAC,QAAS;AACd,UAAI,gBAAgB,MAAM,MAAM,EAAG;AACnC,UAAI,gCAAgC,MAAM,MAAM,EAAG;AAKnD,YAAM,QAAQ,MAAM,WAAW,MAAM;AACrC,UAAI,UAAU,MAAM,QAAQ,OAAO,MAAM,QAAQ,MAAM;AACrD,cAAM,eAAe;AACrB,YAAI,MAAM,SAAU,MAAK,OAAO;AAAA,YAC3B,MAAK,OAAO;AACjB;AAAA,MACF;AACA,UAAI,UAAU,MAAM,QAAQ,OAAO,MAAM,QAAQ,MAAM;AACrD,cAAM,eAAe;AACrB,aAAK,OAAO;AACZ;AAAA,MACF;AACA,YAAM,SAAS,gBAAgB,KAAK;AACpC,UAAI,CAAC,OAAQ;AACb,cAAQ,OAAO,MAAM;AAAA,QACnB,KAAK;AACH,gBAAM,eAAe;AACrB,eAAK,OAAO,MAAM,KAAK;AACvB;AAAA,QACF,KAAK;AACH,gBAAM,eAAe;AACrB,eAAK,OAAO,MAAM,IAAI;AACtB;AAAA,QACF,KAAK,aAAa;AAChB,gBAAM,eAAe;AACrB,gBAAM,MAAM,SAAS,QAAQ,SAAS;AACtC,cAAI,IAAK,UAAS,GAAG;AACrB;AAAA,QACF;AAAA,QACA,KAAK,aAAa;AAChB,gBAAM,eAAe;AACrB,gBAAM,UAAU,OAAO;AACvB,gBAAM,OAAO,UAAU,SAAS,QAAQ,OAAO,SAAS,IAAI;AAC5D,cAAI,KAAM,UAAS,IAAI;AACvB;AAAA,QACF;AAAA,QACA,KAAK,gBAAgB;AACnB,gBAAM,eAAe;AACrB,gBAAM,UAAU,OAAO;AACvB,gBAAM,OAAO,UAAU,YAAY,QAAQ,WAAW,MAAM,IAAI;AAChE,cAAI,KAAM,UAAS,IAAI;AACvB;AAAA,QACF;AAAA,QACA,KAAK;AACH,gBAAM,eAAe;AACrB,eAAK,QAAQ;AACb;AAAA,QACF,KAAK;AACH,gBAAM,eAAe;AACrB,eAAK,WAAW;AAChB;AAAA,QACF,KAAK;AACH,mBAAS,IAAI;AACb;AAAA,QACF,KAAK;AAAA,QACL,KAAK,cAAc;AAOjB,gBAAM,UAAU,OAAO;AAGvB,cAAI,WAAW,MAAM,QAAQ,OAAO,QAAQ,WAAW;AACrD,gBAAI,QAAQ,UAAU,QAAQ,KAAK,GAAG;AACpC,oBAAM,eAAe;AACrB;AAAA,YACF;AAAA,UACF;AACA,cAAI,CAAC,WAAW,CAAC,QAAQ,iBAAiB,QAAQ,SAAU;AAC5D,gBAAM,OAAO,OAAO,SAAS,eAAe,OAAO,OAAO;AAC1D,cAAI,QAAQ,cAAc,SAAS,IAAI,EAAG,OAAM,eAAe;AAC/D;AAAA,QACF;AAAA,QACA;AAIE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,MAAM,OAAO,QAAQ,WAAW,SAAS,YAAY,QAAQ,MAAM;AAAA,EACtE;AAEA,QAAM,iBAAiB,YAAY,MAAM;AACvC,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAC/B,UAAM,OAAO,UAAU,OAAO,QAAQ,SAAS,EAAE;AAAA,MAAI,CAAC,QACpD,IAAI,IAAI,CAAC,YAAY,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,SAAS,CAAC;AAAA,IACxE;AACA,WAAO,UAAU,IAAI;AAAA,EACvB,GAAG,CAAC,OAAO,QAAQ,SAAS,CAAC;AAE7B,QAAM,qBAAqB,YAAY,MAAM;AAC3C,QAAI,CAAC,SAAS,CAAC,OAAQ,QAAO,EAAE,QAAQ,CAAC,GAAe,WAAW,CAAC,EAAc;AAClF,WAAO;AAAA,MACL,QAAQ,OAAO,MAAM,OAAO,IAAI,OAAO,KAAK,CAAC;AAAA,MAC7C,WAAW,UAAU,MAAM,OAAO,IAAI,OAAO,KAAK,CAAC;AAAA,IACrD;AAAA,EACF,GAAG,CAAC,OAAO,QAAQ,QAAQ,SAAS,CAAC;AAErC,QAAM,SAAS;AAAA,IACb,CAAC,UAAqC;AACpC,UAAI,CAAC,WAAW,WAAW,CAAC,SAAS,gBAAgB,MAAM,MAAM,EAAG;AACpE,UAAI,gCAAgC,MAAM,MAAM,EAAG;AACnD,YAAM,OAAO,eAAe;AAC5B,UAAI,CAAC,KAAM;AACX,YAAM,eAAe;AACrB,YAAM,eAAe,QAAQ,cAAc,IAAI;AAG/C,YAAM,EAAE,QAAQ,YAAY,WAAW,cAAc,IAAI,mBAAmB;AAC5E,YAAM,QAAQ,KAAK,IAAI,GAAG,WAAW,SAAS,cAAc,MAAM;AAClE,iBAAW,SAAS,SAAS,UAAU,KAAK,IAAI,UAAU,IAAI,SAAS,OAAO,GAAG;AAAA,IACnF;AAAA,IACA,CAAC,OAAO,gBAAgB,kBAAkB;AAAA,EAC5C;AAEA,QAAM,QAAQ;AAAA,IACZ,CAAC,UAAqC;AACpC,UAAI,CAAC,WAAW,WAAW,CAAC,SAAS,gBAAgB,MAAM,MAAM,EAAG;AACpE,UAAI,gCAAgC,MAAM,MAAM,EAAG;AACnD,YAAM,OAAO,eAAe;AAC5B,UAAI,CAAC,KAAM;AACX,YAAM,eAAe;AACrB,YAAM,eAAe,QAAQ,cAAc,IAAI;AAC/C,WAAK,QAAQ;AAAA,IACf;AAAA,IACA,CAAC,OAAO,gBAAgB,OAAO;AAAA,EACjC;AAEA,QAAM,UAAU;AAAA,IACd,CAAC,UAAqC;AACpC,YAAM,UAAU,WAAW;AAC3B,UAAI,CAAC,WAAW,QAAQ,UAAU,SAAS,CAAC,SAAS,gBAAgB,MAAM,MAAM,EAAG;AACpF,UAAI,gCAAgC,MAAM,MAAM,EAAG;AACnD,YAAM,OAAO,MAAM,eAAe,QAAQ,YAAY,KAAK;AAC3D,UAAI,CAAC,KAAM;AACX,YAAM,eAAe;AACrB,UAAI,YAAY,EAAG;AACnB,UAAI,QAAQ,mBAAmB,IAAI;AACnC,UAAI,WAAW,OAAO,QAAQ,MAAM,MAAM,KAAK;AAC/C,UAAI,WAAW,UAAU,QAAQ,MAAM,MAAM,SAAS;AACtD,UAAI,aAAa,MAAM,aAAa,GAAI;AAOxC,UAAI,QAAQ;AACV,cAAM,YAAY,MAAM;AACxB,cAAM,YAAY,MAAM,OAAO,CAAC,QAAQ,SAAS,KAAK,IAAI,QAAQ,KAAK,MAAM,GAAG,CAAC;AACjF,cAAM,SAAS,OAAO,KAAK,OAAO,KAAK;AACvC,cAAM,QAAQ,OAAO,KAAK,OAAO,KAAK;AACtC,mBAAW,OAAO;AAClB,mBAAW,OAAO;AAClB,YACE,YAAY,KACZ,YAAY,MACX,SAAS,aAAa,QAAQ,cAC/B,SAAS,cAAc,KACvB,QAAQ,cAAc,GACtB;AACA,gBAAM,QAAoB,CAAC;AAC3B,mBAAS,IAAI,GAAG,IAAI,QAAQ,KAAK,GAAG;AAClC,kBAAM,OAAiB,CAAC;AACxB,qBAAS,IAAI,GAAG,IAAI,OAAO,KAAK,EAAG,MAAK,KAAK,MAAM,IAAI,SAAS,IAAI,IAAI,SAAS,KAAK,EAAE;AACxF,kBAAM,KAAK,IAAI;AAAA,UACjB;AACA,kBAAQ;AAAA,QACV;AAAA,MACF;AAGA;AACE,cAAM,UAAU,KAAK,IAAI,WAAW,KAAK,IAAI,MAAM,QAAQ,CAAC,IAAI,GAAG,OAAO,SAAS,CAAC;AACpF,cAAM,SAAS,MAAM,OAAO,CAAC,GAAG,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG,CAAC;AACpE,cAAM,UAAU,KAAK,IAAI,WAAW,SAAS,GAAG,UAAU,SAAS,CAAC;AACpE,cAAM,SAAsB,EAAE,OAAO,OAAO,QAAQ,GAAI,WAAW,UAAU,QAAQ,EAAG;AACxF,cAAM,QAAqB,EAAE,OAAO,OAAO,OAAO,GAAI,WAAW,UAAU,OAAO,EAAG;AACrF,iBAAS,EAAE,QAAQ,MAAM,CAAC;AAAA,MAC5B;AACA,YAAM,UAAuB,CAAC;AAC9B,YAAM,UAA0B,CAAC;AASjC,UAAI,gBAAgB;AACpB,YAAM,WAA0C,CAAC;AACjD,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,cAAM,OAAO,MAAM,CAAC,KAAK,CAAC;AAC1B,cAAM,QAAQ,OAAO,WAAW,CAAC;AACjC,YAAI,UAAU,QAAW;AACvB,gBAAM,SAAiC,CAAC;AACxC,mBAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,kBAAM,WAAW,UAAU,WAAW,CAAC;AACvC,gBAAI,aAAa,QAAW;AAC1B,+BAAiB;AACjB;AAAA,YACF;AACA,mBAAO,QAAQ,IAAI,KAAK,CAAC,KAAK;AAAA,UAChC;AACA,mBAAS,KAAK,MAAM;AACpB;AAAA,QACF;AACA,iBAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,gBAAM,WAAW,UAAU,WAAW,CAAC;AACvC,cAAI,aAAa,QAAW;AAC1B,6BAAiB;AACjB;AAAA,UACF;AACA,cAAI,CAAC,QAAQ,iBAAiB,QAAQ,GAAG;AACvC,oBAAQ,KAAK;AAAA,cACX;AAAA,cACA;AAAA,cACA,QACE,QAAQ,gBAAgB,QAAQ,KAChC,GAAG,QAAQ;AAAA,YACf,CAAC;AACD;AAAA,UACF;AACA,kBAAQ,KAAK,EAAE,OAAO,UAAU,OAAO,KAAK,CAAC,KAAK,KAAK,CAAC;AAAA,QAC1D;AAAA,MACF;AACA,YAAM,WAAW,cAAc,OAAO;AACtC,YAAM,WAAW,CAAC,GAAG,SAAS,GAAG,SAAS,QAAQ;AAIlD,UAAI,QAAQ,aAAa;AACvB,cAAM,WAAW,SAAS;AAC1B,gBAAQ,YAAY;AAAA,UAClB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO,YAAY;AACjB,kBAAM,aAAa,UAAU,SAAS,QAAQ;AAAA,UAChD;AAAA,QACF,CAAC;AACD;AAAA,MACF;AACA,WAAK,aAAa,SAAS,UAAU,SAAS,QAAQ;AACtD,YAAM,UAAU,gBAAgB,SAAS,OAAO,CAAC,GAAG,QAAQ,IAAI,OAAO,KAAK,GAAG,EAAE,QAAQ,CAAC;AAC1F,YAAM,OAAO,UAAU,IAAI,IAAI,OAAO,yDAAyD;AAC/F,oBAAc,UAAU,UAAU,SAAS,SAAS,MAAM,UAAU,IAAI,EAAE;AAAA,IAC5E;AAAA,IACA,CAAC,OAAO,QAAQ,QAAQ,WAAW,cAAc,eAAe,eAAe,WAAW;AAAA,EAC5F;AAEA,QAAM,YAAY;AAAA,IAChB,CAAC,OAAe,aAA2C;AACzD,YAAM,UAAuB,EAAE,OAAO,WAAW,SAAS;AAC1D,YAAM,WAAW,aAAa,IAAI,KAAK,KAAK;AAC5C,YAAM,WAAW,gBAAgB,IAAI,QAAQ,KAAK;AAClD,YAAM,WACJ,WAAW,QACX,YAAY,OAAO,MACnB,YAAY,OAAO,MACnB,YAAY,OAAO,MACnB,YAAY,OAAO;AACrB,YAAM,SAAS,UAAU,QAAQ,SAAS,MAAM,OAAO,OAAO;AAC9D,YAAM,gBACJ,eAAe,QACf,YAAY,WAAW,OACvB,YAAY,WAAW,UACvB,YAAY,WAAW,QACvB,YAAY,WAAW;AACzB,aAAO;AAAA,QACL,uBAAuB;AAAA,QACvB,uBAAuB;AAAA,QACvB,GAAI,WAAW,EAAE,4BAA4B,OAAgB,IAAI,CAAC;AAAA,QAClE,GAAI,SAAS,EAAE,0BAA0B,OAAgB,IAAI,CAAC;AAAA,QAC9D,GAAI,gBAAgB,EAAE,gCAAgC,OAAgB,IAAI,CAAC;AAAA,QAC3E,aAAa,CAAC,UAAU;AACtB,cAAI,MAAM,WAAW,EAAG;AAKxB,cAAI,gBAAgB,MAAM,UAAU,MAAM,MAAM,aAAa,EAAG;AAKhE,cAAI,gBAAgB,MAAM,UAAU,IAAI,EAAG;AAO3C,gBAAM,eAAe;AACrB,gBAAM,SAAS,MAAM;AACrB,cAAI,OAAO,gBAAgB,eAAe,kBAAkB,aAAa;AACvE,kBAAM,YAAY,OAAO,QAAqB,uCAAuC;AAErF,kBAAM,UAAU,WAAW,cAAmC,IAAI,iBAAiB,GAAG;AACtF,aAAC,WAAW,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,UACvD;AACA;AAAA,YAAS,CAAC,aACR,MAAM,YAAY,WACd,EAAE,QAAQ,SAAS,QAAQ,OAAO,QAAQ,IAC1C,EAAE,QAAQ,SAAS,OAAO,QAAQ;AAAA,UACxC;AACA,sBAAY,UAAU;AAAA,QACxB;AAAA,QACA,cAAc,MAAM;AAClB,cAAI,YAAY,YAAY,UAAU;AACpC,qBAAS,CAAC,aAAc,WAAW,EAAE,QAAQ,SAAS,QAAQ,OAAO,QAAQ,IAAI,QAAS;AAC1F;AAAA,UACF;AACA,cAAI,YAAY,YAAY,UAAU,QAAQ;AAC5C;AAAA,cACE;AAAA,gBACE,EAAE,KAAK,OAAO,IAAI,MAAM,OAAO,IAAI,QAAQ,OAAO,IAAI,OAAO,OAAO,GAAG;AAAA,gBACvE;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,cAAc,iBAAiB,QAAQ,OAAO,UAAU;AAAA,EAC3D;AAEA,QAAM,mBAAmB;AAAA,IACvB,CAAC,OAAe,aAAqB;AACnC,YAAM,UAAU,WAAW;AAC3B,UAAI,CAAC,WAAW,QAAQ,eAAe,SAAS,QAAQ,YAAY,CAAC,OAAQ,QAAO;AACpF,aAAO,OAAO,OAAO,EAAE,MAAM,SAAS,UAAU,OAAO,EAAE,MAAM;AAAA,IACjE;AAAA,IACA,CAAC,QAAQ,QAAQ,SAAS;AAAA,EAC5B;AAEA,QAAM,gBAAgB;AAAA,IACpB,CAAC,UAAuE;AACtE,YAAM,eAAe;AACrB,YAAM,gBAAgB;AACtB,kBAAY,UAAU;AAAA,IACxB;AAAA,IACA,CAAC;AAAA,EACH;AAIA,QAAM,gBAAgB,OAAwB,IAAI;AAClD,gBAAc,UAAU;AACxB,QAAM,YAAY,OAA2B,IAAI;AACjD,YAAU,UAAU;AACpB,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AAOd,UAAM,SAAS,MAAM;AACnB,YAAM,cAAc,YAAY;AAChC,kBAAY,UAAU;AACtB,UAAI,gBAAgB,OAAQ;AAC5B,YAAM,SAAS,cAAc;AAC7B,YAAM,SAAS,UAAU;AACzB,oBAAc,IAAI;AAClB,UAAI,CAAC,UAAU,CAAC,OAAQ;AACxB,UAAI,YAAY,EAAG;AACnB,YAAM,OAAO,gBAAgB;AAC7B,UAAI,CAAC,KAAM;AACX,YAAM,SAAS,UAAU;AAAA,QACvB,GAAG;AAAA,QACH,QAAQ,EAAE,KAAK,OAAO,IAAI,MAAM,OAAO,IAAI,QAAQ,OAAO,IAAI,OAAO,OAAO,GAAG;AAAA,QAC/E;AAAA,MACF,CAAC;AACD,YAAM,WAAW,cAAc,OAAO,OAAO;AAC7C,WAAK,aAAa,SAAS,UAAU,QAAQ,QAAQ;AACrD,oBAAc,CAAC,GAAG,OAAO,SAAS,GAAG,SAAS,QAAQ,GAAG,OAAO,OAAO;AAAA,IACzE;AACA,WAAO,iBAAiB,WAAW,MAAM;AACzC,WAAO,MAAM,OAAO,oBAAoB,WAAW,MAAM;AAAA,EAC3D,GAAG,CAAC,SAAS,iBAAiB,cAAc,eAAe,eAAe,WAAW,CAAC;AAetF,QAAM,gBAAgB;AAAA,IACpB,CAAC,SAAiB;AAChB,UAAI,WAAW,QAAS,YAAW,QAAQ,QAAQ;AACnD,YAAM,UAAU,WAAW;AAC3B,YAAM,UAAU,OAAO;AACvB,UAAI,CAAC,WAAW,CAAC,WAAW,QAAQ,YAAY,CAAC,QAAQ,cAAe;AACxE,UAAI,KAAK,KAAK,MAAM,GAAI;AACxB,cAAQ,cAAc,SAAS,IAAI;AAAA,IACrC;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAEA,QAAM,mBAAgD;AAAA,IACpD,KAAK,CAAC,YAAY;AAChB,iBAAW,UAAU;AAAA,IACvB;AAAA,IACA,UAAU;AAAA,IACV,cAAc;AAAA,IACd,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,CAAC,iBAAiB,GAAG;AAAA,IACrB,OAAO;AAAA,IACP,SAAS,CAAC,UAAU;AAClB,YAAM,OAAQ,MAAM,aAAqD;AACzE,UAAI,OAAO,SAAS,aAAa,KAAK,WAAW,iBAAiB,KAAK,KAAK,WAAW,gBAAgB,IAAI;AACzG,cAAM,cAAc,QAAQ;AAC5B;AAAA,MACF;AAEA,UAAI,aAAa,QAAS;AAC1B,oBAAc,MAAM,cAAc,KAAK;AAAA,IACzC;AAAA,IACA,oBAAoB,MAAM;AACxB,mBAAa,UAAU;AAAA,IACzB;AAAA,IACA,kBAAkB,CAAC,UAAU;AAC3B,mBAAa,UAAU;AACvB,oBAAc,MAAM,QAAQ,MAAM,cAAc,KAAK;AAAA,IACvD;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,YAAY;AAAA,IACrB;AAAA,IACA,gBAAgB;AAAA,MACd,SAAS,CAAC,UAAU;AAClB,YAAI,MAAM,WAAW,MAAM,cAAe;AAC1C,mBAAW,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,MACnD;AAAA,MACA,UAAU,UAAU,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM,KAAK,SAAS;AAAA,IAC7B,SAAS,MAAM,OAAO,SAAS;AAAA,IAC/B,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACF;","names":["move"]}
1
+ {"version":3,"sources":["../../src/data-table/useSpreadsheetGrid.ts","../../src/data-table/grid-selection.ts","../../src/data-table/grid-clipboard.ts","../../src/data-table/fill.ts","../../src/data-table/cell-undo.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/data-table — THE SPREADSHEET GESTURES, in the shared\n * table.\n *\n * A range you can drag out, arrow keys and Tab that mean something, Cmd-C /\n * Cmd-X / Cmd-V against real Excel and Sheets blocks, Cmd-D to fill down, a\n * drag handle at the corner of a selection, and an undo stack. All of it\n * existed, and all of it lived inside ONE route's hand-rolled grid where no\n * other table in the product could reach it.\n *\n * WHAT THIS HOOK OWNS AND WHAT IT DOES NOT\n *\n * It owns the SELECTION and the GESTURES. It does not own the data: every\n * mutation leaves as a list of `CellPatch` through `onPatch`, so the surface\n * applies it through the door it already has and this table never invents a\n * persistence path. That is also why undo is honest — the stack holds the\n * before-values the caller gave us, so an undo is just another patch list.\n *\n * ADDRESSES ARE (rowId, columnId), NEVER (rowIndex, colIndex). Rows reload,\n * realtime reorders them, a filter changes underneath a person's hand — an\n * index-based selection silently jumps to a DIFFERENT cell when that happens,\n * and the next keystroke then edits the wrong row. Indexes appear here only as\n * a derived rectangle, recomputed from ids on every render.\n *\n * THE CLICK LAW is inherited from `./grid-selection`: a single click may SELECT\n * a cell; it may never drop a person into a free-text buffer. Selecting is not\n * editing, and this hook deliberately does not open editors — the table's own\n * `editable` path still owns that.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n allRange,\n classifyGridKey,\n columnRange,\n moveSelection,\n rangeBounds,\n rangeRows,\n rowRange,\n sameCell,\n type CellAddress,\n type CellRange,\n type RangeBounds,\n} from \"./grid-selection\";\nimport { gridToTsv, parseClipboardGrid } from \"./grid-clipboard\";\nimport { fillDown, fillRange, fillTargetFor, type CellPatch, type FillRect } from \"./fill\";\nimport {\n createUndoStack,\n popRedo,\n popUndo,\n pushUndo,\n undoEntryFor,\n type UndoStack,\n} from \"./cell-undo\";\n\n/** What a set of cell patches was for, so a consumer can label its own toast. */\nexport type SpreadsheetIntent = \"paste\" | \"fill\" | \"fill-down\" | \"undo\" | \"redo\" | \"clear\";\n\n/** One cell a paste did not write, with the sentence that says why. */\nexport interface PasteRefusal {\n /** Null when the cell belongs to no row on the grid. */\n rowId: string | null;\n columnId: string;\n reason: string;\n /**\n * WHAT WAS REFUSED, kept rather than dropped.\n *\n * Without it a consumer showing a person their fifty pasted rows can say a\n * cell was skipped but not WHICH one or what was in it — measured on Rincon\n * Plumbing's September billing run, 2026-09-21: every refusal on an existing\n * record read \"Row 0, amount_due\" because the value never reached the code\n * that knew the line and the column's name. A refusal nobody can locate is\n * only half a refusal.\n */\n value?: unknown;\n}\n\n/**\n * What a paste WOULD do, handed over before any of it happens.\n *\n * Addresses are (rowId, columnId) like everywhere else in the grid, never\n * indices — a row filtered out between the copy and the paste is not a row.\n */\nexport interface SpreadsheetPastePlan {\n /** The cells that would be written, already screened by `validateCell`. */\n accepted: CellPatch[];\n /** Cells `validateCell` or `writableColumnIds` turned away, each with its sentence. */\n refusals: PasteRefusal[];\n /**\n * Rows of the pasted block that fell past the last row of the grid, in the\n * grid's current column order (so a hidden or reordered column is honoured).\n * Each entry maps a columnId to the raw clipboard text for that cell.\n */\n overflow: Array<Record<string, string>>;\n /** Cells that fell past the RIGHT edge — there is no column to hold them. */\n pastRightEdge: number;\n /** Write `accepted` through `onPatch`, exactly as an unconfirmed paste would. */\n apply: () => Promise<void>;\n}\n\nexport interface SpreadsheetGridOptions {\n /** Row ids currently rendered, in visual order. */\n rowIds: readonly string[];\n /** Visible column ids, in visual order. */\n columnIds: readonly string[];\n /** Reads a cell's current value, for copy and for fill. */\n valueAt: (rowId: string, columnId: string) => unknown;\n /** Whether a column can take a written value at all. */\n isColumnWritable: (columnId: string) => boolean;\n /** Why a column refuses, as a sentence. Used in the skip list. */\n columnRefusal?: ((columnId: string) => string | undefined) | undefined;\n /**\n * Applies a batch of writes. The consumer owns the door. Optional only for a\n * `readOnly` grid, which never writes; a writable grid without it refuses\n * every write out loud rather than dropping it.\n */\n onPatch?: ((patches: CellPatch[], intent: SpreadsheetIntent) => void | Promise<void>) | undefined;\n /**\n * SELECT AND COPY, NEVER CHANGE. A reader gets the whole range cursor — click,\n * drag, shift-click, arrows, select-all, Cmd-C — and no gesture that writes:\n * no fill handle, and cut, paste, clear, fill-down, undo and redo each say\n * `readOnlyReason` instead of doing anything. Separate from editing on\n * purpose: whether a person may CHANGE a table is not whether they may COPY\n * a range out of it.\n */\n readOnly?: boolean | undefined;\n /** The sentence a refused write says on a read-only grid. */\n readOnlyReason?: string | undefined;\n /** 0 turns undo off. */\n undoDepth?: number | undefined;\n /** The little square at the corner of a selection. */\n fillHandle?: boolean | undefined;\n /** Excel/Sheets TSV paste. */\n paste?: boolean | undefined;\n /** Says what just happened, and why something did not. */\n notify?: ((message: string) => void) | undefined;\n /**\n * Hand the whole paste over instead of applying it. See\n * `MatrxDataTableSpreadsheetConfig.onPastePlan` for why this exists.\n */\n onPastePlan?: ((plan: SpreadsheetPastePlan) => void) | undefined;\n /**\n * OPEN THE EDITOR on the focused cell. Return true when the editor actually\n * opened, so the keystroke is consumed; return false and the key is left to\n * whatever else wants it.\n *\n * `seed` is the character the person typed when they typed instead of\n * pressing Enter — type-to-replace, which every spreadsheet has and which is\n * how most values are actually entered. Null means Enter/F2: open the editor\n * on the value that is already there.\n *\n * THE CLICK LAW still holds: this hook never opens an editor on a CLICK. A\n * deliberate keystroke aimed at the focused cell is a different gesture from\n * a click that might have been aimed at the row.\n */\n onEditRequest?: ((address: CellAddress, seed: string | null) => boolean) | undefined;\n /**\n * Judge ONE value before a paste, a fill or a clear writes it. Return a\n * sentence to refuse that cell; return undefined to accept. A refused cell is\n * left alone and the person is told which rule refused it — a rule a paste\n * can walk around is not a rule.\n */\n validateCell?: ((args: { rowId: string; columnId: string; value: unknown }) => string | undefined) | undefined;\n /**\n * SPACE OPENS THE ROW (Airtable's expand). Return true when the row opened, so the keystroke is\n * consumed; absent or false and Space keeps its older meaning (open the cell's editor).\n */\n onOpenRow?: ((rowId: string) => boolean) | undefined;\n}\n\n/**\n * WHERE THE CURSOR GOES WHEN AN EDITOR CLOSES. Google Sheets and Airtable: Enter moves down, Tab\n * moves right (Shift-Tab left), Escape stays — and in every case the GRID has the keyboard again.\n */\nexport type SpreadsheetEditExit = \"down\" | \"up\" | \"right\" | \"left\" | \"stay\";\n\n/**\n * THE GRID'S HANDS, for a consumer whose cells carry their own editors (`controllerRef`).\n *\n * An editor owns the keyboard while it is open; when it closes, focus has to come BACK to the grid\n * or the next arrow, Enter and Cmd-Z reach the page body and the grid feels dead (merged-grid review\n * 2026-09-26, A1). The table's own editors are covered by the grid's key net; an editor the\n * consumer draws says when it closed, and how, through `endEdit`.\n */\nexport interface SpreadsheetController {\n /**\n * An editor closed. `from` is the cell it was editing; the cursor moves from there by `exit`.\n * `how: \"keyboard\"` (Enter / Tab / Escape) always takes the keyboard back; `\"pointer\"` (a pick in\n * a list, a click away) takes it back only when nothing else has it — a click on a toolbar\n * button keeps its focus.\n */\n endEdit: (\n exit?: SpreadsheetEditExit,\n from?: { rowId: string; columnId: string } | null,\n how?: \"keyboard\" | \"pointer\",\n ) => void;\n /** Put the cursor on a cell, or a rectangle from `anchor` to `focus`. */\n select: (anchor: { rowId: string; columnId: string }, focus?: { rowId: string; columnId: string }) => void;\n /** Give the grid the keyboard. */\n focusGrid: () => void;\n}\n\nexport interface SpreadsheetCellProps {\n \"data-matrx-cell-row\": string;\n \"data-matrx-cell-col\": string;\n \"data-matrx-cell-selected\"?: \"true\";\n \"data-matrx-cell-active\"?: \"true\";\n \"data-matrx-cell-fill-preview\"?: \"true\";\n onMouseDown: (event: {\n shiftKey: boolean;\n button: number;\n preventDefault: () => void;\n /** The cell element, used to give the grid keyboard focus. */\n currentTarget?: unknown;\n /**\n * What was actually pressed. A press inside an overlay this cell portalled out\n * of the table is NOT a press on the cell — see `camePastAPortal`.\n */\n target?: unknown;\n }) => void;\n onMouseEnter: () => void;\n}\n\nexport interface SpreadsheetGrid {\n /** The current range, or null when nothing is selected. */\n range: CellRange | null;\n /** The rectangle the range spans, recomputed from ids every render. */\n bounds: RangeBounds | null;\n /** True when this hook is doing anything at all. */\n enabled: boolean;\n /** Props for the hidden textarea the grid's focus lives in (type-to-edit). */\n typeCatcherProps: SpreadsheetTypeCatcherProps;\n /** Props for the scroll container that owns the keyboard and the clipboard. */\n containerProps: {\n /** Focus landing on the container itself (Tab into the grid) moves on to the type catcher. */\n onFocus: (event: { target: unknown; currentTarget: unknown }) => void;\n tabIndex: number;\n onKeyDown: (event: SpreadsheetKeyEvent) => void;\n /**\n * THE KEY NET: Enter, Tab or Escape pressed INSIDE an editor (the table's own or a portalled\n * picker) that leaves focus on the page body gets the keyboard back to the grid, with the\n * cursor moved the way Sheets moves it. Capture phase, because an editor stops propagation.\n */\n onKeyDownCapture: (event: SpreadsheetKeyEvent) => void;\n onCopy: (event: SpreadsheetClipboardEvent) => void;\n onCut: (event: SpreadsheetClipboardEvent) => void;\n onPaste: (event: SpreadsheetClipboardEvent) => void;\n };\n /** Props for one data cell. */\n cellProps: (rowId: string, columnId: string) => SpreadsheetCellProps;\n /** True for the one cell that carries the drag handle. */\n isFillHandleCell: (rowId: string, columnId: string) => boolean;\n /** Starts a fill-handle drag. */\n startFillDrag: (event: { preventDefault: () => void; stopPropagation: () => void }) => void;\n canUndo: boolean;\n canRedo: boolean;\n /** The selected rectangle as an Excel/Sheets-compatible TSV block. */\n selectionTsv: () => string;\n /** The ids the current rectangle covers, in visual order. */\n selectionAddresses: () => { rowIds: string[]; columnIds: string[] };\n /** Focus return, cursor moves and selection, for a consumer that draws its own editors. */\n controller: SpreadsheetController;\n}\n\nexport interface SpreadsheetKeyEvent {\n key: string;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n target: EventTarget | null;\n preventDefault: () => void;\n}\n\nexport interface SpreadsheetClipboardEvent {\n clipboardData: { getData: (type: string) => string; setData: (type: string, value: string) => void } | null;\n target: EventTarget | null;\n preventDefault: () => void;\n}\n\n/** A keystroke aimed at a text box is the text box's, never the grid's. */\nfunction targetsAnEditor(target: unknown): boolean {\n const element = target instanceof Element ? target : null;\n if (!element) return false;\n // The TYPE CATCHER is a textarea only so it can take text; it is the grid's own focus, never\n // an editor, and every grid key must still reach the grid through it.\n if (element.closest(`[${TYPE_CATCHER_ATTR}]`)) return false;\n return element.closest(\"input, textarea, select, [contenteditable='true']\") !== null;\n}\n\n/** Marks the grid's hidden type catcher (type-to-edit). */\nexport const TYPE_CATCHER_ATTR = \"data-matrx-type-catcher\";\n\n/**\n * Invisible and out of the layout, yet focusable (never `display: none` or `visibility:\n * hidden`, which would make it unfocusable). `fixed` at the viewport's corner so focusing it\n * never scrolls the page, 1px so a browser still treats it as a real text field for IME.\n */\nconst TYPE_CATCHER_STYLE = {\n position: \"fixed\",\n top: 0,\n left: 0,\n width: 1,\n height: 1,\n padding: 0,\n border: 0,\n opacity: 0,\n overflow: \"hidden\",\n resize: \"none\",\n pointerEvents: \"none\",\n} as const;\n\n/** The hidden textarea the grid's keyboard focus lives in. */\nexport interface SpreadsheetTypeCatcherProps {\n ref: (element: HTMLTextAreaElement | null) => void;\n tabIndex: -1;\n \"aria-label\": string;\n inputMode: \"none\";\n autoComplete: \"off\";\n autoCorrect: \"off\";\n autoCapitalize: \"off\";\n spellCheck: false;\n rows: 1;\n [TYPE_CATCHER_ATTR]: \"\";\n style: typeof TYPE_CATCHER_STYLE;\n onInput: (event: { currentTarget: { value: string }; nativeEvent?: unknown }) => void;\n onCompositionStart: () => void;\n onCompositionEnd: (event: { data?: string; currentTarget: { value: string } }) => void;\n}\n\n/**\n * 🚨 A PORTALLED OVERLAY IS A REACT DESCENDANT OF THE CELL THAT OPENED IT, AND ITS\n * EVENTS ARRIVE HERE (lane FIX-15, 2026-09-23).\n *\n * React synthetic events propagate through the REACT tree, not the DOM tree. A cell that\n * renders a Radix Popover renders the popover's content through a portal into\n * `document.body` — nowhere near the table in the DOM, but still this cell's child in\n * React. So every mousedown, keystroke and clipboard event inside that popover was\n * delivered to the grid's own handlers as if the person had clicked the cell.\n *\n * WHAT THAT DID ON A LIVE SCREEN: one real mouse click in the relation picker's own\n * `Search` box ran `cellProps.onMouseDown`, whose `preventDefault()` (correct, for a\n * cell) stopped the input from taking focus and whose `focus()` then moved focus to the\n * scroll container. Radix saw focus leave its layer and dismissed the picker. The control\n * was unreachable: the box could not be typed into at all, so the Create offer behind it\n * could never be seen. Measured on production 2026-09-22 (build 93970125f9) and\n * reproduced on this package's own harness.\n *\n * THE TEST IS DOM CONTAINMENT, not a list of known overlays: an event whose real target\n * is not inside the element the handler is bound to was re-parented by a portal and is\n * not the grid's. That covers the relation picker, the person picker, a select, a date\n * picker, a menu — every overlay any cell renders, today and tomorrow.\n */\nfunction camePastAPortal(target: unknown, host: unknown): boolean {\n if (typeof Element === \"undefined\") return false;\n const element = target instanceof Element ? target : null;\n const owner = host instanceof Element ? host : null;\n if (!element || !owner) return false;\n return !owner.contains(element);\n}\n\n/**\n * The same question for the container's own handlers, which have no per-cell element to\n * measure against: an event that did not come from inside A spreadsheet scroll container\n * came past a portal.\n */\nfunction camePastAPortalIntoTheContainer(target: EventTarget | null): boolean {\n if (typeof Element === \"undefined\") return false;\n const element = target instanceof Element ? target : null;\n if (!element) return false;\n return element.closest('[data-matrx-table-spreadsheet=\"true\"]') === null;\n}\n\n/** A menu, picker, dialog or toast — a layer the grid (or its host) opened; a click there is not \"off the table\". */\nconst LAYERS_THE_GRID_OPENS =\n '[data-radix-popper-content-wrapper], [data-radix-portal], [role=\"menu\"], [role=\"listbox\"], [role=\"dialog\"], [role=\"alertdialog\"], [data-sonner-toaster]';\n\nconst NOTHING: CellPatch[] = [];\n\n/** What a refused write says on a read-only grid when the consumer names nothing better. */\nexport const READ_ONLY_REASON = \"This table is view-only — you can select and copy, but not change cells.\";\n\nexport function useSpreadsheetGrid(\n options: SpreadsheetGridOptions | null,\n): SpreadsheetGrid {\n const [range, setRange] = useState<CellRange | null>(null);\n const [fillTarget, setFillTarget] = useState<FillRect | null>(null);\n const [stack, setStack] = useState<UndoStack>(() => createUndoStack(options?.undoDepth ?? 100));\n const draggingRef = useRef<\"select\" | \"fill\" | null>(null);\n const catcherRef = useRef<HTMLTextAreaElement | null>(null);\n const composingRef = useRef(false);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const rowIds = options?.rowIds ?? [];\n const columnIds = useMemo(\n () => (options ? options.columnIds.filter((id) => id !== \"\") : []),\n [options],\n );\n\n // A selection whose row or column is gone is a selection pointing at nothing.\n // Drop it rather than letting the next keystroke land somewhere arbitrary.\n useEffect(() => {\n if (!range) return;\n const rowSet = new Set(rowIds);\n const colSet = new Set(columnIds);\n const alive =\n rowSet.has(range.anchor.rowId) &&\n rowSet.has(range.focus.rowId) &&\n colSet.has(range.anchor.fieldName) &&\n colSet.has(range.focus.fieldName);\n if (!alive) setRange(null);\n }, [range, rowIds, columnIds]);\n\n const bounds = useMemo(\n () => (range ? rangeBounds(range, rowIds, columnIds) : null),\n [range, rowIds, columnIds],\n );\n\n /**\n * 🚨 INDEXES, NOT `indexOf`.\n *\n * `cellProps` runs for every rendered cell on every render and needs the\n * row's and the column's position. `rowIds.indexOf(rowId)` there is O(rows)\n * per cell: on a hundred-thousand-row table with forty rows and six columns\n * in the window, twenty-four million comparisons PER FRAME. Nothing in jsdom\n * could show it — the DOM was right, it was just slow.\n */\n const rowIndexById = useMemo(() => {\n const map = new Map<string, number>();\n for (let i = 0; i < rowIds.length; i += 1) map.set(rowIds[i]!, i);\n return map;\n }, [rowIds]);\n const columnIndexById = useMemo(() => {\n const map = new Map<string, number>();\n for (let i = 0; i < columnIds.length; i += 1) map.set(columnIds[i]!, i);\n return map;\n }, [columnIds]);\n\n /**\n * THE ONE GATE every write passes through.\n *\n * A column rule that only the inline editor enforced would be a rule a paste\n * walks around — so the same judgement runs here, on the patches a paste, a\n * fill or a clear produced, and a refused cell is dropped and NAMED rather\n * than written or silently skipped.\n */\n const screenPatches = useCallback(\n (patches: CellPatch[]): { accepted: CellPatch[]; refusals: PasteRefusal[] } => {\n const validate = optionsRef.current?.validateCell;\n if (!validate) return { accepted: patches, refusals: [] };\n const accepted: CellPatch[] = [];\n const refusals: PasteRefusal[] = [];\n for (const patch of patches) {\n let reason: string | undefined;\n try {\n reason = validate({ rowId: patch.rowId, columnId: patch.columnId, value: patch.value });\n } catch {\n reason = undefined;\n }\n if (reason) refusals.push({ rowId: patch.rowId, columnId: patch.columnId, reason, value: patch.value });\n else accepted.push(patch);\n }\n return { accepted, refusals };\n },\n [],\n );\n\n const applyPatches = useCallback(\n async (patches: CellPatch[], intent: SpreadsheetIntent, label: string) => {\n const current = optionsRef.current;\n if (!current || patches.length === 0) return;\n const depth = current.undoDepth ?? 100;\n if (depth > 0 && intent !== \"undo\" && intent !== \"redo\") {\n const before: CellPatch[] = patches.map((patch) => ({\n rowId: patch.rowId,\n columnId: patch.columnId,\n value: current.valueAt(patch.rowId, patch.columnId) ?? null,\n }));\n setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));\n }\n if (!current.onPatch) return;\n await current.onPatch(patches, intent);\n },\n [],\n );\n\n /**\n * THE READ-ONLY GATE. Every gesture that would write asks this first; on a\n * reader's grid it says why and the gesture stops — never a silent no-op.\n */\n const refuseWrite = useCallback((): boolean => {\n const current = optionsRef.current;\n if (!current) return true;\n if (!current.readOnly && current.onPatch) return false;\n current.notify?.(current.readOnlyReason ?? READ_ONLY_REASON);\n return true;\n }, []);\n\n const readValueAt = useCallback((rowIndex: number, columnIndex: number) => {\n const current = optionsRef.current;\n const rowId = current?.rowIds[rowIndex];\n const columnId = current?.columnIds[columnIndex];\n if (!current || rowId === undefined || columnId === undefined) return null;\n return current.valueAt(rowId, columnId);\n }, []);\n\n const fillRequestBase = useCallback(() => {\n const current = optionsRef.current;\n if (!current) return null;\n return {\n valueAt: readValueAt,\n columnIds: [...current.columnIds],\n rowIds: [...current.rowIds],\n isColumnWritable: current.isColumnWritable,\n };\n }, [readValueAt]);\n\n const announceSkips = useCallback(\n (skipped: PasteRefusal[], summary: string) => {\n const current = optionsRef.current;\n if (!current?.notify) return;\n if (skipped.length === 0) {\n current.notify(summary);\n return;\n }\n // Never a silent drop: the first refusal is named, and the rest counted.\n const first = skipped[0];\n const reason = current.columnRefusal?.(first!.columnId) ?? first!.reason;\n const rest = skipped.length > 1 ? ` ${skipped.length - 1} more were left alone.` : \"\";\n current.notify(`${summary} ${reason}${rest}`);\n },\n [],\n );\n\n const doFillDown = useCallback(async () => {\n if (refuseWrite()) return;\n const base = fillRequestBase();\n if (!base || !bounds) return;\n const result = fillDown({\n ...base,\n range: { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },\n });\n const screened = screenPatches(result.patches);\n await applyPatches(screened.accepted, \"fill-down\", \"Filled down\");\n announceSkips([...result.skipped, ...screened.refusals], result.summary);\n }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);\n\n const doClear = useCallback(async () => {\n if (refuseWrite()) return;\n const current = optionsRef.current;\n if (!current || !bounds) return;\n const patches: CellPatch[] = [];\n const skipped: PasteRefusal[] = [];\n for (let r = bounds.r0; r <= bounds.r1; r += 1) {\n for (let c = bounds.c0; c <= bounds.c1; c += 1) {\n const rowId = current.rowIds[r];\n const columnId = current.columnIds[c];\n if (rowId === undefined || columnId === undefined) continue;\n if (!current.isColumnWritable(columnId)) {\n skipped.push({\n rowId,\n columnId,\n reason: current.columnRefusal?.(columnId) ?? `${columnId} cannot be cleared.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: null });\n }\n }\n const screened = screenPatches(patches);\n await applyPatches(screened.accepted, \"clear\", \"Cleared\");\n announceSkips(\n [...skipped, ...screened.refusals],\n `Cleared ${screened.accepted.length} cells.`,\n );\n }, [bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);\n\n const doUndo = useCallback(async () => {\n if (refuseWrite()) return;\n const taken = popUndo(stack);\n if (!taken.entry) return;\n setStack(taken.stack);\n await applyPatches(taken.entry.undo, \"undo\", taken.entry.label);\n optionsRef.current?.notify?.(taken.entry.label);\n }, [stack, applyPatches, refuseWrite]);\n\n const doRedo = useCallback(async () => {\n if (refuseWrite()) return;\n const taken = popRedo(stack);\n if (!taken.entry) return;\n setStack(taken.stack);\n await applyPatches(taken.entry.redo, \"redo\", taken.entry.label);\n }, [stack, applyPatches, refuseWrite]);\n\n const move = useCallback(\n (direction: Parameters<typeof moveSelection>[1], extend: boolean) => {\n setRange((previous) => {\n const from = previous ? previous.focus : null;\n const next = moveSelection(from, direction, rowIds, columnIds);\n if (!next) return previous;\n if (extend && previous) return { anchor: previous.anchor, focus: next };\n return { anchor: next, focus: next };\n });\n },\n [rowIds, columnIds],\n );\n\n /*\n * ═══ THE KEYBOARD COMES BACK (merged-grid review 2026-09-26, A1) ══════════════════════════════\n *\n * An open editor owns focus; when it closes (Enter, Tab, Escape, a pick in a list) the element\n * that had focus is unmounted and the browser drops focus on <body>. From there no arrow, no\n * Enter and no Cmd-Z reaches the grid until somebody clicks — the old /data grid's\n * `refocusGrid` handoff, which this primitive never had. Sheets and Airtable: the cursor moves\n * (down on Enter, right on Tab) and the grid has the keyboard, every time.\n */\n const rowIdsRef = useRef<readonly string[]>(rowIds);\n rowIdsRef.current = rowIds;\n const columnIdsRef = useRef<readonly string[]>(columnIds);\n columnIdsRef.current = columnIds;\n const rangeRef = useRef<CellRange | null>(range);\n rangeRef.current = range;\n\n const focusGrid = useCallback(() => {\n const catcher = catcherRef.current;\n if (catcher && catcher.isConnected) {\n catcher.focus({ preventScroll: true });\n return;\n }\n }, []);\n\n /** Nobody has the keyboard: it fell to the page body (or the element holding it is gone). */\n const focusIsLost = useCallback((): boolean => {\n if (typeof document === \"undefined\") return false;\n const active = document.activeElement;\n return !active || active === document.body || active === document.documentElement || !active.isConnected;\n }, []);\n\n /** Lost, or still inside this grid (an editor's own leftover, a cell's inner control). */\n const focusIsLostOrOurs = useCallback((): boolean => {\n if (focusIsLost()) return true;\n const container = catcherRef.current?.closest('[data-matrx-table-spreadsheet=\"true\"]');\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n return Boolean(container && active && container.contains(active));\n }, [focusIsLost]);\n\n const endEdit = useCallback(\n (\n exit: SpreadsheetEditExit = \"stay\",\n from: { rowId: string; columnId: string } | null = null,\n how: \"keyboard\" | \"pointer\" = \"keyboard\",\n ) => {\n const move: Parameters<typeof moveSelection>[1] | null =\n exit === \"down\" ? \"down\" : exit === \"up\" ? \"up\" : exit === \"right\" ? \"nextCell\" : exit === \"left\" ? \"prevCell\" : null;\n const at: CellAddress | null = from ? { rowId: from.rowId, fieldName: from.columnId } : null;\n setRange((previous) => {\n // A POINTER EXIT NEVER MOVES A CURSOR THE PERSON HAS SINCE MOVED: the click that closed a\n // picker by landing on another cell already put the cursor there.\n if (how === \"pointer\" && previous && at && !sameCell(previous.focus, at)) return previous;\n if (move === null) return previous ?? (at ? { anchor: at, focus: at } : previous);\n const start = at ?? previous?.focus ?? null;\n if (!start) return previous;\n const next = moveSelection(start, move, rowIdsRef.current, columnIdsRef.current);\n return next ? { anchor: next, focus: next } : previous;\n });\n if (how === \"keyboard\") {\n focusGrid();\n return;\n }\n // A pick in a list: the list's own element may still hold focus for this tick, and is gone\n // on the next. Take the keyboard back only if nobody else took it.\n if (focusIsLostOrOurs()) focusGrid();\n setTimeout(() => {\n if (focusIsLost()) focusGrid();\n }, 0);\n },\n [focusGrid, focusIsLost, focusIsLostOrOurs],\n );\n\n const select = useCallback(\n (anchor: { rowId: string; columnId: string }, focus?: { rowId: string; columnId: string }) => {\n const a: CellAddress = { rowId: anchor.rowId, fieldName: anchor.columnId };\n const f: CellAddress = focus ? { rowId: focus.rowId, fieldName: focus.columnId } : a;\n setRange({ anchor: a, focus: f });\n },\n [],\n );\n\n const onKeyDownCapture = useCallback(\n (event: SpreadsheetKeyEvent) => {\n const key = event.key;\n if (key !== \"Enter\" && key !== \"Tab\" && key !== \"Escape\") return;\n // Only keys pressed INSIDE an editor: the table's own inputs, or a portalled picker's list.\n const element = event.target instanceof Element ? event.target : null;\n if (!element || element.closest(`[${TYPE_CATCHER_ATTR}]`)) return;\n const inEditor =\n targetsAnEditor(event.target) ||\n (camePastAPortalIntoTheContainer(event.target) &&\n element.closest('input, textarea, select, [contenteditable=\"true\"], [role=\"listbox\"], [role=\"option\"], [role=\"combobox\"]') !== null);\n if (!inEditor) return;\n // Shift-Enter / Alt-Enter is the newline in a multi-line editor, never an exit.\n if (key === \"Enter\" && (event.shiftKey || event.altKey)) return;\n const exit: SpreadsheetEditExit = key === \"Enter\" ? \"down\" : key === \"Tab\" ? (event.shiftKey ? \"left\" : \"right\") : \"stay\";\n const at = rangeRef.current?.focus ?? null;\n setTimeout(() => {\n // The consumer already handed focus back (`endEdit`), or the editor kept it (a refused\n // value stays open): nothing to do.\n if (!focusIsLost()) return;\n endEdit(exit, at ? { rowId: at.rowId, columnId: at.fieldName } : null, \"keyboard\");\n }, 0);\n },\n [endEdit, focusIsLost],\n );\n\n /*\n * ═══ A CLICK OFF THE TABLE LETS GO (lane DATA-V2-BASICS, 2026-09-27) ═══════════════════════════\n *\n * Arman: \"when you click off of the table, the cells don't lose focus but they should because\n * that's often the easiest way you get yourself out of strange state conditions or multiple cells\n * being selected.\" A 12-cell range survived a click on the page header and on the sidebar, and\n * the keyboard had already fallen to <body>, so Escape could not clear it either. Sheets, Excel\n * and Airtable let go on an outside click. So while a range is held: a pointerdown outside the\n * TABLE (its root — cells, toolbar, footer) and outside every layer it may have opened (a menu,\n * a picker, a dialog, a toast) clears the range and takes the keyboard off the grid. An open cell\n * editor loses focus with it, which saves it exactly as a Tab away would.\n */\n const holdsARange = range !== null;\n useEffect(() => {\n if (!holdsARange || typeof document === \"undefined\") return;\n const onPointerDown = (event: Event) => {\n const target = event.target instanceof Element ? event.target : null;\n const container = catcherRef.current?.closest<HTMLElement>('[data-matrx-table-spreadsheet=\"true\"]');\n if (!target || !container || !container.isConnected) return;\n const table = container.closest<HTMLElement>(\"[data-matrx-table]\") ?? container;\n if (table.contains(target) || target.closest(LAYERS_THE_GRID_OPENS)) return;\n setRange(null);\n setFillTarget(null);\n const active = document.activeElement;\n if (active instanceof HTMLElement && container.contains(active)) active.blur();\n };\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown, true);\n }, [holdsARange]);\n\n const controller = useMemo<SpreadsheetController>(() => ({ endEdit, select, focusGrid }), [endEdit, select, focusGrid]);\n\n const onKeyDown = useCallback(\n (event: SpreadsheetKeyEvent) => {\n const current = optionsRef.current;\n if (!current) return;\n if (targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n // Undo and redo are not in the grid key vocabulary, because in the app\n // they were the editor's. Here the stack belongs to the GRID, so the\n // keystrokes do too — and they are recognised before anything else so a\n // Cmd-Z can never be read as a move.\n const accel = event.metaKey || event.ctrlKey;\n if (accel && (event.key === \"z\" || event.key === \"Z\")) {\n event.preventDefault();\n if (event.shiftKey) void doRedo();\n else void doUndo();\n return;\n }\n if (accel && (event.key === \"y\" || event.key === \"Y\")) {\n event.preventDefault();\n void doRedo();\n return;\n }\n const action = classifyGridKey(event);\n if (!action) return;\n switch (action.kind) {\n case \"move\":\n event.preventDefault();\n move(action.move, false);\n return;\n case \"extend\":\n event.preventDefault();\n move(action.move, true);\n return;\n case \"selectAll\": {\n event.preventDefault();\n const all = allRange(rowIds, columnIds);\n if (all) setRange(all);\n return;\n }\n case \"selectRow\": {\n event.preventDefault();\n const focused = range?.focus;\n const next = focused ? rowRange(focused.rowId, columnIds) : null;\n if (next) setRange(next);\n return;\n }\n case \"selectColumn\": {\n event.preventDefault();\n const focused = range?.focus;\n const next = focused ? columnRange(focused.fieldName, rowIds) : null;\n if (next) setRange(next);\n return;\n }\n case \"clearCell\":\n event.preventDefault();\n void doClear();\n return;\n case \"fillDown\":\n event.preventDefault();\n void doFillDown();\n return;\n case \"escape\":\n setRange(null);\n return;\n case \"edit\":\n case \"editSeeded\": {\n // ENTER OPENS THE CELL, and so does simply typing into it. The\n // gesture is the grid's to RECOGNISE and the table's to PERFORM: the\n // hook asks, and only consumes the keystroke when an editor actually\n // opened. Answering false leaves the key to whatever else wants it,\n // which is why a read-only column still types nothing instead of\n // swallowing the character.\n const focused = range?.focus;\n // SPACE EXPANDS THE ROW (Airtable): the record opens beside the grid, and the grid keeps\n // the keyboard so the arrows walk the panel from row to row.\n if (focused && event.key === \" \" && current.onOpenRow) {\n if (current.onOpenRow(focused.rowId)) {\n event.preventDefault();\n return;\n }\n }\n if (!focused || !current.onEditRequest || current.readOnly) return;\n const seed = action.kind === \"editSeeded\" ? action.seed : null;\n if (current.onEditRequest(focused, seed)) event.preventDefault();\n return;\n }\n default:\n // `copy`, `cut` and `paste` belong to the clipboard handlers below.\n // Claiming them here would take the gesture away from the thing\n // that actually performs it.\n return;\n }\n },\n [move, range, rowIds, columnIds, doClear, doFillDown, doUndo, doRedo],\n );\n\n const selectionAsTsv = useCallback(() => {\n const current = optionsRef.current;\n if (!current || !range) return \"\";\n const grid = rangeRows(range, rowIds, columnIds).map((row) =>\n row.map((address) => current.valueAt(address.rowId, address.fieldName)),\n );\n return gridToTsv(grid);\n }, [range, rowIds, columnIds]);\n\n const selectionAddresses = useCallback(() => {\n if (!range || !bounds) return { rowIds: [] as string[], columnIds: [] as string[] };\n return {\n rowIds: rowIds.slice(bounds.r0, bounds.r1 + 1),\n columnIds: columnIds.slice(bounds.c0, bounds.c1 + 1),\n };\n }, [range, bounds, rowIds, columnIds]);\n\n const onCopy = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n const text = selectionAsTsv();\n if (!text) return;\n event.preventDefault();\n event.clipboardData?.setData(\"text/plain\", text);\n // SAID, NOT SILENT (review B2: \"No Copied toast\"). A copy leaves no mark on the grid, so the\n // person is told it happened and how much went.\n const { rowIds: copiedRows, columnIds: copiedColumns } = selectionAddresses();\n const cells = Math.max(1, copiedRows.length * copiedColumns.length);\n optionsRef.current?.notify?.(`Copied ${cells} ${cells === 1 ? \"cell\" : \"cells\"}.`);\n },\n [range, selectionAsTsv, selectionAddresses],\n );\n\n const onCut = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n const text = selectionAsTsv();\n if (!text) return;\n event.preventDefault();\n event.clipboardData?.setData(\"text/plain\", text);\n void doClear();\n },\n [range, selectionAsTsv, doClear],\n );\n\n const onPaste = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n const current = optionsRef.current;\n if (!current || current.paste === false || !range || targetsAnEditor(event.target)) return;\n if (camePastAPortalIntoTheContainer(event.target)) return;\n const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) return;\n event.preventDefault();\n if (refuseWrite()) return;\n let block = parseClipboardGrid(text);\n let startRow = rowIds.indexOf(range.focus.rowId);\n let startCol = columnIds.indexOf(range.focus.fieldName);\n if (startRow === -1 || startCol === -1) return;\n /*\n * ONE VALUE OVER A SELECTED RANGE FILLS THE RANGE (review B2: \"pasting one value over a\n * 3-cell range writes only 1 cell\"). Google Sheets and Excel: a copied block whose size\n * divides the selected rectangle is repeated across it — one value fills every cell, a\n * 1×2 fills a 3×2 row by row. A block that does not divide it pastes once, at the top-left.\n */\n if (bounds) {\n const blockRows = block.length;\n const blockCols = block.reduce((widest, line) => Math.max(widest, line.length), 0);\n const height = bounds.r1 - bounds.r0 + 1;\n const width = bounds.c1 - bounds.c0 + 1;\n startRow = bounds.r0;\n startCol = bounds.c0;\n if (\n blockRows > 0 &&\n blockCols > 0 &&\n (height > blockRows || width > blockCols) &&\n height % blockRows === 0 &&\n width % blockCols === 0\n ) {\n const tiled: string[][] = [];\n for (let r = 0; r < height; r += 1) {\n const line: string[] = [];\n for (let c = 0; c < width; c += 1) line.push(block[r % blockRows]?.[c % blockCols] ?? \"\");\n tiled.push(line);\n }\n block = tiled;\n }\n }\n // THE PASTED BLOCK IS WHAT IS SELECTED AFTERWARDS, as in Sheets: the next Cmd-C, Delete or\n // fill acts on what just landed, and the grid keeps the keyboard.\n {\n const lastRow = Math.min(startRow + Math.max(block.length, 1) - 1, rowIds.length - 1);\n const widest = block.reduce((w, line) => Math.max(w, line.length), 1);\n const lastCol = Math.min(startCol + widest - 1, columnIds.length - 1);\n const anchor: CellAddress = { rowId: rowIds[startRow]!, fieldName: columnIds[startCol]! };\n const focus: CellAddress = { rowId: rowIds[lastRow]!, fieldName: columnIds[lastCol]! };\n setRange({ anchor, focus });\n }\n const patches: CellPatch[] = [];\n const skipped: PasteRefusal[] = [];\n // PAST THE RIGHT EDGE AND PAST THE BOTTOM ARE DIFFERENT ANSWERS. There is\n // no column to hold a cell past the right edge and there never will be\n // one on this grid, so it is counted. A row past the BOTTOM is a row the\n // consumer can make — it is collected whole, in the grid's column order,\n // and handed over. Until 2026-09-21 both were counted together and\n // dropped, which is how a paste of fifty spreadsheet rows into a\n // three-row table wrote three rows and announced that forty-seven \"landed\n // past the edge of the table and were ignored\".\n let pastRightEdge = 0;\n const overflow: Array<Record<string, string>> = [];\n for (let r = 0; r < block.length; r += 1) {\n const line = block[r] ?? [];\n const rowId = rowIds[startRow + r];\n if (rowId === undefined) {\n const beyond: Record<string, string> = {};\n for (let c = 0; c < line.length; c += 1) {\n const columnId = columnIds[startCol + c];\n if (columnId === undefined) {\n pastRightEdge += 1;\n continue;\n }\n beyond[columnId] = line[c] ?? \"\";\n }\n overflow.push(beyond);\n continue;\n }\n for (let c = 0; c < line.length; c += 1) {\n const columnId = columnIds[startCol + c];\n if (columnId === undefined) {\n pastRightEdge += 1;\n continue;\n }\n if (!current.isColumnWritable(columnId)) {\n skipped.push({\n rowId,\n columnId,\n reason:\n current.columnRefusal?.(columnId) ??\n `${columnId} cannot take a pasted value.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: line[c] ?? null });\n }\n }\n const screened = screenPatches(patches);\n const refusals = [...skipped, ...screened.refusals];\n // THE CONSUMER MAY WANT TO SHOW THIS BEFORE ANY OF IT HAPPENS. When it\n // does, NOTHING is applied here and nothing is announced: the plan is the\n // whole answer, and `apply()` is the only way it becomes a write.\n if (current.onPastePlan) {\n const accepted = screened.accepted;\n current.onPastePlan({\n accepted,\n refusals,\n overflow,\n pastRightEdge,\n apply: async () => {\n await applyPatches(accepted, \"paste\", \"Pasted\");\n },\n });\n return;\n }\n void applyPatches(screened.accepted, \"paste\", \"Pasted\");\n const outside = pastRightEdge + overflow.reduce((n, row) => n + Object.keys(row).length, 0);\n const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : \"\";\n announceSkips(refusals, `Pasted ${screened.accepted.length} cells.${tail}`);\n },\n [range, bounds, rowIds, columnIds, applyPatches, announceSkips, screenPatches, refuseWrite],\n );\n\n const cellProps = useCallback(\n (rowId: string, columnId: string): SpreadsheetCellProps => {\n const address: CellAddress = { rowId, fieldName: columnId };\n const rowIndex = rowIndexById.get(rowId) ?? -1;\n const colIndex = columnIndexById.get(columnId) ?? -1;\n const selected =\n bounds !== null &&\n rowIndex >= bounds.r0 &&\n rowIndex <= bounds.r1 &&\n colIndex >= bounds.c0 &&\n colIndex <= bounds.c1;\n const active = range !== null && sameCell(range.focus, address);\n const inFillPreview =\n fillTarget !== null &&\n rowIndex >= fillTarget.top &&\n rowIndex <= fillTarget.bottom &&\n colIndex >= fillTarget.left &&\n colIndex <= fillTarget.right;\n return {\n \"data-matrx-cell-row\": rowId,\n \"data-matrx-cell-col\": columnId,\n ...(selected ? { \"data-matrx-cell-selected\": \"true\" as const } : {}),\n ...(active ? { \"data-matrx-cell-active\": \"true\" as const } : {}),\n ...(inFillPreview ? { \"data-matrx-cell-fill-preview\": \"true\" as const } : {}),\n onMouseDown: (event) => {\n if (event.button !== 0) return;\n // A press inside a popover, menu or dialog this cell rendered through a\n // portal is not a press on the cell. See `camePastAPortal`: without this,\n // the `preventDefault()` below stole focus from the control the person was\n // actually pressing and the overlay dismissed itself.\n if (camePastAPortal(event.target ?? null, event.currentTarget)) return;\n // A press inside the cell's OWN open editor (its text box) is the text box's: the person\n // is placing the caret or fixing a refused value, and taking focus to the type catcher\n // left the editor open with no way to type into it or Escape out of it (merged-grid\n // review lane A, 2026-09-26: a refused typed date could not be corrected by mouse).\n if (targetsAnEditor(event.target ?? null)) return;\n // 🚨 `preventDefault` ON A MOUSEDOWN ALSO PREVENTS FOCUS. Without the\n // line below, clicking a cell selected it and then every keystroke —\n // Enter, a typed character, an arrow, Cmd-Z — went to the document\n // body instead of the grid. jsdom could never show it, because a\n // jsdom test dispatches the keydown ON the container by hand; a real\n // browser routes it to whatever actually has focus, and nothing did.\n event.preventDefault();\n const origin = event.currentTarget;\n if (typeof HTMLElement !== \"undefined\" && origin instanceof HTMLElement) {\n const container = origin.closest<HTMLElement>('[data-matrx-table-spreadsheet=\"true\"]');\n // The grid's focus is its TYPE CATCHER, so typed text has somewhere to land.\n const catcher = container?.querySelector<HTMLTextAreaElement>(`[${TYPE_CATCHER_ATTR}]`);\n (catcher ?? container)?.focus({ preventScroll: true });\n }\n setRange((previous) =>\n event.shiftKey && previous\n ? { anchor: previous.anchor, focus: address }\n : { anchor: address, focus: address },\n );\n draggingRef.current = \"select\";\n },\n onMouseEnter: () => {\n if (draggingRef.current === \"select\") {\n setRange((previous) => (previous ? { anchor: previous.anchor, focus: address } : previous));\n return;\n }\n if (draggingRef.current === \"fill\" && bounds) {\n setFillTarget(\n fillTargetFor(\n { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },\n rowIndex,\n colIndex,\n ),\n );\n }\n },\n };\n },\n [rowIndexById, columnIndexById, bounds, range, fillTarget],\n );\n\n const isFillHandleCell = useCallback(\n (rowId: string, columnId: string) => {\n const current = optionsRef.current;\n if (!current || current.fillHandle === false || current.readOnly || !bounds) return false;\n return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId;\n },\n [bounds, rowIds, columnIds],\n );\n\n const startFillDrag = useCallback(\n (event: { preventDefault: () => void; stopPropagation: () => void }) => {\n event.preventDefault();\n event.stopPropagation();\n draggingRef.current = \"fill\";\n },\n [],\n );\n\n // The drag ends on the window, wherever the pointer is — so the commit has\n // to live where the target last was, not where the mouse came up.\n const fillTargetRef = useRef<FillRect | null>(null);\n fillTargetRef.current = fillTarget;\n const boundsRef = useRef<RangeBounds | null>(null);\n boundsRef.current = bounds;\n useEffect(() => {\n if (!options) return;\n // 🚨 ONE mouseup handler, not two. There used to be a second listener whose\n // only job was to clear `draggingRef` — registered FIRST, so on every real\n // mouse-up it ran first, nulled the ref, and this commit then saw no drag\n // in progress and did nothing. The fill handle dragged, previewed, and\n // silently filled nothing. jsdom never fires a window mouse-up, so only a\n // real pointer could find it.\n const commit = () => {\n const wasDragging = draggingRef.current;\n draggingRef.current = null;\n if (wasDragging !== \"fill\") return;\n const target = fillTargetRef.current;\n const source = boundsRef.current;\n setFillTarget(null);\n if (!target || !source) return;\n if (refuseWrite()) return;\n const base = fillRequestBase();\n if (!base) return;\n const result = fillRange({\n ...base,\n source: { top: source.r0, left: source.c0, bottom: source.r1, right: source.c1 },\n target,\n });\n const screened = screenPatches(result.patches);\n void applyPatches(screened.accepted, \"fill\", \"Filled\");\n announceSkips([...result.skipped, ...screened.refusals], result.summary);\n };\n window.addEventListener(\"mouseup\", commit);\n return () => window.removeEventListener(\"mouseup\", commit);\n }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches, refuseWrite]);\n\n /*\n * ═══ THE TYPE CATCHER — TYPING HAS TWO DOORS ═══════════════════════════════════════════════\n *\n * `keydown` catches a plain letter (`editSeeded` above). A lot of real typing never produces a\n * one-character keydown: an IME composition (key \"Process\"), an Option / AltGr character\n * (refused by `isTypingKey` as a possible shortcut), a dead-key accent, dictation, the\n * browser's own insertText. A focused <div> cannot take text, so all of that went nowhere and\n * the next Enter opened the OLD value. The grid's focus therefore lives in a hidden textarea\n * inside the container (`inputMode=\"none\"`, so a phone raises no keyboard for a tap): keys the\n * grid knows still bubble to `onKeyDown` and are prevented there exactly as before; any TEXT\n * that lands in the catcher opens the selected cell's editor seeded with it. A paste or a drop\n * is the clipboard's, never a seed; a space alone opens nothing; a read-only grid takes nothing.\n */\n const takeTypedText = useCallback(\n (text: string) => {\n if (catcherRef.current) catcherRef.current.value = \"\";\n const current = optionsRef.current;\n const focused = range?.focus;\n if (!current || !focused || current.readOnly || !current.onEditRequest) return;\n if (text.trim() === \"\") return;\n current.onEditRequest(focused, text);\n },\n [range],\n );\n\n const typeCatcherProps: SpreadsheetTypeCatcherProps = {\n ref: (element) => {\n catcherRef.current = element;\n },\n tabIndex: -1,\n \"aria-label\": \"Type to edit the selected cell\",\n inputMode: \"none\",\n autoComplete: \"off\",\n autoCorrect: \"off\",\n autoCapitalize: \"off\",\n spellCheck: false,\n rows: 1,\n [TYPE_CATCHER_ATTR]: \"\",\n style: TYPE_CATCHER_STYLE,\n onInput: (event) => {\n const kind = (event.nativeEvent as { inputType?: unknown } | undefined)?.inputType;\n if (typeof kind === \"string\" && (kind.startsWith(\"insertFromPaste\") || kind.startsWith(\"insertFromDrop\"))) {\n event.currentTarget.value = \"\";\n return;\n }\n // Mid-composition the IME owns the text; the composed result arrives once, on compositionend.\n if (composingRef.current) return;\n takeTypedText(event.currentTarget.value);\n },\n onCompositionStart: () => {\n composingRef.current = true;\n },\n onCompositionEnd: (event) => {\n composingRef.current = false;\n takeTypedText(event.data || event.currentTarget.value);\n },\n };\n\n return {\n range,\n bounds,\n enabled: options !== null,\n typeCatcherProps,\n containerProps: {\n onFocus: (event) => {\n if (event.target !== event.currentTarget) return;\n catcherRef.current?.focus({ preventScroll: true });\n },\n tabIndex: options ? 0 : -1,\n onKeyDown,\n onKeyDownCapture,\n onCopy,\n onCut,\n onPaste,\n },\n cellProps,\n isFillHandleCell,\n startFillDrag,\n canUndo: stack.past.length > 0,\n canRedo: stack.future.length > 0,\n selectionTsv: selectionAsTsv,\n selectionAddresses,\n controller,\n };\n}\n\nexport { NOTHING as NO_PATCHES };\n","/**\n * Grid selection model for user data tables.\n * @ai-matrx/design-system/data-table/grid-selection\n *\n * This moved here from the app so every table in the product shares one\n * range-selection model.\n *\n * THE THREE STATES. A spreadsheet grid has three, not two, and collapsing them\n * to two is what made this grid feel like a web page instead of a tool:\n *\n * 1. NOTHING SELECTED — the grid at rest.\n * 2. SELECTED — one cell is current. Nothing has changed. You can\n * look, copy, and move with the keyboard.\n * 3. EDITING — a value is being changed.\n *\n * State 2 is the one that was missing, and its absence is why every edit needed\n * a double-click and why arrow keys, Tab, copy and fill-down were all\n * impossible: none of them mean anything without a notion of \"the current cell\".\n *\n * 🚨 THE CLICK LAW — a single click may SELECT a cell, TOGGLE a two-state\n * value, or OPEN a chooser. It may NEVER drop the user into a free-text buffer.\n * Opening a menu is not a mutation and toggling a checkbox is instantly\n * reversible, but landing in a text buffer turns every attempt to select-and-\n * copy into an accidental edit. `directClickKinds()` below is the whole list of\n * what a single click may act on; adding a free-text editor to it is a defect.\n *\n * A CHOOSER OPENS ON THE SECOND CLICK. A choice cell's first click SELECTS it\n * (so Cmd-C, Cmd-X, the arrow keys and the right-click menu all have a current\n * cell); a click on the cell that is ALREADY selected opens the option list —\n * the Airtable gesture. Opening the chooser on the very first click moved\n * focus into its search box, and every keyboard shortcut then went to that box\n * instead of the grid: a choice column could not be copied at all (2026-09-14).\n * Checkboxes and ratings stay one-click: a mis-click on those is instantly\n * undone in place and steals no focus.\n *\n * CLIPBOARD KEYS. Cmd-C / Cmd-X / Cmd-V on a selected cell act on THAT CELL —\n * copy its value, cut it (copy + clear), paste over it. A paste carrying a\n * spreadsheet block (tabs / line breaks) lands as a block from the selected\n * cell downward and rightward (`grid-clipboard.ts`). None of these need an\n * editor open, which is the whole point: a normal table copies what you point\n * at, not only what you are typing into.\n *\n * Addresses are (rowId, fieldName), never (rowIndex, colIndex): the grid\n * reloads after every write, and realtime reorders rows underneath the user. An\n * index-based selection silently jumps to a DIFFERENT cell when that happens —\n * and the next keystroke then edits the wrong row.\n *\n * Pure module: no React, no DOM. Everything here is a function of the current\n * address and the row/field lists, which is what makes the navigation testable\n * without mounting a grid.\n */\n\nexport type CellAddress = { rowId: string; fieldName: string };\n\nexport type GridMove =\n | \"up\"\n | \"down\"\n | \"left\"\n | \"right\"\n | \"nextCell\"\n | \"prevCell\"\n | \"rowStart\"\n | \"rowEnd\"\n | \"gridStart\"\n | \"gridEnd\";\n\n/** What a keystroke means when a cell is selected but NOT being edited. */\nexport type GridKeyAction =\n | { kind: \"move\"; move: GridMove }\n /** Shift+arrow: grow the range toward `move`, anchor fixed. */\n | { kind: \"extend\"; move: GridMove }\n | { kind: \"selectAll\" }\n | { kind: \"selectRow\" }\n | { kind: \"selectColumn\" }\n | { kind: \"fillDown\" }\n | { kind: \"edit\" }\n | { kind: \"editSeeded\"; seed: string }\n | { kind: \"clearCell\" }\n | { kind: \"copy\" }\n | { kind: \"cut\" }\n | { kind: \"paste\" }\n | { kind: \"escape\" }\n | null;\n\n// ─── Ranges ─────────────────────────────────────────────────────────────────\n//\n// A RANGE is an anchor (where the selection started — the cell with the ring)\n// and a focus (where it was extended to), both by ADDRESS. The rectangle they\n// span is resolved against the grid's current row and column order at the\n// moment it is needed, so a range survives a re-sort or a hidden column the\n// same way a single selection does: by naming cells, not positions.\n\nexport type CellRange = { anchor: CellAddress; focus: CellAddress };\n\n/** The rectangle a range spans, as inclusive indexes into the grid's order. */\nexport type RangeBounds = { r0: number; r1: number; c0: number; c1: number };\n\nexport function rangeBounds(\n range: CellRange,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): RangeBounds | null {\n const ra = rowIds.indexOf(range.anchor.rowId);\n const rf = rowIds.indexOf(range.focus.rowId);\n const ca = fieldNames.indexOf(range.anchor.fieldName);\n const cf = fieldNames.indexOf(range.focus.fieldName);\n if (ra === -1 || rf === -1 || ca === -1 || cf === -1) return null;\n return {\n r0: Math.min(ra, rf),\n r1: Math.max(ra, rf),\n c0: Math.min(ca, cf),\n c1: Math.max(ca, cf),\n };\n}\n\nexport function rangeSize(bounds: RangeBounds): {\n rows: number;\n cols: number;\n cells: number;\n} {\n const rows = bounds.r1 - bounds.r0 + 1;\n const cols = bounds.c1 - bounds.c0 + 1;\n return { rows, cols, cells: rows * cols };\n}\n\nexport function boundsContain(\n bounds: RangeBounds,\n rowIndex: number,\n colIndex: number,\n): boolean {\n return (\n rowIndex >= bounds.r0 &&\n rowIndex <= bounds.r1 &&\n colIndex >= bounds.c0 &&\n colIndex <= bounds.c1\n );\n}\n\n/** Every cell of the range in reading order (row by row, left to right). */\nexport function cellsInRange(\n range: CellRange,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellAddress[] {\n const b = rangeBounds(range, rowIds, fieldNames);\n if (!b) return [];\n const out: CellAddress[] = [];\n // `rangeBounds` derives every index from `indexOf`, so both lookups below are\n // in range; the guards make that explicit instead of asserting it.\n for (let r = b.r0; r <= b.r1; r += 1) {\n const rowId = rowIds[r];\n if (rowId === undefined) continue;\n for (let c = b.c0; c <= b.c1; c += 1) {\n const fieldName = fieldNames[c];\n if (fieldName === undefined) continue;\n out.push({ rowId, fieldName });\n }\n }\n return out;\n}\n\n/** The range as a 2-D grid of addresses — what a block copy serialises. */\nexport function rangeRows(\n range: CellRange,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellAddress[][] {\n const b = rangeBounds(range, rowIds, fieldNames);\n if (!b) return [];\n const rows: CellAddress[][] = [];\n // Same in-range guarantee as `cellsInRange`.\n for (let r = b.r0; r <= b.r1; r += 1) {\n const rowId = rowIds[r];\n if (rowId === undefined) continue;\n const row: CellAddress[] = [];\n for (let c = b.c0; c <= b.c1; c += 1) {\n const fieldName = fieldNames[c];\n if (fieldName === undefined) continue;\n row.push({ rowId, fieldName });\n }\n rows.push(row);\n }\n return rows;\n}\n\nexport function isSingleCellRange(range: CellRange): boolean {\n return sameCell(range.anchor, range.focus);\n}\n\n/** The whole row, first column to last — what clicking a row's edge selects. */\nexport function rowRange(\n rowId: string,\n fieldNames: readonly string[],\n): CellRange | null {\n const firstField = fieldNames[0];\n const lastField = fieldNames[fieldNames.length - 1];\n // A non-empty list always has both ends; the null here is the same \"no range\n // to make\" answer the empty-list case gives.\n if (firstField === undefined || lastField === undefined) return null;\n return {\n anchor: { rowId, fieldName: firstField },\n focus: { rowId, fieldName: lastField },\n };\n}\n\n/** The whole column across the page — what clicking a header's edge selects. */\nexport function columnRange(\n fieldName: string,\n rowIds: readonly string[],\n): CellRange | null {\n const firstRow = rowIds[0];\n const lastRow = rowIds[rowIds.length - 1];\n if (firstRow === undefined || lastRow === undefined) return null;\n return {\n anchor: { rowId: firstRow, fieldName },\n focus: { rowId: lastRow, fieldName },\n };\n}\n\n/** Every cell on the page. */\nexport function allRange(\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellRange | null {\n const firstRow = rowIds[0];\n const lastRow = rowIds[rowIds.length - 1];\n const firstField = fieldNames[0];\n const lastField = fieldNames[fieldNames.length - 1];\n if (\n firstRow === undefined ||\n lastRow === undefined ||\n firstField === undefined ||\n lastField === undefined\n ) {\n return null;\n }\n return {\n anchor: { rowId: firstRow, fieldName: firstField },\n focus: { rowId: lastRow, fieldName: lastField },\n };\n}\n\nexport function sameCell(\n a: CellAddress | null,\n b: CellAddress | null,\n): boolean {\n if (!a || !b) return a === b;\n return a.rowId === b.rowId && a.fieldName === b.fieldName;\n}\n\n/**\n * Where does `move` land from `current`?\n *\n * Returns null when the move is impossible (no selection, empty grid). Vertical\n * moves CLAMP at the edges — running into the top of the grid should park you\n * on the first row, not clear the selection, because a selection that vanishes\n * when you overshoot loses your place. Horizontal Tab moves WRAP to the next or\n * previous row, which is what makes Tab a usable data-entry gesture.\n */\nexport function moveSelection(\n current: CellAddress | null,\n move: GridMove,\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): CellAddress | null {\n if (rowIds.length === 0 || fieldNames.length === 0) return null;\n\n const firstRowId = rowIds[0];\n const firstFieldName = fieldNames[0];\n // Both lists are non-empty by the check above; `null` here is the same \"no\n // move is possible\" answer the empty-grid case gives.\n if (firstRowId === undefined || firstFieldName === undefined) return null;\n const first: CellAddress = { rowId: firstRowId, fieldName: firstFieldName };\n if (!current) return first;\n\n let r = rowIds.indexOf(current.rowId);\n let c = fieldNames.indexOf(current.fieldName);\n // The selected row or column disappeared (deleted, filtered out, re-sorted\n // off the page). Fall back to the start rather than computing from -1, which\n // would quietly resolve to the last element.\n if (r === -1 || c === -1) return first;\n\n const lastRow = rowIds.length - 1;\n const lastCol = fieldNames.length - 1;\n const clamp = (n: number, max: number) => Math.min(Math.max(n, 0), max);\n\n switch (move) {\n case \"up\":\n r = clamp(r - 1, lastRow);\n break;\n case \"down\":\n r = clamp(r + 1, lastRow);\n break;\n case \"left\":\n c = clamp(c - 1, lastCol);\n break;\n case \"right\":\n c = clamp(c + 1, lastCol);\n break;\n case \"rowStart\":\n c = 0;\n break;\n case \"rowEnd\":\n c = lastCol;\n break;\n case \"gridStart\":\n r = 0;\n c = 0;\n break;\n case \"gridEnd\":\n r = lastRow;\n c = lastCol;\n break;\n case \"nextCell\":\n if (c < lastCol) c += 1;\n else if (r < lastRow) {\n r += 1;\n c = 0;\n }\n break;\n case \"prevCell\":\n if (c > 0) c -= 1;\n else if (r > 0) {\n r -= 1;\n c = lastCol;\n }\n break;\n }\n\n // Every branch above clamps into range or leaves r/c at their `indexOf`\n // values, so both reads land; falling back to `first` matches what this\n // function already does when an index cannot be resolved.\n const rowId = rowIds[r];\n const fieldName = fieldNames[c];\n if (rowId === undefined || fieldName === undefined) return first;\n return { rowId, fieldName };\n}\n\n/**\n * A single printable character typed on a selected cell starts an edit seeded\n * with it — the spreadsheet reflex of \"just start typing to replace\".\n *\n * Modifier combinations are excluded so Cmd-C / Ctrl-R never get mistaken for\n * typing, and only genuinely printable single characters qualify: `key` is the\n * character itself for those, and a multi-character name (\"ArrowUp\", \"F3\") for\n * everything else.\n */\nexport function isTypingKey(e: {\n key: string;\n ctrlKey: boolean;\n metaKey: boolean;\n altKey: boolean;\n}): boolean {\n if (e.ctrlKey || e.metaKey || e.altKey) return false;\n return e.key.length === 1 && e.key !== \" \";\n}\n\n/**\n * Translate a keystroke into a grid action. Called ONLY when a cell is selected\n * and not being edited — an editor owns its own keys while it is open.\n */\nexport function classifyGridKey(e: {\n key: string;\n shiftKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n altKey: boolean;\n}): GridKeyAction {\n const mod = e.ctrlKey || e.metaKey;\n\n // Shift+arrow GROWS the selection instead of moving it — the spreadsheet\n // gesture for \"these cells\". Shift+Tab stays a move (previous cell).\n const arrowMove: Record<string, GridMove> = {\n ArrowUp: mod ? \"gridStart\" : \"up\",\n ArrowDown: mod ? \"gridEnd\" : \"down\",\n ArrowLeft: mod ? \"rowStart\" : \"left\",\n ArrowRight: mod ? \"rowEnd\" : \"right\",\n Home: mod ? \"gridStart\" : \"rowStart\",\n End: mod ? \"gridEnd\" : \"rowEnd\",\n };\n // Read the entry rather than testing `in`: `in` also answers true for\n // inherited Object.prototype names (\"constructor\", \"toString\"), which used to\n // produce a move action carrying an undefined move.\n const arrow = arrowMove[e.key];\n if (arrow !== undefined) {\n return { kind: e.shiftKey ? \"extend\" : \"move\", move: arrow };\n }\n\n switch (e.key) {\n case \"Tab\":\n return { kind: \"move\", move: e.shiftKey ? \"prevCell\" : \"nextCell\" };\n case \"Enter\":\n case \"F2\":\n return { kind: \"edit\" };\n case \" \":\n // Shift+Space selects the row and Ctrl/Cmd+Space the column (Excel);\n // plain Space opens the editor without seeding it, so a boolean or\n // choice cell can be operated entirely from the keyboard.\n if (e.shiftKey) return { kind: \"selectRow\" };\n if (mod) return { kind: \"selectColumn\" };\n return { kind: \"edit\" };\n case \"a\":\n case \"A\":\n if (mod) return { kind: \"selectAll\" };\n break;\n case \"d\":\n case \"D\":\n if (mod) return { kind: \"fillDown\" };\n break;\n case \"Escape\":\n return { kind: \"escape\" };\n case \"Delete\":\n case \"Backspace\":\n return { kind: \"clearCell\" };\n case \"c\":\n case \"C\":\n if (mod) return { kind: \"copy\" };\n break;\n case \"x\":\n case \"X\":\n if (mod) return { kind: \"cut\" };\n break;\n case \"v\":\n case \"V\":\n if (mod) return { kind: \"paste\" };\n break;\n }\n\n if (isTypingKey(e)) return { kind: \"editSeeded\", seed: e.key };\n return null;\n}\n\n/**\n * Editor kinds a SINGLE click may operate directly, without first entering edit\n * mode. See THE CLICK LAW at the top of this file — this list is closed sets\n * and two-state values only, where a mis-click is obvious and instantly undone.\n */\nexport function directClickKinds(): readonly string[] {\n return [\"checkbox\", \"rating\", \"select\", \"multiselect\"] as const;\n}\n\nexport function isDirectClickEditor(editorKind: string | undefined): boolean {\n return editorKind !== undefined && directClickKinds().includes(editorKind);\n}\n\n/** Stable DOM key for a cell, so selection can scroll itself into view. */\nexport function cellDomKey(address: CellAddress): string {\n return `${address.rowId}::${address.fieldName}`;\n}\n","/**\n * Clipboard model for the user-data-table grid.\n * @ai-matrx/design-system/data-table/grid-clipboard\n *\n * This moved here from the app so every table in the product shares one\n * range-selection model.\n *\n * WHAT A SPREADSHEET PUTS ON THE CLIPBOARD. Excel, Google Sheets and Numbers\n * all copy a range as TAB-separated rows, one line per row, and quote any cell\n * whose text contains a tab, a line break or a double quote (doubling the\n * quotes inside). That is also what they ACCEPT on paste. Speaking exactly\n * that dialect is what makes \"copy three columns in Sheets, click a cell here,\n * Cmd-V\" land as a block instead of as one cell holding a wall of text — and\n * what makes the reverse trip (copy here, paste into Sheets) split into cells.\n *\n * WHY THE PLAN IS A PURE FUNCTION. Pasting a block is a write to many cells at\n * once, and \"which cells\" is the part that goes wrong: off by one column, the\n * anchor row filtered out, a block wider than the grid. Computing the landing\n * cells here, with no React and no DOM, is what lets that mapping be tested\n * cell by cell — and lets the caller decide separately what to do with the\n * rows that did not fit.\n *\n * Addresses are (rowId, fieldName), like the rest of the grid — never indices.\n */\n\nimport type { CellAddress } from \"./grid-selection\";\n\n/** One cell as text for the clipboard. Objects go out as JSON. */\nexport function cellClipboardText(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/** Quote a cell the way Excel does, only when it has to be. */\nfunction tsvCell(text: string): string {\n return /[\\t\\r\\n\"]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\n/** A rectangular block of cells as spreadsheet TSV. */\nexport function gridToTsv(block: readonly (readonly unknown[])[]): string {\n return block\n .map((row) => row.map((cell) => tsvCell(cellClipboardText(cell))).join(\"\\t\"))\n .join(\"\\n\");\n}\n\n/**\n * Parse clipboard text as a spreadsheet block.\n *\n * Handles the Excel quoting dialect (a quoted cell may span lines and carries\n * `\"\"` for a literal quote), both `\\n` and `\\r\\n` line endings, and the single\n * trailing line break Excel and Sheets append to every copy — without that\n * strip, every paste would be one empty row taller than what was copied.\n *\n * Plain text with no tabs and no quotes still parses: one column, one row per\n * line, which is exactly what Sheets does with the same text. A single value\n * with no line break is a 1×1 block.\n */\nexport function parseClipboardGrid(text: string): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let cell = \"\";\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const ch = text[i];\n\n // A quote opens a quoted cell only at the START of a cell.\n if (cell === \"\" && ch === '\"') {\n // Quoted cell: read to the closing quote, honouring doubled quotes.\n let j = i + 1;\n let out = \"\";\n let closed = false;\n while (j < n) {\n const c = text[j];\n if (c === '\"') {\n if (text[j + 1] === '\"') {\n out += '\"';\n j += 2;\n continue;\n }\n closed = true;\n j += 1;\n break;\n }\n out += c;\n j += 1;\n }\n if (closed && (j >= n || text[j] === \"\\t\" || text[j] === \"\\n\" || text[j] === \"\\r\")) {\n cell = out;\n i = j;\n // Fall through to the delimiter handling below with `cell` complete.\n if (i >= n) break;\n const d = text[i];\n if (d === \"\\t\") {\n row.push(cell);\n cell = \"\";\n i += 1;\n continue;\n }\n // line break\n row.push(cell);\n rows.push(row);\n row = [];\n cell = \"\";\n i += d === \"\\r\" && text[i + 1] === \"\\n\" ? 2 : 1;\n continue;\n }\n // Not a well-formed quoted cell (a value that merely starts with a\n // quote). Treat the quote literally and keep going.\n cell += ch;\n i += 1;\n continue;\n }\n\n if (ch === \"\\t\") {\n row.push(cell);\n cell = \"\";\n i += 1;\n continue;\n }\n if (ch === \"\\n\" || ch === \"\\r\") {\n row.push(cell);\n rows.push(row);\n row = [];\n cell = \"\";\n i += ch === \"\\r\" && text[i + 1] === \"\\n\" ? 2 : 1;\n continue;\n }\n cell += ch;\n i += 1;\n }\n\n // Flush the last cell — unless the text ended on a line break, in which\n // case that break was the trailing one every spreadsheet appends.\n const endedOnBreak = n > 0 && (text[n - 1] === \"\\n\" || text[n - 1] === \"\\r\");\n if (!endedOnBreak) {\n row.push(cell);\n rows.push(row);\n } else if (row.length > 0) {\n row.push(cell);\n rows.push(row);\n }\n\n return rows.length === 0 ? [[\"\"]] : rows;\n}\n\n/** One landing cell of a paste, with the raw text that goes into it. */\nexport type PasteCell = CellAddress & { raw: string };\n\nexport type PastePlan = {\n /** Cells inside the grid, in reading order (row by row). */\n cells: PasteCell[];\n /**\n * Block rows below the last grid row, trimmed to the columns that fit from\n * the anchor column rightward. The caller decides whether these become new\n * rows — the plan only reports them so nothing is dropped silently.\n */\n overflowRows: string[][];\n /** How many block columns fell off the right edge (per row, at most). */\n clippedColumns: number;\n /** The columns (field names) the block spans, anchor first. */\n fieldNames: string[];\n};\n\n/**\n * Where does `block` land when pasted with its top-left cell at `anchor`?\n *\n * Rows extend downward from the anchor row and columns rightward from the\n * anchor column, in the GRID's current order (so a hidden or re-ordered column\n * is honoured — you paste into what you see). A block wider than the remaining\n * columns is clipped on the right; a block taller than the remaining rows is\n * reported as `overflowRows` rather than lost.\n *\n * Returns null when the anchor is not on the grid — the row was filtered out\n * or the column hidden since the selection was made. Writing \"somewhere near\"\n * is worse than refusing.\n */\nexport function planPaste(\n anchor: CellAddress,\n block: readonly (readonly string[])[],\n rowIds: readonly string[],\n fieldNames: readonly string[],\n): PastePlan | null {\n const r0 = rowIds.indexOf(anchor.rowId);\n const c0 = fieldNames.indexOf(anchor.fieldName);\n if (r0 === -1 || c0 === -1) return null;\n\n const width = block.reduce((w, row) => Math.max(w, row.length), 0);\n const colsAvailable = fieldNames.length - c0;\n const colsUsed = Math.min(width, colsAvailable);\n const targetFields = fieldNames.slice(c0, c0 + colsUsed);\n\n const cells: PasteCell[] = [];\n const overflowRows: string[][] = [];\n\n block.forEach((blockRow, dr) => {\n const rowId = rowIds[r0 + dr];\n if (rowId === undefined) {\n overflowRows.push(targetFields.map((_, dc) => blockRow[dc] ?? \"\"));\n return;\n }\n targetFields.forEach((fieldName, dc) => {\n // A ragged block (some rows shorter) leaves the cells it does not\n // reach alone rather than blanking them — matches Sheets.\n if (dc >= blockRow.length) return;\n const raw = blockRow[dc];\n // The bound check above puts this index inside the row; a hole would be\n // \"not reached\" too, and is left alone the same way.\n if (raw === undefined) return;\n cells.push({ rowId, fieldName, raw });\n });\n });\n\n return {\n cells,\n overflowRows,\n clippedColumns: Math.max(0, width - colsAvailable),\n fieldNames: targetFields,\n };\n}\n\n/** True when the parsed block is a single value — the common Cmd-V case. */\nexport function isSingleCell(block: readonly (readonly string[])[]): boolean {\n // A one-row block always has that row; `?.` keeps the read total.\n return block.length === 1 && block[0]?.length === 1;\n}\n\n/**\n * Would writing `next` over `prior` change what is stored? Objects compare by\n * their JSON, the same test the inline editor uses to skip a no-op write.\n */\nexport function storedValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || a === undefined || b === undefined) {\n return (a ?? null) === (b ?? null);\n }\n if (typeof a === \"object\" || typeof b === \"object\") {\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n }\n return false;\n}\n","/**\n * THE FILL MODEL — what a fill-down and a drag of the fill handle produce.\n *\n * WHY THE LITTLE SQUARE EXISTS. Cmd-D fills down, and a keyboard user is happy.\n * But the first thing a spreadsheet person reaches for is the small square in\n * the corner of the selection: they grab it, drag, and the column fills. A grid\n * without that square is a grid they have to be taught to use. This module is\n * the whole answer to \"what does that drag mean\", expressed as data so the same\n * rules hold for the keyboard shortcut, the handle, and anything later.\n *\n * PURE MODULE: no React, no DOM, no network. A fill is a pure function of the\n * rectangles, the current values, and the row/column order — which is what makes\n * every spreadsheet convention below testable without mounting a grid.\n *\n * THE CONVENTIONS THIS ENCODES, AND WHY EACH ONE IS NOT NEGOTIABLE:\n *\n * TILING. A two-cell pattern filling five cells lays down `a b a b a`. Users\n * select a pattern precisely so it repeats; writing the first value five times\n * is the behaviour of a grid that has never been used for data entry.\n *\n * A SINGLE NUMBER IS NOT A SERIES. One cell holding `5` dragged down gives\n * five 5s, not 5, 6, 7, 8, 9. This is the classic bug: it looks clever, and it\n * silently corrupts every column of ids, quantities and prices a user drags.\n * A run needs at least two values agreeing on a step before it is a run, and\n * `1, 2, 4` — inconsistent — is not one either.\n *\n * DIRECTION. A fill upward continues the run upward: `3, 2` dragged up gives\n * `1, 0, -1`. The series is continued, never mirrored.\n *\n * ONE AXIS. A diagonal drag resolves to whichever axis moved further, exactly\n * as every spreadsheet does; a drag that stays inside the selection fills\n * nothing rather than guessing.\n *\n * NOTHING FAILS SILENTLY. A column that cannot take a written value produces\n * no patch AND one `skipped` entry per cell carrying a plain sentence, so the\n * grid can tell the user what did not happen. Nothing here throws: a\n * `valueAt` that blows up is read as blank and its dependent cell is skipped\n * with a reason.\n *\n * The rectangles are index-based because a drag is a geometric gesture over the\n * grid's CURRENT visual order; the RESULT is addressed by `rowId`/`columnId`, so\n * the patches survive the re-sort that follows a write (the same reason\n * `grid-selection.ts` addresses cells by id).\n */\n\nimport { boundsContain, type RangeBounds } from \"./grid-selection\";\n\n/** A rectangle of the grid, as inclusive indexes into the visual order. */\nexport interface FillRect {\n top: number;\n left: number;\n bottom: number;\n right: number;\n}\n\nexport interface FillRequest {\n /** The cells the user had selected — the PATTERN. */\n source: { top: number; left: number; bottom: number; right: number };\n /** The rectangle the handle was dragged to. Must share an edge with source. */\n target: { top: number; left: number; bottom: number; right: number };\n /** Reads the current value of a cell. Column is addressed by index into `columnIds`. */\n valueAt: (rowIndex: number, columnIndex: number) => unknown;\n /** Column ids in visual order, for the returned patches. */\n columnIds: string[];\n /** Row ids in visual order, for the returned patches. */\n rowIds: string[];\n /** Per-column opt-out: a column that cannot take a written value (a formula column, a read-only column). */\n isColumnWritable?: (columnId: string) => boolean;\n /** \"copy\" repeats the pattern; \"series\" continues a detected run. Default \"series\". */\n mode?: \"copy\" | \"series\";\n}\n\nexport interface CellPatch {\n rowId: string;\n columnId: string;\n value: unknown;\n}\n\nexport interface FillSkip {\n rowId: string;\n columnId: string;\n reason: string;\n}\n\nexport interface FillResult {\n patches: CellPatch[];\n /** Cells inside the target that were skipped, and the plain-English reason. */\n skipped: FillSkip[];\n /** What the fill did, for a status line: e.g. \"Filled 24 cells down 4 columns.\" */\n summary: string;\n}\n\nexport type SeriesKind = \"number\" | \"date\" | \"none\";\n\nexport interface SeriesDetection {\n kind: SeriesKind;\n step: number;\n}\n\ntype FillDirection = \"down\" | \"up\" | \"right\" | \"left\";\n\nconst NO_SERIES: SeriesDetection = { kind: \"none\", step: 0 };\nconst MS_PER_DAY = 86_400_000;\n\n// ─── Small English helpers ──────────────────────────────────────────────────\n//\n// A status line a person reads is part of the feature, not decoration: \"Filled\n// 24 cells down 4 columns.\" is the difference between a grid that reports what\n// it did and one that changes 24 values behind your back.\n\nfunction plural(count: number, one: string, many: string): string {\n return `${count} ${count === 1 ? one : many}`;\n}\n\n// ─── Geometry ───────────────────────────────────────────────────────────────\n\nfunction isValidRect(rect: FillRect): boolean {\n return (\n Number.isInteger(rect.top) &&\n Number.isInteger(rect.left) &&\n Number.isInteger(rect.bottom) &&\n Number.isInteger(rect.right) &&\n rect.top <= rect.bottom &&\n rect.left <= rect.right\n );\n}\n\nfunction asBounds(rect: FillRect): RangeBounds {\n return { r0: rect.top, r1: rect.bottom, c0: rect.left, c1: rect.right };\n}\n\n/**\n * Which way does this fill run? Null when the target does not share an edge\n * with the source, which is the only shape a handle drag can produce and the\n * only shape whose tiling is defined.\n */\nfunction directionOf(source: FillRect, target: FillRect): FillDirection | null {\n if (!isValidRect(source) || !isValidRect(target)) return null;\n if (target.top === source.bottom + 1) return \"down\";\n if (target.bottom === source.top - 1) return \"up\";\n if (target.left === source.right + 1) return \"right\";\n if (target.right === source.left - 1) return \"left\";\n return null;\n}\n\n/**\n * Where does index `i` read from, given a pattern occupying `a..b`?\n *\n * This is the tiling rule, per axis: past the end the pattern repeats forward,\n * before the start it repeats backward, so `a b` extended in either direction\n * stays `… a b [a b] a b …`.\n */\nexport function tileIndex(i: number, a: number, b: number): number {\n const n = b - a + 1;\n if (n <= 0) return a;\n if (i >= a && i <= b) return i;\n if (i > b) return a + ((i - a) % n);\n return b - ((a - 1 - i) % n);\n}\n\n// ─── Series detection ───────────────────────────────────────────────────────\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DATE_ISH = /^\\d{4}-\\d{2}-\\d{2}([T ]|$)/;\n\nfunction dateMs(v: unknown): number | null {\n if (v instanceof Date) {\n const t = v.getTime();\n return Number.isFinite(t) ? t : null;\n }\n if (typeof v === \"string\" && DATE_ISH.test(v)) {\n const t = Date.parse(v);\n return Number.isFinite(t) ? t : null;\n }\n return null;\n}\n\n/**\n * Detects a numeric or date run in a list of values. Exported because it is the\n * interesting part — and because the single-value case is where grids get this\n * wrong, so it deserves its own name and its own tests.\n */\nexport function detectSeries(values: unknown[]): SeriesDetection {\n // ONE VALUE IS NEVER A RUN. Two values are the minimum evidence of intent.\n if (!Array.isArray(values) || values.length < 2) return NO_SERIES;\n\n if (values.every(isFiniteNumber)) {\n const nums = values as number[];\n const first = nums[0];\n const second = nums[1];\n if (first === undefined || second === undefined) return NO_SERIES;\n const step = second - first;\n for (let i = 1; i < nums.length; i += 1) {\n const prev = nums[i - 1];\n const cur = nums[i];\n if (prev === undefined || cur === undefined) return NO_SERIES;\n if (cur - prev !== step) return NO_SERIES;\n }\n return { kind: \"number\", step };\n }\n\n const stamps = values.map(dateMs);\n if (stamps.every((t): t is number => t !== null)) {\n const first = stamps[0];\n const second = stamps[1];\n if (first === undefined || second === undefined) return NO_SERIES;\n const deltaMs = second - first;\n // Whole days only, in UTC. A run of month-ends is a different feature and\n // guessing at it would silently write the wrong dates.\n if (deltaMs % MS_PER_DAY !== 0) return NO_SERIES;\n for (let i = 1; i < stamps.length; i += 1) {\n const prev = stamps[i - 1];\n const cur = stamps[i];\n if (prev === undefined || cur === undefined) return NO_SERIES;\n if (cur - prev !== deltaMs) return NO_SERIES;\n }\n return { kind: \"date\", step: deltaMs / MS_PER_DAY };\n }\n\n return NO_SERIES;\n}\n\nfunction formatDateLike(ms: number, sample: unknown): string {\n const iso = new Date(ms).toISOString();\n if (typeof sample === \"string\" && DATE_ONLY.test(sample)) return iso.slice(0, 10);\n return iso;\n}\n\n/**\n * Continue `line` by `k` steps past its END, in reading order. Filling upward\n * passes the same positive `k`: the run is continued, never reflected, which is\n * why `3, 2` filled upward reads 1, 0, -1 away from the selection.\n */\nfunction extrapolate(line: unknown[], series: SeriesDetection, k: number): unknown {\n const last = line[line.length - 1];\n if (series.kind === \"number\" && isFiniteNumber(last)) {\n return last + k * series.step;\n }\n if (series.kind === \"date\") {\n const base = dateMs(last);\n if (base === null) return jsonable(last);\n return formatDateLike(base + k * series.step * MS_PER_DAY, last);\n }\n return jsonable(last);\n}\n\n/** Every value we hand back is plain data — a Date becomes its ISO string. */\nfunction jsonable(value: unknown): unknown {\n if (value instanceof Date) {\n const t = value.getTime();\n return Number.isFinite(t) ? value.toISOString() : null;\n }\n if (value === undefined) return null;\n return value;\n}\n\n// ─── The fill ───────────────────────────────────────────────────────────────\n\nfunction emptyResult(summary: string): FillResult {\n return { patches: [], skipped: [], summary };\n}\n\nexport function fillRange(request: FillRequest): FillResult {\n const { source, target, columnIds, rowIds } = request;\n const mode = request.mode ?? \"series\";\n const isWritable = request.isColumnWritable ?? (() => true);\n\n const direction = directionOf(source, target);\n if (!direction) {\n return emptyResult(\n \"Filled 0 cells because the fill area does not touch the selection.\",\n );\n }\n if (rowIds.length === 0 || columnIds.length === 0) {\n return emptyResult(\"Filled 0 cells because the table is empty.\");\n }\n\n // A read that throws is a blank, never an exception escaping the model.\n const read = (r: number, c: number): { ok: boolean; value: unknown } => {\n try {\n return { ok: true, value: request.valueAt(r, c) };\n } catch {\n return { ok: false, value: undefined };\n }\n };\n\n const vertical = direction === \"down\" || direction === \"up\";\n const forward = direction === \"down\" || direction === \"right\";\n\n const fillStart = vertical ? source.top : source.left;\n const fillEnd = vertical ? source.bottom : source.right;\n const crossStart = vertical ? source.left : source.top;\n const crossEnd = vertical ? source.right : source.bottom;\n\n const maxFill = (vertical ? rowIds.length : columnIds.length) - 1;\n const maxCross = (vertical ? columnIds.length : rowIds.length) - 1;\n\n const rawFillFrom = vertical ? target.top : target.left;\n const rawFillTo = vertical ? target.bottom : target.right;\n const rawCrossFrom = vertical ? target.left : target.top;\n const rawCrossTo = vertical ? target.right : target.bottom;\n\n const fillFrom = Math.max(rawFillFrom, 0);\n const fillTo = Math.min(rawFillTo, maxFill);\n const crossFrom = Math.max(rawCrossFrom, 0);\n const crossTo = Math.min(rawCrossTo, maxCross);\n\n const requested =\n Math.max(rawFillTo - rawFillFrom + 1, 0) *\n Math.max(rawCrossTo - rawCrossFrom + 1, 0);\n const inside =\n Math.max(fillTo - fillFrom + 1, 0) * Math.max(crossTo - crossFrom + 1, 0);\n const outside = Math.max(requested - inside, 0);\n\n const sourceBounds = asBounds(source);\n const patches: CellPatch[] = [];\n const skipped: FillSkip[] = [];\n const touchedColumns = new Set<string>();\n\n // The pattern line for each cross position, read ONCE. Reading it per target\n // cell would call the host's `valueAt` thousands of times for a long drag.\n const lines = new Map<number, { values: unknown[]; reads: boolean[]; series: SeriesDetection }>();\n const lineFor = (crossIndex: number) => {\n const srcCross = tileIndex(crossIndex, crossStart, crossEnd);\n const cached = lines.get(srcCross);\n if (cached) return cached;\n const values: unknown[] = [];\n const reads: boolean[] = [];\n for (let f = fillStart; f <= fillEnd; f += 1) {\n const r = vertical ? f : srcCross;\n const c = vertical ? srcCross : f;\n const got = read(r, c);\n values.push(got.value);\n reads.push(got.ok);\n }\n const series = mode === \"series\" ? detectSeries(values) : NO_SERIES;\n const line = { values, reads, series };\n lines.set(srcCross, line);\n return line;\n };\n\n for (let f = fillFrom; f <= fillTo; f += 1) {\n for (let x = crossFrom; x <= crossTo; x += 1) {\n const rowIndex = vertical ? f : x;\n const colIndex = vertical ? x : f;\n const rowId = rowIds[rowIndex];\n const columnId = columnIds[colIndex];\n if (rowId === undefined || columnId === undefined) continue;\n\n // The pattern is never rewritten with itself.\n if (boundsContain(sourceBounds, rowIndex, colIndex)) continue;\n\n if (!isWritable(columnId)) {\n skipped.push({\n rowId,\n columnId,\n reason: `${columnId} cannot be filled because that column is read-only.`,\n });\n continue;\n }\n\n const line = lineFor(x);\n if (line.series.kind !== \"none\") {\n const k = forward ? f - fillEnd : fillStart - f;\n patches.push({ rowId, columnId, value: jsonable(extrapolate(line.values, line.series, k)) });\n touchedColumns.add(columnId);\n continue;\n }\n\n const srcFill = tileIndex(f, fillStart, fillEnd);\n const patternIndex = srcFill - fillStart;\n if (line.reads[patternIndex] !== true) {\n skipped.push({\n rowId,\n columnId,\n reason: `The value to copy into ${columnId} could not be read, so that cell was left alone.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: jsonable(line.values[patternIndex]) });\n touchedColumns.add(columnId);\n }\n }\n\n return { patches, skipped, summary: summarize(direction, patches, skipped, touchedColumns, outside) };\n}\n\nfunction summarize(\n direction: FillDirection,\n patches: CellPatch[],\n skipped: FillSkip[],\n touchedColumns: Set<string>,\n outside: number,\n): string {\n const parts: string[] = [];\n if (patches.length === 0) {\n parts.push(\"Filled 0 cells because there was nothing to fill.\");\n } else if (direction === \"down\" || direction === \"up\") {\n const word = direction === \"down\" ? \"down\" : \"up\";\n parts.push(\n `Filled ${plural(patches.length, \"cell\", \"cells\")} ${word} ${plural(touchedColumns.size, \"column\", \"columns\")}.`,\n );\n } else {\n const word = direction === \"right\" ? \"right\" : \"left\";\n parts.push(\n `Filled ${plural(patches.length, \"cell\", \"cells\")} ${word} across ${plural(touchedColumns.size, \"column\", \"columns\")}.`,\n );\n }\n if (skipped.length > 0) {\n parts.push(`Skipped ${plural(skipped.length, \"cell\", \"cells\")} that could not be filled.`);\n }\n if (outside > 0) {\n parts.push(`Ignored ${plural(outside, \"cell\", \"cells\")} outside the table.`);\n }\n return parts.join(\" \");\n}\n\n/** Cmd-D: fill the top row of the selection into the rest of it. */\nexport function fillDown(\n request: Omit<FillRequest, \"source\" | \"target\"> & { range: FillRequest[\"source\"] },\n): FillResult {\n const { range, ...rest } = request;\n if (!isValidRect(range)) {\n return emptyResult(\"Filled 0 cells because the selection is not a rectangle.\");\n }\n if (range.top === range.bottom) {\n return emptyResult(\"Filled 0 cells because the selection is a single row.\");\n }\n return fillRange({\n ...rest,\n source: { top: range.top, left: range.left, bottom: range.top, right: range.right },\n target: { top: range.top + 1, left: range.left, bottom: range.bottom, right: range.right },\n });\n}\n\n/**\n * The rectangle a drag from a handle at the selection's corner to (row, column)\n * produces — or null when the drag stayed inside the selection and there is\n * nothing to fill.\n *\n * A DIAGONAL DRAG RESOLVES TO ONE AXIS: whichever moved further, with a tie\n * going to the vertical, because filling a column is the gesture people mean.\n */\nexport function fillTargetFor(\n source: FillRequest[\"source\"],\n toRow: number,\n toColumn: number,\n): FillRequest[\"target\"] | null {\n if (!isValidRect(source)) return null;\n if (!Number.isInteger(toRow) || !Number.isInteger(toColumn)) return null;\n\n const below = toRow - source.bottom;\n const above = source.top - toRow;\n const rightOf = toColumn - source.right;\n const leftOf = source.left - toColumn;\n\n const vertical = Math.max(below, above, 0);\n const horizontal = Math.max(rightOf, leftOf, 0);\n if (vertical === 0 && horizontal === 0) return null;\n\n if (vertical >= horizontal) {\n if (below > 0) {\n return { top: source.bottom + 1, bottom: toRow, left: source.left, right: source.right };\n }\n return { top: toRow, bottom: source.top - 1, left: source.left, right: source.right };\n }\n if (rightOf > 0) {\n return { top: source.top, bottom: source.bottom, left: source.right + 1, right: toColumn };\n }\n return { top: source.top, bottom: source.bottom, left: toColumn, right: source.left - 1 };\n}\n","/**\n * THE CELL UNDO STACK — the model behind Cmd-Z in a grid.\n *\n * WHY IT SHIPS WITH EASY EDITING, NOT AFTER IT. Lowering the friction to change\n * data raises the rate of accidental change: a click toggles a boolean, a bare\n * keystroke starts an edit, Delete empties a cell, and one drag of the fill\n * handle rewrites a column. Every one of those is a good affordance ONLY if the\n * floor underneath it is recoverable.\n *\n * WHAT THIS FILE IS. A bounded, PURE stack of reversible edits: no React, no\n * network, no toasts. The app's `useCellUndo` hook owned all three plus the\n * stack, which meant the stack's rules — depth, the redo contract, what an undo\n * of an added row means — could only be exercised by mounting a component and\n * mocking a database. Those rules are spreadsheet law, not app trivia, so they\n * belong in a module anyone can test in a millisecond and any client can reuse.\n *\n * WHY IT IS PATCHES, NOT CELL COORDINATES. An entry carries the patches that\n * undo it and the patches that redo it, captured BEFORE the write and never\n * derived after it: re-reading a cell later to discover what to restore races\n * with realtime and with agent writes, and would happily \"undo\" to a value\n * somebody else just set. Patches also make the entry shape indifferent to WHAT\n * changed — one cell, a filled column, or the cells of a row that was added or\n * deleted — which is how row and column operations join the same Cmd-Z the user\n * already trusts instead of living outside it.\n *\n * PURITY IS THE POINT. Every function returns a NEW stack and never mutates the\n * one it was handed, so the stack can live in React state, in a store, or in a\n * ref without the \"my undo history changed under me\" class of bug.\n */\n\nimport type { CellPatch } from \"./fill\";\n\nexport type { CellPatch };\n\nexport interface UndoEntry {\n /** What the user did, in plain English, for the toast: \"Undid the change to Day rate.\" */\n label: string;\n /** The patches that UNDO it. */\n undo: CellPatch[];\n /** The patches that REDO it. */\n redo: CellPatch[];\n}\n\nexport interface UndoStack {\n past: UndoEntry[];\n future: UndoEntry[];\n depth: number;\n}\n\n/**\n * How many steps back we remember. Deliberately generous — the cost is a few\n * kilobytes of session memory, and the failure mode of a too-short stack is a\n * user discovering their mistake is unrecoverable.\n */\nexport const DEFAULT_UNDO_DEPTH = 100;\n\nexport function createUndoStack(depth: number = DEFAULT_UNDO_DEPTH): UndoStack {\n const bounded = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 0;\n return { past: [], future: [], depth: bounded };\n}\n\n/**\n * Record an edit that already happened.\n *\n * Pushing CLEARS the redo future — the standard editor contract: you cannot\n * redo into a future you have just diverged from. Past `depth`, the OLDEST\n * entry is dropped, so the stack is bounded memory rather than a leak.\n */\nexport function pushUndo(stack: UndoStack, entry: UndoEntry): UndoStack {\n if (stack.depth <= 0) {\n // Undo is off. Pushing keeps the stack empty rather than silently growing\n // a history nothing will ever read.\n return { past: [], future: [], depth: stack.depth };\n }\n const past = [...stack.past, entry];\n while (past.length > stack.depth) past.shift();\n return { past, future: [], depth: stack.depth };\n}\n\nexport function popUndo(stack: UndoStack): { stack: UndoStack; entry: UndoEntry | null } {\n const entry = stack.past[stack.past.length - 1];\n if (entry === undefined) return { stack, entry: null };\n return {\n stack: {\n past: stack.past.slice(0, -1),\n future: [...stack.future, entry],\n depth: stack.depth,\n },\n entry,\n };\n}\n\nexport function popRedo(stack: UndoStack): { stack: UndoStack; entry: UndoEntry | null } {\n const entry = stack.future[stack.future.length - 1];\n if (entry === undefined) return { stack, entry: null };\n const past = [...stack.past, entry];\n while (past.length > stack.depth) past.shift();\n return {\n stack: { past, future: stack.future.slice(0, -1), depth: stack.depth },\n entry,\n };\n}\n\nfunction keyOf(patch: CellPatch): string {\n // JSON, not a separator character: a row id or column id containing whatever\n // separator we picked would collide two different cells into one key, and the\n // undo would restore the wrong one.\n return JSON.stringify([patch.rowId, patch.columnId]);\n}\n\n/**\n * Build an entry from a before/after pair, so a caller never assembles one by\n * hand — hand-assembled inverses are how an undo ends up restoring the wrong\n * cell.\n *\n * An after-patch with NO matching before is still undoable: its undo sets the\n * value back to `null`. That is the added-row case — a cell that did not exist\n * before the change has no prior value, and \"undo\" for it means empty, not\n * \"leave whatever is there\".\n */\nexport function undoEntryFor(\n before: CellPatch[],\n after: CellPatch[],\n label: string,\n): UndoEntry {\n const priors = new Map<string, CellPatch>();\n for (const patch of before) priors.set(keyOf(patch), patch);\n\n const undo: CellPatch[] = after.map((patch) => {\n const prior = priors.get(keyOf(patch));\n return {\n rowId: patch.rowId,\n columnId: patch.columnId,\n value: prior ? prior.value : null,\n };\n });\n\n return {\n label,\n undo,\n redo: after.map((patch) => ({\n rowId: patch.rowId,\n columnId: patch.columnId,\n value: patch.value,\n })),\n };\n}\n\n/** True when there is a step to take back. */\nexport function canUndo(stack: UndoStack): boolean {\n return stack.depth > 0 && stack.past.length > 0;\n}\n\n/** True when there is a step to reapply. */\nexport function canRedo(stack: UndoStack): boolean {\n return stack.depth > 0 && stack.future.length > 0;\n}\n"],"mappings":"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","names":["move"]}