twico-ui 1.13.0 → 1.14.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 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.1](https://github.com/EncryptionL/twico-ui/compare/v1.14.0...v1.14.1) (2026-07-14)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **styles:** input renders invisibly under React 19 hoist dedup; add Datatable external-search + renderEditCell ([#234](https://github.com/EncryptionL/twico-ui/issues/234), [#235](https://github.com/EncryptionL/twico-ui/issues/235), [#236](https://github.com/EncryptionL/twico-ui/issues/236)) ([f661ea9](https://github.com/EncryptionL/twico-ui/commit/f661ea90dab1bf4b97ee02e8f103fb44ce668f43)), closes [#232](https://github.com/EncryptionL/twico-ui/issues/232)
11
+
12
+ # [1.14.0](https://github.com/EncryptionL/twico-ui/compare/v1.13.0...v1.14.0) (2026-07-13)
13
+
14
+
15
+ ### Features
16
+
17
+ * **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)
18
+
5
19
  # [1.13.0](https://github.com/EncryptionL/twico-ui/compare/v1.12.0...v1.13.0) (2026-07-13)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -7091,7 +7091,8 @@ var FIELD_CSS2 = `
7091
7091
  .twc-field__req { color: var(--color-danger); }
7092
7092
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
7093
7093
  .twc-field__error { font-size: var(--text-xs); color: var(--color-danger-subtle-fg); font-weight: var(--font-medium); }
7094
-
7094
+ `;
7095
+ var INPUT_EXTRA_CSS = `
7095
7096
  .twc-input {
7096
7097
  --_h: var(--control-h-md);
7097
7098
  display: flex; align-items: center; gap: var(--space-2);
@@ -7100,6 +7101,7 @@ var FIELD_CSS2 = `
7100
7101
  background: var(--color-surface);
7101
7102
  border: var(--border-thin) solid var(--color-border);
7102
7103
  border-radius: var(--radius-md);
7104
+ overflow: hidden;
7103
7105
  transition: border-color var(--duration-fast) var(--ease-standard),
7104
7106
  box-shadow var(--duration-fast) var(--ease-standard),
7105
7107
  background-color var(--duration-fast) var(--ease-standard);
@@ -7133,9 +7135,6 @@ var FIELD_CSS2 = `
7133
7135
  .twc-input__reveal:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7134
7136
  .twc-input__reveal:focus-visible { outline: none; box-shadow: var(--ring); }
7135
7137
  .twc-input__reveal svg { width: 17px; height: 17px; }
7136
- `;
7137
- var INPUT_EXTRA_CSS = `
7138
- .twc-input { overflow: hidden; }
7139
7138
  .twc-input__addon { align-self: stretch; display: inline-flex; align-items: center; padding-inline: var(--space-3);
7140
7139
  background: var(--color-surface-sunken); color: var(--color-text-muted); font-size: var(--text-sm);
7141
7140
  font-weight: var(--font-semibold); white-space: nowrap; flex: none; }
@@ -8646,6 +8645,53 @@ function Caret({ pos }) {
8646
8645
  const left = Math.max(12, Math.min(pos.anchorX - pos.left, pos.width - 20));
8647
8646
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__caret", style: { left } });
8648
8647
  }
8648
+ function AsyncFilterValue({ col, value, onChange }) {
8649
+ const [opts, setOpts] = React51__namespace.default.useState([]);
8650
+ const [loading, setLoading] = React51__namespace.default.useState(false);
8651
+ const reqRef = React51__namespace.default.useRef(0);
8652
+ const debRef = React51__namespace.default.useRef();
8653
+ const load = React51__namespace.default.useCallback((query) => {
8654
+ const id = ++reqRef.current;
8655
+ setLoading(true);
8656
+ Promise.resolve(col.loadValueOptions(query)).then(
8657
+ (res) => {
8658
+ if (reqRef.current === id) {
8659
+ setOpts((Array.isArray(res) ? res : []).map((o) => typeof o === "string" ? { value: o, label: o } : o));
8660
+ setLoading(false);
8661
+ }
8662
+ },
8663
+ () => {
8664
+ if (reqRef.current === id) setLoading(false);
8665
+ }
8666
+ );
8667
+ }, [col]);
8668
+ React51__namespace.default.useEffect(() => {
8669
+ load("");
8670
+ return () => clearTimeout(debRef.current);
8671
+ }, [load]);
8672
+ const onInput = React51__namespace.default.useCallback((q) => {
8673
+ clearTimeout(debRef.current);
8674
+ debRef.current = setTimeout(() => load(q), 250);
8675
+ }, [load]);
8676
+ const selected = Array.isArray(value) ? value : [];
8677
+ const merged = React51__namespace.default.useMemo(() => {
8678
+ const seen2 = new Set(opts.map((o) => o.value));
8679
+ return [...selected.filter((v) => !seen2.has(v)).map((v) => ({ value: v, label: String(v) })), ...opts];
8680
+ }, [opts, selected]);
8681
+ return /* @__PURE__ */ jsxRuntime.jsx(
8682
+ MultiSelect,
8683
+ {
8684
+ portal: true,
8685
+ placeholder: "Any of\u2026",
8686
+ value: selected,
8687
+ options: merged,
8688
+ filter: false,
8689
+ loading,
8690
+ onInputChange: onInput,
8691
+ onChange
8692
+ }
8693
+ );
8694
+ }
8649
8695
  function EditCell({ col, value, options, onChange, onCommit, onCommitValue, onCancel, onKeyDown }) {
8650
8696
  const ref = React51__namespace.default.useRef(null);
8651
8697
  const isSelect = col.editType === "select" || options && col.editType !== "text";
@@ -8846,6 +8892,9 @@ function Datatable({
8846
8892
  rowGrouping = [],
8847
8893
  rowNumbers = false,
8848
8894
  searchFields = null,
8895
+ searchable = true,
8896
+ quickFilter,
8897
+ onQuickFilterChange,
8849
8898
  rowPinning = false,
8850
8899
  rowReorder = false,
8851
8900
  rowResize = false,
@@ -8928,7 +8977,13 @@ function Datatable({
8928
8977
  return { left, right };
8929
8978
  });
8930
8979
  const [density, setDensity] = React51__namespace.default.useState(densityProp);
8931
- const [quick, setQuick] = React51__namespace.default.useState("");
8980
+ const [internalQuick, setInternalQuick] = React51__namespace.default.useState("");
8981
+ const quick = quickFilter !== void 0 ? quickFilter : internalQuick;
8982
+ const commitQuick = (v) => {
8983
+ onQuickFilterChange?.(v);
8984
+ if (quickFilter === void 0) setInternalQuick(v);
8985
+ commitPage(0);
8986
+ };
8932
8987
  const [selected, setSelected] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
8933
8988
  const [activeRow, setActiveRow] = React51__namespace.default.useState(null);
8934
8989
  const [activeCell, setActiveCell] = React51__namespace.default.useState(null);
@@ -9808,7 +9863,7 @@ function Datatable({
9808
9863
  setSelected(/* @__PURE__ */ new Set());
9809
9864
  }
9810
9865
  const [editing, setEditing] = React51__namespace.default.useState(null);
9811
- const isColEditable = (c) => c.type !== "actions" && (c.editable ?? (editMode && c.editable !== false));
9866
+ const isColEditable = (c) => c.type !== "actions" && c.editable !== false && (c.editable === true || c.renderEditCell != null || editMode);
9812
9867
  function beginEdit(rowK, col, row) {
9813
9868
  if (!isColEditable(col)) return;
9814
9869
  setEditing({ key: rowK, field: col.field, value: row[col.field] ?? "" });
@@ -10139,7 +10194,13 @@ function Datatable({
10139
10194
  !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
10140
10195
  rowHandle(k, midIdx, row),
10141
10196
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: isActions ? renderActions(c, row) : c.renderCell ? c.renderCell(val, row) : c.valueFormatter ? c.valueFormatter(val, row) : val })
10142
- ] }) : isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
10197
+ ] }) : isEditing ? c.renderEditCell ? (
10198
+ // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
10199
+ // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
10200
+ // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
10201
+ // trip the outside-click auto-cancel.
10202
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__editor-wrap", children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit }) })
10203
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
10143
10204
  EditCell,
10144
10205
  {
10145
10206
  col: c,
@@ -10429,13 +10490,10 @@ function Datatable({
10429
10490
  }
10430
10491
  )
10431
10492
  ] }) : null,
10432
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__search", children: [
10493
+ searchable ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__search", children: [
10433
10494
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.search }),
10434
- /* @__PURE__ */ jsxRuntime.jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => {
10435
- setQuick(e.target.value);
10436
- commitPage(0);
10437
- } })
10438
- ] })
10495
+ /* @__PURE__ */ jsxRuntime.jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) })
10496
+ ] }) : null
10439
10497
  ] }),
10440
10498
  !pivotActive && activeGroupBy.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__groupbar", children: [
10441
10499
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.group }),
@@ -11294,7 +11352,14 @@ function Datatable({
11294
11352
  }))
11295
11353
  }
11296
11354
  ) }),
11297
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? /* @__PURE__ */ jsxRuntime.jsx(
11355
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsxRuntime.jsx(
11356
+ AsyncFilterValue,
11357
+ {
11358
+ col,
11359
+ value: f.value,
11360
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11361
+ }
11362
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
11298
11363
  MultiSelect,
11299
11364
  {
11300
11365
  portal: true,