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 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 widthOf = (c) => widths[c.field] ?? c.width ?? 160;
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.width ?? 160) + delta) }));
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 <= 0;
10861
- const atLast = midIdx === -1 || midIdx >= movableMidFields.length - 1;
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
  }