@ai-matrx/design-system 0.22.0 → 0.25.0

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 (93) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/dist/data-table/cell-undo.cjs +98 -0
  3. package/dist/data-table/cell-undo.cjs.map +1 -0
  4. package/dist/data-table/cell-undo.d.cts +85 -0
  5. package/dist/data-table/cell-undo.d.ts +85 -0
  6. package/dist/data-table/cell-undo.js +78 -0
  7. package/dist/data-table/cell-undo.js.map +1 -0
  8. package/dist/data-table/copy-helpers.d.cts +6 -2
  9. package/dist/data-table/copy-helpers.d.ts +6 -2
  10. package/dist/data-table/coverage.cjs +116 -0
  11. package/dist/data-table/coverage.cjs.map +1 -0
  12. package/dist/data-table/coverage.d.cts +65 -0
  13. package/dist/data-table/coverage.d.ts +65 -0
  14. package/dist/data-table/coverage.js +96 -0
  15. package/dist/data-table/coverage.js.map +1 -0
  16. package/dist/data-table/fill.cjs +299 -0
  17. package/dist/data-table/fill.cjs.map +1 -0
  18. package/dist/data-table/fill.d.cts +129 -0
  19. package/dist/data-table/fill.d.ts +129 -0
  20. package/dist/data-table/fill.js +277 -0
  21. package/dist/data-table/fill.js.map +1 -0
  22. package/dist/data-table/filter-engine.d.cts +6 -2
  23. package/dist/data-table/filter-engine.d.ts +6 -2
  24. package/dist/data-table/grid-clipboard.cjs +165 -0
  25. package/dist/data-table/grid-clipboard.cjs.map +1 -0
  26. package/dist/data-table/grid-clipboard.d.cts +85 -0
  27. package/dist/data-table/grid-clipboard.d.ts +85 -0
  28. package/dist/data-table/grid-clipboard.js +145 -0
  29. package/dist/data-table/grid-clipboard.js.map +1 -0
  30. package/dist/data-table/grid-selection.cjs +258 -0
  31. package/dist/data-table/grid-selection.cjs.map +1 -0
  32. package/dist/data-table/grid-selection.d.cts +166 -0
  33. package/dist/data-table/grid-selection.d.ts +166 -0
  34. package/dist/data-table/grid-selection.js +238 -0
  35. package/dist/data-table/grid-selection.js.map +1 -0
  36. package/dist/data-table/grouping.cjs +302 -0
  37. package/dist/data-table/grouping.cjs.map +1 -0
  38. package/dist/data-table/grouping.d.cts +119 -0
  39. package/dist/data-table/grouping.d.ts +119 -0
  40. package/dist/data-table/grouping.js +282 -0
  41. package/dist/data-table/grouping.js.map +1 -0
  42. package/dist/data-table/host.d.cts +6 -2
  43. package/dist/data-table/host.d.ts +6 -2
  44. package/dist/data-table/index.cjs +2433 -246
  45. package/dist/data-table/index.cjs.map +1 -1
  46. package/dist/data-table/index.d.cts +11 -3
  47. package/dist/data-table/index.d.ts +11 -3
  48. package/dist/data-table/index.js +2425 -238
  49. package/dist/data-table/index.js.map +1 -1
  50. package/dist/data-table/infer-filter.d.cts +6 -2
  51. package/dist/data-table/infer-filter.d.ts +6 -2
  52. package/dist/data-table/layered-filters.d.cts +6 -2
  53. package/dist/data-table/layered-filters.d.ts +6 -2
  54. package/dist/data-table/query-control.d.cts +6 -2
  55. package/dist/data-table/query-control.d.ts +6 -2
  56. package/dist/data-table/spreadsheet.cjs +1014 -0
  57. package/dist/data-table/spreadsheet.cjs.map +1 -0
  58. package/dist/data-table/spreadsheet.d.cts +118 -0
  59. package/dist/data-table/spreadsheet.d.ts +118 -0
  60. package/dist/data-table/spreadsheet.js +994 -0
  61. package/dist/data-table/spreadsheet.js.map +1 -0
  62. package/dist/data-table/types.cjs.map +1 -1
  63. package/dist/data-table/types.d.cts +6 -2
  64. package/dist/data-table/types.d.ts +6 -2
  65. package/dist/data-table/url-state.d.cts +6 -2
  66. package/dist/data-table/url-state.d.ts +6 -2
  67. package/dist/data-table/validation.cjs +229 -0
  68. package/dist/data-table/validation.cjs.map +1 -0
  69. package/dist/data-table/validation.d.cts +125 -0
  70. package/dist/data-table/validation.d.ts +125 -0
  71. package/dist/data-table/validation.js +209 -0
  72. package/dist/data-table/validation.js.map +1 -0
  73. package/dist/data-table/virtual-window.cjs +203 -0
  74. package/dist/data-table/virtual-window.cjs.map +1 -0
  75. package/dist/data-table/virtual-window.d.cts +84 -0
  76. package/dist/data-table/virtual-window.d.ts +84 -0
  77. package/dist/data-table/virtual-window.js +183 -0
  78. package/dist/data-table/virtual-window.js.map +1 -0
  79. package/dist/data-table/xlsx.d.cts +6 -2
  80. package/dist/data-table/xlsx.d.ts +6 -2
  81. package/dist/field-formats/index.d.cts +2 -2
  82. package/dist/field-formats/index.d.ts +2 -2
  83. package/dist/formulas/index.cjs +1041 -0
  84. package/dist/formulas/index.cjs.map +1 -0
  85. package/dist/formulas/index.d.cts +389 -0
  86. package/dist/formulas/index.d.ts +389 -0
  87. package/dist/formulas/index.js +1019 -0
  88. package/dist/formulas/index.js.map +1 -0
  89. package/dist/{types-BdbvlF9-.d.cts → types-CJWU1Gae.d.cts} +1 -1
  90. package/dist/{types-BdbvlF9-.d.ts → types-CJWU1Gae.d.ts} +1 -1
  91. package/dist/{types-Sn249eoy.d.cts → types-CmQrOz-T.d.cts} +154 -2
  92. package/dist/{types-D-flTpK0.d.ts → types-nlLCMiqd.d.ts} +154 -2
  93. package/package.json +109 -2
@@ -0,0 +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\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 /** Applies a batch of writes. The consumer owns the door. */\n onPatch: (patches: CellPatch[], intent: SpreadsheetIntent) => void | Promise<void>;\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\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: { shiftKey: boolean; button: number; preventDefault: () => void }) => 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 scroll container that owns the keyboard and the clipboard. */\n containerProps: {\n tabIndex: number;\n onKeyDown: (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}\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: EventTarget | null): boolean {\n const element = target instanceof Element ? target : null;\n if (!element) return false;\n return element.closest(\"input, textarea, select, [contenteditable='true']\") !== null;\n}\n\nconst NOTHING: CellPatch[] = [];\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 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 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 await current.onPatch(patches, intent);\n },\n [],\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: Array<{ columnId: string; reason: string }>, 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 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 await applyPatches(result.patches, \"fill-down\", \"Filled down\");\n announceSkips(result.skipped, result.summary);\n }, [fillRequestBase, bounds, applyPatches, announceSkips]);\n\n const doClear = useCallback(async () => {\n const current = optionsRef.current;\n if (!current || !bounds) return;\n const patches: CellPatch[] = [];\n const skipped: Array<{ columnId: string; reason: string }> = [];\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 columnId,\n reason: current.columnRefusal?.(columnId) ?? `${columnId} cannot be cleared.`,\n });\n continue;\n }\n patches.push({ rowId, columnId, value: null });\n }\n }\n await applyPatches(patches, \"clear\", \"Cleared\");\n announceSkips(skipped, `Cleared ${patches.length} cells.`);\n }, [bounds, applyPatches, announceSkips]);\n\n const doUndo = useCallback(async () => {\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]);\n\n const doRedo = useCallback(async () => {\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]);\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 const onKeyDown = useCallback(\n (event: SpreadsheetKeyEvent) => {\n const current = optionsRef.current;\n if (!current) return;\n if (targetsAnEditor(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 default:\n // `edit`, `editSeeded`, `copy`, `cut` and `paste` belong to the\n // table's own editing path and 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 onCopy = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;\n const text = selectionAsTsv();\n if (!text) return;\n event.preventDefault();\n event.clipboardData?.setData(\"text/plain\", text);\n },\n [range, selectionAsTsv],\n );\n\n const onCut = useCallback(\n (event: SpreadsheetClipboardEvent) => {\n if (!optionsRef.current || !range || targetsAnEditor(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 const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) return;\n event.preventDefault();\n const block = parseClipboardGrid(text);\n const start = range.focus;\n const startRow = rowIds.indexOf(start.rowId);\n const startCol = columnIds.indexOf(start.fieldName);\n if (startRow === -1 || startCol === -1) return;\n const patches: CellPatch[] = [];\n const skipped: Array<{ columnId: string; reason: string }> = [];\n let outside = 0;\n for (let r = 0; r < block.length; r += 1) {\n const line = block[r] ?? [];\n for (let c = 0; c < line.length; c += 1) {\n const rowId = rowIds[startRow + r];\n const columnId = columnIds[startCol + c];\n if (rowId === undefined || columnId === undefined) {\n outside += 1;\n continue;\n }\n if (!current.isColumnWritable(columnId)) {\n skipped.push({\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 void applyPatches(patches, \"paste\", \"Pasted\");\n const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : \"\";\n announceSkips(skipped, `Pasted ${patches.length} cells.${tail}`);\n },\n [range, rowIds, columnIds, applyPatches, announceSkips],\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 event.preventDefault();\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 || !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 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 void applyPatches(result.patches, \"fill\", \"Filled\");\n announceSkips(result.skipped, result.summary);\n };\n window.addEventListener(\"mouseup\", commit);\n return () => window.removeEventListener(\"mouseup\", commit);\n }, [options, fillRequestBase, applyPatches, announceSkips]);\n\n return {\n range,\n bounds,\n enabled: options !== null,\n containerProps: {\n tabIndex: options ? 0 : -1,\n onKeyDown,\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 };\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":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/types.ts"],"sourcesContent":["import type { ContentTransferReference } from \"../content-transfer/extensions\";\nimport type { ReactNode } from \"react\";\nimport type { TableSearchRole, TableSearchScope } from \"./ranked-search\";\nimport type { AgentPayloadInput } from \"./copy\";\nimport type { CopyExportConfig } from \"./copy-types\";\nimport type {\n AiCustomSource,\n AiVariant,\n} from \"./copy\";\nimport type { LayeredFilterField, LayeredFilterRule } from \"./layered-filters\";\nimport type { ChoiceColorLookup, TableStyle } from \"./table-style\";\nimport type { FieldFormatConfig } from \"../field-formats/types\";\n\n/** How a column's filter UI behaves. `auto` infers from sample values. */\nexport type ColumnFilterKind =\n \"auto\" | \"text\" | \"select\" | \"boolean\" | \"number\" | false;\n\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * A compact boolean marker with the language people expect for a saved item.\n *\n * `undefined` recognizes only the exact conventional boolean accessor names\n * (`favorite`, `is_favorite`, `favourite`, `is_favourite`, `pinned`, and\n * `is_pinned`). Pass `false` to keep one of those fields as an ordinary\n * boolean column. `\"favorite\"` and `\"pin\"` make the intent explicit for\n * differently named fields.\n */\nexport type ColumnMarker = \"favorite\" | \"pin\" | false;\n\n/** How the table's primary search text is matched. */\nexport type TableSearchMatchMode = \"contains\" | \"whole_words\";\n\n/** Cell value type for typed inline editors (Supabase-style popovers for non-strings). */\nexport type CellEditType =\n | \"string\"\n | \"number\"\n | \"boolean\"\n | \"select\"\n /** Free-text multi-value chips (string[] cells: tags, labels, aliases). */\n | \"tags\"\n | \"date\"\n | false;\n\nexport interface MatrxColumnDef<T> {\n /** Stable id used for sort/filter state. Defaults to `accessorKey` when set. */\n id?: string;\n /** Dot-free key on the row for default value access + auto filter. */\n accessorKey?: keyof T & string;\n /** Custom value for sort/filter when `accessorKey` is insufficient. */\n accessorFn?: (row: T) => unknown;\n /** Value used only for local sort. Falls back to `accessorFn` / `accessorKey`. */\n sortValue?: (row: T) => unknown;\n /** Value used only for local filters. Falls back to `accessorFn` / `accessorKey`. */\n filterValue?: (row: T) => unknown;\n header: ReactNode;\n /** Plain-text name used for header controls when `header` is visual-only. */\n label?: string;\n /** Cell renderer. Defaults to stringified accessor value. */\n cell?: (row: T, index: number) => ReactNode;\n /** Sortable unless explicitly false. Default true. */\n sortable?: boolean;\n /** Direction used by the first header-click sort. Default `\"asc\"`. */\n defaultSortDirection?: SortDirection;\n /**\n * Filter kind. Default `\"auto\"` — every column gets a filter.\n * Pass `false` only when a column truly must not filter (e.g. actions).\n */\n filter?: ColumnFilterKind;\n /** Explicit select options (when filter is `\"select\"` or auto-detected). */\n filterOptions?: Array<{ value: string; label: string }>;\n /**\n * Single-choice select filter. The default select filter is a multi-select\n * that APPENDS (OR semantics) — correct for a status column, wrong for a\n * column whose options are mutually exclusive VIEWS of the list (a\n * record-class scope, a relative-date bucket). There, appending makes the\n * filter inert: the consumer reads one value, the popover accumulates a set,\n * and the first-selected value wins forever (D218). With `filterSingle`,\n * choosing an option REPLACES the selection, and choosing the active option\n * again clears it.\n */\n filterSingle?: boolean;\n /** Allow an exact typed value not present in loaded select options. */\n filterAllowCustom?: boolean;\n /**\n * Inline edit. Default false. `\"string\"` edits in-cell; other types open a\n * small popover (Supabase-style). Edits stay local until Save on the dirty pill.\n */\n editable?: CellEditType;\n /**\n * Per-row edit gate for an `editable` column. Return false to render the\n * plain cell (no pencil, no click-to-edit) for that row — for heterogeneous\n * lists where some kinds cannot take the write (e.g. a transcripts row of\n * kind \"unsorted\" has no user-facing title). Default: every row editable.\n */\n editableIf?: (row: T) => boolean;\n /**\n * How inline edit starts. Default `\"click\"` (click the cell body). `\"pencil\"`\n * shows a hover/focus pencil; the cell body no longer starts edit — so a\n * whole-row click can own that gesture. Forced to `\"pencil\"` when `href` is\n * set (D112 — the body is a real link).\n */\n editTrigger?: \"click\" | \"pencil\";\n /**\n * Options when `editable === \"select\"`. Also used by `\"tags\"` as the\n * suggestion list (existing values), while still allowing new entries.\n */\n editOptions?: Array<{ value: string; label: string }>;\n /**\n * Row link for the primary/title cell (D112): renders the cell content as a\n * real `next/link` anchor, so the row is reachable by keyboard, announced as\n * a link by screen readers, and cmd/middle-clickable into a new tab. The\n * whole-row `onRowOpen` click stays as a mouse convenience; clicks on the\n * anchor never double-fire it. Combine with `editable` and the link renders\n * with a hover/focus pencil that opens the inline editor instead of\n * click-text-to-edit.\n */\n href?: (row: T) => string | undefined;\n /**\n * Canonical entity token for the record this column NAMES. When set, the cell\n * renders through `EntityRef`, so the name carries the full door set — Open,\n * new tab, and Peek — instead of the Open-only `<Link>` that `href` alone\n * produces.\n *\n * THE INVENTORY LAW, applied to this component: the table grew its own door\n * (`href`) beside the platform's (`EntityRef`), and every column that named a\n * record picked one and silently lost the other half. This field collapses\n * them — `href` still works and still forces the pencil trigger, and it\n * OVERRIDES the registry route when both are set (for an admin-side route on\n * a satellite deployment).\n *\n * Needs the record's id: `entityToken` is paired with `entityId`, defaulting\n * to the table's own `getRowId`.\n *\n * **PER ROW, not per column** — a hub can be heterogeneous. `/transcripts`\n * lists transcripts, studio sessions, cleanup runs and an \"unsorted\" bucket\n * in one table, each with its own destination; a constant token would have\n * sent a session id to the transcript processor route and opened the\n * transcript peek on a record that is not one. Return `undefined` for a row\n * that names no entity — it falls back to the plain `href` link, or to inert\n * text. Pair with a per-row `href` when the kinds diverge.\n */\n entityToken?: string | ((row: T) => string | undefined);\n /**\n * NOTE: give the column a `cell` when you set this. Without one, a\n * UUID-shaped value renders the default `MatrxUuidCell`, which has its own\n * controls — wrapping those in the door's anchor would nest interactive\n * elements inside a link, and is redundant besides. The shell detects that\n * combination, skips the door, and screams once per column.\n */\n /** The id `entityToken` refers to. Defaults to the table's `getRowId(row)`. */\n entityId?: (row: T) => string | undefined;\n /**\n * Drop this column below `sm` (the phone breakpoint).\n *\n * A wide table on a phone becomes a horizontal scroller: the frozen\n * identity column stays, and everything after the second column sits off\n * the right edge where a reviewer will never find it. Marking the columns\n * that do NOT earn their width on a phone is how a surface declares an\n * INTENTIONAL mobile column set instead of an accidental one.\n *\n * The column is still fully sortable/filterable from the toolbar and still\n * rides every copy/export payload — this hides the CELL, not the data.\n * Default `false` = today's behavior everywhere.\n */\n mobileHidden?: boolean;\n /**\n * Built-in cell kinds. `\"uuid\"` / `\"fk\"` use MatrxUuidCell (short + copy +\n * optional open). `\"auto\"` (default) detects UUID-shaped strings.\n */\n cellKind?: \"auto\" | \"uuid\" | \"fk\" | \"text\";\n /**\n * FK / UUID navigation. Prefer `onOpen` → WindowPanel of the target.\n * Return `\"forbidden\"` when the caller lacks access.\n */\n fk?: {\n label?: string;\n /**\n * Canonical entity token this column's ids point at (`agent`, `note`, …).\n * THE DOOR LAW made declarative: the cell resolves route + new tab + peek\n * from the registries, so a column of ids stops being a dead end without\n * hand-wiring a link. `href` / `onOpen` still win when both are set.\n * A function form resolves the token per row (an audit log whose target\n * type varies by row).\n *\n * `\"auto\"` derives the token from the COLUMN NAME (`task_id` → `task`).\n * It is opt-in on purpose: the guess is only correct when you have checked\n * the actual FK. `scheduler.sch_run.task_id` references `scheduler.sch_task`,\n * not the workspace `task` the name implies, and `app_id` /\n * `conversation_id` / `file_id` / `workflow_id` each have several candidate\n * tables. A wrong door opens a DIFFERENT record — worse than no door.\n */\n token?: string | null | \"auto\" | ((row: T) => string | null | undefined);\n href?: (id: string, row: T) => string | null | undefined;\n onOpen?: (\n id: string,\n row: T,\n ) => void | \"forbidden\" | Promise<void | \"forbidden\">;\n /** Force non-navigable for this column. */\n forbidden?: boolean | ((id: string, row: T) => boolean);\n };\n /**\n * How this column's value READS — money, percent, a date style, decimal\n * places. The vocabulary is the platform's one field-format registry\n * (`@ai-matrx/design-system/field-formats`), the same `{id, options}` a user\n * data table stores on its column and the record store stores on its Field,\n * so `$1,234.56` is the same `$1,234.56` on every table in the product.\n *\n * A declarative format, not another render function, because a `cell`\n * callback is invisible to everything else: with `format` the column also\n * right-aligns itself, and a consumer's own `cell` asks the primitive for the\n * same text through `formatColumnValue(col, value)` rather than growing a\n * second money formatter beside this one.\n *\n * THE FALLBACK LAW travels with it: a value that does not fit the declared\n * format is never blanked and never throws — it renders its base-type text in\n * amber with the reason on hover, so the person SEES the mismatch.\n *\n * `cell` still wins when both are set.\n */\n format?: FieldFormatConfig;\n /**\n * Keep this column in place while the rest of the table scrolls sideways.\n *\n * Only the LEADING RUN freezes, only from `sm:` up, and only when the widths\n * the offsets need are known — `./frozen-columns.ts` says why each of those\n * three is a rule and not a default. Give a frozen column an explicit\n * `width`; without one the table declines to freeze anything rather than pin\n * a column at a guessed offset.\n */\n frozen?: boolean;\n className?: string;\n headerClassName?: string;\n width?: string | number;\n /** Enable desktop resizing for this column. Defaults to true when the table enables it. */\n resizable?: boolean;\n /** Desktop resize boundary. Defaults to 80px. */\n minWidth?: number;\n /** Desktop resize boundary. Defaults to 1200px. */\n maxWidth?: number;\n align?: \"left\" | \"center\" | \"right\";\n /**\n * ICON COLUMN. A column whose whole content is one glyph — a star, a lock, a\n * status dot — and whose `width` is therefore a lie without this flag.\n *\n * `width` is only a hint on a table cell: min-content wins. A 40px star column\n * still rendered ~70px wide because the HEADER carried three separate\n * controls beside the glyph (the sort button, its arrow, the filter funnel),\n * and the cell carried the default `px-2` on both sides. Every surface that\n * wanted a tight icon column was paying for chrome it never used.\n *\n * `compact` fixes it AT THE PRIMITIVE, and does NOT cost the column anything:\n * horizontal padding drops to `px-1`, and the header collapses its three\n * controls into ONE popover trigger that still offers Sort ascending / Sort\n * descending / Clear sort / the full filter body. The column stays fully\n * sortable and filterable — the affordances moved into the menu, they did not\n * disappear. Active sort/filter still show, as a 2px dot on the trigger.\n *\n * Pair it with `width` and `align: \"center\"`.\n */\n compact?: boolean;\n /**\n * Saved-item marker semantics for a compact boolean column. Markers default\n * to 40px, keep the existing sort/filter menu, and name its choices as\n * Favorites/Pins. See `ColumnMarker` for automatic recognition and opt-out.\n */\n marker?: ColumnMarker;\n /** Hide from the table (still available in column picker when we add it). */\n hidden?: boolean;\n /** Whether the package-owned Columns dialog may hide this column. Default true. */\n hideable?: boolean;\n}\n\n/** How a text filter matches. Default `\"contains\"`. */\nexport type TextFilterMode = \"contains\" | \"empty\" | \"not_empty\" | \"null\" | \"not_null\";\n\n/** Active per-column filter value. Shape depends on filter kind. */\nexport type ColumnFilterValue =\n | { kind: \"text\"; value: string; mode?: TextFilterMode; negated?: boolean }\n | {\n kind: \"select\";\n /** Single-choice value (legacy writers). Ignored when `values` is set. */\n value: string;\n /** Multi-choice OR set — an explicit empty array matches no rows. */\n values?: string[];\n /** Exclude matching values instead of including them. */\n negated?: boolean;\n }\n | { kind: \"boolean\"; value: boolean; negated?: boolean }\n | { kind: \"number\"; min?: number | undefined; max?: number | undefined; negated?: boolean };\n\nexport type ColumnFiltersState = Record<string, ColumnFilterValue | undefined>;\n\nexport interface SortState {\n id: string;\n direction: SortDirection;\n}\n\n/**\n * Opt-in durable URL state for a local table.\n *\n * Every table gets an explicit stable id, producing namespaced parameters such\n * as `table.accounts.q` and `table.accounts.sort`. This prevents collisions\n * with page-owned parameters and with sibling tables on the same route.\n */\nexport interface MatrxDataTableUrlStateConfig {\n /** Stable lowercase identifier: letters, numbers, and hyphens; max 64 chars. */\n id: string;\n /** Initial sort when the URL carries none. Default: none. */\n defaultSort?: SortState | null | undefined;\n /** Browser history behavior for table transitions. Default: `push`. */\n history?: \"push\" | \"replace\" | undefined;\n /**\n * History behavior while typing search/any-of text. `session` pushes the\n * first edit, then replaces rapid keystrokes. Default: `session`.\n */\n textHistory?: \"session\" | \"push\" | \"replace\" | undefined;\n /** Persist the open side-panel row. Default true. */\n selectedRow?: boolean;\n /** Persist the open table-owned window row. Default true. */\n windowRow?: boolean;\n /** Persist checkbox selection. Opt-in because large selections lengthen URLs. */\n selection?: boolean;\n}\n\n/**\n * Complete view state for a remotely queried table page. The table owns none\n * of this state in controlled mode: callers may mirror it to URL search params\n * and use it as part of a direct database-query cache key.\n */\nexport interface MatrxDataTableQueryState {\n /** One-based page number, matching the table's pagination UI. */\n page: number;\n pageSize: number;\n search: string;\n /** Defaults to `contains` when omitted, preserving every existing table. */\n searchMatchMode?: TableSearchMatchMode;\n /** Selected intelligent-search scope. Omitted means the backward-compatible All scope. */\n searchScope?: string;\n anyOf: string;\n /** Ordered AND rules from the compact advanced-filter builder. */\n layeredFilters?: LayeredFilterRule[];\n columnFilters: ColumnFiltersState;\n sort: SortState | null;\n}\n\n/**\n * Optional data-processing contract. Omit it (or use `local`) to preserve the\n * original in-memory filter/sort/pagination behavior. In controlled mode,\n * `data` is already the current page and the caller performs all querying.\n */\n/** Structural match for @ai-matrx/data/react usePaginatedData. */\nexport interface MatrxTablePagination<T> {\n queryKey: string;\n rows: T[];\n loading: boolean;\n isFetchingNextPage: boolean;\n error: Error | null;\n hasNextPage: boolean;\n loadNextPage: () => Promise<void>;\n refresh: () => void;\n totalItems: number | undefined;\n loadAll?: { isLoading: boolean; request: () => void; stop: () => void };\n retrySource?: () => void;\n sourceUntil?: string;\n /** Source records differ from rendered rows when the host groups occurrences. */\n sourceRecords?: { loaded: number; total?: number | undefined; label: string; rowLabel: string; matchedRecords?: number };\n sourcePageSize?: { value: number; options: readonly number[]; onChange: (value: number) => void };\n}\n\n/**\n * A controlled-local table can retain local search/pagination while its source\n * owns column filters and/or sorting. `sourceTotal` is display metadata only:\n * it never changes local page slicing, because the loaded rows remain local.\n */\nexport interface MatrxDataTableSourceProcessing {\n search?: \"local\" | \"source\";\n /** Source accepts and ranks `searchScope`; omit to keep scope UI local-only. */\n intelligentSearch?: \"local\" | \"source\";\n columnFilters?: \"local\" | \"source\";\n sort?: \"local\" | \"source\";\n sourceTotal?: number;\n}\n\nexport type MatrxDataTableQueryControl<T = unknown> =\n | { mode: \"local\" }\n | {\n /** Accumulated server rows; page stays 1 and sort/filter remain server-owned. */\n mode: \"controlled-append\";\n state: MatrxDataTableQueryState;\n onStateChange: (next: MatrxDataTableQueryState) => void;\n pagination: MatrxTablePagination<T>;\n sourceProcessing?: MatrxDataTableSourceProcessing;\n /** Source-connected tables append on user scroll by default. */\n scroll?: { thresholdPx?: number; intentTimeoutMs?: number } & (\n | { mode?: \"scroll\" }\n | { mode: \"manual\"; reason: string; approvedBy: string }\n /** Temporary configuration state: automatic append is off, but Load more stays available. */\n | { mode: \"suspended\"; reason: string }\n );\n }\n | {\n /** Local rows, but every query control is owned by the caller (for URL state). */\n mode: \"controlled-local\";\n state: MatrxDataTableQueryState;\n onStateChange: (next: MatrxDataTableQueryState) => void;\n sourceProcessing?: MatrxDataTableSourceProcessing;\n }\n | {\n mode: \"controlled\";\n state: MatrxDataTableQueryState;\n /** Total rows matching the controlled query, not just `data.length`. */\n totalItems: number;\n onStateChange: (next: MatrxDataTableQueryState) => void;\n /** Declare source-owned query behavior before enabling source-aware controls. */\n sourceProcessing?: MatrxDataTableSourceProcessing;\n };\n\n/**\n * Toolbar facets — first-class, Mars-extensible filter controls above the grid.\n * Start with button-group; add radio / switch / complex later without forking.\n */\nexport type ToolbarFacet =\n | {\n type: \"button-group\";\n id: string;\n label?: string;\n value: string;\n /** Reset target for per-facet + global clear. Default: first option value. */\n defaultValue?: string;\n options: Array<{\n value: string;\n label: string;\n icon?: ReactNode;\n }>;\n onChange: (value: string) => void;\n }\n | {\n type: \"custom\";\n id: string;\n render: () => ReactNode;\n /** Declare filter state and its reset together so global Clear includes custom controls. */\n filter?: { active: boolean; onReset: () => void };\n };\n\n/**\n * Cross-column OR search — matches if ANY listed column contains the query.\n * Relationships use case: filter by entity type without picking source vs target.\n */\nexport interface AnyOfColumnSearch {\n columnIds: string[];\n placeholder?: string;\n /** Controlled value. Uncontrolled if omitted. */\n value?: string;\n onChange?: (value: string) => void;\n}\n\nexport interface MatrxDataTableToolbar {\n /** Optional human-readable table title, rendered by the shared title row. */\n title?: string;\n /** Global search across all accessor values. Default true. */\n search?: boolean;\n searchPlaceholder?: string;\n searchValue?: string;\n onSearchChange?: (value: string) => void;\n /**\n * Show a compact, visible choice between substring and whole-word search.\n * Omit it when a data source cannot honor both modes server-side.\n */\n searchMatch?: {\n defaultMode?: TableSearchMatchMode;\n };\n /** Optional field-scope controls. Local search always ranks all loaded fields before pagination. */\n intelligentSearch?: {\n roles?: Readonly<Record<string, TableSearchRole>>;\n scopes?: readonly TableSearchScope[];\n };\n /**\n * OR-search across specific columns (e.g. source_type OR target_type).\n * Shown as its own input beside global search when set.\n */\n anyOf?: AnyOfColumnSearch;\n /**\n * Optional compact advanced-filter builder beside the regular search. In\n * controlled mode rules live in `query.state.layeredFilters`; local tables\n * evaluate them against matching column ids.\n */\n layeredFilters?: {\n fields: readonly LayeredFilterField[];\n maxRules?: number;\n label?: string;\n };\n /** Extensible facet strip (button groups, later radios/switches/…). */\n facets?: ToolbarFacet[];\n /** Source controls in a separate wrapping row below the standard toolbar. */\n leading?: ReactNode;\n /**\n * Standard host refresh affordance. The package owns its placement, busy\n * state, accessible tooltip, and glyphs (refresh arrows at rest, spinner\n * only while busy); the host remains the sole data owner.\n */\n refresh?: {\n onRefresh: () => void | Promise<void>;\n label?: string;\n };\n /**\n * Standard create affordance. The host owns authorization and the actual\n * create flow; disabledReason is exposed through the shared tooltip.\n */\n add?: {\n onAdd: () => void | Promise<void>;\n disabled?: boolean;\n disabledReason?: string;\n };\n /** Right-side domain actions, rendered after standard table controls. */\n actions?: ReactNode;\n}\n\n/** Query fields safe to persist in a named table view. */\nexport type TableViewQuery = Omit<MatrxDataTableQueryState, \"page\">;\n\n/** Portable, persistence-neutral table state saved by a host adapter. */\nexport interface TableViewSnapshot {\n __kind: \"matrx-table-view\";\n version: 1;\n /** Page/cursor/selection/loaded rows are deliberately never saved. */\n query: TableViewQuery;\n /** `widths` is sparse so views saved before resize remain valid. */\n columns: { order: string[]; hidden: string[]; widths?: Record<string, number> };\n /**\n * The view's COLORS — color-by-a-column, rules, and manual highlights\n * (`./table-style.ts`). Optional so every view saved before colors existed\n * still parses; absent means \"this view paints nothing\".\n *\n * Colors belong to the VIEW and not to the data, for the same reason the\n * filters do: two people can read one table two ways, and neither reading is\n * a fact about the records. That is also why this snapshot is the only place\n * a style is carried — copy, export and agents read the rows, never this.\n */\n style?: TableStyle;\n}\n\n/** Host port for durable named views. The host owns actor, tenancy and CRUD. */\nexport interface TableSavedViewsProps {\n tableId: string;\n snapshot: TableViewSnapshot;\n defaultSnapshot: TableViewSnapshot;\n onApply: (snapshot: TableViewSnapshot) => void;\n}\n\n/** Optional deferred-apply copy for column filter popovers. */\nexport interface MatrxDataTableFilterUi {\n /** Enables the Include/Exclude control. Default false for remote-safe filters. */\n allowNegation?: boolean;\n /** Enables null/not-null text modes. Default false for remote-safe filters. */\n allowNullModes?: boolean;\n applyLabel?: string;\n showCancel?: boolean;\n footerHint?: ReactNode;\n}\n\nexport interface MatrxDataTableCopyConfig<T> {\n /** Explicit registered identities; never inferred from table labels or row shapes. */\n rowReferences?: (row: T) => readonly ContentTransferReference[];\n references?: (visible: T[], all: T[]) => readonly ContentTransferReference[];\n triggerVariant?: \"transparent\" | \"glass\" | \"outline\";\n /** Toast / tooltip label base, e.g. \"Relationship rule\". */\n label: string;\n listLabel?: string;\n location: string;\n rowKind: string;\n listKind: string;\n rowDescription?: string;\n listDescription?: string;\n humanRow: (row: T) => string;\n /** Project row for agent JSON. Default: full row. */\n agentRow?: (row: T) => unknown;\n rowAttributes?: (\n row: T,\n ) => Record<string, string | number | boolean | null | undefined>;\n listAttributes?: (\n visible: T[],\n all: T[],\n ) => Record<string, string | number | boolean | null | undefined>;\n /**\n * Live view state rendered inside the list payload's <context>. Unlike\n * per-row data, this remains present when the current view has zero rows.\n */\n listContext?: (\n visible: T[],\n all: T[],\n ) => Record<string, string | number | boolean | null | undefined>;\n /**\n * Additional row-scoped AI actions. Use this to fold a domain action such\n * as a paste-ready repair brief into the table-owned row copy menu instead\n * of rendering a separate third control. Builders run at click time.\n */\n rowAiVariants?: (row: T) => AiVariant[];\n /**\n * Graded AI variants for the toolbar's view copy (e.g. \"Top 25\", \"Summary\n * only\"). When set, the toolbar's Copy-for-AI upgrades to a dropdown with\n * these variants + the full-view payload as the automatic \"Everything\"\n * escape hatch. Receives (visible, all) rows at render; builders run at\n * click time.\n */\n aiVariants?: (visible: T[], all: T[]) => AiVariant[];\n /** Custom-preview source (options dialog + live size counts) for the view. */\n aiCustom?: (visible: T[], all: T[]) => AiCustomSource;\n /** Additional domain exports in the same canonical menu; builders run on demand. */\n export?: (visible: T[], all: T[]) => CopyExportConfig;\n /** Show toolbar copy (this view). Default true when copy is set. */\n showToolbar?: boolean;\n /** Show per-row copy. Default true when copy is set. */\n showRow?: boolean;\n}\n\nexport interface MatrxDataTableDetailConfig<T> {\n /** Side-panel title. Default: first string column or \"Details\". */\n title?: (row: T) => ReactNode;\n description?: (row: T) => ReactNode | undefined;\n /** Override the default key/value inspector. */\n render?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /** Header actions inside the side panel. */\n headerActions?: (row: T) => ReactNode;\n defaultWidth?: number;\n enabled?: boolean;\n /**\n * Maps a field name to the entity token its id points at, turning that field\n * into a door (route + peek) in the default inspector — side panel AND row\n * window.\n *\n * There is no default guess: this inspector renders whatever columns the row\n * has, and a wrong door opens a DIFFERENT record (`sch_run.task_id` is a\n * SCHEDULED task, not a workspace `task`). A table whose FKs you HAVE checked\n * can pass `tokenFromColumnName` (`components/official/entity-ref/doors`) to\n * open every `<token>_id` field at once.\n *\n * The ROW is passed too, because a table whose target type varies per row (an\n * audit log, an exposure report) cannot answer from the column name alone.\n */\n tokenForField?: (key: string, row: T) => string | null;\n}\n\n/** Actions a record-owned control can use without reaching into table state. */\nexport interface MatrxDataTableRecordControls {\n /** Close the row's side-panel detail, if open. */\n closeDetail: () => void;\n /** Open the row in the canonical adjustable side panel. */\n openDetail: () => void;\n /** Open the row in its canonical table-owned WindowPanel. */\n openWindow: () => void;\n /** Close the row's table-owned WindowPanel, if open. */\n closeWindow: () => void;\n /** Whether this row currently has visible, unpersisted inline edits. */\n hasPendingEdits: boolean;\n /**\n * Discard this row's pending inline edits.\n *\n * Row actions that persist the already-merged visible row (for example an\n * explicit Confirm action) call this only after that write succeeds. This\n * prevents the floating Save pill from later replaying the same draft as a\n * different, weaker write.\n */\n discardPendingEdits: () => void;\n beginEdit?: () => void;\n saveEdits?: () => void | Promise<void>;\n cancelEdits?: () => void;\n /** Whether this row's optional inline detail is expanded in the table. */\n isExpanded?: boolean;\n /** Toggle this row's optional inline detail. */\n toggleExpanded?: () => void;\n}\n\n/**\n * Controlled inline detail rendered immediately below its owning table row.\n *\n * Use this when detail belongs in the scan path of a dense table. The caller\n * owns which one row is expanded and supplies the domain-specific body; the\n * canonical renderer retains sorting, filtering, selection, row actions, and\n * the correct column span.\n */\nexport interface MatrxDataTableExpandedDetailConfig<T> {\n expandedId: string | null;\n onExpandedIdChange: (id: string | null) => void;\n canExpand?: (row: T) => boolean;\n render: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n className?: string;\n}\n\n/** Opaque host-owned row menu descriptor. The package never interprets it. */\nexport interface MatrxDataTableContextMenuConfig<T> {\n resolveRowContext: (row: T, controls: MatrxDataTableRecordControls) => unknown;\n}\n\nexport interface MatrxDataTableWindowConfig<T> {\n /** Window title. */\n title?: (row: T) => string;\n /**\n * @deprecated Prefer `renderView` + `renderEdit` so the window stays editable.\n * Full-body override with no View/Edit tabs.\n */\n render?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /** View tab body. Defaults to DataRowInspector. */\n renderView?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /**\n * Edit tab body. When set, the WindowPanel shows View / Edit sidebar tabs\n * (WindowPanel built-in sidebar). Defaults to `detail.render` when present.\n * Pass `false` to keep a view-only window even when `detail.render` exists.\n */\n renderEdit?:\n ((row: T, controls: MatrxDataTableRecordControls) => ReactNode) | false;\n /**\n * Called when the panel icon opens the window — hydrate edit state here\n * without opening the side panel (prefer this over `onRowOpen` for windows).\n */\n onOpen?: (row: T) => void;\n /**\n * Make full-row click open the WindowPanel instead of the side panel.\n * The trailing row action and the window header then expose the side panel\n * as the explicit secondary presentation. Default false.\n */\n openOnRowClick?: boolean;\n /** Which tab to open. Default: `\"edit\"` when an edit body exists. */\n defaultTab?: \"view\" | \"edit\";\n /** Show the panel-icon that opens the window. Default true when detail enabled. */\n enabled?: boolean;\n width?: number;\n height?: number;\n}\n\nexport interface MatrxDataTableEmptyState {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n}\n\n/** Pending cell edits keyed by row id → partial field map. */\nexport type CellEditsMap = Record<string, Record<string, unknown>>;\n\nexport interface MatrxDataTableEditConfig<T> {\n /** Enable inline editing for columns with `editable` set. */\n enabled?: boolean;\n /**\n * Persist all pending edits. Called when the user clicks Save on the dirty pill.\n * Return resolved when done; throw/reject to keep the draft (with toast).\n */\n onSave: (edits: CellEditsMap, rows: T[]) => void | Promise<void>;\n /** Persist each committed cell immediately. Failed writes remain in the\n * dirty pill so the user can retry or cancel them. */\n autoSave?: boolean;\n /** Optional cancel hook (draft already discarded). */\n onCancel?: () => void;\n}\n\nexport interface MatrxDataTableHierarchyConfig<T> {\n /** Complete hierarchy when `data` is only the current controlled page. */\n rows?: T[];\n getParentId: (row: T) => string | null;\n /** Persist the exact structural intent represented by the drop shadow. */\n onMove: (row: T, move: MatrxDataTableHierarchyMove) => void | Promise<void>;\n /** Enables sibling insertion shadows in addition to parent/root drops. */\n manualOrder?: boolean;\n canReparent?: (row: T) => boolean;\n itemLabel?: (row: T) => string;\n rootDropLabel?: string;\n}\n\nexport interface MatrxDataTableHierarchyMove {\n parentId: string | null;\n /**\n * Insert immediately before this sibling. Null means first child for\n * \"inside\"/\"root\", and LAST sibling for \"after\" (target had no successor).\n */\n beforeId: string | null;\n position: \"before\" | \"after\" | \"inside\" | \"root\";\n targetId: string | null;\n}\n\n/**\n * Multi-row selection — a leading checkbox column plus a bulk bar that appears\n * only while rows are checked.\n *\n * OPT-IN and fully CONTROLLED: the consumer owns the id set, so selection\n * survives (or is deliberately cleared by) a re-fetch, a filter change, or an\n * optimistic list update — the table never holds hidden selection state that\n * can disagree with the surface around it.\n *\n * Why this is a primitive and not a per-surface checkbox column: a register\n * the user cannot clear in bulk is a register they stop reading, and the fifth\n * hand-rolled selection column — each with its own shift-click, its own\n * select-all semantics, its own bar — is exactly the fork `components/official/`\n * exists to prevent.\n *\n * Selection does not alter mobile horizontal scrolling. The complete row moves\n * as one surface below `sm`; no leading column is allowed to pin over the data.\n */\nexport interface MatrxDataTableSelectionConfig<T> {\n /** Selected row ids (`getRowId`). Ids not on the current page are kept. */\n selectedIds: string[];\n onSelectedIdsChange: (ids: string[]) => void;\n /**\n * The bulk bar's actions, given the currently-selected rows THAT ARE LOADED.\n * Selection can outlive a page change, so also take `selectedIds` when an\n * action only needs ids.\n */\n actions?: (selected: T[], selectedIds: string[]) => ReactNode;\n /**\n * Rows that cannot be acted on in bulk render NO checkbox at all — the cell\n * stays for alignment, the control is absent from the DOM. A greyed control\n * still advertises a choice the surface underneath would refuse.\n */\n isRowSelectable?: (row: T) => boolean;\n /** Singular noun for the bar's count (\"finding\" → \"3 findings selected\"). */\n noun?: string;\n}\n\n/** Table-owned state and actions exposed to an opt-in phone card renderer. */\nexport interface MatrxDataTableMobileCardControls {\n /** Render a declared column with the same editable/draft/link behavior as the grid. */\n renderCell: (columnId: string) => ReactNode;\n /** Whether this row is in the canonical selection set. */\n selected: boolean;\n /** Whether the consumer allows this row to be selected. */\n selectable: boolean;\n /** Update selection through the table's controlled/URL-backed contract. */\n onSelectedChange: (selected: boolean) => void;\n /**\n * The table's canonical per-row copy controls plus consumer row actions.\n * Render this instead of rebuilding either action path inside the card.\n */\n actions: ReactNode;\n /** Optional inline-detail state for card renderers. */\n isExpanded?: boolean;\n /** Toggle this row's optional inline detail. */\n toggleExpanded?: () => void;\n /** Optional inline detail body; cards choose its placement. */\n expandedDetail: ReactNode;\n}\n\nexport type MatrxDataTableDensity = \"condensed\" | \"normal\" | \"spacious\";\n\n/**\n * What the table needs in order to paint a `TableStyle` itself.\n *\n * `fieldOf` exists because a style names DATA fields (\"tint the row when\n * `status` is Blocked\") while the table addresses COLUMNS. In the common case\n * the column id IS the field name and the default is right; a table whose\n * column ids are decorated (`records-grid-notes`) maps them here.\n */\nexport interface MatrxDataTableStyleConfig<T> {\n /** The style document. */\n style: TableStyle;\n /** The row's data as the rules read it. Defaults to the row itself. */\n documentOf?: (row: T) => Record<string, unknown>;\n /** Column id to the field name a rule would name. Defaults to identity. */\n fieldOf?: (columnId: string) => string;\n /**\n * The color a choice column's option paints with, for color-by. The consumer\n * supplies it because option colors live on the resolved option list, which\n * may come from a shared pick list this package cannot reach.\n */\n choiceColorFor?: ChoiceColorLookup;\n}\n\nexport interface MatrxDataTableProps<T> {\n data: T[];\n columns: MatrxColumnDef<T>[];\n /** Table-local visual density. An explicit value wins over the host default. */\n density?: MatrxDataTableDensity;\n /** Stable persistence identity for host-installed named views. */\n tableId?: string;\n /** Declared reset state for the host Saved Views control. */\n defaultViewSnapshot?: TableViewSnapshot;\n /** Working view tabs, on by default. Disable for embedded/single-purpose tables. */\n viewTabs?: boolean;\n /** Related-data controls shown at the right of the working-view tab strip. */\n relatedTableActions?: ReactNode;\n /** Optional URL/source-owned column order and visibility. */\n columnState?: {\n order: string[];\n hidden: string[];\n onChange: (next: { order: string[]; hidden: string[] }) => void;\n /** Optional source-owned desktop widths keyed by canonical column id. */\n widths?: Record<string, number>;\n /** Required to persist controlled width changes across remounts. */\n onWidthsChange?: (widths: Record<string, number>) => void;\n };\n /** Enables package-owned drag reordering in table headers. Default true. */\n reorderableColumns?: boolean;\n /** Enables desktop column resizing. Default true; handles are absent below `sm`. */\n resizableColumns?: boolean;\n columnManager?: { open: boolean; onOpenChange: (open: boolean) => void };\n getRowId: (row: T) => string;\n /**\n * Additional row identity included in local global search without becoming\n * a visible/sortable/filterable column. Use for canonical composite keys or\n * aliases whose displayed parts live in separate columns.\n *\n * Ignored in remote controlled mode, where the query owner applies search.\n */\n searchText?: (row: T) => string;\n /**\n * Hierarchy-aware local processing seam. The canonical table still owns the\n * toolbar, URL state, headers, pagination, editing, copy, and rendering; the\n * consumer only preserves domain ordering that a flat sort would destroy.\n *\n * The processor must honor every active query control in `state` and return\n * the complete filtered/sorted local result before pagination. It is ignored\n * in remote controlled mode, where `data` is already the queried page.\n */\n processLocalRows?: (rows: T[], state: MatrxDataTableQueryState) => T[];\n /** Initial local sort when neither URL state nor a controlled query owns it. */\n defaultSort?: SortState | null;\n isLoading?: boolean;\n /**\n * Background refresh state. Unlike `isLoading`, this preserves rendered rows\n * and shows only the table's non-blocking refresh indicator.\n */\n isFetching?: boolean;\n /**\n * Controlled query state for direct remote data sources. The component never\n * fetches data itself; it only emits state changes to the caller.\n */\n query?: MatrxDataTableQueryControl<T>;\n /**\n * Persist local query and record-view state in namespaced URL parameters.\n * This is intentionally opt-in and cannot be combined with controlled query\n * mode; remote tables use `useTableUrlState({ tableId })` in their query owner.\n */\n urlState?: MatrxDataTableUrlStateConfig;\n\n toolbar?: MatrxDataTableToolbar;\n /** Hide the package toolbar while retaining table-owned query behavior. */\n hideToolbar?: boolean;\n /** Hide numbered pagination; local rows remain available to the caller. */\n hidePagination?: boolean;\n /** Optional deferred Apply/Cancel copy for column filter popovers. */\n filterUi?: MatrxDataTableFilterUi;\n /** Row click opens the side panel unless `window.openOnRowClick` is true. */\n detail?: MatrxDataTableDetailConfig<T>;\n /** Controlled detail rendered directly below a table row. */\n expandedDetail?: MatrxDataTableExpandedDetailConfig<T>;\n /** Panel icon opens a WindowPanel (page-local; supports ReactNode override). */\n window?: MatrxDataTableWindowConfig<T>;\n /** Copy + Copy for AI (rows + this view). Omit for the declared-column default; false suppresses it. */\n copy?: false | MatrxDataTableCopyConfig<T>;\n /** Where canonical per-row Alchemy appears. Inline preserves current behavior. */\n rowCopyPlacement?: \"inline\" | \"menu\";\n /** One pane-level right-click resolver; host owns the opaque descriptor. */\n contextMenu?: MatrxDataTableContextMenuConfig<T>;\n /** Inline edit session with floating Save/Cancel pill. */\n edit?: MatrxDataTableEditConfig<T>;\n /** Opt-in tree reparenting owned by the canonical row renderer. */\n hierarchy?: MatrxDataTableHierarchyConfig<T>;\n\n /** Controlled selection (selected row id for highlight). */\n selectedId?: string | null | undefined;\n onSelectedIdChange?: ((id: string | null) => void) | undefined;\n\n /** Controlled table-owned window row (normally supplied by URL state). */\n windowRowId?: string | null | undefined;\n onWindowRowIdChange?: ((id: string | null) => void) | undefined;\n\n /** Opt-in multi-row checkbox selection + a bulk action bar. */\n selection?: MatrxDataTableSelectionConfig<T>;\n\n /** Extra row actions rendered in a trailing Actions column. */\n rowActions?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /** Extra rows rendered in the table footer with the same columns as data rows. */\n footerRows?: T[];\n /** Per-row presentation class, after canonical selection/highlight state. */\n rowClassName?: (row: T, index: number) => string | undefined;\n /**\n * Per-CELL presentation class, after the column's own static `className`.\n *\n * The primitive had `rowClassName` and a static per-column `className`, and\n * nothing in between — so every table that needed to say something about ONE\n * cell had to grow a `cell` render function and paint inside it, where the\n * table can no longer see it. This is the missing rung, and `tableStyle`\n * below is the declarative thing built on top of it.\n */\n cellClassName?: (\n row: T,\n columnId: string,\n index: number,\n ) => string | undefined;\n /**\n * COLORS, declaratively — color by a column, rules, manual highlights.\n *\n * The model is `./table-style.ts`: one document, evaluated live on every\n * render so a tint is never stale, with a documented precedence between the\n * three ways a cell can earn a color. Pass the document and the table paints\n * itself; the consumer owns only where that document is STORED and how a\n * person edits it.\n */\n tableStyle?: MatrxDataTableStyleConfig<T>;\n /** Highlight an index in the currently rendered page without selecting it. */\n highlightedIndex?: number | undefined;\n /** Receives the complete visible local view after local processing, before paging. Attaching after absence receives one snapshot; callback identity changes alone do not notify. */\n onViewChange?: (rows: T[]) => void;\n /**\n * Wrap the whole `<tr>`. Return `children` unchanged for no-op.\n *\n * `rowActions` only reaches the actions CELL, so anything that must own the\n * entire row — a right-click menu, a drag handle, a drop target — had no\n * seam and would have forced a surface to fork the table. THE INVENTORY LAW:\n * the fork is the defect, so the seam exists instead.\n *\n * Whatever you return must render `children` as a direct `<tbody>` child, so\n * the wrapper has to be a component that emits the `<tr>` unchanged\n * (`ItemContextMenu` does — it renders a Radix trigger with `asChild`).\n */\n rowWrapper?: (row: T, children: ReactNode) => ReactNode;\n\n emptyState?: MatrxDataTableEmptyState;\n /** Default 25. Pass 0 to show all. */\n pageSize?: number;\n /** Complete local results reveal on physical scroll by default. Numbered\n * paging is an explicit exception requiring its reason and approving owner. */\n localPagination?:\n | { mode?: \"progressive\" }\n | { mode: \"numbered\"; reason: string; approvedBy: string };\n pageSizeOptions?: number[];\n zebra?: boolean;\n /**\n * Keep the one semantic table header visible while its nearest vertical\n * scroll surface moves. Defaults to true. Set false only where surrounding\n * chrome already supplies the column labels.\n */\n stickyHeader?: boolean;\n className?: string;\n tableClassName?: string;\n /**\n * Optional compact row presentation rendered below `mobileCardsBreakpoint`\n * in place of the horizontal table. The caller supplies the record summary\n * because only the product surface knows which values and actions are\n * essential at narrow widths; MatrxDataTable still owns query state,\n * loading/empty states, and pagination.\n *\n * `controls.actions` carries the table-owned copy controls and consumer row\n * actions, so a card does not fork them. Prefer the default horizontal table\n * unless the product explicitly requires every essential value/action to be\n * discoverable without horizontal scrolling.\n */\n mobileCards?: (\n row: T,\n index: number,\n controls: MatrxDataTableMobileCardControls,\n ) => ReactNode;\n /** Card/table handoff. `sm` is phone-only; `lg` includes portrait tablets. */\n mobileCardsBreakpoint?: \"sm\" | \"lg\";\n /**\n * Resolves a record's canonical destination for modified row gestures.\n * Cmd/Ctrl/Shift-click and middle-click open this URL in a new browsing\n * context; an unresolved URL deliberately does not fall back to row open.\n * Plain row clicks retain the table's detail/window/onRowOpen behavior.\n */\n getRowHref?: (row: T) => string | undefined;\n /** Called after a row is selected for detail (in addition to opening the panel). */\n onRowOpen?: (row: T) => void;\n}\n\n/** Re-export for callers building custom agent payloads. */\nexport type { AgentPayloadInput };\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../src/data-table/types.ts"],"sourcesContent":["import type { ContentTransferReference } from \"../content-transfer/extensions\";\nimport type { ReactNode } from \"react\";\nimport type { TableSearchRole, TableSearchScope } from \"./ranked-search\";\nimport type { AgentPayloadInput } from \"./copy\";\nimport type { CopyExportConfig } from \"./copy-types\";\nimport type {\n AiCustomSource,\n AiVariant,\n} from \"./copy\";\nimport type { LayeredFilterField, LayeredFilterRule } from \"./layered-filters\";\nimport type { ChoiceColorLookup, TableStyle } from \"./table-style\";\nimport type { FieldFormatConfig } from \"../field-formats/types\";\nimport type { GroupAggregateSpec, GroupOrder } from \"./grouping\";\nimport type { CellPatch } from \"./fill\";\nimport type { SpreadsheetIntent } from \"./useSpreadsheetGrid\";\n\n/** How a column's filter UI behaves. `auto` infers from sample values. */\nexport type ColumnFilterKind =\n \"auto\" | \"text\" | \"select\" | \"boolean\" | \"number\" | false;\n\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * A compact boolean marker with the language people expect for a saved item.\n *\n * `undefined` recognizes only the exact conventional boolean accessor names\n * (`favorite`, `is_favorite`, `favourite`, `is_favourite`, `pinned`, and\n * `is_pinned`). Pass `false` to keep one of those fields as an ordinary\n * boolean column. `\"favorite\"` and `\"pin\"` make the intent explicit for\n * differently named fields.\n */\nexport type ColumnMarker = \"favorite\" | \"pin\" | false;\n\n/** How the table's primary search text is matched. */\nexport type TableSearchMatchMode = \"contains\" | \"whole_words\";\n\n/** Cell value type for typed inline editors (Supabase-style popovers for non-strings). */\nexport type CellEditType =\n | \"string\"\n | \"number\"\n | \"boolean\"\n | \"select\"\n /** Free-text multi-value chips (string[] cells: tags, labels, aliases). */\n | \"tags\"\n | \"date\"\n | false;\n\nexport interface MatrxColumnDef<T> {\n /** Stable id used for sort/filter state. Defaults to `accessorKey` when set. */\n id?: string;\n /** Dot-free key on the row for default value access + auto filter. */\n accessorKey?: keyof T & string;\n /** Custom value for sort/filter when `accessorKey` is insufficient. */\n accessorFn?: (row: T) => unknown;\n /** Value used only for local sort. Falls back to `accessorFn` / `accessorKey`. */\n sortValue?: (row: T) => unknown;\n /** Value used only for local filters. Falls back to `accessorFn` / `accessorKey`. */\n filterValue?: (row: T) => unknown;\n header: ReactNode;\n /** Plain-text name used for header controls when `header` is visual-only. */\n label?: string;\n /** Cell renderer. Defaults to stringified accessor value. */\n cell?: (row: T, index: number) => ReactNode;\n /** Sortable unless explicitly false. Default true. */\n sortable?: boolean;\n /** Direction used by the first header-click sort. Default `\"asc\"`. */\n defaultSortDirection?: SortDirection;\n /**\n * Filter kind. Default `\"auto\"` — every column gets a filter.\n * Pass `false` only when a column truly must not filter (e.g. actions).\n */\n filter?: ColumnFilterKind;\n /** Explicit select options (when filter is `\"select\"` or auto-detected). */\n filterOptions?: Array<{ value: string; label: string }>;\n /**\n * Single-choice select filter. The default select filter is a multi-select\n * that APPENDS (OR semantics) — correct for a status column, wrong for a\n * column whose options are mutually exclusive VIEWS of the list (a\n * record-class scope, a relative-date bucket). There, appending makes the\n * filter inert: the consumer reads one value, the popover accumulates a set,\n * and the first-selected value wins forever (D218). With `filterSingle`,\n * choosing an option REPLACES the selection, and choosing the active option\n * again clears it.\n */\n filterSingle?: boolean;\n /** Allow an exact typed value not present in loaded select options. */\n filterAllowCustom?: boolean;\n /**\n * Inline edit. Default false. `\"string\"` edits in-cell; other types open a\n * small popover (Supabase-style). Edits stay local until Save on the dirty pill.\n */\n editable?: CellEditType;\n /**\n * Per-row edit gate for an `editable` column. Return false to render the\n * plain cell (no pencil, no click-to-edit) for that row — for heterogeneous\n * lists where some kinds cannot take the write (e.g. a transcripts row of\n * kind \"unsorted\" has no user-facing title). Default: every row editable.\n */\n editableIf?: (row: T) => boolean;\n /**\n * How inline edit starts. Default `\"click\"` (click the cell body). `\"pencil\"`\n * shows a hover/focus pencil; the cell body no longer starts edit — so a\n * whole-row click can own that gesture. Forced to `\"pencil\"` when `href` is\n * set (D112 — the body is a real link).\n */\n editTrigger?: \"click\" | \"pencil\";\n /**\n * Options when `editable === \"select\"`. Also used by `\"tags\"` as the\n * suggestion list (existing values), while still allowing new entries.\n */\n editOptions?: Array<{ value: string; label: string }>;\n /**\n * Row link for the primary/title cell (D112): renders the cell content as a\n * real `next/link` anchor, so the row is reachable by keyboard, announced as\n * a link by screen readers, and cmd/middle-clickable into a new tab. The\n * whole-row `onRowOpen` click stays as a mouse convenience; clicks on the\n * anchor never double-fire it. Combine with `editable` and the link renders\n * with a hover/focus pencil that opens the inline editor instead of\n * click-text-to-edit.\n */\n href?: (row: T) => string | undefined;\n /**\n * Canonical entity token for the record this column NAMES. When set, the cell\n * renders through `EntityRef`, so the name carries the full door set — Open,\n * new tab, and Peek — instead of the Open-only `<Link>` that `href` alone\n * produces.\n *\n * THE INVENTORY LAW, applied to this component: the table grew its own door\n * (`href`) beside the platform's (`EntityRef`), and every column that named a\n * record picked one and silently lost the other half. This field collapses\n * them — `href` still works and still forces the pencil trigger, and it\n * OVERRIDES the registry route when both are set (for an admin-side route on\n * a satellite deployment).\n *\n * Needs the record's id: `entityToken` is paired with `entityId`, defaulting\n * to the table's own `getRowId`.\n *\n * **PER ROW, not per column** — a hub can be heterogeneous. `/transcripts`\n * lists transcripts, studio sessions, cleanup runs and an \"unsorted\" bucket\n * in one table, each with its own destination; a constant token would have\n * sent a session id to the transcript processor route and opened the\n * transcript peek on a record that is not one. Return `undefined` for a row\n * that names no entity — it falls back to the plain `href` link, or to inert\n * text. Pair with a per-row `href` when the kinds diverge.\n */\n entityToken?: string | ((row: T) => string | undefined);\n /**\n * NOTE: give the column a `cell` when you set this. Without one, a\n * UUID-shaped value renders the default `MatrxUuidCell`, which has its own\n * controls — wrapping those in the door's anchor would nest interactive\n * elements inside a link, and is redundant besides. The shell detects that\n * combination, skips the door, and screams once per column.\n */\n /** The id `entityToken` refers to. Defaults to the table's `getRowId(row)`. */\n entityId?: (row: T) => string | undefined;\n /**\n * Drop this column below `sm` (the phone breakpoint).\n *\n * A wide table on a phone becomes a horizontal scroller: the frozen\n * identity column stays, and everything after the second column sits off\n * the right edge where a reviewer will never find it. Marking the columns\n * that do NOT earn their width on a phone is how a surface declares an\n * INTENTIONAL mobile column set instead of an accidental one.\n *\n * The column is still fully sortable/filterable from the toolbar and still\n * rides every copy/export payload — this hides the CELL, not the data.\n * Default `false` = today's behavior everywhere.\n */\n mobileHidden?: boolean;\n /**\n * Built-in cell kinds. `\"uuid\"` / `\"fk\"` use MatrxUuidCell (short + copy +\n * optional open). `\"auto\"` (default) detects UUID-shaped strings.\n */\n cellKind?: \"auto\" | \"uuid\" | \"fk\" | \"text\";\n /**\n * FK / UUID navigation. Prefer `onOpen` → WindowPanel of the target.\n * Return `\"forbidden\"` when the caller lacks access.\n */\n fk?: {\n label?: string;\n /**\n * Canonical entity token this column's ids point at (`agent`, `note`, …).\n * THE DOOR LAW made declarative: the cell resolves route + new tab + peek\n * from the registries, so a column of ids stops being a dead end without\n * hand-wiring a link. `href` / `onOpen` still win when both are set.\n * A function form resolves the token per row (an audit log whose target\n * type varies by row).\n *\n * `\"auto\"` derives the token from the COLUMN NAME (`task_id` → `task`).\n * It is opt-in on purpose: the guess is only correct when you have checked\n * the actual FK. `scheduler.sch_run.task_id` references `scheduler.sch_task`,\n * not the workspace `task` the name implies, and `app_id` /\n * `conversation_id` / `file_id` / `workflow_id` each have several candidate\n * tables. A wrong door opens a DIFFERENT record — worse than no door.\n */\n token?: string | null | \"auto\" | ((row: T) => string | null | undefined);\n href?: (id: string, row: T) => string | null | undefined;\n onOpen?: (\n id: string,\n row: T,\n ) => void | \"forbidden\" | Promise<void | \"forbidden\">;\n /** Force non-navigable for this column. */\n forbidden?: boolean | ((id: string, row: T) => boolean);\n };\n /**\n * How this column's value READS — money, percent, a date style, decimal\n * places. The vocabulary is the platform's one field-format registry\n * (`@ai-matrx/design-system/field-formats`), the same `{id, options}` a user\n * data table stores on its column and the record store stores on its Field,\n * so `$1,234.56` is the same `$1,234.56` on every table in the product.\n *\n * A declarative format, not another render function, because a `cell`\n * callback is invisible to everything else: with `format` the column also\n * right-aligns itself, and a consumer's own `cell` asks the primitive for the\n * same text through `formatColumnValue(col, value)` rather than growing a\n * second money formatter beside this one.\n *\n * THE FALLBACK LAW travels with it: a value that does not fit the declared\n * format is never blanked and never throws — it renders its base-type text in\n * amber with the reason on hover, so the person SEES the mismatch.\n *\n * `cell` still wins when both are set.\n */\n format?: FieldFormatConfig;\n /**\n * Keep this column in place while the rest of the table scrolls sideways.\n *\n * Only the LEADING RUN freezes, only from `sm:` up, and only when the widths\n * the offsets need are known — `./frozen-columns.ts` says why each of those\n * three is a rule and not a default. Give a frozen column an explicit\n * `width`; without one the table declines to freeze anything rather than pin\n * a column at a guessed offset.\n */\n frozen?: boolean;\n className?: string;\n headerClassName?: string;\n width?: string | number;\n /** Enable desktop resizing for this column. Defaults to true when the table enables it. */\n resizable?: boolean;\n /** Desktop resize boundary. Defaults to 80px. */\n minWidth?: number;\n /** Desktop resize boundary. Defaults to 1200px. */\n maxWidth?: number;\n align?: \"left\" | \"center\" | \"right\";\n /**\n * ICON COLUMN. A column whose whole content is one glyph — a star, a lock, a\n * status dot — and whose `width` is therefore a lie without this flag.\n *\n * `width` is only a hint on a table cell: min-content wins. A 40px star column\n * still rendered ~70px wide because the HEADER carried three separate\n * controls beside the glyph (the sort button, its arrow, the filter funnel),\n * and the cell carried the default `px-2` on both sides. Every surface that\n * wanted a tight icon column was paying for chrome it never used.\n *\n * `compact` fixes it AT THE PRIMITIVE, and does NOT cost the column anything:\n * horizontal padding drops to `px-1`, and the header collapses its three\n * controls into ONE popover trigger that still offers Sort ascending / Sort\n * descending / Clear sort / the full filter body. The column stays fully\n * sortable and filterable — the affordances moved into the menu, they did not\n * disappear. Active sort/filter still show, as a 2px dot on the trigger.\n *\n * Pair it with `width` and `align: \"center\"`.\n */\n compact?: boolean;\n /**\n * Saved-item marker semantics for a compact boolean column. Markers default\n * to 40px, keep the existing sort/filter menu, and name its choices as\n * Favorites/Pins. See `ColumnMarker` for automatic recognition and opt-out.\n */\n marker?: ColumnMarker;\n /** Hide from the table (still available in column picker when we add it). */\n hidden?: boolean;\n /** Whether the package-owned Columns dialog may hide this column. Default true. */\n hideable?: boolean;\n}\n\n/** How a text filter matches. Default `\"contains\"`. */\nexport type TextFilterMode = \"contains\" | \"empty\" | \"not_empty\" | \"null\" | \"not_null\";\n\n/** Active per-column filter value. Shape depends on filter kind. */\nexport type ColumnFilterValue =\n | { kind: \"text\"; value: string; mode?: TextFilterMode; negated?: boolean }\n | {\n kind: \"select\";\n /** Single-choice value (legacy writers). Ignored when `values` is set. */\n value: string;\n /** Multi-choice OR set — an explicit empty array matches no rows. */\n values?: string[];\n /** Exclude matching values instead of including them. */\n negated?: boolean;\n }\n | { kind: \"boolean\"; value: boolean; negated?: boolean }\n | { kind: \"number\"; min?: number | undefined; max?: number | undefined; negated?: boolean };\n\nexport type ColumnFiltersState = Record<string, ColumnFilterValue | undefined>;\n\nexport interface SortState {\n id: string;\n direction: SortDirection;\n}\n\n/**\n * Opt-in durable URL state for a local table.\n *\n * Every table gets an explicit stable id, producing namespaced parameters such\n * as `table.accounts.q` and `table.accounts.sort`. This prevents collisions\n * with page-owned parameters and with sibling tables on the same route.\n */\nexport interface MatrxDataTableUrlStateConfig {\n /** Stable lowercase identifier: letters, numbers, and hyphens; max 64 chars. */\n id: string;\n /** Initial sort when the URL carries none. Default: none. */\n defaultSort?: SortState | null | undefined;\n /** Browser history behavior for table transitions. Default: `push`. */\n history?: \"push\" | \"replace\" | undefined;\n /**\n * History behavior while typing search/any-of text. `session` pushes the\n * first edit, then replaces rapid keystrokes. Default: `session`.\n */\n textHistory?: \"session\" | \"push\" | \"replace\" | undefined;\n /** Persist the open side-panel row. Default true. */\n selectedRow?: boolean;\n /** Persist the open table-owned window row. Default true. */\n windowRow?: boolean;\n /** Persist checkbox selection. Opt-in because large selections lengthen URLs. */\n selection?: boolean;\n}\n\n/**\n * Complete view state for a remotely queried table page. The table owns none\n * of this state in controlled mode: callers may mirror it to URL search params\n * and use it as part of a direct database-query cache key.\n */\nexport interface MatrxDataTableQueryState {\n /** One-based page number, matching the table's pagination UI. */\n page: number;\n pageSize: number;\n search: string;\n /** Defaults to `contains` when omitted, preserving every existing table. */\n searchMatchMode?: TableSearchMatchMode;\n /** Selected intelligent-search scope. Omitted means the backward-compatible All scope. */\n searchScope?: string;\n anyOf: string;\n /** Ordered AND rules from the compact advanced-filter builder. */\n layeredFilters?: LayeredFilterRule[];\n columnFilters: ColumnFiltersState;\n sort: SortState | null;\n}\n\n/**\n * Optional data-processing contract. Omit it (or use `local`) to preserve the\n * original in-memory filter/sort/pagination behavior. In controlled mode,\n * `data` is already the current page and the caller performs all querying.\n */\n/** Structural match for @ai-matrx/data/react usePaginatedData. */\nexport interface MatrxTablePagination<T> {\n queryKey: string;\n rows: T[];\n loading: boolean;\n isFetchingNextPage: boolean;\n error: Error | null;\n hasNextPage: boolean;\n loadNextPage: () => Promise<void>;\n refresh: () => void;\n totalItems: number | undefined;\n loadAll?: { isLoading: boolean; request: () => void; stop: () => void };\n retrySource?: () => void;\n sourceUntil?: string;\n /** Source records differ from rendered rows when the host groups occurrences. */\n sourceRecords?: { loaded: number; total?: number | undefined; label: string; rowLabel: string; matchedRecords?: number };\n sourcePageSize?: { value: number; options: readonly number[]; onChange: (value: number) => void };\n}\n\n/**\n * A controlled-local table can retain local search/pagination while its source\n * owns column filters and/or sorting. `sourceTotal` is display metadata only:\n * it never changes local page slicing, because the loaded rows remain local.\n */\nexport interface MatrxDataTableSourceProcessing {\n search?: \"local\" | \"source\";\n /** Source accepts and ranks `searchScope`; omit to keep scope UI local-only. */\n intelligentSearch?: \"local\" | \"source\";\n columnFilters?: \"local\" | \"source\";\n sort?: \"local\" | \"source\";\n sourceTotal?: number;\n}\n\nexport type MatrxDataTableQueryControl<T = unknown> =\n | { mode: \"local\" }\n | {\n /** Accumulated server rows; page stays 1 and sort/filter remain server-owned. */\n mode: \"controlled-append\";\n state: MatrxDataTableQueryState;\n onStateChange: (next: MatrxDataTableQueryState) => void;\n pagination: MatrxTablePagination<T>;\n sourceProcessing?: MatrxDataTableSourceProcessing;\n /** Source-connected tables append on user scroll by default. */\n scroll?: { thresholdPx?: number; intentTimeoutMs?: number } & (\n | { mode?: \"scroll\" }\n | { mode: \"manual\"; reason: string; approvedBy: string }\n /** Temporary configuration state: automatic append is off, but Load more stays available. */\n | { mode: \"suspended\"; reason: string }\n );\n }\n | {\n /** Local rows, but every query control is owned by the caller (for URL state). */\n mode: \"controlled-local\";\n state: MatrxDataTableQueryState;\n onStateChange: (next: MatrxDataTableQueryState) => void;\n sourceProcessing?: MatrxDataTableSourceProcessing;\n }\n | {\n mode: \"controlled\";\n state: MatrxDataTableQueryState;\n /** Total rows matching the controlled query, not just `data.length`. */\n totalItems: number;\n onStateChange: (next: MatrxDataTableQueryState) => void;\n /** Declare source-owned query behavior before enabling source-aware controls. */\n sourceProcessing?: MatrxDataTableSourceProcessing;\n };\n\n/**\n * Toolbar facets — first-class, Mars-extensible filter controls above the grid.\n * Start with button-group; add radio / switch / complex later without forking.\n */\nexport type ToolbarFacet =\n | {\n type: \"button-group\";\n id: string;\n label?: string;\n value: string;\n /** Reset target for per-facet + global clear. Default: first option value. */\n defaultValue?: string;\n options: Array<{\n value: string;\n label: string;\n icon?: ReactNode;\n }>;\n onChange: (value: string) => void;\n }\n | {\n type: \"custom\";\n id: string;\n render: () => ReactNode;\n /** Declare filter state and its reset together so global Clear includes custom controls. */\n filter?: { active: boolean; onReset: () => void };\n };\n\n/**\n * Cross-column OR search — matches if ANY listed column contains the query.\n * Relationships use case: filter by entity type without picking source vs target.\n */\nexport interface AnyOfColumnSearch {\n columnIds: string[];\n placeholder?: string;\n /** Controlled value. Uncontrolled if omitted. */\n value?: string;\n onChange?: (value: string) => void;\n}\n\nexport interface MatrxDataTableToolbar {\n /** Optional human-readable table title, rendered by the shared title row. */\n title?: string;\n /** Global search across all accessor values. Default true. */\n search?: boolean;\n searchPlaceholder?: string;\n searchValue?: string;\n onSearchChange?: (value: string) => void;\n /**\n * Show a compact, visible choice between substring and whole-word search.\n * Omit it when a data source cannot honor both modes server-side.\n */\n searchMatch?: {\n defaultMode?: TableSearchMatchMode;\n };\n /** Optional field-scope controls. Local search always ranks all loaded fields before pagination. */\n intelligentSearch?: {\n roles?: Readonly<Record<string, TableSearchRole>>;\n scopes?: readonly TableSearchScope[];\n };\n /**\n * OR-search across specific columns (e.g. source_type OR target_type).\n * Shown as its own input beside global search when set.\n */\n anyOf?: AnyOfColumnSearch;\n /**\n * Optional compact advanced-filter builder beside the regular search. In\n * controlled mode rules live in `query.state.layeredFilters`; local tables\n * evaluate them against matching column ids.\n */\n layeredFilters?: {\n fields: readonly LayeredFilterField[];\n maxRules?: number;\n label?: string;\n };\n /** Extensible facet strip (button groups, later radios/switches/…). */\n facets?: ToolbarFacet[];\n /** Source controls in a separate wrapping row below the standard toolbar. */\n leading?: ReactNode;\n /**\n * Standard host refresh affordance. The package owns its placement, busy\n * state, accessible tooltip, and glyphs (refresh arrows at rest, spinner\n * only while busy); the host remains the sole data owner.\n */\n refresh?: {\n onRefresh: () => void | Promise<void>;\n label?: string;\n };\n /**\n * Standard create affordance. The host owns authorization and the actual\n * create flow; disabledReason is exposed through the shared tooltip.\n */\n add?: {\n onAdd: () => void | Promise<void>;\n disabled?: boolean;\n disabledReason?: string;\n };\n /** Right-side domain actions, rendered after standard table controls. */\n actions?: ReactNode;\n}\n\n/** Query fields safe to persist in a named table view. */\nexport type TableViewQuery = Omit<MatrxDataTableQueryState, \"page\">;\n\n/** Portable, persistence-neutral table state saved by a host adapter. */\nexport interface TableViewSnapshot {\n __kind: \"matrx-table-view\";\n version: 1;\n /** Page/cursor/selection/loaded rows are deliberately never saved. */\n query: TableViewQuery;\n /** `widths` is sparse so views saved before resize remain valid. */\n columns: { order: string[]; hidden: string[]; widths?: Record<string, number> };\n /**\n * The view's COLORS — color-by-a-column, rules, and manual highlights\n * (`./table-style.ts`). Optional so every view saved before colors existed\n * still parses; absent means \"this view paints nothing\".\n *\n * Colors belong to the VIEW and not to the data, for the same reason the\n * filters do: two people can read one table two ways, and neither reading is\n * a fact about the records. That is also why this snapshot is the only place\n * a style is carried — copy, export and agents read the rows, never this.\n */\n style?: TableStyle;\n}\n\n/** Host port for durable named views. The host owns actor, tenancy and CRUD. */\nexport interface TableSavedViewsProps {\n tableId: string;\n snapshot: TableViewSnapshot;\n defaultSnapshot: TableViewSnapshot;\n onApply: (snapshot: TableViewSnapshot) => void;\n}\n\n/** Optional deferred-apply copy for column filter popovers. */\nexport interface MatrxDataTableFilterUi {\n /** Enables the Include/Exclude control. Default false for remote-safe filters. */\n allowNegation?: boolean;\n /** Enables null/not-null text modes. Default false for remote-safe filters. */\n allowNullModes?: boolean;\n applyLabel?: string;\n showCancel?: boolean;\n footerHint?: ReactNode;\n}\n\nexport interface MatrxDataTableCopyConfig<T> {\n /** Explicit registered identities; never inferred from table labels or row shapes. */\n rowReferences?: (row: T) => readonly ContentTransferReference[];\n references?: (visible: T[], all: T[]) => readonly ContentTransferReference[];\n triggerVariant?: \"transparent\" | \"glass\" | \"outline\";\n /** Toast / tooltip label base, e.g. \"Relationship rule\". */\n label: string;\n listLabel?: string;\n location: string;\n rowKind: string;\n listKind: string;\n rowDescription?: string;\n listDescription?: string;\n humanRow: (row: T) => string;\n /** Project row for agent JSON. Default: full row. */\n agentRow?: (row: T) => unknown;\n rowAttributes?: (\n row: T,\n ) => Record<string, string | number | boolean | null | undefined>;\n listAttributes?: (\n visible: T[],\n all: T[],\n ) => Record<string, string | number | boolean | null | undefined>;\n /**\n * Live view state rendered inside the list payload's <context>. Unlike\n * per-row data, this remains present when the current view has zero rows.\n */\n listContext?: (\n visible: T[],\n all: T[],\n ) => Record<string, string | number | boolean | null | undefined>;\n /**\n * Additional row-scoped AI actions. Use this to fold a domain action such\n * as a paste-ready repair brief into the table-owned row copy menu instead\n * of rendering a separate third control. Builders run at click time.\n */\n rowAiVariants?: (row: T) => AiVariant[];\n /**\n * Graded AI variants for the toolbar's view copy (e.g. \"Top 25\", \"Summary\n * only\"). When set, the toolbar's Copy-for-AI upgrades to a dropdown with\n * these variants + the full-view payload as the automatic \"Everything\"\n * escape hatch. Receives (visible, all) rows at render; builders run at\n * click time.\n */\n aiVariants?: (visible: T[], all: T[]) => AiVariant[];\n /** Custom-preview source (options dialog + live size counts) for the view. */\n aiCustom?: (visible: T[], all: T[]) => AiCustomSource;\n /** Additional domain exports in the same canonical menu; builders run on demand. */\n export?: (visible: T[], all: T[]) => CopyExportConfig;\n /** Show toolbar copy (this view). Default true when copy is set. */\n showToolbar?: boolean;\n /** Show per-row copy. Default true when copy is set. */\n showRow?: boolean;\n}\n\nexport interface MatrxDataTableDetailConfig<T> {\n /** Side-panel title. Default: first string column or \"Details\". */\n title?: (row: T) => ReactNode;\n description?: (row: T) => ReactNode | undefined;\n /** Override the default key/value inspector. */\n render?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /** Header actions inside the side panel. */\n headerActions?: (row: T) => ReactNode;\n defaultWidth?: number;\n enabled?: boolean;\n /**\n * Maps a field name to the entity token its id points at, turning that field\n * into a door (route + peek) in the default inspector — side panel AND row\n * window.\n *\n * There is no default guess: this inspector renders whatever columns the row\n * has, and a wrong door opens a DIFFERENT record (`sch_run.task_id` is a\n * SCHEDULED task, not a workspace `task`). A table whose FKs you HAVE checked\n * can pass `tokenFromColumnName` (`components/official/entity-ref/doors`) to\n * open every `<token>_id` field at once.\n *\n * The ROW is passed too, because a table whose target type varies per row (an\n * audit log, an exposure report) cannot answer from the column name alone.\n */\n tokenForField?: (key: string, row: T) => string | null;\n}\n\n/** Actions a record-owned control can use without reaching into table state. */\nexport interface MatrxDataTableRecordControls {\n /** Close the row's side-panel detail, if open. */\n closeDetail: () => void;\n /** Open the row in the canonical adjustable side panel. */\n openDetail: () => void;\n /** Open the row in its canonical table-owned WindowPanel. */\n openWindow: () => void;\n /** Close the row's table-owned WindowPanel, if open. */\n closeWindow: () => void;\n /** Whether this row currently has visible, unpersisted inline edits. */\n hasPendingEdits: boolean;\n /**\n * Discard this row's pending inline edits.\n *\n * Row actions that persist the already-merged visible row (for example an\n * explicit Confirm action) call this only after that write succeeds. This\n * prevents the floating Save pill from later replaying the same draft as a\n * different, weaker write.\n */\n discardPendingEdits: () => void;\n beginEdit?: () => void;\n saveEdits?: () => void | Promise<void>;\n cancelEdits?: () => void;\n /** Whether this row's optional inline detail is expanded in the table. */\n isExpanded?: boolean;\n /** Toggle this row's optional inline detail. */\n toggleExpanded?: () => void;\n}\n\n/**\n * Controlled inline detail rendered immediately below its owning table row.\n *\n * Use this when detail belongs in the scan path of a dense table. The caller\n * owns which one row is expanded and supplies the domain-specific body; the\n * canonical renderer retains sorting, filtering, selection, row actions, and\n * the correct column span.\n */\nexport interface MatrxDataTableExpandedDetailConfig<T> {\n expandedId: string | null;\n onExpandedIdChange: (id: string | null) => void;\n canExpand?: (row: T) => boolean;\n render: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n className?: string;\n}\n\n/** Opaque host-owned row menu descriptor. The package never interprets it. */\nexport interface MatrxDataTableContextMenuConfig<T> {\n resolveRowContext: (row: T, controls: MatrxDataTableRecordControls) => unknown;\n}\n\nexport interface MatrxDataTableWindowConfig<T> {\n /** Window title. */\n title?: (row: T) => string;\n /**\n * @deprecated Prefer `renderView` + `renderEdit` so the window stays editable.\n * Full-body override with no View/Edit tabs.\n */\n render?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /** View tab body. Defaults to DataRowInspector. */\n renderView?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /**\n * Edit tab body. When set, the WindowPanel shows View / Edit sidebar tabs\n * (WindowPanel built-in sidebar). Defaults to `detail.render` when present.\n * Pass `false` to keep a view-only window even when `detail.render` exists.\n */\n renderEdit?:\n ((row: T, controls: MatrxDataTableRecordControls) => ReactNode) | false;\n /**\n * Called when the panel icon opens the window — hydrate edit state here\n * without opening the side panel (prefer this over `onRowOpen` for windows).\n */\n onOpen?: (row: T) => void;\n /**\n * Make full-row click open the WindowPanel instead of the side panel.\n * The trailing row action and the window header then expose the side panel\n * as the explicit secondary presentation. Default false.\n */\n openOnRowClick?: boolean;\n /** Which tab to open. Default: `\"edit\"` when an edit body exists. */\n defaultTab?: \"view\" | \"edit\";\n /** Show the panel-icon that opens the window. Default true when detail enabled. */\n enabled?: boolean;\n width?: number;\n height?: number;\n}\n\nexport interface MatrxDataTableEmptyState {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n}\n\n/** Pending cell edits keyed by row id → partial field map. */\nexport type CellEditsMap = Record<string, Record<string, unknown>>;\n\nexport interface MatrxDataTableEditConfig<T> {\n /** Enable inline editing for columns with `editable` set. */\n enabled?: boolean;\n /**\n * Persist all pending edits. Called when the user clicks Save on the dirty pill.\n * Return resolved when done; throw/reject to keep the draft (with toast).\n */\n onSave: (edits: CellEditsMap, rows: T[]) => void | Promise<void>;\n /** Persist each committed cell immediately. Failed writes remain in the\n * dirty pill so the user can retry or cancel them. */\n autoSave?: boolean;\n /** Optional cancel hook (draft already discarded). */\n onCancel?: () => void;\n}\n\nexport interface MatrxDataTableHierarchyConfig<T> {\n /** Complete hierarchy when `data` is only the current controlled page. */\n rows?: T[];\n getParentId: (row: T) => string | null;\n /** Persist the exact structural intent represented by the drop shadow. */\n onMove: (row: T, move: MatrxDataTableHierarchyMove) => void | Promise<void>;\n /** Enables sibling insertion shadows in addition to parent/root drops. */\n manualOrder?: boolean;\n canReparent?: (row: T) => boolean;\n itemLabel?: (row: T) => string;\n rootDropLabel?: string;\n}\n\nexport interface MatrxDataTableHierarchyMove {\n parentId: string | null;\n /**\n * Insert immediately before this sibling. Null means first child for\n * \"inside\"/\"root\", and LAST sibling for \"after\" (target had no successor).\n */\n beforeId: string | null;\n position: \"before\" | \"after\" | \"inside\" | \"root\";\n targetId: string | null;\n}\n\n/**\n * Multi-row selection — a leading checkbox column plus a bulk bar that appears\n * only while rows are checked.\n *\n * OPT-IN and fully CONTROLLED: the consumer owns the id set, so selection\n * survives (or is deliberately cleared by) a re-fetch, a filter change, or an\n * optimistic list update — the table never holds hidden selection state that\n * can disagree with the surface around it.\n *\n * Why this is a primitive and not a per-surface checkbox column: a register\n * the user cannot clear in bulk is a register they stop reading, and the fifth\n * hand-rolled selection column — each with its own shift-click, its own\n * select-all semantics, its own bar — is exactly the fork `components/official/`\n * exists to prevent.\n *\n * Selection does not alter mobile horizontal scrolling. The complete row moves\n * as one surface below `sm`; no leading column is allowed to pin over the data.\n */\nexport interface MatrxDataTableSelectionConfig<T> {\n /** Selected row ids (`getRowId`). Ids not on the current page are kept. */\n selectedIds: string[];\n onSelectedIdsChange: (ids: string[]) => void;\n /**\n * The bulk bar's actions, given the currently-selected rows THAT ARE LOADED.\n * Selection can outlive a page change, so also take `selectedIds` when an\n * action only needs ids.\n */\n actions?: (selected: T[], selectedIds: string[]) => ReactNode;\n /**\n * Rows that cannot be acted on in bulk render NO checkbox at all — the cell\n * stays for alignment, the control is absent from the DOM. A greyed control\n * still advertises a choice the surface underneath would refuse.\n */\n isRowSelectable?: (row: T) => boolean;\n /** Singular noun for the bar's count (\"finding\" → \"3 findings selected\"). */\n noun?: string;\n}\n\n/** Table-owned state and actions exposed to an opt-in phone card renderer. */\nexport interface MatrxDataTableMobileCardControls {\n /** Render a declared column with the same editable/draft/link behavior as the grid. */\n renderCell: (columnId: string) => ReactNode;\n /** Whether this row is in the canonical selection set. */\n selected: boolean;\n /** Whether the consumer allows this row to be selected. */\n selectable: boolean;\n /** Update selection through the table's controlled/URL-backed contract. */\n onSelectedChange: (selected: boolean) => void;\n /**\n * The table's canonical per-row copy controls plus consumer row actions.\n * Render this instead of rebuilding either action path inside the card.\n */\n actions: ReactNode;\n /** Optional inline-detail state for card renderers. */\n isExpanded?: boolean;\n /** Toggle this row's optional inline detail. */\n toggleExpanded?: () => void;\n /** Optional inline detail body; cards choose its placement. */\n expandedDetail: ReactNode;\n}\n\nexport type MatrxDataTableDensity = \"condensed\" | \"normal\" | \"spacious\";\n\n/**\n * What the table needs in order to paint a `TableStyle` itself.\n *\n * `fieldOf` exists because a style names DATA fields (\"tint the row when\n * `status` is Blocked\") while the table addresses COLUMNS. In the common case\n * the column id IS the field name and the default is right; a table whose\n * column ids are decorated (`records-grid-notes`) maps them here.\n */\nexport interface MatrxDataTableStyleConfig<T> {\n /** The style document. */\n style: TableStyle;\n /** The row's data as the rules read it. Defaults to the row itself. */\n documentOf?: (row: T) => Record<string, unknown>;\n /** Column id to the field name a rule would name. Defaults to identity. */\n fieldOf?: (columnId: string) => string;\n /**\n * The color a choice column's option paints with, for color-by. The consumer\n * supplies it because option colors live on the resolved option list, which\n * may come from a shared pick list this package cannot reach.\n */\n choiceColorFor?: ChoiceColorLookup;\n}\n\n/**\n * GROUPING — \"group these rows by Status\" with collapsible sections, a count\n * and a subtotal on each.\n *\n * This is the primitive's answer to the biggest gap against Airtable, Google\n * Sheets and Notion. It is opt-in, and it is CONTROLLED for the two pieces of\n * state a person expects to survive: which column groups, and which sections\n * are shut. Both are ordinary values, so a saved view carries them like any\n * other reading of the table.\n *\n * Grouping runs AFTER local processing and over the rows the table is about to\n * render. It never re-sorts inside a group: the sort the person chose is the\n * order they see.\n */\nexport interface MatrxDataTableGroupingConfig<T> {\n /** Column id to group by. `null` means grouping is available but off. */\n columnId: string | null;\n /** Emitted when the person picks a different column (or clears it). */\n onColumnIdChange?: ((columnId: string | null) => void) | undefined;\n /**\n * Columns offered in the group-by control. Defaults to every visible column\n * that is not an actions column. A column with thousands of distinct values\n * is a bad grouping and the consumer is the only one who knows that.\n */\n groupableColumnIds?: string[] | undefined;\n /** Per-column subtotal shown on every group header, keyed by column id. */\n aggregates?: Record<string, GroupAggregateSpec> | undefined;\n /** Controlled collapsed group keys (`TableGroup.key`). */\n collapsed?: string[] | undefined;\n onCollapsedChange?: ((collapsed: string[]) => void) | undefined;\n /** Section order. Default `\"value-asc\"`; blanks always sort last. */\n order?: GroupOrder | undefined;\n /** Label for the section of rows whose grouping value is blank. */\n emptyLabel?: string | undefined;\n /** Singular noun for a group's summary (\"job\" → \"12 jobs\"). */\n rowNoun?: string | undefined;\n /**\n * Reads the grouping value for a column. Defaults to the column's own\n * accessor.\n *\n * NOT named `valueOf`: every object in JavaScript already has one, so an\n * optional config field by that name is always \"present\" and a `config.valueOf\n * ? … : …` check silently calls `Object.prototype.valueOf` and groups every\n * row under the config object itself. It did exactly that here once.\n */\n readCell?: ((row: T, columnId: string) => unknown) | undefined;\n}\n\n/**\n * VIRTUALIZATION — render the rows a person can actually see.\n *\n * Off by default, because most tables in the product are short and a windowed\n * body costs a scroll container contract that a short table does not need.\n * On, it engages only past `threshold` rows, so a 40-row table behaves exactly\n * as it always did and a 100,000-row table scrolls.\n */\nexport interface MatrxDataTableVirtualizeConfig {\n enabled?: boolean | undefined;\n /** Row height in px used for the window math. Default follows density. */\n rowHeight?: number | undefined;\n /** Rows rendered beyond each edge of the viewport. Default 8. */\n overscan?: number | undefined;\n /** Row count below which the body renders whole. Default 150. */\n threshold?: number | undefined;\n /**\n * Window the COLUMNS too, for tables hundreds of columns wide. Frozen\n * columns are always rendered, never windowed out.\n */\n columns?: boolean | undefined;\n /** Column width in px used for the horizontal window. Default 160. */\n columnWidth?: number | undefined;\n}\n\n/**\n * THE HONEST COUNT.\n *\n * A table that filters over part of its rows and says nothing is a screen that\n * lies. Declare what the answer actually covers and the table states it, in\n * plain English, above the rows — and never presents a partial count as exact.\n *\n * Omit this entirely and the table says nothing, which is correct for a table\n * whose rows are all in hand.\n */\nexport interface MatrxDataTableCoverageConfig {\n /** Rows the source says match the current query, when it can say. */\n matched?: number | undefined;\n /** Rows in the table irrespective of the query, when known. */\n total?: number | undefined;\n /** A ceiling the source imposed on this answer (a fetch cap). */\n cap?: number | undefined;\n /** Who answered the filters and sort. Default `\"client\"`. */\n answeredBy?: \"source\" | \"client\" | undefined;\n /** Singular noun for the sentence. Default \"row\". */\n noun?: string | undefined;\n}\n\n/**\n * SPREADSHEET GESTURES — range selection, Excel paste, fill, undo.\n *\n * The `/data` grid had all of this and nothing else in the product could reach\n * it. Opt-in, because a reference table with one editable column does not want\n * a cell cursor; a table a person works IN does.\n *\n * The table owns the gestures and the selection rectangle; the CONSUMER owns\n * the writes. Every mutation arrives as a list of `CellPatch`, so the caller\n * applies it through the door it already has and the table never invents a\n * persistence path of its own. That is also what makes undo honest: the stack\n * holds before-values, and an undo is just another patch list.\n */\nexport interface MatrxDataTableSpreadsheetConfig<T> {\n enabled?: boolean | undefined;\n /**\n * Apply cell writes. Called for a paste, a fill-down, a fill-handle drag, a\n * clear, an undo and a redo. The table re-reads from `data`, so a write the\n * caller refuses simply never appears.\n */\n onPatch: (patches: CellPatch[], intent: SpreadsheetIntent) => void | Promise<void>;\n /** Column ids a patch may target. Defaults to every column marked `editable`. */\n writableColumnIds?: string[] | undefined;\n /** Why a column refuses a write, as a sentence a person can act on. */\n columnRefusal?: ((columnId: string) => string | undefined) | undefined;\n /** Reads a cell for copy and fill. Defaults to the column's own accessor. */\n readCell?: ((row: T, columnId: string) => unknown) | undefined;\n /** The little square at the corner of a selection. Default true. */\n fillHandle?: boolean | undefined;\n /** Paste real Excel/Sheets TSV into the selection. Default true. */\n paste?: boolean | undefined;\n /** Undo depth. 0 turns undo off. Default 100. */\n undoDepth?: number | undefined;\n /** Says what just happened, and why something did not. */\n notify?: ((message: string) => void) | undefined;\n}\n\nexport interface MatrxDataTableProps<T> {\n data: T[];\n columns: MatrxColumnDef<T>[];\n /** Table-local visual density. An explicit value wins over the host default. */\n density?: MatrxDataTableDensity;\n /** Stable persistence identity for host-installed named views. */\n tableId?: string;\n /** Declared reset state for the host Saved Views control. */\n defaultViewSnapshot?: TableViewSnapshot;\n /** Working view tabs, on by default. Disable for embedded/single-purpose tables. */\n viewTabs?: boolean;\n /** Related-data controls shown at the right of the working-view tab strip. */\n relatedTableActions?: ReactNode;\n /** Optional URL/source-owned column order and visibility. */\n columnState?: {\n order: string[];\n hidden: string[];\n onChange: (next: { order: string[]; hidden: string[] }) => void;\n /** Optional source-owned desktop widths keyed by canonical column id. */\n widths?: Record<string, number>;\n /** Required to persist controlled width changes across remounts. */\n onWidthsChange?: (widths: Record<string, number>) => void;\n };\n /** Enables package-owned drag reordering in table headers. Default true. */\n reorderableColumns?: boolean;\n /** Enables desktop column resizing. Default true; handles are absent below `sm`. */\n resizableColumns?: boolean;\n columnManager?: { open: boolean; onOpenChange: (open: boolean) => void };\n getRowId: (row: T) => string;\n /**\n * Additional row identity included in local global search without becoming\n * a visible/sortable/filterable column. Use for canonical composite keys or\n * aliases whose displayed parts live in separate columns.\n *\n * Ignored in remote controlled mode, where the query owner applies search.\n */\n searchText?: (row: T) => string;\n /**\n * Hierarchy-aware local processing seam. The canonical table still owns the\n * toolbar, URL state, headers, pagination, editing, copy, and rendering; the\n * consumer only preserves domain ordering that a flat sort would destroy.\n *\n * The processor must honor every active query control in `state` and return\n * the complete filtered/sorted local result before pagination. It is ignored\n * in remote controlled mode, where `data` is already the queried page.\n */\n processLocalRows?: (rows: T[], state: MatrxDataTableQueryState) => T[];\n /** Initial local sort when neither URL state nor a controlled query owns it. */\n defaultSort?: SortState | null;\n isLoading?: boolean;\n /**\n * Background refresh state. Unlike `isLoading`, this preserves rendered rows\n * and shows only the table's non-blocking refresh indicator.\n */\n isFetching?: boolean;\n /**\n * Controlled query state for direct remote data sources. The component never\n * fetches data itself; it only emits state changes to the caller.\n */\n query?: MatrxDataTableQueryControl<T>;\n /**\n * Persist local query and record-view state in namespaced URL parameters.\n * This is intentionally opt-in and cannot be combined with controlled query\n * mode; remote tables use `useTableUrlState({ tableId })` in their query owner.\n */\n urlState?: MatrxDataTableUrlStateConfig;\n\n toolbar?: MatrxDataTableToolbar;\n /** Hide the package toolbar while retaining table-owned query behavior. */\n hideToolbar?: boolean;\n /** Hide numbered pagination; local rows remain available to the caller. */\n hidePagination?: boolean;\n /** Optional deferred Apply/Cancel copy for column filter popovers. */\n filterUi?: MatrxDataTableFilterUi;\n /** Row click opens the side panel unless `window.openOnRowClick` is true. */\n detail?: MatrxDataTableDetailConfig<T>;\n /** Controlled detail rendered directly below a table row. */\n expandedDetail?: MatrxDataTableExpandedDetailConfig<T>;\n /** Panel icon opens a WindowPanel (page-local; supports ReactNode override). */\n window?: MatrxDataTableWindowConfig<T>;\n /** Copy + Copy for AI (rows + this view). Omit for the declared-column default; false suppresses it. */\n copy?: false | MatrxDataTableCopyConfig<T>;\n /** Where canonical per-row Alchemy appears. Inline preserves current behavior. */\n rowCopyPlacement?: \"inline\" | \"menu\";\n /** One pane-level right-click resolver; host owns the opaque descriptor. */\n contextMenu?: MatrxDataTableContextMenuConfig<T>;\n /** Inline edit session with floating Save/Cancel pill. */\n edit?: MatrxDataTableEditConfig<T>;\n /** Opt-in tree reparenting owned by the canonical row renderer. */\n hierarchy?: MatrxDataTableHierarchyConfig<T>;\n\n /** Controlled selection (selected row id for highlight). */\n selectedId?: string | null | undefined;\n onSelectedIdChange?: ((id: string | null) => void) | undefined;\n\n /** Controlled table-owned window row (normally supplied by URL state). */\n windowRowId?: string | null | undefined;\n onWindowRowIdChange?: ((id: string | null) => void) | undefined;\n\n /** Opt-in multi-row checkbox selection + a bulk action bar. */\n selection?: MatrxDataTableSelectionConfig<T>;\n\n /** Extra row actions rendered in a trailing Actions column. */\n rowActions?: (row: T, controls: MatrxDataTableRecordControls) => ReactNode;\n /** Extra rows rendered in the table footer with the same columns as data rows. */\n footerRows?: T[];\n /** Per-row presentation class, after canonical selection/highlight state. */\n rowClassName?: (row: T, index: number) => string | undefined;\n /**\n * Per-CELL presentation class, after the column's own static `className`.\n *\n * The primitive had `rowClassName` and a static per-column `className`, and\n * nothing in between — so every table that needed to say something about ONE\n * cell had to grow a `cell` render function and paint inside it, where the\n * table can no longer see it. This is the missing rung, and `tableStyle`\n * below is the declarative thing built on top of it.\n */\n cellClassName?: (\n row: T,\n columnId: string,\n index: number,\n ) => string | undefined;\n /**\n * COLORS, declaratively — color by a column, rules, manual highlights.\n *\n * The model is `./table-style.ts`: one document, evaluated live on every\n * render so a tint is never stale, with a documented precedence between the\n * three ways a cell can earn a color. Pass the document and the table paints\n * itself; the consumer owns only where that document is STORED and how a\n * person edits it.\n */\n tableStyle?: MatrxDataTableStyleConfig<T>;\n /**\n * GROUPS — collapsible sections by a column, with a count and per-column\n * subtotals on each header. Opt-in and controlled; see the config's own doc.\n */\n grouping?: MatrxDataTableGroupingConfig<T>;\n /** Render only the rows in view. Opt-in; engages past its own threshold. */\n virtualize?: boolean | MatrxDataTableVirtualizeConfig;\n /**\n * What the current answer actually covers. Declare it and the table says so\n * on screen when a filter is answering over part of the table, instead of\n * presenting a partial count as the whole truth.\n */\n coverage?: MatrxDataTableCoverageConfig;\n /** Range selection, Excel paste, fill handle, fill-down, undo. Opt-in. */\n spreadsheet?: MatrxDataTableSpreadsheetConfig<T>;\n /**\n * Explicit body row height in px. Overrides the density default. Use it when\n * a saved view remembers a row height the person chose.\n */\n rowHeight?: number;\n /** Highlight an index in the currently rendered page without selecting it. */\n highlightedIndex?: number | undefined;\n /** Receives the complete visible local view after local processing, before paging. Attaching after absence receives one snapshot; callback identity changes alone do not notify. */\n onViewChange?: (rows: T[]) => void;\n /**\n * Wrap the whole `<tr>`. Return `children` unchanged for no-op.\n *\n * `rowActions` only reaches the actions CELL, so anything that must own the\n * entire row — a right-click menu, a drag handle, a drop target — had no\n * seam and would have forced a surface to fork the table. THE INVENTORY LAW:\n * the fork is the defect, so the seam exists instead.\n *\n * Whatever you return must render `children` as a direct `<tbody>` child, so\n * the wrapper has to be a component that emits the `<tr>` unchanged\n * (`ItemContextMenu` does — it renders a Radix trigger with `asChild`).\n */\n rowWrapper?: (row: T, children: ReactNode) => ReactNode;\n\n emptyState?: MatrxDataTableEmptyState;\n /** Default 25. Pass 0 to show all. */\n pageSize?: number;\n /** Complete local results reveal on physical scroll by default. Numbered\n * paging is an explicit exception requiring its reason and approving owner. */\n localPagination?:\n | { mode?: \"progressive\" }\n | { mode: \"numbered\"; reason: string; approvedBy: string };\n pageSizeOptions?: number[];\n zebra?: boolean;\n /**\n * Keep the one semantic table header visible while its nearest vertical\n * scroll surface moves. Defaults to true. Set false only where surrounding\n * chrome already supplies the column labels.\n */\n stickyHeader?: boolean;\n className?: string;\n tableClassName?: string;\n /**\n * Optional compact row presentation rendered below `mobileCardsBreakpoint`\n * in place of the horizontal table. The caller supplies the record summary\n * because only the product surface knows which values and actions are\n * essential at narrow widths; MatrxDataTable still owns query state,\n * loading/empty states, and pagination.\n *\n * `controls.actions` carries the table-owned copy controls and consumer row\n * actions, so a card does not fork them. Prefer the default horizontal table\n * unless the product explicitly requires every essential value/action to be\n * discoverable without horizontal scrolling.\n */\n mobileCards?: (\n row: T,\n index: number,\n controls: MatrxDataTableMobileCardControls,\n ) => ReactNode;\n /** Card/table handoff. `sm` is phone-only; `lg` includes portrait tablets. */\n mobileCardsBreakpoint?: \"sm\" | \"lg\";\n /**\n * Resolves a record's canonical destination for modified row gestures.\n * Cmd/Ctrl/Shift-click and middle-click open this URL in a new browsing\n * context; an unresolved URL deliberately does not fall back to row open.\n * Plain row clicks retain the table's detail/window/onRowOpen behavior.\n */\n getRowHref?: (row: T) => string | undefined;\n /** Called after a row is selected for detail (in addition to opening the panel). */\n onRowOpen?: (row: T) => void;\n}\n\n/** Re-export for callers building custom agent payloads. */\nexport type { AgentPayloadInput };\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -1,7 +1,11 @@
1
1
  import '../content-transfer.cjs';
2
2
  import 'react';
3
- export { A as AnyOfColumnSearch, n as CellEditType, o as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, j as ColumnMarker, M as MatrxColumnDef, t as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, d as MatrxDataTableDensity, u as MatrxDataTableDetailConfig, v as MatrxDataTableEditConfig, w as MatrxDataTableEmptyState, x as MatrxDataTableExpandedDetailConfig, y as MatrxDataTableFilterUi, z as MatrxDataTableHierarchyConfig, D as MatrxDataTableHierarchyMove, F as MatrxDataTableMobileCardControls, h as MatrxDataTableProps, G as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, c as MatrxDataTableRecordControls, H as MatrxDataTableSelectionConfig, I as MatrxDataTableSourceProcessing, J as MatrxDataTableStyleConfig, K as MatrxDataTableToolbar, N as MatrxDataTableUrlStateConfig, O as MatrxDataTableWindowConfig, P as MatrxTablePagination, X as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, a5 as TableViewQuery, i as TableViewSnapshot, a6 as TextFilterMode, a7 as ToolbarFacet } from '../types-Sn249eoy.cjs';
3
+ export { A as AnyOfColumnSearch, n as CellEditType, o as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, j as ColumnMarker, M as MatrxColumnDef, t as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, u as MatrxDataTableCoverageConfig, d as MatrxDataTableDensity, v as MatrxDataTableDetailConfig, w as MatrxDataTableEditConfig, x as MatrxDataTableEmptyState, y as MatrxDataTableExpandedDetailConfig, z as MatrxDataTableFilterUi, D as MatrxDataTableGroupingConfig, F as MatrxDataTableHierarchyConfig, G as MatrxDataTableHierarchyMove, H as MatrxDataTableMobileCardControls, h as MatrxDataTableProps, I as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, c as MatrxDataTableRecordControls, J as MatrxDataTableSelectionConfig, K as MatrxDataTableSourceProcessing, N as MatrxDataTableSpreadsheetConfig, O as MatrxDataTableStyleConfig, P as MatrxDataTableToolbar, Q as MatrxDataTableUrlStateConfig, R as MatrxDataTableVirtualizeConfig, U as MatrxDataTableWindowConfig, V as MatrxTablePagination, $ as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, a9 as TableViewQuery, i as TableViewSnapshot, aa as TextFilterMode, ab as ToolbarFacet } from '../types-CmQrOz-T.cjs';
4
4
  export { AgentPayloadInput } from './copy-types.cjs';
5
- import '../types-BdbvlF9-.cjs';
5
+ import '../types-CJWU1Gae.cjs';
6
+ import './grouping.cjs';
7
+ import './fill.cjs';
8
+ import './spreadsheet.cjs';
6
9
  import '@ai-matrx/kit/content-transfer';
7
10
  import '../content-transfer/icon.cjs';
11
+ import './grid-selection.cjs';
@@ -1,7 +1,11 @@
1
1
  import '../content-transfer.js';
2
2
  import 'react';
3
- export { A as AnyOfColumnSearch, n as CellEditType, o as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, j as ColumnMarker, M as MatrxColumnDef, t as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, d as MatrxDataTableDensity, u as MatrxDataTableDetailConfig, v as MatrxDataTableEditConfig, w as MatrxDataTableEmptyState, x as MatrxDataTableExpandedDetailConfig, y as MatrxDataTableFilterUi, z as MatrxDataTableHierarchyConfig, D as MatrxDataTableHierarchyMove, F as MatrxDataTableMobileCardControls, h as MatrxDataTableProps, G as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, c as MatrxDataTableRecordControls, H as MatrxDataTableSelectionConfig, I as MatrxDataTableSourceProcessing, J as MatrxDataTableStyleConfig, K as MatrxDataTableToolbar, N as MatrxDataTableUrlStateConfig, O as MatrxDataTableWindowConfig, P as MatrxTablePagination, X as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, a5 as TableViewQuery, i as TableViewSnapshot, a6 as TextFilterMode, a7 as ToolbarFacet } from '../types-D-flTpK0.js';
3
+ export { A as AnyOfColumnSearch, n as CellEditType, o as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, j as ColumnMarker, M as MatrxColumnDef, t as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, u as MatrxDataTableCoverageConfig, d as MatrxDataTableDensity, v as MatrxDataTableDetailConfig, w as MatrxDataTableEditConfig, x as MatrxDataTableEmptyState, y as MatrxDataTableExpandedDetailConfig, z as MatrxDataTableFilterUi, D as MatrxDataTableGroupingConfig, F as MatrxDataTableHierarchyConfig, G as MatrxDataTableHierarchyMove, H as MatrxDataTableMobileCardControls, h as MatrxDataTableProps, I as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, c as MatrxDataTableRecordControls, J as MatrxDataTableSelectionConfig, K as MatrxDataTableSourceProcessing, N as MatrxDataTableSpreadsheetConfig, O as MatrxDataTableStyleConfig, P as MatrxDataTableToolbar, Q as MatrxDataTableUrlStateConfig, R as MatrxDataTableVirtualizeConfig, U as MatrxDataTableWindowConfig, V as MatrxTablePagination, $ as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, a9 as TableViewQuery, i as TableViewSnapshot, aa as TextFilterMode, ab as ToolbarFacet } from '../types-nlLCMiqd.js';
4
4
  export { AgentPayloadInput } from './copy-types.js';
5
- import '../types-BdbvlF9-.js';
5
+ import '../types-CJWU1Gae.js';
6
+ import './grouping.js';
7
+ import './fill.js';
8
+ import './spreadsheet.js';
6
9
  import '@ai-matrx/kit/content-transfer';
7
10
  import '../content-transfer/icon.js';
11
+ import './grid-selection.js';
@@ -1,11 +1,15 @@
1
- import { S as SortState, e as MatrxDataTableQueryState } from '../types-Sn249eoy.cjs';
1
+ import { S as SortState, e as MatrxDataTableQueryState } from '../types-CmQrOz-T.cjs';
2
2
  import { UrlHistoryMode } from '@ai-matrx/kit/url-state';
3
3
  import '../content-transfer.cjs';
4
4
  import 'react';
5
5
  import '@ai-matrx/kit/content-transfer';
6
6
  import '../content-transfer/icon.cjs';
7
7
  import './copy-types.cjs';
8
- import '../types-BdbvlF9-.cjs';
8
+ import '../types-CJWU1Gae.cjs';
9
+ import './grouping.cjs';
10
+ import './fill.cjs';
11
+ import './spreadsheet.cjs';
12
+ import './grid-selection.cjs';
9
13
 
10
14
  interface UseTableUrlStateOptions {
11
15
  /** Stable table id used in `table.<id>.*` parameters. */
@@ -1,11 +1,15 @@
1
- import { S as SortState, e as MatrxDataTableQueryState } from '../types-D-flTpK0.js';
1
+ import { S as SortState, e as MatrxDataTableQueryState } from '../types-nlLCMiqd.js';
2
2
  import { UrlHistoryMode } from '@ai-matrx/kit/url-state';
3
3
  import '../content-transfer.js';
4
4
  import 'react';
5
5
  import '@ai-matrx/kit/content-transfer';
6
6
  import '../content-transfer/icon.js';
7
7
  import './copy-types.js';
8
- import '../types-BdbvlF9-.js';
8
+ import '../types-CJWU1Gae.js';
9
+ import './grouping.js';
10
+ import './fill.js';
11
+ import './spreadsheet.js';
12
+ import './grid-selection.js';
9
13
 
10
14
  interface UseTableUrlStateOptions {
11
15
  /** Stable table id used in `table.<id>.*` parameters. */