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/dist/index.d.cts
CHANGED
|
@@ -1843,6 +1843,10 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1843
1843
|
* full row objects server-side rather than relying on `selectedRows`.
|
|
1844
1844
|
*/
|
|
1845
1845
|
batchActions?: DatatableBatchAction<T>[];
|
|
1846
|
+
/** #322: fired when the checkbox row selection changes — `(keys, rows)`. The row-mode analogue of
|
|
1847
|
+
* `onCellSelectionChange`, so a consumer can track the selection and drive a batch action's own
|
|
1848
|
+
* `disabled`/`hidden` (which also accept predicates). */
|
|
1849
|
+
onRowSelectionChange?: (keys: Array<string | number>, rows: T[]) => void;
|
|
1846
1850
|
/** Row height preset. With `showDensity`, the toolbar density button cycles it locally; changing this prop re-applies it. @default "comfortable" */
|
|
1847
1851
|
density?: "compact" | "standard" | "comfortable";
|
|
1848
1852
|
/**
|
|
@@ -1956,8 +1960,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1956
1960
|
* TSV, Ctrl/Cmd+X cuts (copy then clear), Ctrl/Cmd+V pastes onto the target rectangle from the active
|
|
1957
1961
|
* cell. Paste is format-restricted by column `copyType` (incompatible cells are skipped) and commits
|
|
1958
1962
|
* through `onRowUpdate`/`onRowsChange` in one batched change; outcomes are announced via `aria-live`.
|
|
1959
|
-
*
|
|
1963
|
+
* Copy/cut/paste also show a brief **visible** in-grid confirmation toast + a flash on the affected cells
|
|
1964
|
+
* (#320), so sighted users get feedback too. @default false */
|
|
1960
1965
|
enableClipboard?: boolean;
|
|
1966
|
+
/** #320: fired on a clipboard copy/cut with the copied cells (row-major `{key,field}[]`) and `{ cut }`.
|
|
1967
|
+
* Use it to surface your own toast / integrate an app notification system. */
|
|
1968
|
+
onCellsCopy?: (cells: Array<{ key: string | number; field: string }>, meta: { cut: boolean }) => void;
|
|
1969
|
+
/** #320: fired after a clipboard paste with the outcome — `written` cells committed, `skipped` cells
|
|
1970
|
+
* rejected (read-only or incompatible `copyType`). */
|
|
1971
|
+
onCellsPaste?: (result: { written: number; skipped: number }) => void;
|
|
1961
1972
|
/** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
|
|
1962
1973
|
* toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
|
|
1963
1974
|
* changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
|
|
@@ -2222,7 +2233,12 @@ interface DatatableBatchAction<T = any> {
|
|
|
2222
2233
|
) => void;
|
|
2223
2234
|
/** Render in danger color. */
|
|
2224
2235
|
danger?: boolean;
|
|
2225
|
-
|
|
2236
|
+
/** #322: disable the action — a static boolean, or a predicate evaluated against the current selection so
|
|
2237
|
+
* an action that only applies to a subset of the selected rows can grey out when it doesn't apply. */
|
|
2238
|
+
disabled?: boolean | ((keys: Array<string | number>, rows: T[]) => boolean);
|
|
2239
|
+
/** #322: hide the action entirely when the predicate returns true (e.g. `(_, rows) => rows.every(r => r.isGroup)`
|
|
2240
|
+
* drops "Mark as new thread" when only group rows are selected). A static `true` always hides it. */
|
|
2241
|
+
hidden?: boolean | ((keys: Array<string | number>, rows: T[]) => boolean);
|
|
2226
2242
|
}
|
|
2227
2243
|
|
|
2228
2244
|
declare function Datatable<T = any>(props: DatatableProps<T>): React.JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -1843,6 +1843,10 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1843
1843
|
* full row objects server-side rather than relying on `selectedRows`.
|
|
1844
1844
|
*/
|
|
1845
1845
|
batchActions?: DatatableBatchAction<T>[];
|
|
1846
|
+
/** #322: fired when the checkbox row selection changes — `(keys, rows)`. The row-mode analogue of
|
|
1847
|
+
* `onCellSelectionChange`, so a consumer can track the selection and drive a batch action's own
|
|
1848
|
+
* `disabled`/`hidden` (which also accept predicates). */
|
|
1849
|
+
onRowSelectionChange?: (keys: Array<string | number>, rows: T[]) => void;
|
|
1846
1850
|
/** Row height preset. With `showDensity`, the toolbar density button cycles it locally; changing this prop re-applies it. @default "comfortable" */
|
|
1847
1851
|
density?: "compact" | "standard" | "comfortable";
|
|
1848
1852
|
/**
|
|
@@ -1956,8 +1960,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
1956
1960
|
* TSV, Ctrl/Cmd+X cuts (copy then clear), Ctrl/Cmd+V pastes onto the target rectangle from the active
|
|
1957
1961
|
* cell. Paste is format-restricted by column `copyType` (incompatible cells are skipped) and commits
|
|
1958
1962
|
* through `onRowUpdate`/`onRowsChange` in one batched change; outcomes are announced via `aria-live`.
|
|
1959
|
-
*
|
|
1963
|
+
* Copy/cut/paste also show a brief **visible** in-grid confirmation toast + a flash on the affected cells
|
|
1964
|
+
* (#320), so sighted users get feedback too. @default false */
|
|
1960
1965
|
enableClipboard?: boolean;
|
|
1966
|
+
/** #320: fired on a clipboard copy/cut with the copied cells (row-major `{key,field}[]`) and `{ cut }`.
|
|
1967
|
+
* Use it to surface your own toast / integrate an app notification system. */
|
|
1968
|
+
onCellsCopy?: (cells: Array<{ key: string | number; field: string }>, meta: { cut: boolean }) => void;
|
|
1969
|
+
/** #320: fired after a clipboard paste with the outcome — `written` cells committed, `skipped` cells
|
|
1970
|
+
* rejected (read-only or incompatible `copyType`). */
|
|
1971
|
+
onCellsPaste?: (result: { written: number; skipped: number }) => void;
|
|
1961
1972
|
/** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
|
|
1962
1973
|
* toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
|
|
1963
1974
|
* changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
|
|
@@ -2222,7 +2233,12 @@ interface DatatableBatchAction<T = any> {
|
|
|
2222
2233
|
) => void;
|
|
2223
2234
|
/** Render in danger color. */
|
|
2224
2235
|
danger?: boolean;
|
|
2225
|
-
|
|
2236
|
+
/** #322: disable the action — a static boolean, or a predicate evaluated against the current selection so
|
|
2237
|
+
* an action that only applies to a subset of the selected rows can grey out when it doesn't apply. */
|
|
2238
|
+
disabled?: boolean | ((keys: Array<string | number>, rows: T[]) => boolean);
|
|
2239
|
+
/** #322: hide the action entirely when the predicate returns true (e.g. `(_, rows) => rows.every(r => r.isGroup)`
|
|
2240
|
+
* drops "Mark as new thread" when only group rows are selected). A static `true` always hides it. */
|
|
2241
|
+
hidden?: boolean | ((keys: Array<string | number>, rows: T[]) => boolean);
|
|
2226
2242
|
}
|
|
2227
2243
|
|
|
2228
2244
|
declare function Datatable<T = any>(props: DatatableProps<T>): React.JSX.Element;
|
package/dist/index.mjs
CHANGED
|
@@ -8138,7 +8138,14 @@ var DT_CSS = `
|
|
|
8138
8138
|
/* Stay inside the parent even in a flex/grid container: min-width:0 lets a wide
|
|
8139
8139
|
grid shrink (default min-width:auto would force it to its content width and
|
|
8140
8140
|
overflow), so the internal __scroll handles horizontal scrolling. */
|
|
8141
|
-
width: 100%; min-width: 0; max-width: 100%; }
|
|
8141
|
+
width: 100%; min-width: 0; max-width: 100%; position: relative; }
|
|
8142
|
+
/* #320: visible clipboard feedback \u2014 a transient in-grid toast + a brief flash on the copied/pasted cells,
|
|
8143
|
+
alongside the SR-only aria-live message (position: relative on the root anchors the toast; it does NOT
|
|
8144
|
+
create a containing block for the position: fixed popovers, so those stay viewport-anchored). */
|
|
8145
|
+
.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; }
|
|
8146
|
+
@keyframes twc-dt-copyflash { from { box-shadow: inset 0 0 0 2px var(--color-primary); } to { box-shadow: inset 0 0 0 2px transparent; } }
|
|
8147
|
+
.twc-dt__td[data-copied="true"] { animation: twc-dt-copyflash 1.3s var(--ease-standard); }
|
|
8148
|
+
@media (prefers-reduced-motion: reduce) { .twc-dt__td[data-copied="true"] { animation: none; } }
|
|
8142
8149
|
|
|
8143
8150
|
/* Toolbar */
|
|
8144
8151
|
.twc-dt__toolbar { position: relative; z-index: 10; display: flex; align-items: center; gap: 6px; padding: 8px 10px;
|
|
@@ -9130,6 +9137,7 @@ function Datatable({
|
|
|
9130
9137
|
onServerChange,
|
|
9131
9138
|
onColumnVisibilityChange,
|
|
9132
9139
|
batchActions = [],
|
|
9140
|
+
onRowSelectionChange,
|
|
9133
9141
|
showExport = false,
|
|
9134
9142
|
showDensity = false,
|
|
9135
9143
|
showPivot = false,
|
|
@@ -9155,6 +9163,8 @@ function Datatable({
|
|
|
9155
9163
|
onActiveCellChange,
|
|
9156
9164
|
onCellSelectionChange,
|
|
9157
9165
|
enableClipboard = false,
|
|
9166
|
+
onCellsCopy,
|
|
9167
|
+
onCellsPaste,
|
|
9158
9168
|
showAggregation = false,
|
|
9159
9169
|
ariaLabel = "Data table",
|
|
9160
9170
|
"aria-label": ariaLabelAttr,
|
|
@@ -10380,6 +10390,18 @@ function Datatable({
|
|
|
10380
10390
|
setFocus((f) => ({ r: Math.min(f.r, Math.max(0, leafRows.length - 1)), c: Math.min(f.c, Math.max(0, ordered.length - 1)) }));
|
|
10381
10391
|
}, [leafRows.length, ordered.length, pageVal]);
|
|
10382
10392
|
const selectedRows = React51__default.useMemo(() => rows.filter((r2, i) => selected.has(keyOf(r2, i))), [rows, selected]);
|
|
10393
|
+
const selKeys = React51__default.useMemo(() => [...selected], [selected]);
|
|
10394
|
+
const onRowSelRef = React51__default.useRef(onRowSelectionChange);
|
|
10395
|
+
onRowSelRef.current = onRowSelectionChange;
|
|
10396
|
+
const prevRowSelSigRef = React51__default.useRef("");
|
|
10397
|
+
React51__default.useEffect(() => {
|
|
10398
|
+
const cb = onRowSelRef.current;
|
|
10399
|
+
if (!cb) return;
|
|
10400
|
+
const sig = selKeys.join(",");
|
|
10401
|
+
if (sig === prevRowSelSigRef.current) return;
|
|
10402
|
+
prevRowSelSigRef.current = sig;
|
|
10403
|
+
cb(selKeys, selectedRows);
|
|
10404
|
+
}, [selKeys, selectedRows]);
|
|
10383
10405
|
function clearSelection() {
|
|
10384
10406
|
setSelected(/* @__PURE__ */ new Set());
|
|
10385
10407
|
}
|
|
@@ -10476,11 +10498,11 @@ function Datatable({
|
|
|
10476
10498
|
}
|
|
10477
10499
|
patch[c.field] = v;
|
|
10478
10500
|
});
|
|
10479
|
-
const
|
|
10501
|
+
const selKeys2 = new Set(selected);
|
|
10480
10502
|
const changedRows = [];
|
|
10481
10503
|
const nextAll = rows.map((r2, i) => {
|
|
10482
10504
|
const k = keyOf(r2, i);
|
|
10483
|
-
if (!
|
|
10505
|
+
if (!selKeys2.has(k)) return r2;
|
|
10484
10506
|
const updated = { ...r2, ...patch };
|
|
10485
10507
|
changedRows.push(updated);
|
|
10486
10508
|
return updated;
|
|
@@ -10492,6 +10514,21 @@ function Datatable({
|
|
|
10492
10514
|
}
|
|
10493
10515
|
const [clipboardMsg, setClipboardMsg] = React51__default.useState("");
|
|
10494
10516
|
const clipboardRef = React51__default.useRef(null);
|
|
10517
|
+
const [clipFx, setClipFx] = React51__default.useState(null);
|
|
10518
|
+
const clipFxTimer = React51__default.useRef(null);
|
|
10519
|
+
const onCellsCopyRef = React51__default.useRef(onCellsCopy);
|
|
10520
|
+
onCellsCopyRef.current = onCellsCopy;
|
|
10521
|
+
const onCellsPasteRef = React51__default.useRef(onCellsPaste);
|
|
10522
|
+
onCellsPasteRef.current = onCellsPaste;
|
|
10523
|
+
React51__default.useEffect(() => () => {
|
|
10524
|
+
if (clipFxTimer.current) clearTimeout(clipFxTimer.current);
|
|
10525
|
+
}, []);
|
|
10526
|
+
const announceClip = (msg, rect) => {
|
|
10527
|
+
setClipboardMsg(msg);
|
|
10528
|
+
setClipFx({ msg, rect: rect || null });
|
|
10529
|
+
if (clipFxTimer.current) clearTimeout(clipFxTimer.current);
|
|
10530
|
+
clipFxTimer.current = setTimeout(() => setClipFx(null), 1600);
|
|
10531
|
+
};
|
|
10495
10532
|
const copyTypeOf = (col) => col && col.copyType != null ? col.copyType : col && col.type === "number" ? "number" : "text";
|
|
10496
10533
|
const writeClipboard = (text) => {
|
|
10497
10534
|
const execFallback = () => {
|
|
@@ -10556,6 +10593,7 @@ function Datatable({
|
|
|
10556
10593
|
clipboardRef.current = { tsv, colTypes: grid[0].map((cell) => copyTypeOf(cell.col)), matrix: grid.map((line) => line.map((cell) => cell.value)) };
|
|
10557
10594
|
writeClipboard(tsv);
|
|
10558
10595
|
const n = grid.reduce((a, line) => a + line.length, 0);
|
|
10596
|
+
onCellsCopyRef.current?.(grid.flatMap((line) => line.map((cell) => ({ key: cell.key, field: cell.field }))), { cut: !!cut });
|
|
10559
10597
|
if (cut) {
|
|
10560
10598
|
const patchByKey = /* @__PURE__ */ new Map();
|
|
10561
10599
|
let clearable = 0;
|
|
@@ -10567,9 +10605,9 @@ function Datatable({
|
|
|
10567
10605
|
patchByKey.set(cell.key, p);
|
|
10568
10606
|
}
|
|
10569
10607
|
writeCellPatches(patchByKey);
|
|
10570
|
-
|
|
10608
|
+
announceClip(`Cut ${clearable} cell${clearable === 1 ? "" : "s"}${clearable < n ? `, ${n - clearable} read-only kept` : ""}`, cellRect);
|
|
10571
10609
|
} else {
|
|
10572
|
-
|
|
10610
|
+
announceClip(`Copied ${n} cell${n === 1 ? "" : "s"}`, cellRect);
|
|
10573
10611
|
}
|
|
10574
10612
|
};
|
|
10575
10613
|
const pasteSelection = async () => {
|
|
@@ -10624,7 +10662,10 @@ function Datatable({
|
|
|
10624
10662
|
}
|
|
10625
10663
|
}
|
|
10626
10664
|
writeCellPatches(patchByKey);
|
|
10627
|
-
|
|
10665
|
+
onCellsPasteRef.current?.({ written, skipped });
|
|
10666
|
+
const maxCols = matrix.reduce((a, m) => Math.max(a, m.length), 0);
|
|
10667
|
+
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;
|
|
10668
|
+
announceClip(`Pasted ${written} cell${written === 1 ? "" : "s"}${skipped ? `, ${skipped} skipped (incompatible column)` : ""}`, pasteRect);
|
|
10628
10669
|
};
|
|
10629
10670
|
React51__default.useEffect(() => {
|
|
10630
10671
|
if (!editing) return;
|
|
@@ -11158,6 +11199,7 @@ function Datatable({
|
|
|
11158
11199
|
const isEditing = editing && editing.key === k && editing.field === c.field;
|
|
11159
11200
|
const cellActive = selectionMode === "cell" && activeCell && activeCell.key === k && activeCell.field === c.field;
|
|
11160
11201
|
const cellSelected = selectionMode === "cell" && cellRect && ri >= cellRect.r0 && ri <= cellRect.r1 && ci >= cellRect.c0 && ci <= cellRect.c1;
|
|
11202
|
+
const cellFlash = clipFx && clipFx.rect && ri >= clipFx.rect.r0 && ri <= clipFx.rect.r1 && ci >= clipFx.rect.c0 && ci <= clipFx.rect.c1;
|
|
11161
11203
|
const cellId = selectionMode === "cell" ? `${gridId}-${ri}-${ci}` : void 0;
|
|
11162
11204
|
const display = c.valueFormatter ? c.valueFormatter(val, row) : val;
|
|
11163
11205
|
const cellTitle = !isActions && !c.renderCell && !isEditing && (typeof display === "string" || typeof display === "number") ? String(display) : void 0;
|
|
@@ -11178,6 +11220,7 @@ function Datatable({
|
|
|
11178
11220
|
"data-editing": isEditing || void 0,
|
|
11179
11221
|
"data-cell-active": cellActive || void 0,
|
|
11180
11222
|
"data-cell-selected": cellSelected || void 0,
|
|
11223
|
+
"data-copied": cellFlash || void 0,
|
|
11181
11224
|
"data-wrap": wrapped.has(c.field) || void 0,
|
|
11182
11225
|
"data-pin": st.pin,
|
|
11183
11226
|
"data-pin-edge": st.edge,
|
|
@@ -11349,21 +11392,26 @@ function Datatable({
|
|
|
11349
11392
|
/* @__PURE__ */ jsx(Svg, { d: I.pencil }),
|
|
11350
11393
|
"Edit"
|
|
11351
11394
|
] }) : null,
|
|
11352
|
-
batchActions.map((a, i) =>
|
|
11353
|
-
"
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
"
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
a.
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11395
|
+
batchActions.map((a, i) => {
|
|
11396
|
+
const isHidden = typeof a.hidden === "function" ? a.hidden(selKeys, selectedRows) : !!a.hidden;
|
|
11397
|
+
if (isHidden) return null;
|
|
11398
|
+
const isDisabled = typeof a.disabled === "function" ? a.disabled(selKeys, selectedRows) : !!a.disabled;
|
|
11399
|
+
return /* @__PURE__ */ jsxs(
|
|
11400
|
+
"button",
|
|
11401
|
+
{
|
|
11402
|
+
type: "button",
|
|
11403
|
+
className: "twc-dt__batch-btn",
|
|
11404
|
+
"data-danger": a.danger || void 0,
|
|
11405
|
+
disabled: isDisabled,
|
|
11406
|
+
onClick: (e) => a.onClick?.(selKeys, selectedRows, clearSelection, { anchorEl: e.currentTarget }),
|
|
11407
|
+
children: [
|
|
11408
|
+
a.icon,
|
|
11409
|
+
a.label
|
|
11410
|
+
]
|
|
11411
|
+
},
|
|
11412
|
+
i
|
|
11413
|
+
);
|
|
11414
|
+
})
|
|
11367
11415
|
] })
|
|
11368
11416
|
] }) : null,
|
|
11369
11417
|
isDiff && diff.showSummary !== false ? /* @__PURE__ */ jsxs("span", { className: "twc-dt__diff-summary", "aria-label": "Diff summary", children: [
|
|
@@ -11540,6 +11588,7 @@ function Datatable({
|
|
|
11540
11588
|
] }) : null,
|
|
11541
11589
|
/* @__PURE__ */ jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: reorderMsg }),
|
|
11542
11590
|
/* @__PURE__ */ jsx("div", { className: "twc-dt__sr", role: "status", "aria-live": "polite", children: clipboardMsg }),
|
|
11591
|
+
clipFx ? /* @__PURE__ */ jsx("div", { className: "twc-dt__clip-toast", "aria-hidden": "true", children: clipFx.msg }) : null,
|
|
11543
11592
|
pivotActive ? renderPivot() : /* @__PURE__ */ jsx(
|
|
11544
11593
|
"div",
|
|
11545
11594
|
{
|