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/dist/index.d.cts CHANGED
@@ -1762,6 +1762,14 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1762
1762
  /** Restrict the toolbar quick-search to these column `field`s (client mode); defaults to every
1763
1763
  * visible column. Mirrors `runDatatableQuery`'s `options.searchFields`. */
1764
1764
  searchFields?: string[];
1765
+ /** Render the built-in toolbar quick-search box. Set `false` to externalize search — supply your
1766
+ * own input wired to `quickFilter`/`onQuickFilterChange` (mirrors CardGrid). @default true */
1767
+ searchable?: boolean;
1768
+ /** Controlled quick-search string; pair with `onQuickFilterChange` to drive search from outside
1769
+ * (the value still flows into client filtering and the `onServerChange` query). */
1770
+ quickFilter?: string;
1771
+ /** Fires with the new quick-search string as the user types (and when a host sets it). */
1772
+ onQuickFilterChange?: (value: string) => void;
1765
1773
  /**
1766
1774
  * Actions shown in the toolbar when one or more rows are selected (requires
1767
1775
  * `checkboxSelection`). Each handler receives the selected keys, the resolved
@@ -1982,6 +1990,13 @@ interface DatatableColumn<T = any> {
1982
1990
  editable?: boolean;
1983
1991
  /** Editor type. "select" (or any column with `valueOptions`) renders a dropdown; else a text/number input by column type. */
1984
1992
  editType?: "text" | "number" | "select";
1993
+ /** Full escape hatch for the inline cell editor — render your own control (a searchable / creatable /
1994
+ * async `Combobox`, a `MasterCombobox`, …) for a value backed by a large, extensible vocabulary.
1995
+ * Providing it makes the column editable (unless `editable: false`) and takes precedence over the
1996
+ * built-in select/text editor. Call `commit(nextValue)` to save (fires `onRowUpdate`/`onRowsChange`)
1997
+ * or `cancel()` to discard. Twico overlay dropdowns (portaled as `.twc-pop`) are exempt from the
1998
+ * cell's outside-click auto-cancel, so a Combobox popover works inside the cell. */
1999
+ renderEditCell?: (args: { value: any; row: T; field: string; commit: (nextValue: any) => void; cancel: () => void }) => React.ReactNode;
1985
2000
  /** Hide the column header ⋮ menu. @default false */
1986
2001
  disableColumnMenu?: boolean;
1987
2002
  /** Start with this column's cell text wrapped onto multiple lines (the row grows down) instead of
@@ -1989,6 +2004,11 @@ interface DatatableColumn<T = any> {
1989
2004
  wrapText?: boolean;
1990
2005
  /** Distinct values for the "is any of" multi-value filter (recommended in server mode). */
1991
2006
  valueOptions?: Array<string | { value: string; label: string }>;
2007
+ /** Async loader for the "is any of" filter's options — searchable/lazy server-backed values (for a
2008
+ * large reference/master list). Called with the type-ahead query (debounced; `""` primes the list)
2009
+ * and takes precedence over `valueOptions` for the filter picker, which then loads server-side
2010
+ * (no local filtering) with a loading state. */
2011
+ loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
1992
2012
  /** Format the raw value to a string/number for display. */
1993
2013
  valueFormatter?: (value: any, row: T) => React.ReactNode;
1994
2014
  /** Fully custom cell renderer (badges, avatars, etc.). */
package/dist/index.d.ts CHANGED
@@ -1762,6 +1762,14 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1762
1762
  /** Restrict the toolbar quick-search to these column `field`s (client mode); defaults to every
1763
1763
  * visible column. Mirrors `runDatatableQuery`'s `options.searchFields`. */
1764
1764
  searchFields?: string[];
1765
+ /** Render the built-in toolbar quick-search box. Set `false` to externalize search — supply your
1766
+ * own input wired to `quickFilter`/`onQuickFilterChange` (mirrors CardGrid). @default true */
1767
+ searchable?: boolean;
1768
+ /** Controlled quick-search string; pair with `onQuickFilterChange` to drive search from outside
1769
+ * (the value still flows into client filtering and the `onServerChange` query). */
1770
+ quickFilter?: string;
1771
+ /** Fires with the new quick-search string as the user types (and when a host sets it). */
1772
+ onQuickFilterChange?: (value: string) => void;
1765
1773
  /**
1766
1774
  * Actions shown in the toolbar when one or more rows are selected (requires
1767
1775
  * `checkboxSelection`). Each handler receives the selected keys, the resolved
@@ -1982,6 +1990,13 @@ interface DatatableColumn<T = any> {
1982
1990
  editable?: boolean;
1983
1991
  /** Editor type. "select" (or any column with `valueOptions`) renders a dropdown; else a text/number input by column type. */
1984
1992
  editType?: "text" | "number" | "select";
1993
+ /** Full escape hatch for the inline cell editor — render your own control (a searchable / creatable /
1994
+ * async `Combobox`, a `MasterCombobox`, …) for a value backed by a large, extensible vocabulary.
1995
+ * Providing it makes the column editable (unless `editable: false`) and takes precedence over the
1996
+ * built-in select/text editor. Call `commit(nextValue)` to save (fires `onRowUpdate`/`onRowsChange`)
1997
+ * or `cancel()` to discard. Twico overlay dropdowns (portaled as `.twc-pop`) are exempt from the
1998
+ * cell's outside-click auto-cancel, so a Combobox popover works inside the cell. */
1999
+ renderEditCell?: (args: { value: any; row: T; field: string; commit: (nextValue: any) => void; cancel: () => void }) => React.ReactNode;
1985
2000
  /** Hide the column header ⋮ menu. @default false */
1986
2001
  disableColumnMenu?: boolean;
1987
2002
  /** Start with this column's cell text wrapped onto multiple lines (the row grows down) instead of
@@ -1989,6 +2004,11 @@ interface DatatableColumn<T = any> {
1989
2004
  wrapText?: boolean;
1990
2005
  /** Distinct values for the "is any of" multi-value filter (recommended in server mode). */
1991
2006
  valueOptions?: Array<string | { value: string; label: string }>;
2007
+ /** Async loader for the "is any of" filter's options — searchable/lazy server-backed values (for a
2008
+ * large reference/master list). Called with the type-ahead query (debounced; `""` primes the list)
2009
+ * and takes precedence over `valueOptions` for the filter picker, which then loads server-side
2010
+ * (no local filtering) with a loading state. */
2011
+ loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
1992
2012
  /** Format the raw value to a string/number for display. */
1993
2013
  valueFormatter?: (value: any, row: T) => React.ReactNode;
1994
2014
  /** Fully custom cell renderer (badges, avatars, etc.). */
package/dist/index.mjs CHANGED
@@ -7070,7 +7070,8 @@ var FIELD_CSS2 = `
7070
7070
  .twc-field__req { color: var(--color-danger); }
7071
7071
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
7072
7072
  .twc-field__error { font-size: var(--text-xs); color: var(--color-danger-subtle-fg); font-weight: var(--font-medium); }
7073
-
7073
+ `;
7074
+ var INPUT_EXTRA_CSS = `
7074
7075
  .twc-input {
7075
7076
  --_h: var(--control-h-md);
7076
7077
  display: flex; align-items: center; gap: var(--space-2);
@@ -7079,6 +7080,7 @@ var FIELD_CSS2 = `
7079
7080
  background: var(--color-surface);
7080
7081
  border: var(--border-thin) solid var(--color-border);
7081
7082
  border-radius: var(--radius-md);
7083
+ overflow: hidden;
7082
7084
  transition: border-color var(--duration-fast) var(--ease-standard),
7083
7085
  box-shadow var(--duration-fast) var(--ease-standard),
7084
7086
  background-color var(--duration-fast) var(--ease-standard);
@@ -7112,9 +7114,6 @@ var FIELD_CSS2 = `
7112
7114
  .twc-input__reveal:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7113
7115
  .twc-input__reveal:focus-visible { outline: none; box-shadow: var(--ring); }
7114
7116
  .twc-input__reveal svg { width: 17px; height: 17px; }
7115
- `;
7116
- var INPUT_EXTRA_CSS = `
7117
- .twc-input { overflow: hidden; }
7118
7117
  .twc-input__addon { align-self: stretch; display: inline-flex; align-items: center; padding-inline: var(--space-3);
7119
7118
  background: var(--color-surface-sunken); color: var(--color-text-muted); font-size: var(--text-sm);
7120
7119
  font-weight: var(--font-semibold); white-space: nowrap; flex: none; }
@@ -8625,6 +8624,53 @@ function Caret({ pos }) {
8625
8624
  const left = Math.max(12, Math.min(pos.anchorX - pos.left, pos.width - 20));
8626
8625
  return /* @__PURE__ */ jsx("span", { className: "twc-dt__caret", style: { left } });
8627
8626
  }
8627
+ function AsyncFilterValue({ col, value, onChange }) {
8628
+ const [opts, setOpts] = React51__default.useState([]);
8629
+ const [loading, setLoading] = React51__default.useState(false);
8630
+ const reqRef = React51__default.useRef(0);
8631
+ const debRef = React51__default.useRef();
8632
+ const load = React51__default.useCallback((query) => {
8633
+ const id = ++reqRef.current;
8634
+ setLoading(true);
8635
+ Promise.resolve(col.loadValueOptions(query)).then(
8636
+ (res) => {
8637
+ if (reqRef.current === id) {
8638
+ setOpts((Array.isArray(res) ? res : []).map((o) => typeof o === "string" ? { value: o, label: o } : o));
8639
+ setLoading(false);
8640
+ }
8641
+ },
8642
+ () => {
8643
+ if (reqRef.current === id) setLoading(false);
8644
+ }
8645
+ );
8646
+ }, [col]);
8647
+ React51__default.useEffect(() => {
8648
+ load("");
8649
+ return () => clearTimeout(debRef.current);
8650
+ }, [load]);
8651
+ const onInput = React51__default.useCallback((q) => {
8652
+ clearTimeout(debRef.current);
8653
+ debRef.current = setTimeout(() => load(q), 250);
8654
+ }, [load]);
8655
+ const selected = Array.isArray(value) ? value : [];
8656
+ const merged = React51__default.useMemo(() => {
8657
+ const seen2 = new Set(opts.map((o) => o.value));
8658
+ return [...selected.filter((v) => !seen2.has(v)).map((v) => ({ value: v, label: String(v) })), ...opts];
8659
+ }, [opts, selected]);
8660
+ return /* @__PURE__ */ jsx(
8661
+ MultiSelect,
8662
+ {
8663
+ portal: true,
8664
+ placeholder: "Any of\u2026",
8665
+ value: selected,
8666
+ options: merged,
8667
+ filter: false,
8668
+ loading,
8669
+ onInputChange: onInput,
8670
+ onChange
8671
+ }
8672
+ );
8673
+ }
8628
8674
  function EditCell({ col, value, options, onChange, onCommit, onCommitValue, onCancel, onKeyDown }) {
8629
8675
  const ref = React51__default.useRef(null);
8630
8676
  const isSelect = col.editType === "select" || options && col.editType !== "text";
@@ -8825,6 +8871,9 @@ function Datatable({
8825
8871
  rowGrouping = [],
8826
8872
  rowNumbers = false,
8827
8873
  searchFields = null,
8874
+ searchable = true,
8875
+ quickFilter,
8876
+ onQuickFilterChange,
8828
8877
  rowPinning = false,
8829
8878
  rowReorder = false,
8830
8879
  rowResize = false,
@@ -8907,7 +8956,13 @@ function Datatable({
8907
8956
  return { left, right };
8908
8957
  });
8909
8958
  const [density, setDensity] = React51__default.useState(densityProp);
8910
- const [quick, setQuick] = React51__default.useState("");
8959
+ const [internalQuick, setInternalQuick] = React51__default.useState("");
8960
+ const quick = quickFilter !== void 0 ? quickFilter : internalQuick;
8961
+ const commitQuick = (v) => {
8962
+ onQuickFilterChange?.(v);
8963
+ if (quickFilter === void 0) setInternalQuick(v);
8964
+ commitPage(0);
8965
+ };
8911
8966
  const [selected, setSelected] = React51__default.useState(() => /* @__PURE__ */ new Set());
8912
8967
  const [activeRow, setActiveRow] = React51__default.useState(null);
8913
8968
  const [activeCell, setActiveCell] = React51__default.useState(null);
@@ -9787,7 +9842,7 @@ function Datatable({
9787
9842
  setSelected(/* @__PURE__ */ new Set());
9788
9843
  }
9789
9844
  const [editing, setEditing] = React51__default.useState(null);
9790
- const isColEditable = (c) => c.type !== "actions" && (c.editable ?? (editMode && c.editable !== false));
9845
+ const isColEditable = (c) => c.type !== "actions" && c.editable !== false && (c.editable === true || c.renderEditCell != null || editMode);
9791
9846
  function beginEdit(rowK, col, row) {
9792
9847
  if (!isColEditable(col)) return;
9793
9848
  setEditing({ key: rowK, field: col.field, value: row[col.field] ?? "" });
@@ -10118,7 +10173,13 @@ function Datatable({
10118
10173
  !checkboxSelection && reorderable && ci === 0 && !isEditing ? /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: "100%" }, children: [
10119
10174
  rowHandle(k, midIdx, row),
10120
10175
  /* @__PURE__ */ 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 })
10121
- ] }) : isEditing ? /* @__PURE__ */ jsx(
10176
+ ] }) : isEditing ? c.renderEditCell ? (
10177
+ // #236: full escape hatch — the host drops in its own editor (searchable/creatable/
10178
+ // async Combobox, …) and drives the lifecycle via commit/cancel. Wrapped in
10179
+ // .twc-dt__editor-wrap so inline clicks (and any .twc-pop portal dropdown) don't
10180
+ // trip the outside-click auto-cancel.
10181
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__editor-wrap", children: c.renderEditCell({ value: editing.value, row, field: c.field, commit: (v) => commitEdit(v), cancel: cancelEdit }) })
10182
+ ) : /* @__PURE__ */ jsx(
10122
10183
  EditCell,
10123
10184
  {
10124
10185
  col: c,
@@ -10408,13 +10469,10 @@ function Datatable({
10408
10469
  }
10409
10470
  )
10410
10471
  ] }) : null,
10411
- /* @__PURE__ */ jsxs("div", { className: "twc-dt__search", children: [
10472
+ searchable ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__search", children: [
10412
10473
  /* @__PURE__ */ jsx(Svg, { d: I.search }),
10413
- /* @__PURE__ */ jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => {
10414
- setQuick(e.target.value);
10415
- commitPage(0);
10416
- } })
10417
- ] })
10474
+ /* @__PURE__ */ jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) })
10475
+ ] }) : null
10418
10476
  ] }),
10419
10477
  !pivotActive && activeGroupBy.length ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__groupbar", children: [
10420
10478
  /* @__PURE__ */ jsx(Svg, { d: I.group }),
@@ -11273,7 +11331,14 @@ function Datatable({
11273
11331
  }))
11274
11332
  }
11275
11333
  ) }),
11276
- /* @__PURE__ */ jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? /* @__PURE__ */ jsx(
11334
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsx(
11335
+ AsyncFilterValue,
11336
+ {
11337
+ col,
11338
+ value: f.value,
11339
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11340
+ }
11341
+ ) : /* @__PURE__ */ jsx(
11277
11342
  MultiSelect,
11278
11343
  {
11279
11344
  portal: true,