twico-ui 1.24.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 +19 -0
- package/dist/index.cjs +282 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.mjs +282 -28
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -1825,8 +1825,9 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1825
1825
|
resizableFilters?: boolean;
|
|
1826
1826
|
/** #304: extend the drag-resize grip to the other toolbar popovers too — Columns, Aggregation, Pivot,
|
|
1827
1827
|
* and Batch-edit — each with a corner grip + keyboard resize (Arrows, Home/End, Enter/double-click to
|
|
1828
|
-
* reset).
|
|
1829
|
-
*
|
|
1828
|
+
* reset). Independent of `resizableFilters` (which controls the Filters panel): each defaults `true` and
|
|
1829
|
+
* can be turned off on its own. Each popover's size persists independently via `stateKey` (`popoverSizes`).
|
|
1830
|
+
* Great for a wide grid whose Columns list is otherwise capped. Set `false` to opt a grid out. @default true */
|
|
1830
1831
|
resizablePopovers?: boolean;
|
|
1831
1832
|
/** Raise the drag cap for the filter panel's column/operator fields, in px (default col 360 / op 260) —
|
|
1832
1833
|
* the #289-deferred "optional prop to raise the cap". Only affects `resizableFilters`. */
|
|
@@ -1937,7 +1938,10 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1937
1938
|
onStateChange?: (state: DatatableState) => void;
|
|
1938
1939
|
/** Show a "Go to" page jumper in the footer when there are more than 5 pages. @default true */
|
|
1939
1940
|
showPageJumper?: boolean;
|
|
1940
|
-
/** 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" */
|
|
1941
1945
|
selectionMode?: "none" | "row" | "cell";
|
|
1942
1946
|
/** Fired when a row is clicked in "row" selection mode: (row, key). */
|
|
1943
1947
|
onRowClick?: (row: T, key: string | number) => void;
|
|
@@ -1945,6 +1949,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1945
1949
|
onCellClick?: (value: any, row: T, field: string) => void;
|
|
1946
1950
|
/** Fired when the active cell changes: ({ key, field } | null). */
|
|
1947
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;
|
|
1948
1961
|
/** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
|
|
1949
1962
|
* toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
|
|
1950
1963
|
* changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
|
|
@@ -2156,6 +2169,11 @@ interface DatatableColumn<T = any> {
|
|
|
2156
2169
|
loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
|
|
2157
2170
|
/** Format the raw value to a string/number for display. */
|
|
2158
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;
|
|
2159
2177
|
/** Fully custom cell renderer (badges, avatars, etc.). */
|
|
2160
2178
|
renderCell?: (value: any, row: T) => React.ReactNode;
|
|
2161
2179
|
/** Diff-mode (#239) per-column equality override — return true when the two values are equal.
|
package/dist/index.d.ts
CHANGED
|
@@ -1825,8 +1825,9 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1825
1825
|
resizableFilters?: boolean;
|
|
1826
1826
|
/** #304: extend the drag-resize grip to the other toolbar popovers too — Columns, Aggregation, Pivot,
|
|
1827
1827
|
* and Batch-edit — each with a corner grip + keyboard resize (Arrows, Home/End, Enter/double-click to
|
|
1828
|
-
* reset).
|
|
1829
|
-
*
|
|
1828
|
+
* reset). Independent of `resizableFilters` (which controls the Filters panel): each defaults `true` and
|
|
1829
|
+
* can be turned off on its own. Each popover's size persists independently via `stateKey` (`popoverSizes`).
|
|
1830
|
+
* Great for a wide grid whose Columns list is otherwise capped. Set `false` to opt a grid out. @default true */
|
|
1830
1831
|
resizablePopovers?: boolean;
|
|
1831
1832
|
/** Raise the drag cap for the filter panel's column/operator fields, in px (default col 360 / op 260) —
|
|
1832
1833
|
* the #289-deferred "optional prop to raise the cap". Only affects `resizableFilters`. */
|
|
@@ -1937,7 +1938,10 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1937
1938
|
onStateChange?: (state: DatatableState) => void;
|
|
1938
1939
|
/** Show a "Go to" page jumper in the footer when there are more than 5 pages. @default true */
|
|
1939
1940
|
showPageJumper?: boolean;
|
|
1940
|
-
/** 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" */
|
|
1941
1945
|
selectionMode?: "none" | "row" | "cell";
|
|
1942
1946
|
/** Fired when a row is clicked in "row" selection mode: (row, key). */
|
|
1943
1947
|
onRowClick?: (row: T, key: string | number) => void;
|
|
@@ -1945,6 +1949,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1945
1949
|
onCellClick?: (value: any, row: T, field: string) => void;
|
|
1946
1950
|
/** Fired when the active cell changes: ({ key, field } | null). */
|
|
1947
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;
|
|
1948
1961
|
/** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
|
|
1949
1962
|
* toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
|
|
1950
1963
|
* changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
|
|
@@ -2156,6 +2169,11 @@ interface DatatableColumn<T = any> {
|
|
|
2156
2169
|
loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
|
|
2157
2170
|
/** Format the raw value to a string/number for display. */
|
|
2158
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;
|
|
2159
2177
|
/** Fully custom cell renderer (badges, avatars, etc.). */
|
|
2160
2178
|
renderCell?: (value: any, row: T) => React.ReactNode;
|
|
2161
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,
|
|
@@ -9159,7 +9166,8 @@ function Datatable({
|
|
|
9159
9166
|
onQuickFilterChange,
|
|
9160
9167
|
toolbarActions,
|
|
9161
9168
|
resizableFilters = true,
|
|
9162
|
-
resizablePopovers =
|
|
9169
|
+
resizablePopovers = true,
|
|
9170
|
+
// #314: default-on (matches resizableFilters) — set false to opt a grid out
|
|
9163
9171
|
filterFieldMaxWidth,
|
|
9164
9172
|
rowPinning = false,
|
|
9165
9173
|
rowReorder = false,
|
|
@@ -9277,6 +9285,8 @@ function Datatable({
|
|
|
9277
9285
|
const [selected, setSelected] = React51__default.useState(() => /* @__PURE__ */ new Set());
|
|
9278
9286
|
const [activeRow, setActiveRow] = React51__default.useState(null);
|
|
9279
9287
|
const [activeCell, setActiveCell] = React51__default.useState(null);
|
|
9288
|
+
const [anchorCell, setAnchorCell] = React51__default.useState(null);
|
|
9289
|
+
const gridId = React51__default.useId();
|
|
9280
9290
|
const [internalPage, setInternalPage] = React51__default.useState(0);
|
|
9281
9291
|
const [internalRpp, setInternalRpp] = React51__default.useState(pageSize > 0 ? pageSize : 10);
|
|
9282
9292
|
const pageControlled = page !== void 0;
|
|
@@ -9376,6 +9386,7 @@ function Datatable({
|
|
|
9376
9386
|
for (const [id, sz] of Object.entries(s.popoverSizes)) {
|
|
9377
9387
|
if (!sz || typeof sz !== "object" || typeof sz.w !== "number" || typeof sz.h !== "number") continue;
|
|
9378
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());
|
|
9379
9390
|
}
|
|
9380
9391
|
if (Object.keys(next).length) setPopSizes(next);
|
|
9381
9392
|
}
|
|
@@ -9827,6 +9838,38 @@ function Datatable({
|
|
|
9827
9838
|
middleRows.forEach((r2, i) => m.set(keyOf(r2, keyIndex.get(keyOf(r2)) ?? i), i));
|
|
9828
9839
|
return m;
|
|
9829
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]);
|
|
9830
9873
|
const canReorderRows = rowReorder && !activeGroupBy.length && !sort;
|
|
9831
9874
|
function onRowDrop(targetKey) {
|
|
9832
9875
|
const fromKey = rowDrag.from;
|
|
@@ -10239,6 +10282,7 @@ function Datatable({
|
|
|
10239
10282
|
const next = { key: k, field: col.field };
|
|
10240
10283
|
setActiveCell(next);
|
|
10241
10284
|
setActiveRow(k);
|
|
10285
|
+
if (!(e.shiftKey && anchorCell)) setAnchorCell(next);
|
|
10242
10286
|
onCellClick?.(row[col.field], row, col.field);
|
|
10243
10287
|
onActiveCellChange?.(next);
|
|
10244
10288
|
}
|
|
@@ -10250,6 +10294,24 @@ function Datatable({
|
|
|
10250
10294
|
if (editing) return;
|
|
10251
10295
|
const td = e.target.closest(".twc-dt__td[data-r]");
|
|
10252
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
|
+
}
|
|
10253
10315
|
let r2 = +td.getAttribute("data-r"), c = +td.getAttribute("data-c");
|
|
10254
10316
|
const maxR = leafRows.length - 1, maxC = ordered.length - 1;
|
|
10255
10317
|
let handled = true;
|
|
@@ -10302,6 +10364,16 @@ function Datatable({
|
|
|
10302
10364
|
e.preventDefault();
|
|
10303
10365
|
setFocus({ r: r2, c });
|
|
10304
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
|
+
}
|
|
10305
10377
|
}
|
|
10306
10378
|
}
|
|
10307
10379
|
React51__default.useEffect(() => {
|
|
@@ -10418,11 +10490,149 @@ function Datatable({
|
|
|
10418
10490
|
setBatchEdit(null);
|
|
10419
10491
|
closeBatchEdit();
|
|
10420
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
|
+
};
|
|
10421
10629
|
React51__default.useEffect(() => {
|
|
10422
10630
|
if (!editing) return;
|
|
10423
10631
|
const onDown = (e) => {
|
|
10424
10632
|
if (e.target.closest(".twc-dt__editor-wrap") || e.target.closest(".twc-pop")) return;
|
|
10425
|
-
|
|
10633
|
+
const col = colByField[editing.field];
|
|
10634
|
+
if (col && col.renderEditCell) setEditing(null);
|
|
10635
|
+
else commitEdit();
|
|
10426
10636
|
};
|
|
10427
10637
|
document.addEventListener("mousedown", onDown, true);
|
|
10428
10638
|
return () => document.removeEventListener("mousedown", onDown, true);
|
|
@@ -10672,6 +10882,7 @@ function Datatable({
|
|
|
10672
10882
|
if (node) {
|
|
10673
10883
|
node.style.removeProperty("--twc-dt-pop-w");
|
|
10674
10884
|
node.style.removeProperty("--twc-dt-pop-h");
|
|
10885
|
+
node.style.removeProperty("left");
|
|
10675
10886
|
node.removeAttribute("data-pop-sized");
|
|
10676
10887
|
}
|
|
10677
10888
|
setPopSizes((s) => {
|
|
@@ -10681,7 +10892,7 @@ function Datatable({
|
|
|
10681
10892
|
return n;
|
|
10682
10893
|
});
|
|
10683
10894
|
};
|
|
10684
|
-
const startPopResize = (id) => (e) => {
|
|
10895
|
+
const startPopResize = (id, side) => (e) => {
|
|
10685
10896
|
if (e.button != null && e.button !== 0) return;
|
|
10686
10897
|
e.preventDefault();
|
|
10687
10898
|
e.stopPropagation();
|
|
@@ -10690,21 +10901,27 @@ function Datatable({
|
|
|
10690
10901
|
const grip = e.currentTarget;
|
|
10691
10902
|
const startX = e.clientX, startY = e.clientY;
|
|
10692
10903
|
const r2 = pop.getBoundingClientRect();
|
|
10693
|
-
const startW = r2.width, startH = r2.height;
|
|
10904
|
+
const startW = r2.width, startH = r2.height, startLeft = r2.left, startRight = r2.right;
|
|
10694
10905
|
const dir = typeof getComputedStyle !== "undefined" && getComputedStyle(pop).direction === "rtl" ? -1 : 1;
|
|
10906
|
+
const toStart = side === "start";
|
|
10695
10907
|
pop.style.setProperty("--twc-dt-pop-w", `${startW}px`);
|
|
10696
10908
|
pop.style.setProperty("--twc-dt-pop-h", `${startH}px`);
|
|
10697
10909
|
grip.setAttribute("data-active", "true");
|
|
10698
10910
|
pop.setAttribute("data-pop-sized", "");
|
|
10699
|
-
let lw = startW, lh = startH;
|
|
10911
|
+
let lw = startW, lh = startH, ll = startLeft;
|
|
10700
10912
|
const onMove = (ev) => {
|
|
10701
|
-
|
|
10913
|
+
const dw = toStart ? (startX - ev.clientX) * dir : (ev.clientX - startX) * dir;
|
|
10914
|
+
lw = clampNum(startW + dw, popMinW(id), fMaxW());
|
|
10702
10915
|
lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
|
|
10703
10916
|
pop.style.setProperty("--twc-dt-pop-w", `${lw}px`);
|
|
10704
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
|
+
}
|
|
10705
10922
|
};
|
|
10706
10923
|
const onUp = () => {
|
|
10707
|
-
setPopSizes((s) => ({ ...s, [id]: { w: lw, h: lh } }));
|
|
10924
|
+
setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w: lw, h: lh, ...toStart ? { left: ll } : {} } }));
|
|
10708
10925
|
grip.removeAttribute("data-active");
|
|
10709
10926
|
window.removeEventListener("pointermove", onMove);
|
|
10710
10927
|
window.removeEventListener("pointerup", onUp);
|
|
@@ -10712,7 +10929,7 @@ function Datatable({
|
|
|
10712
10929
|
window.addEventListener("pointermove", onMove);
|
|
10713
10930
|
window.addEventListener("pointerup", onUp);
|
|
10714
10931
|
};
|
|
10715
|
-
const onPopGripKey = (id) => (e) => {
|
|
10932
|
+
const onPopGripKey = (id, side) => (e) => {
|
|
10716
10933
|
if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
|
|
10717
10934
|
e.preventDefault();
|
|
10718
10935
|
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
@@ -10737,26 +10954,55 @@ function Datatable({
|
|
|
10737
10954
|
w = fMaxW();
|
|
10738
10955
|
h = fMaxH();
|
|
10739
10956
|
}
|
|
10740
|
-
|
|
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 } : {} } }));
|
|
10741
10961
|
};
|
|
10742
|
-
const popStyle = (id) =>
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
{
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
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
|
+
}
|
|
10756
10985
|
}
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
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;
|
|
11005
|
+
const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsx(
|
|
10760
11006
|
"span",
|
|
10761
11007
|
{
|
|
10762
11008
|
className: "twc-dt__f-rz",
|
|
@@ -10911,22 +11157,27 @@ function Datatable({
|
|
|
10911
11157
|
const editable = isColEditable(c);
|
|
10912
11158
|
const isEditing = editing && editing.key === k && editing.field === c.field;
|
|
10913
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;
|
|
10914
11162
|
const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
|
|
10915
11163
|
const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
|
|
10916
11164
|
return /* @__PURE__ */ jsxs(
|
|
10917
11165
|
"td",
|
|
10918
11166
|
{
|
|
11167
|
+
id: cellId,
|
|
10919
11168
|
className: "twc-dt__td",
|
|
10920
11169
|
role: "gridcell",
|
|
10921
11170
|
"data-r": ri,
|
|
10922
11171
|
"data-c": ci,
|
|
10923
11172
|
"aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
|
|
10924
11173
|
tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
|
|
11174
|
+
"aria-selected": cellSelected || void 0,
|
|
10925
11175
|
"data-num": c.type === "number" || void 0,
|
|
10926
11176
|
"data-actions": isActions || void 0,
|
|
10927
11177
|
"data-editable": editable && !isEditing || void 0,
|
|
10928
11178
|
"data-editing": isEditing || void 0,
|
|
10929
11179
|
"data-cell-active": cellActive || void 0,
|
|
11180
|
+
"data-cell-selected": cellSelected || void 0,
|
|
10930
11181
|
"data-wrap": wrapped.has(c.field) || void 0,
|
|
10931
11182
|
"data-pin": st.pin,
|
|
10932
11183
|
"data-pin-edge": st.edge,
|
|
@@ -11288,6 +11539,7 @@ function Datatable({
|
|
|
11288
11539
|
/* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__groupbar-clear", onClick: () => setGroupBy([]), children: "Clear all" })
|
|
11289
11540
|
] }) : null,
|
|
11290
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 }),
|
|
11291
11543
|
pivotActive ? renderPivot() : /* @__PURE__ */ jsx(
|
|
11292
11544
|
"div",
|
|
11293
11545
|
{
|
|
@@ -11309,6 +11561,8 @@ function Datatable({
|
|
|
11309
11561
|
"aria-label": ariaLabelAttr || ariaLabel,
|
|
11310
11562
|
"aria-rowcount": totalRows + 1,
|
|
11311
11563
|
"aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
|
|
11564
|
+
"aria-multiselectable": selectionMode === "cell" || void 0,
|
|
11565
|
+
"aria-activedescendant": activeCellId,
|
|
11312
11566
|
"aria-busy": loading || void 0,
|
|
11313
11567
|
onKeyDown: onGridKeyDown,
|
|
11314
11568
|
children: [
|
|
@@ -12170,7 +12424,7 @@ function Datatable({
|
|
|
12170
12424
|
/* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "and" || void 0, "aria-pressed": filterLogic === "and", onClick: () => setFilterLogic("and"), children: "AND" }),
|
|
12171
12425
|
/* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "or" || void 0, "aria-pressed": filterLogic === "or", onClick: () => setFilterLogic("or"), children: "OR" })
|
|
12172
12426
|
] }) : null,
|
|
12173
|
-
|
|
12427
|
+
resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
|
|
12174
12428
|
filters.length ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
|
|
12175
12429
|
] })
|
|
12176
12430
|
] }),
|
|
@@ -12250,7 +12504,7 @@ function Datatable({
|
|
|
12250
12504
|
/* @__PURE__ */ jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
|
|
12251
12505
|
" Add filter"
|
|
12252
12506
|
] }) }),
|
|
12253
|
-
resizableFilters
|
|
12507
|
+
resizableFilters ? /* @__PURE__ */ jsx(
|
|
12254
12508
|
"span",
|
|
12255
12509
|
{
|
|
12256
12510
|
className: "twc-dt__pop-grip",
|