@ai-matrx/design-system 0.21.14 → 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.
- package/CHANGELOG.md +188 -0
- package/dist/data-table/cell-undo.cjs +98 -0
- package/dist/data-table/cell-undo.cjs.map +1 -0
- package/dist/data-table/cell-undo.d.cts +85 -0
- package/dist/data-table/cell-undo.d.ts +85 -0
- package/dist/data-table/cell-undo.js +78 -0
- package/dist/data-table/cell-undo.js.map +1 -0
- package/dist/data-table/copy-helpers.d.cts +6 -1
- package/dist/data-table/copy-helpers.d.ts +6 -1
- package/dist/data-table/coverage.cjs +116 -0
- package/dist/data-table/coverage.cjs.map +1 -0
- package/dist/data-table/coverage.d.cts +65 -0
- package/dist/data-table/coverage.d.ts +65 -0
- package/dist/data-table/coverage.js +96 -0
- package/dist/data-table/coverage.js.map +1 -0
- package/dist/data-table/fill.cjs +299 -0
- package/dist/data-table/fill.cjs.map +1 -0
- package/dist/data-table/fill.d.cts +129 -0
- package/dist/data-table/fill.d.ts +129 -0
- package/dist/data-table/fill.js +277 -0
- package/dist/data-table/fill.js.map +1 -0
- package/dist/data-table/filter-engine.d.cts +6 -1
- package/dist/data-table/filter-engine.d.ts +6 -1
- package/dist/data-table/grid-clipboard.cjs +165 -0
- package/dist/data-table/grid-clipboard.cjs.map +1 -0
- package/dist/data-table/grid-clipboard.d.cts +85 -0
- package/dist/data-table/grid-clipboard.d.ts +85 -0
- package/dist/data-table/grid-clipboard.js +145 -0
- package/dist/data-table/grid-clipboard.js.map +1 -0
- package/dist/data-table/grid-selection.cjs +258 -0
- package/dist/data-table/grid-selection.cjs.map +1 -0
- package/dist/data-table/grid-selection.d.cts +166 -0
- package/dist/data-table/grid-selection.d.ts +166 -0
- package/dist/data-table/grid-selection.js +238 -0
- package/dist/data-table/grid-selection.js.map +1 -0
- package/dist/data-table/grouping.cjs +302 -0
- package/dist/data-table/grouping.cjs.map +1 -0
- package/dist/data-table/grouping.d.cts +119 -0
- package/dist/data-table/grouping.d.ts +119 -0
- package/dist/data-table/grouping.js +282 -0
- package/dist/data-table/grouping.js.map +1 -0
- package/dist/data-table/host.d.cts +6 -1
- package/dist/data-table/host.d.ts +6 -1
- package/dist/data-table/index.cjs +4120 -806
- package/dist/data-table/index.cjs.map +1 -1
- package/dist/data-table/index.d.cts +99 -3
- package/dist/data-table/index.d.ts +99 -3
- package/dist/data-table/index.js +3971 -653
- package/dist/data-table/index.js.map +1 -1
- package/dist/data-table/infer-filter.d.cts +6 -1
- package/dist/data-table/infer-filter.d.ts +6 -1
- package/dist/data-table/layered-filters.d.cts +6 -1
- package/dist/data-table/layered-filters.d.ts +6 -1
- package/dist/data-table/query-control.d.cts +6 -1
- package/dist/data-table/query-control.d.ts +6 -1
- package/dist/data-table/spreadsheet.cjs +1014 -0
- package/dist/data-table/spreadsheet.cjs.map +1 -0
- package/dist/data-table/spreadsheet.d.cts +118 -0
- package/dist/data-table/spreadsheet.d.ts +118 -0
- package/dist/data-table/spreadsheet.js +994 -0
- package/dist/data-table/spreadsheet.js.map +1 -0
- package/dist/data-table/types.cjs.map +1 -1
- package/dist/data-table/types.d.cts +6 -1
- package/dist/data-table/types.d.ts +6 -1
- package/dist/data-table/url-state.d.cts +6 -1
- package/dist/data-table/url-state.d.ts +6 -1
- package/dist/data-table/validation.cjs +229 -0
- package/dist/data-table/validation.cjs.map +1 -0
- package/dist/data-table/validation.d.cts +125 -0
- package/dist/data-table/validation.d.ts +125 -0
- package/dist/data-table/validation.js +209 -0
- package/dist/data-table/validation.js.map +1 -0
- package/dist/data-table/virtual-window.cjs +203 -0
- package/dist/data-table/virtual-window.cjs.map +1 -0
- package/dist/data-table/virtual-window.d.cts +84 -0
- package/dist/data-table/virtual-window.d.ts +84 -0
- package/dist/data-table/virtual-window.js +183 -0
- package/dist/data-table/virtual-window.js.map +1 -0
- package/dist/data-table/xlsx.d.cts +6 -1
- package/dist/data-table/xlsx.d.ts +6 -1
- package/dist/field-formats/index.cjs +741 -0
- package/dist/field-formats/index.cjs.map +1 -0
- package/dist/field-formats/index.d.cts +45 -0
- package/dist/field-formats/index.d.ts +45 -0
- package/dist/field-formats/index.js +723 -0
- package/dist/field-formats/index.js.map +1 -0
- package/dist/formulas/index.cjs +1041 -0
- package/dist/formulas/index.cjs.map +1 -0
- package/dist/formulas/index.d.cts +389 -0
- package/dist/formulas/index.d.ts +389 -0
- package/dist/formulas/index.js +1019 -0
- package/dist/formulas/index.js.map +1 -0
- package/dist/types-CJWU1Gae.d.cts +204 -0
- package/dist/types-CJWU1Gae.d.ts +204 -0
- package/dist/{types-BgqIKbhq.d.cts → types-CmQrOz-T.d.cts} +382 -1
- package/dist/{types-emLxze_m.d.ts → types-nlLCMiqd.d.ts} +382 -1
- package/package.json +119 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CellAddress } from './grid-selection.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clipboard model for the user-data-table grid.
|
|
5
|
+
* @ai-matrx/design-system/data-table/grid-clipboard
|
|
6
|
+
*
|
|
7
|
+
* This moved here from the app so every table in the product shares one
|
|
8
|
+
* range-selection model.
|
|
9
|
+
*
|
|
10
|
+
* WHAT A SPREADSHEET PUTS ON THE CLIPBOARD. Excel, Google Sheets and Numbers
|
|
11
|
+
* all copy a range as TAB-separated rows, one line per row, and quote any cell
|
|
12
|
+
* whose text contains a tab, a line break or a double quote (doubling the
|
|
13
|
+
* quotes inside). That is also what they ACCEPT on paste. Speaking exactly
|
|
14
|
+
* that dialect is what makes "copy three columns in Sheets, click a cell here,
|
|
15
|
+
* Cmd-V" land as a block instead of as one cell holding a wall of text — and
|
|
16
|
+
* what makes the reverse trip (copy here, paste into Sheets) split into cells.
|
|
17
|
+
*
|
|
18
|
+
* WHY THE PLAN IS A PURE FUNCTION. Pasting a block is a write to many cells at
|
|
19
|
+
* once, and "which cells" is the part that goes wrong: off by one column, the
|
|
20
|
+
* anchor row filtered out, a block wider than the grid. Computing the landing
|
|
21
|
+
* cells here, with no React and no DOM, is what lets that mapping be tested
|
|
22
|
+
* cell by cell — and lets the caller decide separately what to do with the
|
|
23
|
+
* rows that did not fit.
|
|
24
|
+
*
|
|
25
|
+
* Addresses are (rowId, fieldName), like the rest of the grid — never indices.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** One cell as text for the clipboard. Objects go out as JSON. */
|
|
29
|
+
declare function cellClipboardText(value: unknown): string;
|
|
30
|
+
/** A rectangular block of cells as spreadsheet TSV. */
|
|
31
|
+
declare function gridToTsv(block: readonly (readonly unknown[])[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Parse clipboard text as a spreadsheet block.
|
|
34
|
+
*
|
|
35
|
+
* Handles the Excel quoting dialect (a quoted cell may span lines and carries
|
|
36
|
+
* `""` for a literal quote), both `\n` and `\r\n` line endings, and the single
|
|
37
|
+
* trailing line break Excel and Sheets append to every copy — without that
|
|
38
|
+
* strip, every paste would be one empty row taller than what was copied.
|
|
39
|
+
*
|
|
40
|
+
* Plain text with no tabs and no quotes still parses: one column, one row per
|
|
41
|
+
* line, which is exactly what Sheets does with the same text. A single value
|
|
42
|
+
* with no line break is a 1×1 block.
|
|
43
|
+
*/
|
|
44
|
+
declare function parseClipboardGrid(text: string): string[][];
|
|
45
|
+
/** One landing cell of a paste, with the raw text that goes into it. */
|
|
46
|
+
type PasteCell = CellAddress & {
|
|
47
|
+
raw: string;
|
|
48
|
+
};
|
|
49
|
+
type PastePlan = {
|
|
50
|
+
/** Cells inside the grid, in reading order (row by row). */
|
|
51
|
+
cells: PasteCell[];
|
|
52
|
+
/**
|
|
53
|
+
* Block rows below the last grid row, trimmed to the columns that fit from
|
|
54
|
+
* the anchor column rightward. The caller decides whether these become new
|
|
55
|
+
* rows — the plan only reports them so nothing is dropped silently.
|
|
56
|
+
*/
|
|
57
|
+
overflowRows: string[][];
|
|
58
|
+
/** How many block columns fell off the right edge (per row, at most). */
|
|
59
|
+
clippedColumns: number;
|
|
60
|
+
/** The columns (field names) the block spans, anchor first. */
|
|
61
|
+
fieldNames: string[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Where does `block` land when pasted with its top-left cell at `anchor`?
|
|
65
|
+
*
|
|
66
|
+
* Rows extend downward from the anchor row and columns rightward from the
|
|
67
|
+
* anchor column, in the GRID's current order (so a hidden or re-ordered column
|
|
68
|
+
* is honoured — you paste into what you see). A block wider than the remaining
|
|
69
|
+
* columns is clipped on the right; a block taller than the remaining rows is
|
|
70
|
+
* reported as `overflowRows` rather than lost.
|
|
71
|
+
*
|
|
72
|
+
* Returns null when the anchor is not on the grid — the row was filtered out
|
|
73
|
+
* or the column hidden since the selection was made. Writing "somewhere near"
|
|
74
|
+
* is worse than refusing.
|
|
75
|
+
*/
|
|
76
|
+
declare function planPaste(anchor: CellAddress, block: readonly (readonly string[])[], rowIds: readonly string[], fieldNames: readonly string[]): PastePlan | null;
|
|
77
|
+
/** True when the parsed block is a single value — the common Cmd-V case. */
|
|
78
|
+
declare function isSingleCell(block: readonly (readonly string[])[]): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Would writing `next` over `prior` change what is stored? Objects compare by
|
|
81
|
+
* their JSON, the same test the inline editor uses to skip a no-op write.
|
|
82
|
+
*/
|
|
83
|
+
declare function storedValuesEqual(a: unknown, b: unknown): boolean;
|
|
84
|
+
|
|
85
|
+
export { type PasteCell, type PastePlan, cellClipboardText, gridToTsv, isSingleCell, parseClipboardGrid, planPaste, storedValuesEqual };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CellAddress } from './grid-selection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clipboard model for the user-data-table grid.
|
|
5
|
+
* @ai-matrx/design-system/data-table/grid-clipboard
|
|
6
|
+
*
|
|
7
|
+
* This moved here from the app so every table in the product shares one
|
|
8
|
+
* range-selection model.
|
|
9
|
+
*
|
|
10
|
+
* WHAT A SPREADSHEET PUTS ON THE CLIPBOARD. Excel, Google Sheets and Numbers
|
|
11
|
+
* all copy a range as TAB-separated rows, one line per row, and quote any cell
|
|
12
|
+
* whose text contains a tab, a line break or a double quote (doubling the
|
|
13
|
+
* quotes inside). That is also what they ACCEPT on paste. Speaking exactly
|
|
14
|
+
* that dialect is what makes "copy three columns in Sheets, click a cell here,
|
|
15
|
+
* Cmd-V" land as a block instead of as one cell holding a wall of text — and
|
|
16
|
+
* what makes the reverse trip (copy here, paste into Sheets) split into cells.
|
|
17
|
+
*
|
|
18
|
+
* WHY THE PLAN IS A PURE FUNCTION. Pasting a block is a write to many cells at
|
|
19
|
+
* once, and "which cells" is the part that goes wrong: off by one column, the
|
|
20
|
+
* anchor row filtered out, a block wider than the grid. Computing the landing
|
|
21
|
+
* cells here, with no React and no DOM, is what lets that mapping be tested
|
|
22
|
+
* cell by cell — and lets the caller decide separately what to do with the
|
|
23
|
+
* rows that did not fit.
|
|
24
|
+
*
|
|
25
|
+
* Addresses are (rowId, fieldName), like the rest of the grid — never indices.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** One cell as text for the clipboard. Objects go out as JSON. */
|
|
29
|
+
declare function cellClipboardText(value: unknown): string;
|
|
30
|
+
/** A rectangular block of cells as spreadsheet TSV. */
|
|
31
|
+
declare function gridToTsv(block: readonly (readonly unknown[])[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Parse clipboard text as a spreadsheet block.
|
|
34
|
+
*
|
|
35
|
+
* Handles the Excel quoting dialect (a quoted cell may span lines and carries
|
|
36
|
+
* `""` for a literal quote), both `\n` and `\r\n` line endings, and the single
|
|
37
|
+
* trailing line break Excel and Sheets append to every copy — without that
|
|
38
|
+
* strip, every paste would be one empty row taller than what was copied.
|
|
39
|
+
*
|
|
40
|
+
* Plain text with no tabs and no quotes still parses: one column, one row per
|
|
41
|
+
* line, which is exactly what Sheets does with the same text. A single value
|
|
42
|
+
* with no line break is a 1×1 block.
|
|
43
|
+
*/
|
|
44
|
+
declare function parseClipboardGrid(text: string): string[][];
|
|
45
|
+
/** One landing cell of a paste, with the raw text that goes into it. */
|
|
46
|
+
type PasteCell = CellAddress & {
|
|
47
|
+
raw: string;
|
|
48
|
+
};
|
|
49
|
+
type PastePlan = {
|
|
50
|
+
/** Cells inside the grid, in reading order (row by row). */
|
|
51
|
+
cells: PasteCell[];
|
|
52
|
+
/**
|
|
53
|
+
* Block rows below the last grid row, trimmed to the columns that fit from
|
|
54
|
+
* the anchor column rightward. The caller decides whether these become new
|
|
55
|
+
* rows — the plan only reports them so nothing is dropped silently.
|
|
56
|
+
*/
|
|
57
|
+
overflowRows: string[][];
|
|
58
|
+
/** How many block columns fell off the right edge (per row, at most). */
|
|
59
|
+
clippedColumns: number;
|
|
60
|
+
/** The columns (field names) the block spans, anchor first. */
|
|
61
|
+
fieldNames: string[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Where does `block` land when pasted with its top-left cell at `anchor`?
|
|
65
|
+
*
|
|
66
|
+
* Rows extend downward from the anchor row and columns rightward from the
|
|
67
|
+
* anchor column, in the GRID's current order (so a hidden or re-ordered column
|
|
68
|
+
* is honoured — you paste into what you see). A block wider than the remaining
|
|
69
|
+
* columns is clipped on the right; a block taller than the remaining rows is
|
|
70
|
+
* reported as `overflowRows` rather than lost.
|
|
71
|
+
*
|
|
72
|
+
* Returns null when the anchor is not on the grid — the row was filtered out
|
|
73
|
+
* or the column hidden since the selection was made. Writing "somewhere near"
|
|
74
|
+
* is worse than refusing.
|
|
75
|
+
*/
|
|
76
|
+
declare function planPaste(anchor: CellAddress, block: readonly (readonly string[])[], rowIds: readonly string[], fieldNames: readonly string[]): PastePlan | null;
|
|
77
|
+
/** True when the parsed block is a single value — the common Cmd-V case. */
|
|
78
|
+
declare function isSingleCell(block: readonly (readonly string[])[]): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Would writing `next` over `prior` change what is stored? Objects compare by
|
|
81
|
+
* their JSON, the same test the inline editor uses to skip a no-op write.
|
|
82
|
+
*/
|
|
83
|
+
declare function storedValuesEqual(a: unknown, b: unknown): boolean;
|
|
84
|
+
|
|
85
|
+
export { type PasteCell, type PastePlan, cellClipboardText, gridToTsv, isSingleCell, parseClipboardGrid, planPaste, storedValuesEqual };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
|
|
2
|
+
// src/data-table/grid-clipboard.ts
|
|
3
|
+
function cellClipboardText(value) {
|
|
4
|
+
if (value === null || value === void 0) return "";
|
|
5
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
6
|
+
return String(value);
|
|
7
|
+
}
|
|
8
|
+
function tsvCell(text) {
|
|
9
|
+
return /[\t\r\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
10
|
+
}
|
|
11
|
+
function gridToTsv(block) {
|
|
12
|
+
return block.map((row) => row.map((cell) => tsvCell(cellClipboardText(cell))).join(" ")).join("\n");
|
|
13
|
+
}
|
|
14
|
+
function parseClipboardGrid(text) {
|
|
15
|
+
const rows = [];
|
|
16
|
+
let row = [];
|
|
17
|
+
let cell = "";
|
|
18
|
+
let i = 0;
|
|
19
|
+
const n = text.length;
|
|
20
|
+
while (i < n) {
|
|
21
|
+
const ch = text[i];
|
|
22
|
+
if (cell === "" && ch === '"') {
|
|
23
|
+
let j = i + 1;
|
|
24
|
+
let out = "";
|
|
25
|
+
let closed = false;
|
|
26
|
+
while (j < n) {
|
|
27
|
+
const c = text[j];
|
|
28
|
+
if (c === '"') {
|
|
29
|
+
if (text[j + 1] === '"') {
|
|
30
|
+
out += '"';
|
|
31
|
+
j += 2;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
closed = true;
|
|
35
|
+
j += 1;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
out += c;
|
|
39
|
+
j += 1;
|
|
40
|
+
}
|
|
41
|
+
if (closed && (j >= n || text[j] === " " || text[j] === "\n" || text[j] === "\r")) {
|
|
42
|
+
cell = out;
|
|
43
|
+
i = j;
|
|
44
|
+
if (i >= n) break;
|
|
45
|
+
const d = text[i];
|
|
46
|
+
if (d === " ") {
|
|
47
|
+
row.push(cell);
|
|
48
|
+
cell = "";
|
|
49
|
+
i += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
row.push(cell);
|
|
53
|
+
rows.push(row);
|
|
54
|
+
row = [];
|
|
55
|
+
cell = "";
|
|
56
|
+
i += d === "\r" && text[i + 1] === "\n" ? 2 : 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
cell += ch;
|
|
60
|
+
i += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ch === " ") {
|
|
64
|
+
row.push(cell);
|
|
65
|
+
cell = "";
|
|
66
|
+
i += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (ch === "\n" || ch === "\r") {
|
|
70
|
+
row.push(cell);
|
|
71
|
+
rows.push(row);
|
|
72
|
+
row = [];
|
|
73
|
+
cell = "";
|
|
74
|
+
i += ch === "\r" && text[i + 1] === "\n" ? 2 : 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
cell += ch;
|
|
78
|
+
i += 1;
|
|
79
|
+
}
|
|
80
|
+
const endedOnBreak = n > 0 && (text[n - 1] === "\n" || text[n - 1] === "\r");
|
|
81
|
+
if (!endedOnBreak) {
|
|
82
|
+
row.push(cell);
|
|
83
|
+
rows.push(row);
|
|
84
|
+
} else if (row.length > 0) {
|
|
85
|
+
row.push(cell);
|
|
86
|
+
rows.push(row);
|
|
87
|
+
}
|
|
88
|
+
return rows.length === 0 ? [[""]] : rows;
|
|
89
|
+
}
|
|
90
|
+
function planPaste(anchor, block, rowIds, fieldNames) {
|
|
91
|
+
const r0 = rowIds.indexOf(anchor.rowId);
|
|
92
|
+
const c0 = fieldNames.indexOf(anchor.fieldName);
|
|
93
|
+
if (r0 === -1 || c0 === -1) return null;
|
|
94
|
+
const width = block.reduce((w, row) => Math.max(w, row.length), 0);
|
|
95
|
+
const colsAvailable = fieldNames.length - c0;
|
|
96
|
+
const colsUsed = Math.min(width, colsAvailable);
|
|
97
|
+
const targetFields = fieldNames.slice(c0, c0 + colsUsed);
|
|
98
|
+
const cells = [];
|
|
99
|
+
const overflowRows = [];
|
|
100
|
+
block.forEach((blockRow, dr) => {
|
|
101
|
+
const rowId = rowIds[r0 + dr];
|
|
102
|
+
if (rowId === void 0) {
|
|
103
|
+
overflowRows.push(targetFields.map((_, dc) => blockRow[dc] ?? ""));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
targetFields.forEach((fieldName, dc) => {
|
|
107
|
+
if (dc >= blockRow.length) return;
|
|
108
|
+
const raw = blockRow[dc];
|
|
109
|
+
if (raw === void 0) return;
|
|
110
|
+
cells.push({ rowId, fieldName, raw });
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
cells,
|
|
115
|
+
overflowRows,
|
|
116
|
+
clippedColumns: Math.max(0, width - colsAvailable),
|
|
117
|
+
fieldNames: targetFields
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function isSingleCell(block) {
|
|
121
|
+
return block.length === 1 && block[0]?.length === 1;
|
|
122
|
+
}
|
|
123
|
+
function storedValuesEqual(a, b) {
|
|
124
|
+
if (a === b) return true;
|
|
125
|
+
if (a === null || b === null || a === void 0 || b === void 0) {
|
|
126
|
+
return (a ?? null) === (b ?? null);
|
|
127
|
+
}
|
|
128
|
+
if (typeof a === "object" || typeof b === "object") {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
cellClipboardText,
|
|
139
|
+
gridToTsv,
|
|
140
|
+
isSingleCell,
|
|
141
|
+
parseClipboardGrid,
|
|
142
|
+
planPaste,
|
|
143
|
+
storedValuesEqual
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=grid-clipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-table/grid-clipboard.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/data-table/grid-selection.ts
|
|
21
|
+
var grid_selection_exports = {};
|
|
22
|
+
__export(grid_selection_exports, {
|
|
23
|
+
allRange: () => allRange,
|
|
24
|
+
boundsContain: () => boundsContain,
|
|
25
|
+
cellDomKey: () => cellDomKey,
|
|
26
|
+
cellsInRange: () => cellsInRange,
|
|
27
|
+
classifyGridKey: () => classifyGridKey,
|
|
28
|
+
columnRange: () => columnRange,
|
|
29
|
+
directClickKinds: () => directClickKinds,
|
|
30
|
+
isDirectClickEditor: () => isDirectClickEditor,
|
|
31
|
+
isSingleCellRange: () => isSingleCellRange,
|
|
32
|
+
isTypingKey: () => isTypingKey,
|
|
33
|
+
moveSelection: () => moveSelection,
|
|
34
|
+
rangeBounds: () => rangeBounds,
|
|
35
|
+
rangeRows: () => rangeRows,
|
|
36
|
+
rangeSize: () => rangeSize,
|
|
37
|
+
rowRange: () => rowRange,
|
|
38
|
+
sameCell: () => sameCell
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(grid_selection_exports);
|
|
41
|
+
function rangeBounds(range, rowIds, fieldNames) {
|
|
42
|
+
const ra = rowIds.indexOf(range.anchor.rowId);
|
|
43
|
+
const rf = rowIds.indexOf(range.focus.rowId);
|
|
44
|
+
const ca = fieldNames.indexOf(range.anchor.fieldName);
|
|
45
|
+
const cf = fieldNames.indexOf(range.focus.fieldName);
|
|
46
|
+
if (ra === -1 || rf === -1 || ca === -1 || cf === -1) return null;
|
|
47
|
+
return {
|
|
48
|
+
r0: Math.min(ra, rf),
|
|
49
|
+
r1: Math.max(ra, rf),
|
|
50
|
+
c0: Math.min(ca, cf),
|
|
51
|
+
c1: Math.max(ca, cf)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function rangeSize(bounds) {
|
|
55
|
+
const rows = bounds.r1 - bounds.r0 + 1;
|
|
56
|
+
const cols = bounds.c1 - bounds.c0 + 1;
|
|
57
|
+
return { rows, cols, cells: rows * cols };
|
|
58
|
+
}
|
|
59
|
+
function boundsContain(bounds, rowIndex, colIndex) {
|
|
60
|
+
return rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
|
|
61
|
+
}
|
|
62
|
+
function cellsInRange(range, rowIds, fieldNames) {
|
|
63
|
+
const b = rangeBounds(range, rowIds, fieldNames);
|
|
64
|
+
if (!b) return [];
|
|
65
|
+
const out = [];
|
|
66
|
+
for (let r = b.r0; r <= b.r1; r += 1) {
|
|
67
|
+
const rowId = rowIds[r];
|
|
68
|
+
if (rowId === void 0) continue;
|
|
69
|
+
for (let c = b.c0; c <= b.c1; c += 1) {
|
|
70
|
+
const fieldName = fieldNames[c];
|
|
71
|
+
if (fieldName === void 0) continue;
|
|
72
|
+
out.push({ rowId, fieldName });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function rangeRows(range, rowIds, fieldNames) {
|
|
78
|
+
const b = rangeBounds(range, rowIds, fieldNames);
|
|
79
|
+
if (!b) return [];
|
|
80
|
+
const rows = [];
|
|
81
|
+
for (let r = b.r0; r <= b.r1; r += 1) {
|
|
82
|
+
const rowId = rowIds[r];
|
|
83
|
+
if (rowId === void 0) continue;
|
|
84
|
+
const row = [];
|
|
85
|
+
for (let c = b.c0; c <= b.c1; c += 1) {
|
|
86
|
+
const fieldName = fieldNames[c];
|
|
87
|
+
if (fieldName === void 0) continue;
|
|
88
|
+
row.push({ rowId, fieldName });
|
|
89
|
+
}
|
|
90
|
+
rows.push(row);
|
|
91
|
+
}
|
|
92
|
+
return rows;
|
|
93
|
+
}
|
|
94
|
+
function isSingleCellRange(range) {
|
|
95
|
+
return sameCell(range.anchor, range.focus);
|
|
96
|
+
}
|
|
97
|
+
function rowRange(rowId, fieldNames) {
|
|
98
|
+
const firstField = fieldNames[0];
|
|
99
|
+
const lastField = fieldNames[fieldNames.length - 1];
|
|
100
|
+
if (firstField === void 0 || lastField === void 0) return null;
|
|
101
|
+
return {
|
|
102
|
+
anchor: { rowId, fieldName: firstField },
|
|
103
|
+
focus: { rowId, fieldName: lastField }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function columnRange(fieldName, rowIds) {
|
|
107
|
+
const firstRow = rowIds[0];
|
|
108
|
+
const lastRow = rowIds[rowIds.length - 1];
|
|
109
|
+
if (firstRow === void 0 || lastRow === void 0) return null;
|
|
110
|
+
return {
|
|
111
|
+
anchor: { rowId: firstRow, fieldName },
|
|
112
|
+
focus: { rowId: lastRow, fieldName }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function allRange(rowIds, fieldNames) {
|
|
116
|
+
const firstRow = rowIds[0];
|
|
117
|
+
const lastRow = rowIds[rowIds.length - 1];
|
|
118
|
+
const firstField = fieldNames[0];
|
|
119
|
+
const lastField = fieldNames[fieldNames.length - 1];
|
|
120
|
+
if (firstRow === void 0 || lastRow === void 0 || firstField === void 0 || lastField === void 0) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
anchor: { rowId: firstRow, fieldName: firstField },
|
|
125
|
+
focus: { rowId: lastRow, fieldName: lastField }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function sameCell(a, b) {
|
|
129
|
+
if (!a || !b) return a === b;
|
|
130
|
+
return a.rowId === b.rowId && a.fieldName === b.fieldName;
|
|
131
|
+
}
|
|
132
|
+
function moveSelection(current, move, rowIds, fieldNames) {
|
|
133
|
+
if (rowIds.length === 0 || fieldNames.length === 0) return null;
|
|
134
|
+
const firstRowId = rowIds[0];
|
|
135
|
+
const firstFieldName = fieldNames[0];
|
|
136
|
+
if (firstRowId === void 0 || firstFieldName === void 0) return null;
|
|
137
|
+
const first = { rowId: firstRowId, fieldName: firstFieldName };
|
|
138
|
+
if (!current) return first;
|
|
139
|
+
let r = rowIds.indexOf(current.rowId);
|
|
140
|
+
let c = fieldNames.indexOf(current.fieldName);
|
|
141
|
+
if (r === -1 || c === -1) return first;
|
|
142
|
+
const lastRow = rowIds.length - 1;
|
|
143
|
+
const lastCol = fieldNames.length - 1;
|
|
144
|
+
const clamp = (n, max) => Math.min(Math.max(n, 0), max);
|
|
145
|
+
switch (move) {
|
|
146
|
+
case "up":
|
|
147
|
+
r = clamp(r - 1, lastRow);
|
|
148
|
+
break;
|
|
149
|
+
case "down":
|
|
150
|
+
r = clamp(r + 1, lastRow);
|
|
151
|
+
break;
|
|
152
|
+
case "left":
|
|
153
|
+
c = clamp(c - 1, lastCol);
|
|
154
|
+
break;
|
|
155
|
+
case "right":
|
|
156
|
+
c = clamp(c + 1, lastCol);
|
|
157
|
+
break;
|
|
158
|
+
case "rowStart":
|
|
159
|
+
c = 0;
|
|
160
|
+
break;
|
|
161
|
+
case "rowEnd":
|
|
162
|
+
c = lastCol;
|
|
163
|
+
break;
|
|
164
|
+
case "gridStart":
|
|
165
|
+
r = 0;
|
|
166
|
+
c = 0;
|
|
167
|
+
break;
|
|
168
|
+
case "gridEnd":
|
|
169
|
+
r = lastRow;
|
|
170
|
+
c = lastCol;
|
|
171
|
+
break;
|
|
172
|
+
case "nextCell":
|
|
173
|
+
if (c < lastCol) c += 1;
|
|
174
|
+
else if (r < lastRow) {
|
|
175
|
+
r += 1;
|
|
176
|
+
c = 0;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
case "prevCell":
|
|
180
|
+
if (c > 0) c -= 1;
|
|
181
|
+
else if (r > 0) {
|
|
182
|
+
r -= 1;
|
|
183
|
+
c = lastCol;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
const rowId = rowIds[r];
|
|
188
|
+
const fieldName = fieldNames[c];
|
|
189
|
+
if (rowId === void 0 || fieldName === void 0) return first;
|
|
190
|
+
return { rowId, fieldName };
|
|
191
|
+
}
|
|
192
|
+
function isTypingKey(e) {
|
|
193
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return false;
|
|
194
|
+
return e.key.length === 1 && e.key !== " ";
|
|
195
|
+
}
|
|
196
|
+
function classifyGridKey(e) {
|
|
197
|
+
const mod = e.ctrlKey || e.metaKey;
|
|
198
|
+
const arrowMove = {
|
|
199
|
+
ArrowUp: mod ? "gridStart" : "up",
|
|
200
|
+
ArrowDown: mod ? "gridEnd" : "down",
|
|
201
|
+
ArrowLeft: mod ? "rowStart" : "left",
|
|
202
|
+
ArrowRight: mod ? "rowEnd" : "right",
|
|
203
|
+
Home: mod ? "gridStart" : "rowStart",
|
|
204
|
+
End: mod ? "gridEnd" : "rowEnd"
|
|
205
|
+
};
|
|
206
|
+
const arrow = arrowMove[e.key];
|
|
207
|
+
if (arrow !== void 0) {
|
|
208
|
+
return { kind: e.shiftKey ? "extend" : "move", move: arrow };
|
|
209
|
+
}
|
|
210
|
+
switch (e.key) {
|
|
211
|
+
case "Tab":
|
|
212
|
+
return { kind: "move", move: e.shiftKey ? "prevCell" : "nextCell" };
|
|
213
|
+
case "Enter":
|
|
214
|
+
case "F2":
|
|
215
|
+
return { kind: "edit" };
|
|
216
|
+
case " ":
|
|
217
|
+
if (e.shiftKey) return { kind: "selectRow" };
|
|
218
|
+
if (mod) return { kind: "selectColumn" };
|
|
219
|
+
return { kind: "edit" };
|
|
220
|
+
case "a":
|
|
221
|
+
case "A":
|
|
222
|
+
if (mod) return { kind: "selectAll" };
|
|
223
|
+
break;
|
|
224
|
+
case "d":
|
|
225
|
+
case "D":
|
|
226
|
+
if (mod) return { kind: "fillDown" };
|
|
227
|
+
break;
|
|
228
|
+
case "Escape":
|
|
229
|
+
return { kind: "escape" };
|
|
230
|
+
case "Delete":
|
|
231
|
+
case "Backspace":
|
|
232
|
+
return { kind: "clearCell" };
|
|
233
|
+
case "c":
|
|
234
|
+
case "C":
|
|
235
|
+
if (mod) return { kind: "copy" };
|
|
236
|
+
break;
|
|
237
|
+
case "x":
|
|
238
|
+
case "X":
|
|
239
|
+
if (mod) return { kind: "cut" };
|
|
240
|
+
break;
|
|
241
|
+
case "v":
|
|
242
|
+
case "V":
|
|
243
|
+
if (mod) return { kind: "paste" };
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
if (isTypingKey(e)) return { kind: "editSeeded", seed: e.key };
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
function directClickKinds() {
|
|
250
|
+
return ["checkbox", "rating", "select", "multiselect"];
|
|
251
|
+
}
|
|
252
|
+
function isDirectClickEditor(editorKind) {
|
|
253
|
+
return editorKind !== void 0 && directClickKinds().includes(editorKind);
|
|
254
|
+
}
|
|
255
|
+
function cellDomKey(address) {
|
|
256
|
+
return `${address.rowId}::${address.fieldName}`;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=grid-selection.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-table/grid-selection.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
|