twico-ui 1.25.0 → 1.27.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 +14 -0
- package/dist/index.cjs +306 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.mjs +306 -24
- package/dist/index.mjs.map +1 -1
- package/llms.txt +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
|
|
4
4
|
|
|
5
|
+
# [1.27.0](https://github.com/EncryptionL/twico-ui/compare/v1.26.0...v1.27.0) (2026-08-03)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **datatable:** visible clipboard copy/cut feedback + onCellsCopy/onCellsPaste ([#320](https://github.com/EncryptionL/twico-ui/issues/320)) ([cff0aaa](https://github.com/EncryptionL/twico-ui/commit/cff0aaa1d31241c3772bc7c38c975d5033a9e6f7))
|
|
11
|
+
|
|
12
|
+
# [1.26.0](https://github.com/EncryptionL/twico-ui/compare/v1.25.0...v1.26.0) (2026-08-03)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **datatable:** bottom-left popover grip, cell range selection, keyboard clipboard ([#316](https://github.com/EncryptionL/twico-ui/issues/316), [#317](https://github.com/EncryptionL/twico-ui/issues/317), [#318](https://github.com/EncryptionL/twico-ui/issues/318)) ([0c1a583](https://github.com/EncryptionL/twico-ui/commit/0c1a583ef8b983410547f7f1fb92f8660ec926c3))
|
|
18
|
+
|
|
5
19
|
# [1.25.0](https://github.com/EncryptionL/twico-ui/compare/v1.24.0...v1.25.0) (2026-08-03)
|
|
6
20
|
|
|
7
21
|
|
package/dist/index.cjs
CHANGED
|
@@ -8159,7 +8159,14 @@ var DT_CSS = `
|
|
|
8159
8159
|
/* Stay inside the parent even in a flex/grid container: min-width:0 lets a wide
|
|
8160
8160
|
grid shrink (default min-width:auto would force it to its content width and
|
|
8161
8161
|
overflow), so the internal __scroll handles horizontal scrolling. */
|
|
8162
|
-
width: 100%; min-width: 0; max-width: 100%; }
|
|
8162
|
+
width: 100%; min-width: 0; max-width: 100%; position: relative; }
|
|
8163
|
+
/* #320: visible clipboard feedback \u2014 a transient in-grid toast + a brief flash on the copied/pasted cells,
|
|
8164
|
+
alongside the SR-only aria-live message (position: relative on the root anchors the toast; it does NOT
|
|
8165
|
+
create a containing block for the position: fixed popovers, so those stay viewport-anchored). */
|
|
8166
|
+
.twc-dt__clip-toast { position: absolute; inset-block-end: 12px; inset-inline-end: 12px; z-index: 6; background: var(--color-text); color: var(--color-surface); font-size: var(--text-xs); font-weight: var(--font-semibold); padding: 6px 12px; border-radius: var(--radius-md); box-shadow: var(--shadow-md); pointer-events: none; }
|
|
8167
|
+
@keyframes twc-dt-copyflash { from { box-shadow: inset 0 0 0 2px var(--color-primary); } to { box-shadow: inset 0 0 0 2px transparent; } }
|
|
8168
|
+
.twc-dt__td[data-copied="true"] { animation: twc-dt-copyflash 1.3s var(--ease-standard); }
|
|
8169
|
+
@media (prefers-reduced-motion: reduce) { .twc-dt__td[data-copied="true"] { animation: none; } }
|
|
8163
8170
|
|
|
8164
8171
|
/* Toolbar */
|
|
8165
8172
|
.twc-dt__toolbar { position: relative; z-index: 10; display: flex; align-items: center; gap: 6px; padding: 8px 10px;
|
|
@@ -8461,6 +8468,8 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8461
8468
|
.twc-dt__row[data-selectable="true"] { cursor: pointer; }
|
|
8462
8469
|
.twc-dt__row[data-active="true"] .twc-dt__td { background: var(--color-primary-subtle); box-shadow: inset 0 0 0 9999px transparent; }
|
|
8463
8470
|
.twc-dt__row[data-active="true"] .twc-dt__td[data-pin] { background: var(--color-primary-subtle); }
|
|
8471
|
+
/* #317: data-cell-selected fills the range rectangle; data-cell-active keeps the ring on the moving corner. */
|
|
8472
|
+
.twc-dt__td[data-cell-selected="true"] { background: var(--color-primary-subtle); }
|
|
8464
8473
|
.twc-dt__td[data-cell-active="true"] { box-shadow: inset 0 0 0 2px var(--color-primary); background: var(--color-primary-subtle); }
|
|
8465
8474
|
.twc-dt__row:last-child .twc-dt__td { border-bottom: none; }
|
|
8466
8475
|
|
|
@@ -8615,6 +8624,9 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8615
8624
|
.twc-dt__pop-grip:hover, .twc-dt__pop-grip:focus-visible, .twc-dt__pop-grip[data-active="true"] { opacity: 1; }
|
|
8616
8625
|
.twc-dt__pop-grip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
|
|
8617
8626
|
[dir="rtl"] .twc-dt__pop-grip { cursor: nesw-resize; }
|
|
8627
|
+
/* #316: a second grip in the bottom-inline-start corner grows a right-anchored panel toward the empty side (right edge pinned). */
|
|
8628
|
+
.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%); }
|
|
8629
|
+
[dir="rtl"] .twc-dt__pop-grip[data-side="start"] { cursor: nwse-resize; }
|
|
8618
8630
|
@media (pointer: coarse) {
|
|
8619
8631
|
.twc-dt__f-rz { width: 16px; inset-inline-end: -6px; opacity: 0.4; }
|
|
8620
8632
|
.twc-dt__pop-grip { width: 20px; height: 20px; opacity: 0.6; }
|
|
@@ -9169,6 +9181,10 @@ function Datatable({
|
|
|
9169
9181
|
onRowClick,
|
|
9170
9182
|
onCellClick,
|
|
9171
9183
|
onActiveCellChange,
|
|
9184
|
+
onCellSelectionChange,
|
|
9185
|
+
enableClipboard = false,
|
|
9186
|
+
onCellsCopy,
|
|
9187
|
+
onCellsPaste,
|
|
9172
9188
|
showAggregation = false,
|
|
9173
9189
|
ariaLabel = "Data table",
|
|
9174
9190
|
"aria-label": ariaLabelAttr,
|
|
@@ -9299,6 +9315,8 @@ function Datatable({
|
|
|
9299
9315
|
const [selected, setSelected] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
|
|
9300
9316
|
const [activeRow, setActiveRow] = React51__namespace.default.useState(null);
|
|
9301
9317
|
const [activeCell, setActiveCell] = React51__namespace.default.useState(null);
|
|
9318
|
+
const [anchorCell, setAnchorCell] = React51__namespace.default.useState(null);
|
|
9319
|
+
const gridId = React51__namespace.default.useId();
|
|
9302
9320
|
const [internalPage, setInternalPage] = React51__namespace.default.useState(0);
|
|
9303
9321
|
const [internalRpp, setInternalRpp] = React51__namespace.default.useState(pageSize > 0 ? pageSize : 10);
|
|
9304
9322
|
const pageControlled = page !== void 0;
|
|
@@ -9398,6 +9416,7 @@ function Datatable({
|
|
|
9398
9416
|
for (const [id, sz] of Object.entries(s.popoverSizes)) {
|
|
9399
9417
|
if (!sz || typeof sz !== "object" || typeof sz.w !== "number" || typeof sz.h !== "number") continue;
|
|
9400
9418
|
next[id] = { w: clampNum(sz.w, popMinW(id), fMaxW()), h: clampNum(sz.h, 120, fMaxH()) };
|
|
9419
|
+
if (typeof sz.left === "number") next[id].left = clampNum(sz.left, 8, fMaxW());
|
|
9401
9420
|
}
|
|
9402
9421
|
if (Object.keys(next).length) setPopSizes(next);
|
|
9403
9422
|
}
|
|
@@ -9849,6 +9868,38 @@ function Datatable({
|
|
|
9849
9868
|
middleRows.forEach((r2, i) => m.set(keyOf(r2, keyIndex.get(keyOf(r2)) ?? i), i));
|
|
9850
9869
|
return m;
|
|
9851
9870
|
}, [middleRows, keyIndex]);
|
|
9871
|
+
const cellColIndex = React51__namespace.default.useCallback((field) => ordered.findIndex((c) => c.field === field), [ordered]);
|
|
9872
|
+
const cellRect = React51__namespace.default.useMemo(() => {
|
|
9873
|
+
if (selectionMode !== "cell" || !activeCell) return null;
|
|
9874
|
+
const ar = keyIndex.get(activeCell.key), ac = cellColIndex(activeCell.field);
|
|
9875
|
+
if (ar == null || ac < 0) return null;
|
|
9876
|
+
const anchor = anchorCell || activeCell;
|
|
9877
|
+
const nr = keyIndex.get(anchor.key), nc = cellColIndex(anchor.field);
|
|
9878
|
+
const r0 = nr == null ? ar : Math.min(ar, nr), r1 = nr == null ? ar : Math.max(ar, nr);
|
|
9879
|
+
const c0 = nc < 0 ? ac : Math.min(ac, nc), c1 = nc < 0 ? ac : Math.max(ac, nc);
|
|
9880
|
+
return { r0, r1, c0, c1 };
|
|
9881
|
+
}, [selectionMode, activeCell, anchorCell, keyIndex, cellColIndex]);
|
|
9882
|
+
const activeCellId = React51__namespace.default.useMemo(() => {
|
|
9883
|
+
if (selectionMode !== "cell" || !activeCell) return void 0;
|
|
9884
|
+
const ar = keyIndex.get(activeCell.key), ac = cellColIndex(activeCell.field);
|
|
9885
|
+
return ar == null || ac < 0 ? void 0 : `${gridId}-${ar}-${ac}`;
|
|
9886
|
+
}, [selectionMode, activeCell, keyIndex, cellColIndex, gridId]);
|
|
9887
|
+
const onCellSelRef = React51__namespace.default.useRef(onCellSelectionChange);
|
|
9888
|
+
onCellSelRef.current = onCellSelectionChange;
|
|
9889
|
+
const prevSelSigRef = React51__namespace.default.useRef("");
|
|
9890
|
+
React51__namespace.default.useEffect(() => {
|
|
9891
|
+
const cb = onCellSelRef.current;
|
|
9892
|
+
if (selectionMode !== "cell" || !cb) return;
|
|
9893
|
+
const cells = [];
|
|
9894
|
+
if (cellRect) for (let r2 = cellRect.r0; r2 <= cellRect.r1; r2++) for (let c = cellRect.c0; c <= cellRect.c1; c++) {
|
|
9895
|
+
const row = leafRows[r2], col = ordered[c];
|
|
9896
|
+
if (row && col) cells.push({ key: keyOf(row, r2), field: col.field });
|
|
9897
|
+
}
|
|
9898
|
+
const sig = cells.map((x) => `${x.key}|${x.field}`).join(",");
|
|
9899
|
+
if (sig === prevSelSigRef.current) return;
|
|
9900
|
+
prevSelSigRef.current = sig;
|
|
9901
|
+
cb(cells);
|
|
9902
|
+
}, [cellRect, selectionMode, leafRows, ordered]);
|
|
9852
9903
|
const canReorderRows = rowReorder && !activeGroupBy.length && !sort;
|
|
9853
9904
|
function onRowDrop(targetKey) {
|
|
9854
9905
|
const fromKey = rowDrag.from;
|
|
@@ -10261,6 +10312,7 @@ function Datatable({
|
|
|
10261
10312
|
const next = { key: k, field: col.field };
|
|
10262
10313
|
setActiveCell(next);
|
|
10263
10314
|
setActiveRow(k);
|
|
10315
|
+
if (!(e.shiftKey && anchorCell)) setAnchorCell(next);
|
|
10264
10316
|
onCellClick?.(row[col.field], row, col.field);
|
|
10265
10317
|
onActiveCellChange?.(next);
|
|
10266
10318
|
}
|
|
@@ -10272,6 +10324,24 @@ function Datatable({
|
|
|
10272
10324
|
if (editing) return;
|
|
10273
10325
|
const td = e.target.closest(".twc-dt__td[data-r]");
|
|
10274
10326
|
if (!td) return;
|
|
10327
|
+
if (enableClipboard && selectionMode === "cell" && (e.ctrlKey || e.metaKey)) {
|
|
10328
|
+
const k = e.key.toLowerCase();
|
|
10329
|
+
if (k === "c") {
|
|
10330
|
+
e.preventDefault();
|
|
10331
|
+
copySelection(false);
|
|
10332
|
+
return;
|
|
10333
|
+
}
|
|
10334
|
+
if (k === "x") {
|
|
10335
|
+
e.preventDefault();
|
|
10336
|
+
copySelection(true);
|
|
10337
|
+
return;
|
|
10338
|
+
}
|
|
10339
|
+
if (k === "v") {
|
|
10340
|
+
e.preventDefault();
|
|
10341
|
+
pasteSelection();
|
|
10342
|
+
return;
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10275
10345
|
let r2 = +td.getAttribute("data-r"), c = +td.getAttribute("data-c");
|
|
10276
10346
|
const maxR = leafRows.length - 1, maxC = ordered.length - 1;
|
|
10277
10347
|
let handled = true;
|
|
@@ -10324,6 +10394,16 @@ function Datatable({
|
|
|
10324
10394
|
e.preventDefault();
|
|
10325
10395
|
setFocus({ r: r2, c });
|
|
10326
10396
|
focusCell(r2, c);
|
|
10397
|
+
if (selectionMode === "cell") {
|
|
10398
|
+
const nk = keyOf(leafRows[r2], r2), col = ordered[c];
|
|
10399
|
+
if (col) {
|
|
10400
|
+
const nc = { key: nk, field: col.field };
|
|
10401
|
+
setActiveCell(nc);
|
|
10402
|
+
setActiveRow(nk);
|
|
10403
|
+
if (!e.shiftKey) setAnchorCell(nc);
|
|
10404
|
+
onActiveCellChange?.(nc);
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10327
10407
|
}
|
|
10328
10408
|
}
|
|
10329
10409
|
React51__namespace.default.useEffect(() => {
|
|
@@ -10440,6 +10520,161 @@ function Datatable({
|
|
|
10440
10520
|
setBatchEdit(null);
|
|
10441
10521
|
closeBatchEdit();
|
|
10442
10522
|
}
|
|
10523
|
+
const [clipboardMsg, setClipboardMsg] = React51__namespace.default.useState("");
|
|
10524
|
+
const clipboardRef = React51__namespace.default.useRef(null);
|
|
10525
|
+
const [clipFx, setClipFx] = React51__namespace.default.useState(null);
|
|
10526
|
+
const clipFxTimer = React51__namespace.default.useRef(null);
|
|
10527
|
+
const onCellsCopyRef = React51__namespace.default.useRef(onCellsCopy);
|
|
10528
|
+
onCellsCopyRef.current = onCellsCopy;
|
|
10529
|
+
const onCellsPasteRef = React51__namespace.default.useRef(onCellsPaste);
|
|
10530
|
+
onCellsPasteRef.current = onCellsPaste;
|
|
10531
|
+
React51__namespace.default.useEffect(() => () => {
|
|
10532
|
+
if (clipFxTimer.current) clearTimeout(clipFxTimer.current);
|
|
10533
|
+
}, []);
|
|
10534
|
+
const announceClip = (msg, rect) => {
|
|
10535
|
+
setClipboardMsg(msg);
|
|
10536
|
+
setClipFx({ msg, rect: rect || null });
|
|
10537
|
+
if (clipFxTimer.current) clearTimeout(clipFxTimer.current);
|
|
10538
|
+
clipFxTimer.current = setTimeout(() => setClipFx(null), 1600);
|
|
10539
|
+
};
|
|
10540
|
+
const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
|
|
10541
|
+
const writeClipboard = (text) => {
|
|
10542
|
+
const execFallback = () => {
|
|
10543
|
+
try {
|
|
10544
|
+
if (typeof document !== "undefined") {
|
|
10545
|
+
const ta = document.createElement("textarea");
|
|
10546
|
+
ta.value = text;
|
|
10547
|
+
ta.style.position = "fixed";
|
|
10548
|
+
ta.style.opacity = "0";
|
|
10549
|
+
document.body.appendChild(ta);
|
|
10550
|
+
ta.focus();
|
|
10551
|
+
ta.select();
|
|
10552
|
+
document.execCommand("copy");
|
|
10553
|
+
document.body.removeChild(ta);
|
|
10554
|
+
}
|
|
10555
|
+
} catch {
|
|
10556
|
+
}
|
|
10557
|
+
};
|
|
10558
|
+
try {
|
|
10559
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text).catch(execFallback);
|
|
10560
|
+
} catch {
|
|
10561
|
+
}
|
|
10562
|
+
execFallback();
|
|
10563
|
+
return Promise.resolve();
|
|
10564
|
+
};
|
|
10565
|
+
const writeCellPatches = (patchByKey) => {
|
|
10566
|
+
if (!patchByKey.size) return 0;
|
|
10567
|
+
const changed = [];
|
|
10568
|
+
const nextAll = rows.map((r2, i) => {
|
|
10569
|
+
const k = keyOf(r2, i);
|
|
10570
|
+
const patch = patchByKey.get(k);
|
|
10571
|
+
if (!patch) return r2;
|
|
10572
|
+
const updated = { ...r2, ...patch };
|
|
10573
|
+
changed.push([updated, r2, patch]);
|
|
10574
|
+
return updated;
|
|
10575
|
+
});
|
|
10576
|
+
changed.forEach(([updated, orig, patch]) => {
|
|
10577
|
+
for (const f of Object.keys(patch)) onRowUpdate?.(updated, orig, f);
|
|
10578
|
+
});
|
|
10579
|
+
if (onRowsChange && changed.length) onRowsChange(nextAll);
|
|
10580
|
+
return changed.length;
|
|
10581
|
+
};
|
|
10582
|
+
const rectCells = () => {
|
|
10583
|
+
if (!cellRect) return null;
|
|
10584
|
+
const out = [];
|
|
10585
|
+
for (let r2 = cellRect.r0; r2 <= cellRect.r1; r2++) {
|
|
10586
|
+
const row = leafRows[r2];
|
|
10587
|
+
if (!row) continue;
|
|
10588
|
+
const line = [];
|
|
10589
|
+
for (let c = cellRect.c0; c <= cellRect.c1; c++) {
|
|
10590
|
+
const col = ordered[c];
|
|
10591
|
+
if (col) line.push({ key: keyOf(row, r2), field: col.field, col, value: getColVal(col, row) });
|
|
10592
|
+
}
|
|
10593
|
+
out.push(line);
|
|
10594
|
+
}
|
|
10595
|
+
return out.length ? out : null;
|
|
10596
|
+
};
|
|
10597
|
+
const copySelection = (cut) => {
|
|
10598
|
+
const grid = rectCells();
|
|
10599
|
+
if (!grid) return;
|
|
10600
|
+
const tsv = grid.map((line) => line.map((cell) => cell.value == null ? "" : String(cell.value)).join(" ")).join("\n");
|
|
10601
|
+
clipboardRef.current = { tsv, colTypes: grid[0].map((cell) => copyTypeOf(cell.col)), matrix: grid.map((line) => line.map((cell) => cell.value)) };
|
|
10602
|
+
writeClipboard(tsv);
|
|
10603
|
+
const n = grid.reduce((a, line) => a + line.length, 0);
|
|
10604
|
+
onCellsCopyRef.current?.(grid.flatMap((line) => line.map((cell) => ({ key: cell.key, field: cell.field }))), { cut: !!cut });
|
|
10605
|
+
if (cut) {
|
|
10606
|
+
const patchByKey = /* @__PURE__ */ new Map();
|
|
10607
|
+
let clearable = 0;
|
|
10608
|
+
for (const line of grid) for (const cell of line) {
|
|
10609
|
+
if (!isColEditable(cell.col)) continue;
|
|
10610
|
+
clearable++;
|
|
10611
|
+
const p = patchByKey.get(cell.key) || {};
|
|
10612
|
+
p[cell.field] = cell.col.type === "number" ? null : "";
|
|
10613
|
+
patchByKey.set(cell.key, p);
|
|
10614
|
+
}
|
|
10615
|
+
writeCellPatches(patchByKey);
|
|
10616
|
+
announceClip(`Cut ${clearable} cell${clearable === 1 ? "" : "s"}${clearable < n ? `, ${n - clearable} read-only kept` : ""}`, cellRect);
|
|
10617
|
+
} else {
|
|
10618
|
+
announceClip(`Copied ${n} cell${n === 1 ? "" : "s"}`, cellRect);
|
|
10619
|
+
}
|
|
10620
|
+
};
|
|
10621
|
+
const pasteSelection = async () => {
|
|
10622
|
+
if (!cellRect) return;
|
|
10623
|
+
let text = null;
|
|
10624
|
+
try {
|
|
10625
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.readText) text = await navigator.clipboard.readText();
|
|
10626
|
+
} catch {
|
|
10627
|
+
setClipboardMsg("Paste unavailable \u2014 clipboard permission denied");
|
|
10628
|
+
return;
|
|
10629
|
+
}
|
|
10630
|
+
if (text == null) {
|
|
10631
|
+
setClipboardMsg("Nothing to paste");
|
|
10632
|
+
return;
|
|
10633
|
+
}
|
|
10634
|
+
const inApp = clipboardRef.current && clipboardRef.current.tsv === text ? clipboardRef.current : null;
|
|
10635
|
+
let matrix;
|
|
10636
|
+
if (inApp) matrix = inApp.matrix;
|
|
10637
|
+
else {
|
|
10638
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
10639
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
10640
|
+
matrix = lines.map((line) => line.split(" "));
|
|
10641
|
+
}
|
|
10642
|
+
const patchByKey = /* @__PURE__ */ new Map();
|
|
10643
|
+
let written = 0, skipped = 0;
|
|
10644
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
10645
|
+
const rr = cellRect.r0 + i;
|
|
10646
|
+
const row = leafRows[rr];
|
|
10647
|
+
if (!row) break;
|
|
10648
|
+
for (let j = 0; j < matrix[i].length; j++) {
|
|
10649
|
+
const cc = cellRect.c0 + j;
|
|
10650
|
+
const col = ordered[cc];
|
|
10651
|
+
if (!col) break;
|
|
10652
|
+
if (!isColEditable(col)) {
|
|
10653
|
+
skipped++;
|
|
10654
|
+
continue;
|
|
10655
|
+
}
|
|
10656
|
+
if (inApp && inApp.colTypes[j] != null && inApp.colTypes[j] !== copyTypeOf(col)) {
|
|
10657
|
+
skipped++;
|
|
10658
|
+
continue;
|
|
10659
|
+
}
|
|
10660
|
+
let v = matrix[i][j];
|
|
10661
|
+
if (col.type === "number") {
|
|
10662
|
+
v = v === "" ? null : Number(v);
|
|
10663
|
+
if (Number.isNaN(v)) v = null;
|
|
10664
|
+
}
|
|
10665
|
+
const k = keyOf(row, rr);
|
|
10666
|
+
const p = patchByKey.get(k) || {};
|
|
10667
|
+
p[col.field] = v;
|
|
10668
|
+
patchByKey.set(k, p);
|
|
10669
|
+
written++;
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10672
|
+
writeCellPatches(patchByKey);
|
|
10673
|
+
onCellsPasteRef.current?.({ written, skipped });
|
|
10674
|
+
const maxCols = matrix.reduce((a, m) => Math.max(a, m.length), 0);
|
|
10675
|
+
const pasteRect = written ? { r0: cellRect.r0, c0: cellRect.c0, r1: Math.min(cellRect.r0 + matrix.length - 1, leafRows.length - 1), c1: Math.min(cellRect.c0 + maxCols - 1, ordered.length - 1) } : null;
|
|
10676
|
+
announceClip(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`, pasteRect);
|
|
10677
|
+
};
|
|
10443
10678
|
React51__namespace.default.useEffect(() => {
|
|
10444
10679
|
if (!editing) return;
|
|
10445
10680
|
const onDown = (e) => {
|
|
@@ -10696,6 +10931,7 @@ function Datatable({
|
|
|
10696
10931
|
if (node) {
|
|
10697
10932
|
node.style.removeProperty("--twc-dt-pop-w");
|
|
10698
10933
|
node.style.removeProperty("--twc-dt-pop-h");
|
|
10934
|
+
node.style.removeProperty("left");
|
|
10699
10935
|
node.removeAttribute("data-pop-sized");
|
|
10700
10936
|
}
|
|
10701
10937
|
setPopSizes((s) => {
|
|
@@ -10705,7 +10941,7 @@ function Datatable({
|
|
|
10705
10941
|
return n;
|
|
10706
10942
|
});
|
|
10707
10943
|
};
|
|
10708
|
-
const startPopResize = (id) => (e) => {
|
|
10944
|
+
const startPopResize = (id, side) => (e) => {
|
|
10709
10945
|
if (e.button != null && e.button !== 0) return;
|
|
10710
10946
|
e.preventDefault();
|
|
10711
10947
|
e.stopPropagation();
|
|
@@ -10714,21 +10950,27 @@ function Datatable({
|
|
|
10714
10950
|
const grip = e.currentTarget;
|
|
10715
10951
|
const startX = e.clientX, startY = e.clientY;
|
|
10716
10952
|
const r2 = pop.getBoundingClientRect();
|
|
10717
|
-
const startW = r2.width, startH = r2.height;
|
|
10953
|
+
const startW = r2.width, startH = r2.height, startLeft = r2.left, startRight = r2.right;
|
|
10718
10954
|
const dir = typeof getComputedStyle !== "undefined" && getComputedStyle(pop).direction === "rtl" ? -1 : 1;
|
|
10955
|
+
const toStart = side === "start";
|
|
10719
10956
|
pop.style.setProperty("--twc-dt-pop-w", `${startW}px`);
|
|
10720
10957
|
pop.style.setProperty("--twc-dt-pop-h", `${startH}px`);
|
|
10721
10958
|
grip.setAttribute("data-active", "true");
|
|
10722
10959
|
pop.setAttribute("data-pop-sized", "");
|
|
10723
|
-
let lw = startW, lh = startH;
|
|
10960
|
+
let lw = startW, lh = startH, ll = startLeft;
|
|
10724
10961
|
const onMove = (ev) => {
|
|
10725
|
-
|
|
10962
|
+
const dw = toStart ? (startX - ev.clientX) * dir : (ev.clientX - startX) * dir;
|
|
10963
|
+
lw = clampNum(startW + dw, popMinW(id), fMaxW());
|
|
10726
10964
|
lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
|
|
10727
10965
|
pop.style.setProperty("--twc-dt-pop-w", `${lw}px`);
|
|
10728
10966
|
pop.style.setProperty("--twc-dt-pop-h", `${lh}px`);
|
|
10967
|
+
if (toStart) {
|
|
10968
|
+
ll = Math.max(8, startRight - lw);
|
|
10969
|
+
pop.style.left = `${ll}px`;
|
|
10970
|
+
}
|
|
10729
10971
|
};
|
|
10730
10972
|
const onUp = () => {
|
|
10731
|
-
setPopSizes((s) => ({ ...s, [id]: { w: lw, h: lh } }));
|
|
10973
|
+
setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w: lw, h: lh, ...toStart ? { left: ll } : {} } }));
|
|
10732
10974
|
grip.removeAttribute("data-active");
|
|
10733
10975
|
window.removeEventListener("pointermove", onMove);
|
|
10734
10976
|
window.removeEventListener("pointerup", onUp);
|
|
@@ -10736,7 +10978,7 @@ function Datatable({
|
|
|
10736
10978
|
window.addEventListener("pointermove", onMove);
|
|
10737
10979
|
window.addEventListener("pointerup", onUp);
|
|
10738
10980
|
};
|
|
10739
|
-
const onPopGripKey = (id) => (e) => {
|
|
10981
|
+
const onPopGripKey = (id, side) => (e) => {
|
|
10740
10982
|
if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
|
|
10741
10983
|
e.preventDefault();
|
|
10742
10984
|
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
@@ -10761,25 +11003,54 @@ function Datatable({
|
|
|
10761
11003
|
w = fMaxW();
|
|
10762
11004
|
h = fMaxH();
|
|
10763
11005
|
}
|
|
10764
|
-
|
|
11006
|
+
w = clampNum(w, popMinW(id), fMaxW());
|
|
11007
|
+
h = clampNum(h, 120, fMaxH());
|
|
11008
|
+
const left = side === "start" ? Math.max(8, r2.right - w) : void 0;
|
|
11009
|
+
setPopSizes((s) => ({ ...s, [id]: { ...s[id] || {}, w, h, ...left != null ? { left } : {} } }));
|
|
10765
11010
|
};
|
|
10766
|
-
const popStyle = (id) =>
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
{
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
11011
|
+
const popStyle = (id) => {
|
|
11012
|
+
const s = popSizes[id];
|
|
11013
|
+
if (!s) return null;
|
|
11014
|
+
const st = { "--twc-dt-pop-w": `${s.w}px`, "--twc-dt-pop-h": `${s.h}px` };
|
|
11015
|
+
if (s.left != null) st.left = `${s.left}px`;
|
|
11016
|
+
return st;
|
|
11017
|
+
};
|
|
11018
|
+
const popGrip = (id) => resizablePopovers ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11019
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11020
|
+
"span",
|
|
11021
|
+
{
|
|
11022
|
+
className: "twc-dt__pop-grip",
|
|
11023
|
+
"data-side": "end",
|
|
11024
|
+
role: "slider",
|
|
11025
|
+
tabIndex: 0,
|
|
11026
|
+
"aria-label": "Resize panel (Enter or double-click to reset)",
|
|
11027
|
+
"aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
|
|
11028
|
+
onPointerDown: startPopResize(id, "end"),
|
|
11029
|
+
onKeyDown: onPopGripKey(id, "end"),
|
|
11030
|
+
onDoubleClick: (e) => {
|
|
11031
|
+
e.preventDefault();
|
|
11032
|
+
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
11033
|
+
}
|
|
10780
11034
|
}
|
|
10781
|
-
|
|
10782
|
-
|
|
11035
|
+
),
|
|
11036
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11037
|
+
"span",
|
|
11038
|
+
{
|
|
11039
|
+
className: "twc-dt__pop-grip",
|
|
11040
|
+
"data-side": "start",
|
|
11041
|
+
role: "slider",
|
|
11042
|
+
tabIndex: 0,
|
|
11043
|
+
"aria-label": "Resize panel leftward (Enter or double-click to reset)",
|
|
11044
|
+
"aria-valuetext": popSizes[id] ? `Width ${Math.round(popSizes[id].w)} pixels, height ${Math.round(popSizes[id].h)} pixels` : "Default size",
|
|
11045
|
+
onPointerDown: startPopResize(id, "start"),
|
|
11046
|
+
onKeyDown: onPopGripKey(id, "start"),
|
|
11047
|
+
onDoubleClick: (e) => {
|
|
11048
|
+
e.preventDefault();
|
|
11049
|
+
clearPop(id, e.currentTarget.closest(".twc-dt__pop"));
|
|
11050
|
+
}
|
|
11051
|
+
}
|
|
11052
|
+
)
|
|
11053
|
+
] }) : null;
|
|
10783
11054
|
const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
10784
11055
|
"span",
|
|
10785
11056
|
{
|
|
@@ -10935,22 +11206,29 @@ function Datatable({
|
|
|
10935
11206
|
const editable = isColEditable(c);
|
|
10936
11207
|
const isEditing = editing && editing.key === k && editing.field === c.field;
|
|
10937
11208
|
const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
|
|
11209
|
+
const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
|
|
11210
|
+
const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
|
|
11211
|
+
const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
|
|
10938
11212
|
const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
|
|
10939
11213
|
const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
|
|
10940
11214
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10941
11215
|
"td",
|
|
10942
11216
|
{
|
|
11217
|
+
id: cellId,
|
|
10943
11218
|
className: "twc-dt__td",
|
|
10944
11219
|
role: "gridcell",
|
|
10945
11220
|
"data-r": ri,
|
|
10946
11221
|
"data-c": ci,
|
|
10947
11222
|
"aria-colindex": ci + 1 + (checkboxSelection ? 1 : 0),
|
|
10948
11223
|
tabIndex: focus.r === ri && focus.c === ci ? 0 : -1,
|
|
11224
|
+
"aria-selected": cellSelected || void 0,
|
|
10949
11225
|
"data-num": c.type === "number" || void 0,
|
|
10950
11226
|
"data-actions": isActions || void 0,
|
|
10951
11227
|
"data-editable": editable && !isEditing || void 0,
|
|
10952
11228
|
"data-editing": isEditing || void 0,
|
|
10953
11229
|
"data-cell-active": cellActive || void 0,
|
|
11230
|
+
"data-cell-selected": cellSelected || void 0,
|
|
11231
|
+
"data-copied": cellFlash || void 0,
|
|
10954
11232
|
"data-wrap": wrapped.has(c.field) || void 0,
|
|
10955
11233
|
"data-pin": st.pin,
|
|
10956
11234
|
"data-pin-edge": st.edge,
|
|
@@ -11312,6 +11590,8 @@ function Datatable({
|
|
|
11312
11590
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__groupbar-clear", onClick: () => setGroupBy([]), children: "Clear all" })
|
|
11313
11591
|
] }) : null,
|
|
11314
11592
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: reorderMsg }),
|
|
11593
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: clipboardMsg }),
|
|
11594
|
+
clipFx ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__clip-toast", "aria-hidden": "true", children: clipFx.msg }) : null,
|
|
11315
11595
|
pivotActive ? renderPivot() : /* @__PURE__ */ jsxRuntime.jsx(
|
|
11316
11596
|
"div",
|
|
11317
11597
|
{
|
|
@@ -11333,6 +11613,8 @@ function Datatable({
|
|
|
11333
11613
|
"aria-label": ariaLabelAttr || ariaLabel,
|
|
11334
11614
|
"aria-rowcount": totalRows + 1,
|
|
11335
11615
|
"aria-colcount": ordered.length + (checkboxSelection ? 1 : 0),
|
|
11616
|
+
"aria-multiselectable": selectionMode === "cell" || void 0,
|
|
11617
|
+
"aria-activedescendant": activeCellId,
|
|
11336
11618
|
"aria-busy": loading || void 0,
|
|
11337
11619
|
onKeyDown: onGridKeyDown,
|
|
11338
11620
|
children: [
|