twico-ui 1.12.0 → 1.14.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 +78 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.mjs +78 -8
- package/dist/index.mjs.map +1 -1
- package/llms.txt +1 -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.14.0](https://github.com/EncryptionL/twico-ui/compare/v1.13.0...v1.14.0) (2026-07-13)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **datatable:** async/searchable "is any of" filter options via loadValueOptions ([#232](https://github.com/EncryptionL/twico-ui/issues/232)) ([2db2b32](https://github.com/EncryptionL/twico-ui/commit/2db2b3212bcda8cdea9430b6766f02a2b3dd0048)), closes [#208](https://github.com/EncryptionL/twico-ui/issues/208)
|
|
11
|
+
|
|
12
|
+
# [1.13.0](https://github.com/EncryptionL/twico-ui/compare/v1.12.0...v1.13.0) (2026-07-13)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **data-display:** narrow-column sizing + menu gating; fix Move-menu & CardGrid sort clipping ([a4f4937](https://github.com/EncryptionL/twico-ui/commit/a4f4937d00c4c3c9be0b199c8eac98d0d60f5572)), closes [#228](https://github.com/EncryptionL/twico-ui/issues/228) [#226](https://github.com/EncryptionL/twico-ui/issues/226) [#227](https://github.com/EncryptionL/twico-ui/issues/227) [#228](https://github.com/EncryptionL/twico-ui/issues/228)
|
|
18
|
+
|
|
5
19
|
# [1.12.0](https://github.com/EncryptionL/twico-ui/compare/v1.11.1...v1.12.0) (2026-07-10)
|
|
6
20
|
|
|
7
21
|
|
package/dist/index.cjs
CHANGED
|
@@ -8646,6 +8646,53 @@ function Caret({ pos }) {
|
|
|
8646
8646
|
const left = Math.max(12, Math.min(pos.anchorX - pos.left, pos.width - 20));
|
|
8647
8647
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__caret", style: { left } });
|
|
8648
8648
|
}
|
|
8649
|
+
function AsyncFilterValue({ col, value, onChange }) {
|
|
8650
|
+
const [opts, setOpts] = React51__namespace.default.useState([]);
|
|
8651
|
+
const [loading, setLoading] = React51__namespace.default.useState(false);
|
|
8652
|
+
const reqRef = React51__namespace.default.useRef(0);
|
|
8653
|
+
const debRef = React51__namespace.default.useRef();
|
|
8654
|
+
const load = React51__namespace.default.useCallback((query) => {
|
|
8655
|
+
const id = ++reqRef.current;
|
|
8656
|
+
setLoading(true);
|
|
8657
|
+
Promise.resolve(col.loadValueOptions(query)).then(
|
|
8658
|
+
(res) => {
|
|
8659
|
+
if (reqRef.current === id) {
|
|
8660
|
+
setOpts((Array.isArray(res) ? res : []).map((o) => typeof o === "string" ? { value: o, label: o } : o));
|
|
8661
|
+
setLoading(false);
|
|
8662
|
+
}
|
|
8663
|
+
},
|
|
8664
|
+
() => {
|
|
8665
|
+
if (reqRef.current === id) setLoading(false);
|
|
8666
|
+
}
|
|
8667
|
+
);
|
|
8668
|
+
}, [col]);
|
|
8669
|
+
React51__namespace.default.useEffect(() => {
|
|
8670
|
+
load("");
|
|
8671
|
+
return () => clearTimeout(debRef.current);
|
|
8672
|
+
}, [load]);
|
|
8673
|
+
const onInput = React51__namespace.default.useCallback((q) => {
|
|
8674
|
+
clearTimeout(debRef.current);
|
|
8675
|
+
debRef.current = setTimeout(() => load(q), 250);
|
|
8676
|
+
}, [load]);
|
|
8677
|
+
const selected = Array.isArray(value) ? value : [];
|
|
8678
|
+
const merged = React51__namespace.default.useMemo(() => {
|
|
8679
|
+
const seen2 = new Set(opts.map((o) => o.value));
|
|
8680
|
+
return [...selected.filter((v) => !seen2.has(v)).map((v) => ({ value: v, label: String(v) })), ...opts];
|
|
8681
|
+
}, [opts, selected]);
|
|
8682
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
8683
|
+
MultiSelect,
|
|
8684
|
+
{
|
|
8685
|
+
portal: true,
|
|
8686
|
+
placeholder: "Any of\u2026",
|
|
8687
|
+
value: selected,
|
|
8688
|
+
options: merged,
|
|
8689
|
+
filter: false,
|
|
8690
|
+
loading,
|
|
8691
|
+
onInputChange: onInput,
|
|
8692
|
+
onChange
|
|
8693
|
+
}
|
|
8694
|
+
);
|
|
8695
|
+
}
|
|
8649
8696
|
function EditCell({ col, value, options, onChange, onCommit, onCommitValue, onCancel, onKeyDown }) {
|
|
8650
8697
|
const ref = React51__namespace.default.useRef(null);
|
|
8651
8698
|
const isSelect = col.editType === "select" || options && col.editType !== "text";
|
|
@@ -9024,7 +9071,19 @@ function Datatable({
|
|
|
9024
9071
|
rowGroupingKeyRef.current = rowGroupingKey;
|
|
9025
9072
|
setGroupBy(rowGrouping || []);
|
|
9026
9073
|
}, [rowGroupingKey]);
|
|
9027
|
-
const
|
|
9074
|
+
const AUTO_CHROME = 74;
|
|
9075
|
+
const autoWidth = (c) => {
|
|
9076
|
+
const label = typeof c.headerName === "string" ? c.headerName : String(c.field ?? "");
|
|
9077
|
+
return AUTO_CHROME + Math.ceil(label.length * 8);
|
|
9078
|
+
};
|
|
9079
|
+
const clampWidth = (w, c) => {
|
|
9080
|
+
let x = w;
|
|
9081
|
+
if (typeof c.maxWidth === "number") x = Math.min(x, c.maxWidth);
|
|
9082
|
+
if (typeof c.minWidth === "number") x = Math.max(x, c.minWidth);
|
|
9083
|
+
return Math.max(40, x);
|
|
9084
|
+
};
|
|
9085
|
+
const intrinsicWidth = (c) => c.width === "auto" ? autoWidth(c) : typeof c.width === "number" ? c.width : 160;
|
|
9086
|
+
const widthOf = (c) => clampWidth(widths[c.field] ?? intrinsicWidth(c), c);
|
|
9028
9087
|
const [colMenu, setColMenu] = React51__namespace.default.useState(null);
|
|
9029
9088
|
const [menuPos, openMenu, closeMenu] = useFloating();
|
|
9030
9089
|
const [panel, setPanel] = React51__namespace.default.useState(null);
|
|
@@ -10612,7 +10671,7 @@ function Datatable({
|
|
|
10612
10671
|
e.preventDefault();
|
|
10613
10672
|
e.stopPropagation();
|
|
10614
10673
|
const delta = e.key === "ArrowRight" ? 10 : -10;
|
|
10615
|
-
setWidths((m) => ({ ...m, [c.field]: Math.max(72, (m[c.field] ?? c
|
|
10674
|
+
setWidths((m) => ({ ...m, [c.field]: Math.max(72, (m[c.field] ?? intrinsicWidth(c)) + delta) }));
|
|
10616
10675
|
},
|
|
10617
10676
|
title: "Drag to resize \xB7 double-click to fit content"
|
|
10618
10677
|
}
|
|
@@ -10855,10 +10914,10 @@ function Datatable({
|
|
|
10855
10914
|
" ",
|
|
10856
10915
|
groupBy.includes(c.field) ? "Stop grouping" : "Group by this column"
|
|
10857
10916
|
] }) : null,
|
|
10858
|
-
!disableColumnReorder && c.type !== "actions" && !pins.left.includes(c.field) && !pins.right.includes(c.field) ? (() => {
|
|
10917
|
+
!disableColumnReorder && c.reorderable !== false && c.type !== "actions" && !pins.left.includes(c.field) && !pins.right.includes(c.field) && movableMidFields.length > 1 ? (() => {
|
|
10859
10918
|
const midIdx = movableMidFields.indexOf(c.field);
|
|
10860
|
-
const atFirst = midIdx
|
|
10861
|
-
const atLast = midIdx ===
|
|
10919
|
+
const atFirst = midIdx === 0;
|
|
10920
|
+
const atLast = midIdx === movableMidFields.length - 1;
|
|
10862
10921
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10863
10922
|
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", disabled: atFirst, "aria-disabled": atFirst || void 0, onClick: () => {
|
|
10864
10923
|
moveCol(c.field, -1);
|
|
@@ -10894,7 +10953,7 @@ function Datatable({
|
|
|
10894
10953
|
pins.right.includes(c.field) ? "Unpin" : "Pin to right"
|
|
10895
10954
|
] })
|
|
10896
10955
|
] }) : null,
|
|
10897
|
-
c.type !== "actions" ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", "data-active": wrapped.has(c.field) || void 0, onClick: () => {
|
|
10956
|
+
c.type !== "actions" && c.wrappable !== false ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", "data-active": wrapped.has(c.field) || void 0, onClick: () => {
|
|
10898
10957
|
toggleWrap(c.field);
|
|
10899
10958
|
close();
|
|
10900
10959
|
}, children: [
|
|
@@ -11282,7 +11341,14 @@ function Datatable({
|
|
|
11282
11341
|
}))
|
|
11283
11342
|
}
|
|
11284
11343
|
) }),
|
|
11285
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
11344
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
11345
|
+
AsyncFilterValue,
|
|
11346
|
+
{
|
|
11347
|
+
col,
|
|
11348
|
+
value: f.value,
|
|
11349
|
+
onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
|
|
11350
|
+
}
|
|
11351
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
11286
11352
|
MultiSelect,
|
|
11287
11353
|
{
|
|
11288
11354
|
portal: true,
|
|
@@ -11398,7 +11464,7 @@ var CARDGRID_CSS = `
|
|
|
11398
11464
|
.twc-cardgrid__toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
|
|
11399
11465
|
.twc-cardgrid__search { flex: 1 1 200px; min-width: 160px; }
|
|
11400
11466
|
.twc-cardgrid__spacer { flex: 1 1 auto; }
|
|
11401
|
-
.twc-cardgrid__sort { display: inline-flex; align-items: center; gap: var(--space-1); }
|
|
11467
|
+
.twc-cardgrid__sort { display: inline-flex; align-items: center; gap: var(--space-1); flex: none; }
|
|
11402
11468
|
.twc-cardgrid__dir { display: inline-grid; place-items: center; width: 32px; height: 32px; border: var(--border-thin) solid var(--color-border); background: var(--color-surface); color: var(--color-text-muted); cursor: pointer; border-radius: var(--radius-md); transition: background-color var(--duration-fast), color var(--duration-fast); }
|
|
11403
11469
|
.twc-cardgrid__dir:hover { background: var(--color-surface-sunken); color: var(--color-text); }
|
|
11404
11470
|
.twc-cardgrid__dir svg { width: 16px; height: 16px; transition: transform var(--duration-fast) var(--ease-standard); }
|
|
@@ -11446,6 +11512,7 @@ function CardGrid({
|
|
|
11446
11512
|
defaultSort = null,
|
|
11447
11513
|
onSortChange,
|
|
11448
11514
|
sortOptions,
|
|
11515
|
+
sortMinWidth,
|
|
11449
11516
|
// slots / states
|
|
11450
11517
|
toolbar,
|
|
11451
11518
|
emptyState = "No results.",
|
|
@@ -11517,6 +11584,8 @@ function CardGrid({
|
|
|
11517
11584
|
const to = paginated ? Math.min((pageVal + 1) * sizeVal, total) : total;
|
|
11518
11585
|
const rppOptions = Array.from(new Set([...pageSizeOptions || [], pageSize].filter((n) => n > 0))).sort((a, b) => a - b).map((n) => ({ value: String(n), label: String(n) }));
|
|
11519
11586
|
const sortSelectOptions = sortOptions ? [{ value: "", label: "Sort by\u2026" }, ...sortOptions.map((o) => ({ value: o.field, label: o.label || o.field }))] : null;
|
|
11587
|
+
const sortAutoWidth = sortSelectOptions ? Math.min(300, Math.max(120, Math.ceil(Math.max(...sortSelectOptions.map((o) => String(o.label).length)) * 7.2) + 46)) : void 0;
|
|
11588
|
+
const sortWidth = sortMinWidth != null ? sortMinWidth : sortAutoWidth;
|
|
11520
11589
|
const hasToolbar = searchable || sortSelectOptions || toolbar;
|
|
11521
11590
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-cardgrid ${className}`, id: fieldId, ...rest, children: [
|
|
11522
11591
|
styles,
|
|
@@ -11544,6 +11613,7 @@ function CardGrid({
|
|
|
11544
11613
|
value: sortVal?.field || "",
|
|
11545
11614
|
options: sortSelectOptions,
|
|
11546
11615
|
placeholder: "Sort by\u2026",
|
|
11616
|
+
style: { minWidth: sortWidth },
|
|
11547
11617
|
"aria-label": "Sort by",
|
|
11548
11618
|
onChange: (field) => commitSort(field ? { field, dir: sortVal?.dir || "asc" } : null)
|
|
11549
11619
|
}
|