twico-ui 1.26.0 → 1.28.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 +70 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.mjs +70 -21
- package/dist/index.mjs.map +1 -1
- package/llms.txt +5 -2
- 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.28.0](https://github.com/EncryptionL/twico-ui/compare/v1.27.0...v1.28.0) (2026-08-03)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **datatable:** selection-aware batch actions (predicate disabled/hidden) + onRowSelectionChange ([#322](https://github.com/EncryptionL/twico-ui/issues/322)) ([33a1c36](https://github.com/EncryptionL/twico-ui/commit/33a1c36340e36cf0c9cc763cda7ed5d26fd46baa))
|
|
11
|
+
|
|
12
|
+
# [1.27.0](https://github.com/EncryptionL/twico-ui/compare/v1.26.0...v1.27.0) (2026-08-03)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **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))
|
|
18
|
+
|
|
5
19
|
# [1.26.0](https://github.com/EncryptionL/twico-ui/compare/v1.25.0...v1.26.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;
|
|
@@ -9151,6 +9158,7 @@ function Datatable({
|
|
|
9151
9158
|
onServerChange,
|
|
9152
9159
|
onColumnVisibilityChange,
|
|
9153
9160
|
batchActions = [],
|
|
9161
|
+
onRowSelectionChange,
|
|
9154
9162
|
showExport = false,
|
|
9155
9163
|
showDensity = false,
|
|
9156
9164
|
showPivot = false,
|
|
@@ -9176,6 +9184,8 @@ function Datatable({
|
|
|
9176
9184
|
onActiveCellChange,
|
|
9177
9185
|
onCellSelectionChange,
|
|
9178
9186
|
enableClipboard = false,
|
|
9187
|
+
onCellsCopy,
|
|
9188
|
+
onCellsPaste,
|
|
9179
9189
|
showAggregation = false,
|
|
9180
9190
|
ariaLabel = "Data table",
|
|
9181
9191
|
"aria-label": ariaLabelAttr,
|
|
@@ -10401,6 +10411,18 @@ function Datatable({
|
|
|
10401
10411
|
setFocus((f) => ({ r: Math.min(f.r, Math.max(0, leafRows.length - 1)), c: Math.min(f.c, Math.max(0, ordered.length - 1)) }));
|
|
10402
10412
|
}, [leafRows.length, ordered.length, pageVal]);
|
|
10403
10413
|
const selectedRows = React51__namespace.default.useMemo(() => rows.filter((r2, i) => selected.has(keyOf(r2, i))), [rows, selected]);
|
|
10414
|
+
const selKeys = React51__namespace.default.useMemo(() => [...selected], [selected]);
|
|
10415
|
+
const onRowSelRef = React51__namespace.default.useRef(onRowSelectionChange);
|
|
10416
|
+
onRowSelRef.current = onRowSelectionChange;
|
|
10417
|
+
const prevRowSelSigRef = React51__namespace.default.useRef("");
|
|
10418
|
+
React51__namespace.default.useEffect(() => {
|
|
10419
|
+
const cb = onRowSelRef.current;
|
|
10420
|
+
if (!cb) return;
|
|
10421
|
+
const sig = selKeys.join(",");
|
|
10422
|
+
if (sig === prevRowSelSigRef.current) return;
|
|
10423
|
+
prevRowSelSigRef.current = sig;
|
|
10424
|
+
cb(selKeys, selectedRows);
|
|
10425
|
+
}, [selKeys, selectedRows]);
|
|
10404
10426
|
function clearSelection() {
|
|
10405
10427
|
setSelected(/* @__PURE__ */ new Set());
|
|
10406
10428
|
}
|
|
@@ -10497,11 +10519,11 @@ function Datatable({
|
|
|
10497
10519
|
}
|
|
10498
10520
|
patch[c.field] = v;
|
|
10499
10521
|
});
|
|
10500
|
-
const
|
|
10522
|
+
const selKeys2 = new Set(selected);
|
|
10501
10523
|
const changedRows = [];
|
|
10502
10524
|
const nextAll = rows.map((r2, i) => {
|
|
10503
10525
|
const k = keyOf(r2, i);
|
|
10504
|
-
if (!
|
|
10526
|
+
if (!selKeys2.has(k)) return r2;
|
|
10505
10527
|
const updated = { ...r2, ...patch };
|
|
10506
10528
|
changedRows.push(updated);
|
|
10507
10529
|
return updated;
|
|
@@ -10513,6 +10535,21 @@ function Datatable({
|
|
|
10513
10535
|
}
|
|
10514
10536
|
const [clipboardMsg, setClipboardMsg] = React51__namespace.default.useState("");
|
|
10515
10537
|
const clipboardRef = React51__namespace.default.useRef(null);
|
|
10538
|
+
const [clipFx, setClipFx] = React51__namespace.default.useState(null);
|
|
10539
|
+
const clipFxTimer = React51__namespace.default.useRef(null);
|
|
10540
|
+
const onCellsCopyRef = React51__namespace.default.useRef(onCellsCopy);
|
|
10541
|
+
onCellsCopyRef.current = onCellsCopy;
|
|
10542
|
+
const onCellsPasteRef = React51__namespace.default.useRef(onCellsPaste);
|
|
10543
|
+
onCellsPasteRef.current = onCellsPaste;
|
|
10544
|
+
React51__namespace.default.useEffect(() => () => {
|
|
10545
|
+
if (clipFxTimer.current) clearTimeout(clipFxTimer.current);
|
|
10546
|
+
}, []);
|
|
10547
|
+
const announceClip = (msg, rect) => {
|
|
10548
|
+
setClipboardMsg(msg);
|
|
10549
|
+
setClipFx({ msg, rect: rect || null });
|
|
10550
|
+
if (clipFxTimer.current) clearTimeout(clipFxTimer.current);
|
|
10551
|
+
clipFxTimer.current = setTimeout(() => setClipFx(null), 1600);
|
|
10552
|
+
};
|
|
10516
10553
|
const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
|
|
10517
10554
|
const writeClipboard = (text) => {
|
|
10518
10555
|
const execFallback = () => {
|
|
@@ -10577,6 +10614,7 @@ function Datatable({
|
|
|
10577
10614
|
clipboardRef.current = { tsv, colTypes: grid[0].map((cell) => copyTypeOf(cell.col)), matrix: grid.map((line) => line.map((cell) => cell.value)) };
|
|
10578
10615
|
writeClipboard(tsv);
|
|
10579
10616
|
const n = grid.reduce((a, line) => a + line.length, 0);
|
|
10617
|
+
onCellsCopyRef.current?.(grid.flatMap((line) => line.map((cell) => ({ key: cell.key, field: cell.field }))), { cut: !!cut });
|
|
10580
10618
|
if (cut) {
|
|
10581
10619
|
const patchByKey = /* @__PURE__ */ new Map();
|
|
10582
10620
|
let clearable = 0;
|
|
@@ -10588,9 +10626,9 @@ function Datatable({
|
|
|
10588
10626
|
patchByKey.set(cell.key, p);
|
|
10589
10627
|
}
|
|
10590
10628
|
writeCellPatches(patchByKey);
|
|
10591
|
-
|
|
10629
|
+
announceClip(`Cut ${clearable} cell${clearable === 1 ? "" : "s"}${clearable < n ? `, ${n - clearable} read-only kept` : ""}`, cellRect);
|
|
10592
10630
|
} else {
|
|
10593
|
-
|
|
10631
|
+
announceClip(`Copied ${n} cell${n === 1 ? "" : "s"}`, cellRect);
|
|
10594
10632
|
}
|
|
10595
10633
|
};
|
|
10596
10634
|
const pasteSelection = async () => {
|
|
@@ -10645,7 +10683,10 @@ function Datatable({
|
|
|
10645
10683
|
}
|
|
10646
10684
|
}
|
|
10647
10685
|
writeCellPatches(patchByKey);
|
|
10648
|
-
|
|
10686
|
+
onCellsPasteRef.current?.({ written, skipped });
|
|
10687
|
+
const maxCols = matrix.reduce((a, m) => Math.max(a, m.length), 0);
|
|
10688
|
+
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;
|
|
10689
|
+
announceClip(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`, pasteRect);
|
|
10649
10690
|
};
|
|
10650
10691
|
React51__namespace.default.useEffect(() => {
|
|
10651
10692
|
if (!editing) return;
|
|
@@ -11179,6 +11220,7 @@ function Datatable({
|
|
|
11179
11220
|
const isEditing = editing && editing.key === k && editing.field === c.field;
|
|
11180
11221
|
const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
|
|
11181
11222
|
const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
|
|
11223
|
+
const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
|
|
11182
11224
|
const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
|
|
11183
11225
|
const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
|
|
11184
11226
|
const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
|
|
@@ -11199,6 +11241,7 @@ function Datatable({
|
|
|
11199
11241
|
"data-editing": isEditing || void 0,
|
|
11200
11242
|
"data-cell-active": cellActive || void 0,
|
|
11201
11243
|
"data-cell-selected": cellSelected || void 0,
|
|
11244
|
+
"data-copied": cellFlash || void 0,
|
|
11202
11245
|
"data-wrap": wrapped.has(c.field) || void 0,
|
|
11203
11246
|
"data-pin": st.pin,
|
|
11204
11247
|
"data-pin-edge": st.edge,
|
|
@@ -11370,21 +11413,26 @@ function Datatable({
|
|
|
11370
11413
|
/* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.pencil }),
|
|
11371
11414
|
"Edit"
|
|
11372
11415
|
] }) : null,
|
|
11373
|
-
batchActions.map((a, i) =>
|
|
11374
|
-
"
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
"
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
a.
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11416
|
+
batchActions.map((a, i) => {
|
|
11417
|
+
const isHidden = typeof a.hidden === "function" ? a.hidden(selKeys, selectedRows) : !!a.hidden;
|
|
11418
|
+
if (isHidden) return null;
|
|
11419
|
+
const isDisabled = typeof a.disabled === "function" ? a.disabled(selKeys, selectedRows) : !!a.disabled;
|
|
11420
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11421
|
+
"button",
|
|
11422
|
+
{
|
|
11423
|
+
type: "button",
|
|
11424
|
+
className: "twc-dt__batch-btn",
|
|
11425
|
+
"data-danger": a.danger || void 0,
|
|
11426
|
+
disabled: isDisabled,
|
|
11427
|
+
onClick: (e) => a.onClick?.(selKeys, selectedRows, clearSelection, { anchorEl: e.currentTarget }),
|
|
11428
|
+
children: [
|
|
11429
|
+
a.icon,
|
|
11430
|
+
a.label
|
|
11431
|
+
]
|
|
11432
|
+
},
|
|
11433
|
+
i
|
|
11434
|
+
);
|
|
11435
|
+
})
|
|
11388
11436
|
] })
|
|
11389
11437
|
] }) : null,
|
|
11390
11438
|
isDiff && diff.showSummary !== false ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__diff-summary", "aria-label": "Diff summary", children: [
|
|
@@ -11561,6 +11609,7 @@ function Datatable({
|
|
|
11561
11609
|
] }) : null,
|
|
11562
11610
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: reorderMsg }),
|
|
11563
11611
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: clipboardMsg }),
|
|
11612
|
+
clipFx ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__clip-toast", "aria-hidden": "true", children: clipFx.msg }) : null,
|
|
11564
11613
|
pivotActive ? renderPivot() : /* @__PURE__ */ jsxRuntime.jsx(
|
|
11565
11614
|
"div",
|
|
11566
11615
|
{
|