twico-ui 1.25.0 → 1.26.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 +7 -0
- package/dist/index.cjs +274 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.mjs +274 -23
- package/dist/index.mjs.map +1 -1
- package/llms.txt +2 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1938,7 +1938,10 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1938
1938
|
onStateChange?: (state: DatatableState) => void;
|
|
1939
1939
|
/** Show a "Go to" page jumper in the footer when there are more than 5 pages. @default true */
|
|
1940
1940
|
showPageJumper?: boolean;
|
|
1941
|
-
/** Click-to-select mode: "row" highlights the clicked row, "cell"
|
|
1941
|
+
/** Click-to-select mode: "row" highlights the clicked row, "cell" enables spreadsheet-like cell selection.
|
|
1942
|
+
* In "cell" mode the grid supports keyboard navigation and **rectangular range selection** (#317):
|
|
1943
|
+
* Shift+Click / Shift+Arrow extend a range from an anchor; selected cells expose `aria-selected` and the
|
|
1944
|
+
* grid is `aria-multiselectable` with `aria-activedescendant` on the active cell. @default "none" */
|
|
1942
1945
|
selectionMode?: "none" | "row" | "cell";
|
|
1943
1946
|
/** Fired when a row is clicked in "row" selection mode: (row, key). */
|
|
1944
1947
|
onRowClick?: (row: T, key: string | number) => void;
|
|
@@ -1946,6 +1949,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1946
1949
|
onCellClick?: (value: any, row: T, field: string) => void;
|
|
1947
1950
|
/** Fired when the active cell changes: ({ key, field } | null). */
|
|
1948
1951
|
onActiveCellChange?: (cell: { key: string | number; field: string } | null) => void;
|
|
1952
|
+
/** #317: fired when the rectangular cell selection changes ("cell" mode) — every cell inside the
|
|
1953
|
+
* anchor→focus rectangle, in row-major order. Empty array when the selection clears. */
|
|
1954
|
+
onCellSelectionChange?: (cells: Array<{ key: string | number; field: string }>) => void;
|
|
1955
|
+
/** #318: enable spreadsheet clipboard on cells ("cell" mode): Ctrl/Cmd+C copies the active cell/range as
|
|
1956
|
+
* TSV, Ctrl/Cmd+X cuts (copy then clear), Ctrl/Cmd+V pastes onto the target rectangle from the active
|
|
1957
|
+
* cell. Paste is format-restricted by column `copyType` (incompatible cells are skipped) and commits
|
|
1958
|
+
* through `onRowUpdate`/`onRowsChange` in one batched change; outcomes are announced via `aria-live`.
|
|
1959
|
+
* @default false */
|
|
1960
|
+
enableClipboard?: boolean;
|
|
1949
1961
|
/** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
|
|
1950
1962
|
* toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
|
|
1951
1963
|
* changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
|
|
@@ -2157,6 +2169,11 @@ interface DatatableColumn<T = any> {
|
|
|
2157
2169
|
loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
|
|
2158
2170
|
/** Format the raw value to a string/number for display. */
|
|
2159
2171
|
valueFormatter?: (value: any, row: T) => React.ReactNode;
|
|
2172
|
+
/** #318: opaque copy/paste compatibility token for the cell clipboard (`enableClipboard`). On an in-app
|
|
2173
|
+
* paste, a source cell is written to a target only when the two columns' `copyType` match — so e.g. a
|
|
2174
|
+
* "part-name" value never lands in a "measurement" column. Falls back to a number-vs-text bucket derived
|
|
2175
|
+
* from `type` when omitted. Examples: "text", "number", "part-name", "master:suppliers". */
|
|
2176
|
+
copyType?: string;
|
|
2160
2177
|
/** Fully custom cell renderer (badges, avatars, etc.). */
|
|
2161
2178
|
renderCell?: (value: any, row: T) => React.ReactNode;
|
|
2162
2179
|
/** Diff-mode (#239) per-column equality override — return true when the two values are equal.
|
package/dist/index.d.ts
CHANGED
|
@@ -1938,7 +1938,10 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1938
1938
|
onStateChange?: (state: DatatableState) => void;
|
|
1939
1939
|
/** Show a "Go to" page jumper in the footer when there are more than 5 pages. @default true */
|
|
1940
1940
|
showPageJumper?: boolean;
|
|
1941
|
-
/** Click-to-select mode: "row" highlights the clicked row, "cell"
|
|
1941
|
+
/** Click-to-select mode: "row" highlights the clicked row, "cell" enables spreadsheet-like cell selection.
|
|
1942
|
+
* In "cell" mode the grid supports keyboard navigation and **rectangular range selection** (#317):
|
|
1943
|
+
* Shift+Click / Shift+Arrow extend a range from an anchor; selected cells expose `aria-selected` and the
|
|
1944
|
+
* grid is `aria-multiselectable` with `aria-activedescendant` on the active cell. @default "none" */
|
|
1942
1945
|
selectionMode?: "none" | "row" | "cell";
|
|
1943
1946
|
/** Fired when a row is clicked in "row" selection mode: (row, key). */
|
|
1944
1947
|
onRowClick?: (row: T, key: string | number) => void;
|
|
@@ -1946,6 +1949,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1946
1949
|
onCellClick?: (value: any, row: T, field: string) => void;
|
|
1947
1950
|
/** Fired when the active cell changes: ({ key, field } | null). */
|
|
1948
1951
|
onActiveCellChange?: (cell: { key: string | number; field: string } | null) => void;
|
|
1952
|
+
/** #317: fired when the rectangular cell selection changes ("cell" mode) — every cell inside the
|
|
1953
|
+
* anchor→focus rectangle, in row-major order. Empty array when the selection clears. */
|
|
1954
|
+
onCellSelectionChange?: (cells: Array<{ key: string | number; field: string }>) => void;
|
|
1955
|
+
/** #318: enable spreadsheet clipboard on cells ("cell" mode): Ctrl/Cmd+C copies the active cell/range as
|
|
1956
|
+
* TSV, Ctrl/Cmd+X cuts (copy then clear), Ctrl/Cmd+V pastes onto the target rectangle from the active
|
|
1957
|
+
* cell. Paste is format-restricted by column `copyType` (incompatible cells are skipped) and commits
|
|
1958
|
+
* through `onRowUpdate`/`onRowsChange` in one batched change; outcomes are announced via `aria-live`.
|
|
1959
|
+
* @default false */
|
|
1960
|
+
enableClipboard?: boolean;
|
|
1949
1961
|
/** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
|
|
1950
1962
|
* toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
|
|
1951
1963
|
* changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
|
|
@@ -2157,6 +2169,11 @@ interface DatatableColumn<T = any> {
|
|
|
2157
2169
|
loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
|
|
2158
2170
|
/** Format the raw value to a string/number for display. */
|
|
2159
2171
|
valueFormatter?: (value: any, row: T) => React.ReactNode;
|
|
2172
|
+
/** #318: opaque copy/paste compatibility token for the cell clipboard (`enableClipboard`). On an in-app
|
|
2173
|
+
* paste, a source cell is written to a target only when the two columns' `copyType` match — so e.g. a
|
|
2174
|
+
* "part-name" value never lands in a "measurement" column. Falls back to a number-vs-text bucket derived
|
|
2175
|
+
* from `type` when omitted. Examples: "text", "number", "part-name", "master:suppliers". */
|
|
2176
|
+
copyType?: string;
|
|
2160
2177
|
/** Fully custom cell renderer (badges, avatars, etc.). */
|
|
2161
2178
|
renderCell?: (value: any, row: T) => React.ReactNode;
|
|
2162
2179
|
/** Diff-mode (#239) per-column equality override — return true when the two values are equal.
|
package/dist/index.mjs
CHANGED
|
@@ -8440,6 +8440,8 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8440
8440
|
.twc-dt__row[data-selectable="true"] { cursor: pointer; }
|
|
8441
8441
|
.twc-dt__row[data-active="true"] .twc-dt__td { background: var(--color-primary-subtle); box-shadow: inset 0 0 0 9999px transparent; }
|
|
8442
8442
|
.twc-dt__row[data-active="true"] .twc-dt__td[data-pin] { background: var(--color-primary-subtle); }
|
|
8443
|
+
/* #317: data-cell-selected fills the range rectangle; data-cell-active keeps the ring on the moving corner. */
|
|
8444
|
+
.twc-dt__td[data-cell-selected="true"] { background: var(--color-primary-subtle); }
|
|
8443
8445
|
.twc-dt__td[data-cell-active="true"] { box-shadow: inset 0 0 0 2px var(--color-primary); background: var(--color-primary-subtle); }
|
|
8444
8446
|
.twc-dt__row:last-child .twc-dt__td { border-bottom: none; }
|
|
8445
8447
|
|
|
@@ -8594,6 +8596,9 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8594
8596
|
.twc-dt__pop-grip:hover, .twc-dt__pop-grip:focus-visible, .twc-dt__pop-grip[data-active="true"] { opacity: 1; }
|
|
8595
8597
|
.twc-dt__pop-grip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
|
|
8596
8598
|
[dir="rtl"] .twc-dt__pop-grip { cursor: nesw-resize; }
|
|
8599
|
+
/* #316: a second grip in the bottom-inline-start corner grows a right-anchored panel toward the empty side (right edge pinned). */
|
|
8600
|
+
.twc-dt__pop-grip[data-side="start"] { inset-inline-start: 2px; inset-inline-end: auto; cursor: nesw-resize; background: linear-gradient(225deg, transparent 0 55%, var(--color-border-strong) 55% 64%, transparent 64% 73%, var(--color-border-strong) 73% 82%, transparent 82%); }
|
|
8601
|
+
[dir="rtl"] .twc-dt__pop-grip[data-side="start"] { cursor: nwse-resize; }
|
|
8597
8602
|
@media (pointer: coarse) {
|
|
8598
8603
|
.twc-dt__f-rz { width: 16px; inset-inline-end: -6px; opacity: 0.4; }
|
|
8599
8604
|
.twc-dt__pop-grip { width: 20px; height: 20px; opacity: 0.6; }
|
|
@@ -9148,6 +9153,8 @@ function Datatable({
|
|
|
9148
9153
|
onRowClick,
|
|
9149
9154
|
onCellClick,
|
|
9150
9155
|
onActiveCellChange,
|
|
9156
|
+
onCellSelectionChange,
|
|
9157
|
+
enableClipboard = false,
|
|
9151
9158
|
showAggregation = false,
|
|
9152
9159
|
ariaLabel = "Data table",
|
|
9153
9160
|
"aria-label": ariaLabelAttr,
|
|
@@ -9278,6 +9285,8 @@ function Datatable({
|
|
|
9278
9285
|
const [selected, setSelected] = React51__default.useState(() => /* @__PURE__ */ new Set());
|
|
9279
9286
|
const [activeRow, setActiveRow] = React51__default.useState(null);
|
|
9280
9287
|
const [activeCell, setActiveCell] = React51__default.useState(null);
|
|
9288
|
+
const [anchorCell, setAnchorCell] = React51__default.useState(null);
|
|
9289
|
+
const gridId = React51__default.useId();
|
|
9281
9290
|
const [internalPage, setInternalPage] = React51__default.useState(0);
|
|
9282
9291
|
const [internalRpp, setInternalRpp] = React51__default.useState(pageSize > 0 ? pageSize : 10);
|
|
9283
9292
|
const pageControlled = page !== void 0;
|
|
@@ -9377,6 +9386,7 @@ function Datatable({
|
|
|
9377
9386
|
for (const [id, sz] of Object.entries(s.popoverSizes)) {
|
|
9378
9387
|
if (!sz || typeof sz !== "object" || typeof sz.w !== "number" || typeof sz.h !== "number") continue;
|
|
9379
9388
|
next[id] = { w: clampNum(sz.w, popMinW(id), fMaxW()), h: clampNum(sz.h, 120, fMaxH()) };
|
|
9389
|
+
if (typeof sz.left === "number") next[id].left = clampNum(sz.left, 8, fMaxW());
|
|
9380
9390
|
}
|
|
9381
9391
|
if (Object.keys(next).length) setPopSizes(next);
|
|
9382
9392
|
}
|
|
@@ -9828,6 +9838,38 @@ function Datatable({
|
|
|
9828
9838
|
middleRows.forEach((r2, i) => m.set(keyOf(r2, keyIndex.get(keyOf(r2)) ?? i), i));
|
|
9829
9839
|
return m;
|
|
9830
9840
|
}, [middleRows, keyIndex]);
|
|
9841
|
+
const cellColIndex = React51__default.useCallback((field) => ordered.findIndex((c) => c.field === field), [ordered]);
|
|
9842
|
+
const cellRect = React51__default.useMemo(() => {
|
|
9843
|
+
if (selectionMode !== "cell" || !activeCell) return null;
|
|
9844
|
+
const ar = keyIndex.get(activeCell.key), ac = cellColIndex(activeCell.field);
|
|
9845
|
+
if (ar == null || ac < 0) return null;
|
|
9846
|
+
const anchor = anchorCell || activeCell;
|
|
9847
|
+
const nr = keyIndex.get(anchor.key), nc = cellColIndex(anchor.field);
|
|
9848
|
+
const r0 = nr == null ? ar : Math.min(ar, nr), r1 = nr == null ? ar : Math.max(ar, nr);
|
|
9849
|
+
const c0 = nc < 0 ? ac : Math.min(ac, nc), c1 = nc < 0 ? ac : Math.max(ac, nc);
|
|
9850
|
+
return { r0, r1, c0, c1 };
|
|
9851
|
+
}, [selectionMode, activeCell, anchorCell, keyIndex, cellColIndex]);
|
|
9852
|
+
const activeCellId = React51__default.useMemo(() => {
|
|
9853
|
+
if (selectionMode !== "cell" || !activeCell) return void 0;
|
|
9854
|
+
const ar = keyIndex.get(activeCell.key), ac = cellColIndex(activeCell.field);
|
|
9855
|
+
return ar == null || ac < 0 ? void 0 : `${gridId}-${ar}-${ac}`;
|
|
9856
|
+
}, [selectionMode, activeCell, keyIndex, cellColIndex, gridId]);
|
|
9857
|
+
const onCellSelRef = React51__default.useRef(onCellSelectionChange);
|
|
9858
|
+
onCellSelRef.current = onCellSelectionChange;
|
|
9859
|
+
const prevSelSigRef = React51__default.useRef("");
|
|
9860
|
+
React51__default.useEffect(() => {
|
|
9861
|
+
const cb = onCellSelRef.current;
|
|
9862
|
+
if (selectionMode !== "cell" || !cb) return;
|
|
9863
|
+
const cells = [];
|
|
9864
|
+
if (cellRect) for (let r2 = cellRect.r0; r2 <= cellRect.r1; r2++) for (let c = cellRect.c0; c <= cellRect.c1; c++) {
|
|
9865
|
+
const row = leafRows[r2], col = ordered[c];
|
|
9866
|
+
if (row && col) cells.push({ key: keyOf(row, r2), field: col.field });
|
|
9867
|
+
}
|
|
9868
|
+
const sig = cells.map((x) => `${x.key}|${x.field}`).join(",");
|
|
9869
|
+
if (sig === prevSelSigRef.current) return;
|
|
9870
|
+
prevSelSigRef.current = sig;
|
|
9871
|
+
cb(cells);
|
|
9872
|
+
}, [cellRect, selectionMode, leafRows, ordered]);
|
|
9831
9873
|
const canReorderRows = rowReorder && !activeGroupBy.length && !sort;
|
|
9832
9874
|
function onRowDrop(targetKey) {
|
|
9833
9875
|
const fromKey = rowDrag.from;
|
|
@@ -10240,6 +10282,7 @@ function Datatable({
|
|
|
10240
10282
|
const next = { key: k, field: col.field };
|
|
10241
10283
|
setActiveCell(next);
|
|
10242
10284
|
setActiveRow(k);
|
|
10285
|
+
if (!(e.shiftKey && anchorCell)) setAnchorCell(next);
|
|
10243
10286
|
onCellClick?.(row[col.field], row, col.field);
|
|
10244
10287
|
onActiveCellChange?.(next);
|
|
10245
10288
|
}
|
|
@@ -10251,6 +10294,24 @@ function Datatable({
|
|
|
10251
10294
|
if (editing) return;
|
|
10252
10295
|
const td = e.target.closest(".twc-dt__td[data-r]");
|
|
10253
10296
|
if (!td) return;
|
|
10297
|
+
if (enableClipboard && selectionMode === "cell" && (e.ctrlKey || e.metaKey)) {
|
|
10298
|
+
const k = e.key.toLowerCase();
|
|
10299
|
+
if (k === "c") {
|
|
10300
|
+
e.preventDefault();
|
|
10301
|
+
copySelection(false);
|
|
10302
|
+
return;
|
|
10303
|
+
}
|
|
10304
|
+
if (k === "x") {
|
|
10305
|
+
e.preventDefault();
|
|
10306
|
+
copySelection(true);
|
|
10307
|
+
return;
|
|
10308
|
+
}
|
|
10309
|
+
if (k === "v") {
|
|
10310
|
+
e.preventDefault();
|
|
10311
|
+
pasteSelection();
|
|
10312
|
+
return;
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10254
10315
|
let r2 = +td.getAttribute("data-r"), c = +td.getAttribute("data-c");
|
|
10255
10316
|
const maxR = leafRows.length - 1, maxC = ordered.length - 1;
|
|
10256
10317
|
let handled = true;
|
|
@@ -10303,6 +10364,16 @@ function Datatable({
|
|
|
10303
10364
|
e.preventDefault();
|
|
10304
10365
|
setFocus({ r: r2, c });
|
|
10305
10366
|
focusCell(r2, c);
|
|
10367
|
+
if (selectionMode === "cell") {
|
|
10368
|
+
const nk = keyOf(leafRows[r2], r2), col = ordered[c];
|
|
10369
|
+
if (col) {
|
|
10370
|
+
const nc = { key: nk, field: col.field };
|
|
10371
|
+
setActiveCell(nc);
|
|
10372
|
+
setActiveRow(nk);
|
|
10373
|
+
if (!e.shiftKey) setAnchorCell(nc);
|
|
10374
|
+
onActiveCellChange?.(nc);
|
|
10375
|
+
}
|
|
10376
|
+
}
|
|
10306
10377
|
}
|
|
10307
10378
|
}
|
|
10308
10379
|
React51__default.useEffect(() => {
|
|
@@ -10419,6 +10490,142 @@ function Datatable({
|
|
|
10419
10490
|
setBatchEdit(null);
|
|
10420
10491
|
closeBatchEdit();
|
|
10421
10492
|
}
|
|
10493
|
+
const [clipboardMsg, setClipboardMsg] = React51__default.useState("");
|
|
10494
|
+
const clipboardRef = React51__default.useRef(null);
|
|
10495
|
+
const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
|
|
10496
|
+
const writeClipboard = (text) => {
|
|
10497
|
+
const execFallback = () => {
|
|
10498
|
+
try {
|
|
10499
|
+
if (typeof document !== "undefined") {
|
|
10500
|
+
const ta = document.createElement("textarea");
|
|
10501
|
+
ta.value = text;
|
|
10502
|
+
ta.style.position = "fixed";
|
|
10503
|
+
ta.style.opacity = "0";
|
|
10504
|
+
document.body.appendChild(ta);
|
|
10505
|
+
ta.focus();
|
|
10506
|
+
ta.select();
|
|
10507
|
+
document.execCommand("copy");
|
|
10508
|
+
document.body.removeChild(ta);
|
|
10509
|
+
}
|
|
10510
|
+
} catch {
|
|
10511
|
+
}
|
|
10512
|
+
};
|
|
10513
|
+
try {
|
|
10514
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text).catch(execFallback);
|
|
10515
|
+
} catch {
|
|
10516
|
+
}
|
|
10517
|
+
execFallback();
|
|
10518
|
+
return Promise.resolve();
|
|
10519
|
+
};
|
|
10520
|
+
const writeCellPatches = (patchByKey) => {
|
|
10521
|
+
if (!patchByKey.size) return 0;
|
|
10522
|
+
const changed = [];
|
|
10523
|
+
const nextAll = rows.map((r2, i) => {
|
|
10524
|
+
const k = keyOf(r2, i);
|
|
10525
|
+
const patch = patchByKey.get(k);
|
|
10526
|
+
if (!patch) return r2;
|
|
10527
|
+
const updated = { ...r2, ...patch };
|
|
10528
|
+
changed.push([updated, r2, patch]);
|
|
10529
|
+
return updated;
|
|
10530
|
+
});
|
|
10531
|
+
changed.forEach(([updated, orig, patch]) => {
|
|
10532
|
+
for (const f of Object.keys(patch)) onRowUpdate?.(updated, orig, f);
|
|
10533
|
+
});
|
|
10534
|
+
if (onRowsChange && changed.length) onRowsChange(nextAll);
|
|
10535
|
+
return changed.length;
|
|
10536
|
+
};
|
|
10537
|
+
const rectCells = () => {
|
|
10538
|
+
if (!cellRect) return null;
|
|
10539
|
+
const out = [];
|
|
10540
|
+
for (let r2 = cellRect.r0; r2 <= cellRect.r1; r2++) {
|
|
10541
|
+
const row = leafRows[r2];
|
|
10542
|
+
if (!row) continue;
|
|
10543
|
+
const line = [];
|
|
10544
|
+
for (let c = cellRect.c0; c <= cellRect.c1; c++) {
|
|
10545
|
+
const col = ordered[c];
|
|
10546
|
+
if (col) line.push({ key: keyOf(row, r2), field: col.field, col, value: getColVal(col, row) });
|
|
10547
|
+
}
|
|
10548
|
+
out.push(line);
|
|
10549
|
+
}
|
|
10550
|
+
return out.length ? out : null;
|
|
10551
|
+
};
|
|
10552
|
+
const copySelection = (cut) => {
|
|
10553
|
+
const grid = rectCells();
|
|
10554
|
+
if (!grid) return;
|
|
10555
|
+
const tsv = grid.map((line) => line.map((cell) => cell.value == null ? "" : String(cell.value)).join(" ")).join("\n");
|
|
10556
|
+
clipboardRef.current = { tsv, colTypes: grid[0].map((cell) => copyTypeOf(cell.col)), matrix: grid.map((line) => line.map((cell) => cell.value)) };
|
|
10557
|
+
writeClipboard(tsv);
|
|
10558
|
+
const n = grid.reduce((a, line) => a + line.length, 0);
|
|
10559
|
+
if (cut) {
|
|
10560
|
+
const patchByKey = /* @__PURE__ */ new Map();
|
|
10561
|
+
let clearable = 0;
|
|
10562
|
+
for (const line of grid) for (const cell of line) {
|
|
10563
|
+
if (!isColEditable(cell.col)) continue;
|
|
10564
|
+
clearable++;
|
|
10565
|
+
const p = patchByKey.get(cell.key) || {};
|
|
10566
|
+
p[cell.field] = cell.col.type === "number" ? null : "";
|
|
10567
|
+
patchByKey.set(cell.key, p);
|
|
10568
|
+
}
|
|
10569
|
+
writeCellPatches(patchByKey);
|
|
10570
|
+
setClipboardMsg(`Cut ${clearable} cell${clearable === 1 ? "" : "s"}${clearable < n ? `, ${n - clearable} read-only kept` : ""}`);
|
|
10571
|
+
} else {
|
|
10572
|
+
setClipboardMsg(`Copied ${n} cell${n === 1 ? "" : "s"}`);
|
|
10573
|
+
}
|
|
10574
|
+
};
|
|
10575
|
+
const pasteSelection = async () => {
|
|
10576
|
+
if (!cellRect) return;
|
|
10577
|
+
let text = null;
|
|
10578
|
+
try {
|
|
10579
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) text = await navigator.clipboard.readText();
|
|
10580
|
+
} catch {
|
|
10581
|
+
setClipboardMsg("Paste unavailable \u2014 clipboard permission denied");
|
|
10582
|
+
return;
|
|
10583
|
+
}
|
|
10584
|
+
if (text == null) {
|
|
10585
|
+
setClipboardMsg("Nothing to paste");
|
|
10586
|
+
return;
|
|
10587
|
+
}
|
|
10588
|
+
const inApp = clipboardRef.current && clipboardRef.current.tsv === text ? clipboardRef.current : null;
|
|
10589
|
+
let matrix;
|
|
10590
|
+
if (inApp) matrix = inApp.matrix;
|
|
10591
|
+
else {
|
|
10592
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
10593
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
10594
|
+
matrix = lines.map((line) => line.split(" "));
|
|
10595
|
+
}
|
|
10596
|
+
const patchByKey = /* @__PURE__ */ new Map();
|
|
10597
|
+
let written = 0, skipped = 0;
|
|
10598
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
10599
|
+
const rr = cellRect.r0 + i;
|
|
10600
|
+
const row = leafRows[rr];
|
|
10601
|
+
if (!row) break;
|
|
10602
|
+
for (let j = 0; j < matrix[i].length; j++) {
|
|
10603
|
+
const cc = cellRect.c0 + j;
|
|
10604
|
+
const col = ordered[cc];
|
|
10605
|
+
if (!col) break;
|
|
10606
|
+
if (!isColEditable(col)) {
|
|
10607
|
+
skipped++;
|
|
10608
|
+
continue;
|
|
10609
|
+
}
|
|
10610
|
+
if (inApp && inApp.colTypes[j] != null && inApp.colTypes[j] !== copyTypeOf(col)) {
|
|
10611
|
+
skipped++;
|
|
10612
|
+
continue;
|
|
10613
|
+
}
|
|
10614
|
+
let v = matrix[i][j];
|
|
10615
|
+
if (col.type === "number") {
|
|
10616
|
+
v = v === "" ? null : Number(v);
|
|
10617
|
+
if (Number.isNaN(v)) v = null;
|
|
10618
|
+
}
|
|
10619
|
+
const k = keyOf(row, rr);
|
|
10620
|
+
const p = patchByKey.get(k) || {};
|
|
10621
|
+
p[col.field] = v;
|
|
10622
|
+
patchByKey.set(k, p);
|
|
10623
|
+
written++;
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
writeCellPatches(patchByKey);
|
|
10627
|
+
setClipboardMsg(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`);
|
|
10628
|
+
};
|
|
10422
10629
|
React51__default.useEffect(() => {
|
|
10423
10630
|
if (!editing) return;
|
|
10424
10631
|
const onDown = (e) => {
|
|
@@ -10675,6 +10882,7 @@ function Datatable({
|
|
|
10675
10882
|
if (node) {
|
|
10676
10883
|
node.style.removeProperty("--twc-dt-pop-w");
|
|
10677
10884
|
node.style.removeProperty("--twc-dt-pop-h");
|
|
10885
|
+
node.style.removeProperty("left");
|
|
10678
10886
|
node.removeAttribute("data-pop-sized");
|
|
10679
10887
|
}
|
|
10680
10888
|
setPopSizes((s) => {
|
|
@@ -10684,7 +10892,7 @@ function Datatable({
|
|
|
10684
10892
|
return n;
|
|
10685
10893
|
});
|
|
10686
10894
|
};
|
|
10687
|
-
const startPopResize = (id) => (e) => {
|
|
10895
|
+
const startPopResize = (id, side) => (e) => {
|
|
10688
10896
|
if (e.button != null && e.button !== 0) return;
|
|
10689
10897
|
e.preventDefault();
|
|
10690
10898
|
e.stopPropagation();
|
|
@@ -10693,21 +10901,27 @@ function Datatable({
|
|
|
10693
10901
|
const grip = e.currentTarget;
|
|
10694
10902
|
const startX = e.clientX, startY = e.clientY;
|
|
10695
10903
|
const r2 = pop.getBoundingClientRect();
|
|
10696
|
-
const startW = r2.width, startH = r2.height;
|
|
10904
|
+
const startW = r2.width, startH = r2.height, startLeft = r2.left, startRight = r2.right;
|
|
10697
10905
|
const dir = typeof getComputedStyle !== "undefined" && getComputedStyle(pop).direction === "rtl" ? -1 : 1;
|
|
10906
|
+
const toStart = side === "start";
|
|
10698
10907
|
pop.style.setProperty("--twc-dt-pop-w", `${startW}px`);
|
|
10699
10908
|
pop.style.setProperty("--twc-dt-pop-h", `${startH}px`);
|
|
10700
10909
|
grip.setAttribute("data-active", "true");
|
|
10701
10910
|
pop.setAttribute("data-pop-sized", "");
|
|
10702
|
-
let lw = startW, lh = startH;
|
|
10911
|
+
let lw = startW, lh = startH, ll = startLeft;
|
|
10703
10912
|
const onMove = (ev) => {
|
|
10704
|
-
|
|
10913
|
+
const dw = toStart ? (startX - ev.clientX) * dir : (ev.clientX - startX) * dir;
|
|
10914
|
+
lw = clampNum(startW + dw, popMinW(id), fMaxW());
|
|
10705
10915
|
lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
|
|
10706
10916
|
pop.style.setProperty("--twc-dt-pop-w", `${lw}px`);
|
|
10707
10917
|
pop.style.setProperty("--twc-dt-pop-h", `${lh}px`);
|
|
10918
|
+
if (toStart) {
|
|
10919
|
+
ll = Math.max(8, startRight - lw);
|
|
10920
|
+
pop.style.left = `${ll}px`;
|
|
10921
|
+
}
|
|
10708
10922
|
};
|
|
10709
10923
|
const onUp = () => {
|
|
10710
|
-
setPopSizes((s) => ({ ...s, [id]: { w: lw, h: lh } }));
|
|
10924
|
+
setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w: lw, h: lh, ...toStart ? { left: ll } : {} } }));
|
|
10711
10925
|
grip.removeAttribute("data-active");
|
|
10712
10926
|
window.removeEventListener("pointermove", onMove);
|
|
10713
10927
|
window.removeEventListener("pointerup", onUp);
|
|
@@ -10715,7 +10929,7 @@ function Datatable({
|
|
|
10715
10929
|
window.addEventListener("pointermove", onMove);
|
|
10716
10930
|
window.addEventListener("pointerup", onUp);
|
|
10717
10931
|
};
|
|
10718
|
-
const onPopGripKey = (id) => (e) => {
|
|
10932
|
+
const onPopGripKey = (id, side) => (e) => {
|
|
10719
10933
|
if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
|
|
10720
10934
|
e.preventDefault();
|
|
10721
10935
|
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
@@ -10740,25 +10954,54 @@ function Datatable({
|
|
|
10740
10954
|
w = fMaxW();
|
|
10741
10955
|
h = fMaxH();
|
|
10742
10956
|
}
|
|
10743
|
-
|
|
10957
|
+
w = clampNum(w, popMinW(id), fMaxW());
|
|
10958
|
+
h = clampNum(h, 120, fMaxH());
|
|
10959
|
+
const left = side === "start" ? Math.max(8, r2.right - w) : void 0;
|
|
10960
|
+
setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w, h, ...left != null ? { left } : {} } }));
|
|
10744
10961
|
};
|
|
10745
|
-
const popStyle = (id) =>
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
{
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10962
|
+
const popStyle = (id) => {
|
|
10963
|
+
const s = popSizes[id];
|
|
10964
|
+
if (!s) return null;
|
|
10965
|
+
const st = { "--twc-dt-pop-w": `${s.w}px`, "--twc-dt-pop-h": `${s.h}px` };
|
|
10966
|
+
if (s.left != null) st.left = `${s.left}px`;
|
|
10967
|
+
return st;
|
|
10968
|
+
};
|
|
10969
|
+
const popGrip = (id) => resizablePopovers ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10970
|
+
/* @__PURE__ */ jsx(
|
|
10971
|
+
"span",
|
|
10972
|
+
{
|
|
10973
|
+
className: "twc-dt__pop-grip",
|
|
10974
|
+
"data-side": "end",
|
|
10975
|
+
role: "slider",
|
|
10976
|
+
tabIndex: 0,
|
|
10977
|
+
"aria-label": "Resize panel (Enter or double-click to reset)",
|
|
10978
|
+
"aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
|
|
10979
|
+
onPointerDown: startPopResize(id, "end"),
|
|
10980
|
+
onKeyDown: onPopGripKey(id, "end"),
|
|
10981
|
+
onDoubleClick: (e) => {
|
|
10982
|
+
e.preventDefault();
|
|
10983
|
+
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
10984
|
+
}
|
|
10759
10985
|
}
|
|
10760
|
-
|
|
10761
|
-
|
|
10986
|
+
),
|
|
10987
|
+
/* @__PURE__ */ jsx(
|
|
10988
|
+
"span",
|
|
10989
|
+
{
|
|
10990
|
+
className: "twc-dt__pop-grip",
|
|
10991
|
+
"data-side": "start",
|
|
10992
|
+
role: "slider",
|
|
10993
|
+
tabIndex: 0,
|
|
10994
|
+
"aria-label": "Resize panel leftward (Enter or double-click to reset)",
|
|
10995
|
+
"aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
|
|
10996
|
+
onPointerDown: startPopResize(id, "start"),
|
|
10997
|
+
onKeyDown: onPopGripKey(id, "start"),
|
|
10998
|
+
onDoubleClick: (e) => {
|
|
10999
|
+
e.preventDefault();
|
|
11000
|
+
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
11001
|
+
}
|
|
11002
|
+
}
|
|
11003
|
+
)
|
|
11004
|
+
] }) : null;
|
|
10762
11005
|
const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsx(
|
|
10763
11006
|
"span",
|
|
10764
11007
|
{
|
|
@@ -10914,22 +11157,27 @@ function Datatable({
|
|
|
10914
11157
|
const editable = isColEditable(c);
|
|
10915
11158
|
const isEditing = editing && editing.key === k && editing.field === c.field;
|
|
10916
11159
|
const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
|
|
11160
|
+
const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
|
|
11161
|
+
const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
|
|
10917
11162
|
const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
|
|
10918
11163
|
const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
|
|
10919
11164
|
return /* @__PURE__ */ jsxs(
|
|
10920
11165
|
"td",
|
|
10921
11166
|
{
|
|
11167
|
+
id: cellId,
|
|
10922
11168
|
className: "twc-dt__td",
|
|
10923
11169
|
role: "gridcell",
|
|
10924
11170
|
"data-r": ri,
|
|
10925
11171
|
"data-c": ci,
|
|
10926
11172
|
"aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
|
|
10927
11173
|
tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
|
|
11174
|
+
"aria-selected": cellSelected || void 0,
|
|
10928
11175
|
"data-num": c.type === "number" || void 0,
|
|
10929
11176
|
"data-actions": isActions || void 0,
|
|
10930
11177
|
"data-editable": editable && !isEditing || void 0,
|
|
10931
11178
|
"data-editing": isEditing || void 0,
|
|
10932
11179
|
"data-cell-active": cellActive || void 0,
|
|
11180
|
+
"data-cell-selected": cellSelected || void 0,
|
|
10933
11181
|
"data-wrap": wrapped.has(c.field) || void 0,
|
|
10934
11182
|
"data-pin": st.pin,
|
|
10935
11183
|
"data-pin-edge": st.edge,
|
|
@@ -11291,6 +11539,7 @@ function Datatable({
|
|
|
11291
11539
|
/* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__groupbar-clear", onClick: () => setGroupBy([]), children: "Clear all" })
|
|
11292
11540
|
] }) : null,
|
|
11293
11541
|
/* @__PURE__ */ jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: reorderMsg }),
|
|
11542
|
+
/* @__PURE__ */ jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: clipboardMsg }),
|
|
11294
11543
|
pivotActive ? renderPivot() : /* @__PURE__ */ jsx(
|
|
11295
11544
|
"div",
|
|
11296
11545
|
{
|
|
@@ -11312,6 +11561,8 @@ function Datatable({
|
|
|
11312
11561
|
"aria-label": ariaLabelAttr || ariaLabel,
|
|
11313
11562
|
"aria-rowcount": totalRows + 1,
|
|
11314
11563
|
"aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
|
|
11564
|
+
"aria-multiselectable": selectionMode === "cell" || void 0,
|
|
11565
|
+
"aria-activedescendant": activeCellId,
|
|
11315
11566
|
"aria-busy": loading || void 0,
|
|
11316
11567
|
onKeyDown: onGridKeyDown,
|
|
11317
11568
|
children: [
|