twico-ui 1.26.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 +7 -0
- package/dist/index.cjs +35 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +35 -4
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
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
|
+
|
|
5
12
|
# [1.26.0](https://github.com/EncryptionL/twico-ui/compare/v1.25.0...v1.26.0) (2026-08-03)
|
|
6
13
|
|
|
7
14
|
|
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;
|
|
@@ -9176,6 +9183,8 @@ function Datatable({
|
|
|
9176
9183
|
onActiveCellChange,
|
|
9177
9184
|
onCellSelectionChange,
|
|
9178
9185
|
enableClipboard = false,
|
|
9186
|
+
onCellsCopy,
|
|
9187
|
+
onCellsPaste,
|
|
9179
9188
|
showAggregation = false,
|
|
9180
9189
|
ariaLabel = "Data table",
|
|
9181
9190
|
"aria-label": ariaLabelAttr,
|
|
@@ -10513,6 +10522,21 @@ function Datatable({
|
|
|
10513
10522
|
}
|
|
10514
10523
|
const [clipboardMsg, setClipboardMsg] = React51__namespace.default.useState("");
|
|
10515
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
|
+
};
|
|
10516
10540
|
const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
|
|
10517
10541
|
const writeClipboard = (text) => {
|
|
10518
10542
|
const execFallback = () => {
|
|
@@ -10577,6 +10601,7 @@ function Datatable({
|
|
|
10577
10601
|
clipboardRef.current = { tsv, colTypes: grid[0].map((cell) => copyTypeOf(cell.col)), matrix: grid.map((line) => line.map((cell) => cell.value)) };
|
|
10578
10602
|
writeClipboard(tsv);
|
|
10579
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 });
|
|
10580
10605
|
if (cut) {
|
|
10581
10606
|
const patchByKey = /* @__PURE__ */ new Map();
|
|
10582
10607
|
let clearable = 0;
|
|
@@ -10588,9 +10613,9 @@ function Datatable({
|
|
|
10588
10613
|
patchByKey.set(cell.key, p);
|
|
10589
10614
|
}
|
|
10590
10615
|
writeCellPatches(patchByKey);
|
|
10591
|
-
|
|
10616
|
+
announceClip(`Cut ${clearable} cell${clearable === 1 ? "" : "s"}${clearable < n ? `, ${n - clearable} read-only kept` : ""}`, cellRect);
|
|
10592
10617
|
} else {
|
|
10593
|
-
|
|
10618
|
+
announceClip(`Copied ${n} cell${n === 1 ? "" : "s"}`, cellRect);
|
|
10594
10619
|
}
|
|
10595
10620
|
};
|
|
10596
10621
|
const pasteSelection = async () => {
|
|
@@ -10645,7 +10670,10 @@ function Datatable({
|
|
|
10645
10670
|
}
|
|
10646
10671
|
}
|
|
10647
10672
|
writeCellPatches(patchByKey);
|
|
10648
|
-
|
|
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);
|
|
10649
10677
|
};
|
|
10650
10678
|
React51__namespace.default.useEffect(() => {
|
|
10651
10679
|
if (!editing) return;
|
|
@@ -11179,6 +11207,7 @@ function Datatable({
|
|
|
11179
11207
|
const isEditing = editing && editing.key === k && editing.field === c.field;
|
|
11180
11208
|
const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
|
|
11181
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;
|
|
11182
11211
|
const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
|
|
11183
11212
|
const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
|
|
11184
11213
|
const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
|
|
@@ -11199,6 +11228,7 @@ function Datatable({
|
|
|
11199
11228
|
"data-editing": isEditing || void 0,
|
|
11200
11229
|
"data-cell-active": cellActive || void 0,
|
|
11201
11230
|
"data-cell-selected": cellSelected || void 0,
|
|
11231
|
+
"data-copied": cellFlash || void 0,
|
|
11202
11232
|
"data-wrap": wrapped.has(c.field) || void 0,
|
|
11203
11233
|
"data-pin": st.pin,
|
|
11204
11234
|
"data-pin-edge": st.edge,
|
|
@@ -11561,6 +11591,7 @@ function Datatable({
|
|
|
11561
11591
|
] }) : null,
|
|
11562
11592
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: reorderMsg }),
|
|
11563
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,
|
|
11564
11595
|
pivotActive ? renderPivot() : /* @__PURE__ */ jsxRuntime.jsx(
|
|
11565
11596
|
"div",
|
|
11566
11597
|
{
|