twico-ui 1.17.2 → 1.18.1
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 +120 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -2
- package/dist/index.d.ts +48 -2
- package/dist/index.mjs +120 -14
- package/dist/index.mjs.map +1 -1
- package/llms.txt +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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.18.1](https://github.com/EncryptionL/twico-ui/compare/v1.18.0...v1.18.1) (2026-07-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **datatable:** stop the reorder grip from clipping the selection checkbox ([096f5e8](https://github.com/EncryptionL/twico-ui/commit/096f5e8977949bc664ffcec21931c9062d831522)), closes [#261](https://github.com/EncryptionL/twico-ui/issues/261)
|
|
11
|
+
|
|
12
|
+
# [1.18.0](https://github.com/EncryptionL/twico-ui/compare/v1.17.2...v1.18.0) (2026-07-17)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **hooks:** copy off a secure origin via execCommand fallback ([2786e74](https://github.com/EncryptionL/twico-ui/commit/2786e7414aec19832eacb8277830f72ae935307e)), closes [#255](https://github.com/EncryptionL/twico-ui/issues/255)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **datatable:** persist & restore view state via stateKey/initialState/onStateChange ([0830d59](https://github.com/EncryptionL/twico-ui/commit/0830d591e9bfb21395efa72ad67286326471371b)), closes [#45](https://github.com/EncryptionL/twico-ui/issues/45) [#259](https://github.com/EncryptionL/twico-ui/issues/259)
|
|
23
|
+
|
|
5
24
|
## [1.17.2](https://github.com/EncryptionL/twico-ui/compare/v1.17.1...v1.17.2) (2026-07-17)
|
|
6
25
|
|
|
7
26
|
|
package/dist/index.cjs
CHANGED
|
@@ -9011,6 +9011,9 @@ function Datatable({
|
|
|
9011
9011
|
onBatchUpdate,
|
|
9012
9012
|
showBatchEdit = true,
|
|
9013
9013
|
batchEditFields = null,
|
|
9014
|
+
stateKey,
|
|
9015
|
+
initialState,
|
|
9016
|
+
onStateChange,
|
|
9014
9017
|
showPageJumper = true,
|
|
9015
9018
|
selectionMode = "none",
|
|
9016
9019
|
onRowClick,
|
|
@@ -9173,6 +9176,75 @@ function Datatable({
|
|
|
9173
9176
|
const [rowGrab, setRowGrab] = React51__namespace.default.useState(null);
|
|
9174
9177
|
const [reorderMsg, setReorderMsg] = React51__namespace.default.useState("");
|
|
9175
9178
|
const rowFocusRef = React51__namespace.default.useRef(null);
|
|
9179
|
+
const serializeState = () => ({
|
|
9180
|
+
filters: filters.map(({ id, ...f }) => f),
|
|
9181
|
+
// strip the internal row id
|
|
9182
|
+
sort,
|
|
9183
|
+
quickFilter: quick,
|
|
9184
|
+
page: pageVal,
|
|
9185
|
+
pageSize: sizeVal,
|
|
9186
|
+
columnOrder: order,
|
|
9187
|
+
columnWidths: widths,
|
|
9188
|
+
columnVisibility: Object.fromEntries([...hidden].map((f) => [f, false])),
|
|
9189
|
+
// absent = visible
|
|
9190
|
+
columnPinning: Object.fromEntries([...pins.left.map((f) => [f, "left"]), ...pins.right.map((f) => [f, "right"])]),
|
|
9191
|
+
density
|
|
9192
|
+
});
|
|
9193
|
+
const applyState = (s) => {
|
|
9194
|
+
if (!s || typeof s !== "object") return;
|
|
9195
|
+
const known = new Set(cols.map((c) => c.field).filter(Boolean));
|
|
9196
|
+
if (Array.isArray(s.filters)) setFilters(s.filters.filter((f) => f && known.has(f.field)).map((f, i) => ({ id: Date.now() + Math.random() + i, field: f.field, op: f.op, value: f.value })));
|
|
9197
|
+
if ("sort" in s) setSort(s.sort && known.has(s.sort.field) ? { field: s.sort.field, dir: s.sort.dir === "desc" ? "desc" : "asc" } : null);
|
|
9198
|
+
if (typeof s.quickFilter === "string" && quickFilter === void 0) setInternalQuick(s.quickFilter);
|
|
9199
|
+
if (typeof s.page === "number" && !pageControlled) setInternalPage(Math.max(0, Math.floor(s.page)));
|
|
9200
|
+
if (typeof s.pageSize === "number" && !pageSizeControlled) setInternalRpp(s.pageSize > 0 ? s.pageSize : 10);
|
|
9201
|
+
if (Array.isArray(s.columnOrder)) {
|
|
9202
|
+
const savedOrder = s.columnOrder.filter((f) => known.has(f));
|
|
9203
|
+
const missing = cols.map((c) => c.field).filter((f) => f && !savedOrder.includes(f));
|
|
9204
|
+
setOrder([...savedOrder, ...missing]);
|
|
9205
|
+
}
|
|
9206
|
+
if (s.columnWidths && typeof s.columnWidths === "object") setWidths(Object.fromEntries(Object.entries(s.columnWidths).filter(([f, w]) => known.has(f) && typeof w === "number")));
|
|
9207
|
+
if (s.columnVisibility && typeof s.columnVisibility === "object") setHidden(new Set(Object.keys(s.columnVisibility).filter((f) => known.has(f) && s.columnVisibility[f] === false)));
|
|
9208
|
+
if (s.columnPinning && typeof s.columnPinning === "object") {
|
|
9209
|
+
const l = [], r2 = [];
|
|
9210
|
+
for (const [f, side] of Object.entries(s.columnPinning)) {
|
|
9211
|
+
if (!known.has(f)) continue;
|
|
9212
|
+
if (side === "left") l.push(f);
|
|
9213
|
+
else if (side === "right") r2.push(f);
|
|
9214
|
+
}
|
|
9215
|
+
setPins({ left: l, right: r2 });
|
|
9216
|
+
}
|
|
9217
|
+
if (s.density === "compact" || s.density === "standard" || s.density === "comfortable") setDensity(s.density);
|
|
9218
|
+
};
|
|
9219
|
+
const stateReadyRef = React51__namespace.default.useRef(false);
|
|
9220
|
+
const onStateChangeRef = React51__namespace.default.useRef(onStateChange);
|
|
9221
|
+
onStateChangeRef.current = onStateChange;
|
|
9222
|
+
React51__namespace.default.useEffect(() => {
|
|
9223
|
+
if (!stateReadyRef.current) return void 0;
|
|
9224
|
+
if (!stateKey && !onStateChange) return void 0;
|
|
9225
|
+
const state = serializeState();
|
|
9226
|
+
onStateChangeRef.current?.(state);
|
|
9227
|
+
if (stateKey) {
|
|
9228
|
+
try {
|
|
9229
|
+
window.localStorage.setItem(stateKey, JSON.stringify(state));
|
|
9230
|
+
} catch {
|
|
9231
|
+
}
|
|
9232
|
+
}
|
|
9233
|
+
return void 0;
|
|
9234
|
+
}, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, stateKey]);
|
|
9235
|
+
React51__namespace.default.useEffect(() => {
|
|
9236
|
+
let saved = null;
|
|
9237
|
+
if (stateKey) {
|
|
9238
|
+
try {
|
|
9239
|
+
const raw = window.localStorage.getItem(stateKey);
|
|
9240
|
+
if (raw) saved = JSON.parse(raw);
|
|
9241
|
+
} catch {
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9244
|
+
if (!saved && initialState) saved = initialState;
|
|
9245
|
+
if (saved) applyState(saved);
|
|
9246
|
+
stateReadyRef.current = true;
|
|
9247
|
+
}, []);
|
|
9176
9248
|
const rootRef = React51__namespace.default.useRef(null);
|
|
9177
9249
|
const [compact, setCompact] = React51__namespace.default.useState(false);
|
|
9178
9250
|
React51__namespace.default.useEffect(() => {
|
|
@@ -9214,13 +9286,20 @@ function Datatable({
|
|
|
9214
9286
|
return [...kept, ...added];
|
|
9215
9287
|
});
|
|
9216
9288
|
}, [columns]);
|
|
9289
|
+
const densitySyncedRef = React51__namespace.default.useRef(false);
|
|
9217
9290
|
React51__namespace.default.useEffect(() => {
|
|
9218
|
-
setDensity(densityProp);
|
|
9291
|
+
if (densitySyncedRef.current) setDensity(densityProp);
|
|
9292
|
+
else densitySyncedRef.current = true;
|
|
9219
9293
|
}, [densityProp]);
|
|
9220
9294
|
React51__namespace.default.useEffect(() => {
|
|
9221
9295
|
setAggOn(showAggregation);
|
|
9222
9296
|
}, [showAggregation]);
|
|
9297
|
+
const pageSizeSyncedRef = React51__namespace.default.useRef(false);
|
|
9223
9298
|
React51__namespace.default.useEffect(() => {
|
|
9299
|
+
if (!pageSizeSyncedRef.current) {
|
|
9300
|
+
pageSizeSyncedRef.current = true;
|
|
9301
|
+
return;
|
|
9302
|
+
}
|
|
9224
9303
|
if (pageSizeControlled) return;
|
|
9225
9304
|
setInternalRpp(pageSize > 0 ? pageSize : 10);
|
|
9226
9305
|
setInternalPage(0);
|
|
@@ -9372,7 +9451,8 @@ function Datatable({
|
|
|
9372
9451
|
return [...L, ...mid, ...R];
|
|
9373
9452
|
}, [pins, hidden, visibleCols, colByField, order]);
|
|
9374
9453
|
const NUM_W = 56;
|
|
9375
|
-
const
|
|
9454
|
+
const CHK_W = checkboxSelection ? rowReorder ? 68 : 44 : 0;
|
|
9455
|
+
const numLeft = CHK_W;
|
|
9376
9456
|
const leadW = numLeft + (showRowNum ? NUM_W : 0);
|
|
9377
9457
|
const stickyOf = (field) => {
|
|
9378
9458
|
if (pins.left.includes(field)) {
|
|
@@ -10295,7 +10375,7 @@ function Datatable({
|
|
|
10295
10375
|
onDragEnd: reorderable ? () => setRowDrag({ from: null, over: null, after: false }) : void 0,
|
|
10296
10376
|
onClick: (e) => handleRowClick(e, k, row),
|
|
10297
10377
|
children: [
|
|
10298
|
-
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", "data-pin-edge": pins.left.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width:
|
|
10378
|
+
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", "data-pin-edge": pins.left.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W }, children: [
|
|
10299
10379
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
|
|
10300
10380
|
reorderable ? rowHandle(k, midIdx, row) : null,
|
|
10301
10381
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -10703,7 +10783,7 @@ function Datatable({
|
|
|
10703
10783
|
onKeyDown: onGridKeyDown,
|
|
10704
10784
|
children: [
|
|
10705
10785
|
/* @__PURE__ */ jsxRuntime.jsx("thead", { ref: theadRef, children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
|
|
10706
|
-
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("th", { className: "twc-dt__th", role: "columnheader", "aria-label": "Select", "data-pin": "left", "data-pin-edge": pins.left.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width:
|
|
10786
|
+
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("th", { className: "twc-dt__th", role: "columnheader", "aria-label": "Select", "data-pin": "left", "data-pin-edge": pins.left.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W, minWidth: CHK_W }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__th-inner", style: { justifyContent: "center", padding: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10707
10787
|
"span",
|
|
10708
10788
|
{
|
|
10709
10789
|
className: "twc-dt__check",
|
|
@@ -10879,7 +10959,7 @@ function Datatable({
|
|
|
10879
10959
|
})
|
|
10880
10960
|
] }) }),
|
|
10881
10961
|
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(sizeVal, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "twc-dt__row", role: "row", children: [
|
|
10882
|
-
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width:
|
|
10962
|
+
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: CHK_W }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
|
|
10883
10963
|
showRowNum ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "16px", height: 14, borderRadius: 4 } }) }) : null,
|
|
10884
10964
|
ordered.map((c, ci) => {
|
|
10885
10965
|
const st = stickyOf(c.field);
|
|
@@ -10901,7 +10981,7 @@ function Datatable({
|
|
|
10901
10981
|
pinnedBottomRows.map((row) => renderLeaf(row, keyIndex.get(keyOf(row)), "bottom"))
|
|
10902
10982
|
] }) }),
|
|
10903
10983
|
hasAggregation && aggOn && !loading && paged.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("tfoot", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", children: [
|
|
10904
|
-
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": pins.left.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width:
|
|
10984
|
+
checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { role: "gridcell", "data-pin": "left", "data-pin-edge": pins.left.length || showRowNum ? void 0 : "left", style: { insetInlineStart: 0, width: CHK_W } }) : null,
|
|
10905
10985
|
showRowNum ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", "data-pin-edge": pins.left.length ? void 0 : "left", style: { insetInlineStart: numLeft, width: NUM_W } }) : null,
|
|
10906
10986
|
ordered.map((c) => {
|
|
10907
10987
|
const st = stickyOf(c.field);
|
|
@@ -17809,21 +17889,47 @@ function useLocalStorage(key, initialValue) {
|
|
|
17809
17889
|
);
|
|
17810
17890
|
return [stored, setValue];
|
|
17811
17891
|
}
|
|
17892
|
+
function legacyCopy(text) {
|
|
17893
|
+
try {
|
|
17894
|
+
const ta = document.createElement("textarea");
|
|
17895
|
+
ta.value = text;
|
|
17896
|
+
ta.setAttribute("readonly", "");
|
|
17897
|
+
ta.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;padding:0;border:none;opacity:0;pointer-events:none;";
|
|
17898
|
+
document.body.appendChild(ta);
|
|
17899
|
+
const sel = document.getSelection();
|
|
17900
|
+
const prev = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
|
|
17901
|
+
ta.focus();
|
|
17902
|
+
ta.select();
|
|
17903
|
+
ta.setSelectionRange(0, ta.value.length);
|
|
17904
|
+
const ok = document.execCommand("copy");
|
|
17905
|
+
document.body.removeChild(ta);
|
|
17906
|
+
if (prev && sel) {
|
|
17907
|
+
sel.removeAllRanges();
|
|
17908
|
+
sel.addRange(prev);
|
|
17909
|
+
}
|
|
17910
|
+
return ok;
|
|
17911
|
+
} catch {
|
|
17912
|
+
return false;
|
|
17913
|
+
}
|
|
17914
|
+
}
|
|
17812
17915
|
function useCopyToClipboard(timeout = 1500) {
|
|
17813
17916
|
const [copied, setCopied] = React51__namespace.default.useState(false);
|
|
17814
17917
|
const copy = React51__namespace.default.useCallback(async (text) => {
|
|
17815
|
-
if (!canUseDOM2
|
|
17918
|
+
if (!canUseDOM2) {
|
|
17816
17919
|
setCopied(false);
|
|
17817
17920
|
return false;
|
|
17818
17921
|
}
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17823
|
-
|
|
17824
|
-
|
|
17825
|
-
|
|
17922
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
17923
|
+
try {
|
|
17924
|
+
await navigator.clipboard.writeText(text);
|
|
17925
|
+
setCopied(true);
|
|
17926
|
+
return true;
|
|
17927
|
+
} catch {
|
|
17928
|
+
}
|
|
17826
17929
|
}
|
|
17930
|
+
const ok = legacyCopy(String(text ?? ""));
|
|
17931
|
+
setCopied(ok);
|
|
17932
|
+
return ok;
|
|
17827
17933
|
}, []);
|
|
17828
17934
|
React51__namespace.default.useEffect(() => {
|
|
17829
17935
|
if (!copied) return void 0;
|