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/dist/index.d.cts CHANGED
@@ -1946,8 +1946,12 @@ interface DatatableColumn<T = any> {
1946
1946
  headerName?: string;
1947
1947
  /** Data type. "actions" renders per-row action buttons via getActions. @default "string" */
1948
1948
  type?: "string" | "number" | "actions";
1949
- /** Column width in px. @default 160 (120 for actions) */
1950
- width?: number;
1949
+ /** Column width: a px number, or `"auto"` to size to the header's intrinsic width (chrome + label) so a short column fits without truncating or wasting the 160px default. @default 160 (120 for actions) */
1950
+ width?: number | "auto";
1951
+ /** Lower bound (px) for the resolved width, incl. `"auto"` and user resizes. */
1952
+ minWidth?: number;
1953
+ /** Upper bound (px) for the resolved width; `minWidth` wins if they conflict. */
1954
+ maxWidth?: number;
1951
1955
  /** Cell alignment; currently affects the actions column's button justification. @default "right" for number/actions columns, else "left" */
1952
1956
  align?: "left" | "right";
1953
1957
  /** Allow sorting this column. @default true */
@@ -1966,6 +1970,8 @@ interface DatatableColumn<T = any> {
1966
1970
  resizable?: boolean;
1967
1971
  /** Allow drag / menu / keyboard reorder of this column (independent of the table-wide `disableColumnReorder`). @default true */
1968
1972
  reorderable?: boolean;
1973
+ /** Offer the "Wrap text" / "Unwrap text" item in the ⋮ menu. Set `false` for a fixed single-token column (e.g. an ordinal) whose menu should show only Sort + Hide. @default true */
1974
+ wrappable?: boolean;
1969
1975
  /** Summary-footer aggregation: a preset, or a function over the column's values. */
1970
1976
  aggregation?: "sum" | "avg" | "min" | "max" | "count" | ((values: any[], rows: T[]) => React.ReactNode);
1971
1977
  /** Format an aggregation result for the footer (falls back to valueFormatter). */
@@ -1983,6 +1989,11 @@ interface DatatableColumn<T = any> {
1983
1989
  wrapText?: boolean;
1984
1990
  /** Distinct values for the "is any of" multi-value filter (recommended in server mode). */
1985
1991
  valueOptions?: Array<string | { value: string; label: string }>;
1992
+ /** Async loader for the "is any of" filter's options — searchable/lazy server-backed values (for a
1993
+ * large reference/master list). Called with the type-ahead query (debounced; `""` primes the list)
1994
+ * and takes precedence over `valueOptions` for the filter picker, which then loads server-side
1995
+ * (no local filtering) with a loading state. */
1996
+ loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
1986
1997
  /** Format the raw value to a string/number for display. */
1987
1998
  valueFormatter?: (value: any, row: T) => React.ReactNode;
1988
1999
  /** Fully custom cell renderer (badges, avatars, etc.). */
@@ -2130,6 +2141,8 @@ interface CardGridProps<Row = any> extends Omit<React.HTMLAttributes<HTMLDivElem
2130
2141
  onSortChange?: (sort: CardGridSort | null) => void;
2131
2142
  /** Fields offered by the built-in sort control (renders it when provided). */
2132
2143
  sortOptions?: CardGridSortOption[];
2144
+ /** Min width (px or CSS length) for the built-in sort control's trigger, so long option labels aren't clipped. Defaults to an estimate from the longest label. */
2145
+ sortMinWidth?: string | number;
2133
2146
  /** Extra node rendered in the toolbar row (before the sort control). */
2134
2147
  toolbar?: React.ReactNode;
2135
2148
  /** Shown when there are no rows and not loading. @default "No results." */
package/dist/index.d.ts CHANGED
@@ -1946,8 +1946,12 @@ interface DatatableColumn<T = any> {
1946
1946
  headerName?: string;
1947
1947
  /** Data type. "actions" renders per-row action buttons via getActions. @default "string" */
1948
1948
  type?: "string" | "number" | "actions";
1949
- /** Column width in px. @default 160 (120 for actions) */
1950
- width?: number;
1949
+ /** Column width: a px number, or `"auto"` to size to the header's intrinsic width (chrome + label) so a short column fits without truncating or wasting the 160px default. @default 160 (120 for actions) */
1950
+ width?: number | "auto";
1951
+ /** Lower bound (px) for the resolved width, incl. `"auto"` and user resizes. */
1952
+ minWidth?: number;
1953
+ /** Upper bound (px) for the resolved width; `minWidth` wins if they conflict. */
1954
+ maxWidth?: number;
1951
1955
  /** Cell alignment; currently affects the actions column's button justification. @default "right" for number/actions columns, else "left" */
1952
1956
  align?: "left" | "right";
1953
1957
  /** Allow sorting this column. @default true */
@@ -1966,6 +1970,8 @@ interface DatatableColumn<T = any> {
1966
1970
  resizable?: boolean;
1967
1971
  /** Allow drag / menu / keyboard reorder of this column (independent of the table-wide `disableColumnReorder`). @default true */
1968
1972
  reorderable?: boolean;
1973
+ /** Offer the "Wrap text" / "Unwrap text" item in the ⋮ menu. Set `false` for a fixed single-token column (e.g. an ordinal) whose menu should show only Sort + Hide. @default true */
1974
+ wrappable?: boolean;
1969
1975
  /** Summary-footer aggregation: a preset, or a function over the column's values. */
1970
1976
  aggregation?: "sum" | "avg" | "min" | "max" | "count" | ((values: any[], rows: T[]) => React.ReactNode);
1971
1977
  /** Format an aggregation result for the footer (falls back to valueFormatter). */
@@ -1983,6 +1989,11 @@ interface DatatableColumn<T = any> {
1983
1989
  wrapText?: boolean;
1984
1990
  /** Distinct values for the "is any of" multi-value filter (recommended in server mode). */
1985
1991
  valueOptions?: Array<string | { value: string; label: string }>;
1992
+ /** Async loader for the "is any of" filter's options — searchable/lazy server-backed values (for a
1993
+ * large reference/master list). Called with the type-ahead query (debounced; `""` primes the list)
1994
+ * and takes precedence over `valueOptions` for the filter picker, which then loads server-side
1995
+ * (no local filtering) with a loading state. */
1996
+ loadValueOptions?: (query: string) => Promise<Array<string | { value: string; label: string }>>;
1986
1997
  /** Format the raw value to a string/number for display. */
1987
1998
  valueFormatter?: (value: any, row: T) => React.ReactNode;
1988
1999
  /** Fully custom cell renderer (badges, avatars, etc.). */
@@ -2130,6 +2141,8 @@ interface CardGridProps<Row = any> extends Omit<React.HTMLAttributes<HTMLDivElem
2130
2141
  onSortChange?: (sort: CardGridSort | null) => void;
2131
2142
  /** Fields offered by the built-in sort control (renders it when provided). */
2132
2143
  sortOptions?: CardGridSortOption[];
2144
+ /** Min width (px or CSS length) for the built-in sort control's trigger, so long option labels aren't clipped. Defaults to an estimate from the longest label. */
2145
+ sortMinWidth?: string | number;
2133
2146
  /** Extra node rendered in the toolbar row (before the sort control). */
2134
2147
  toolbar?: React.ReactNode;
2135
2148
  /** Shown when there are no rows and not loading. @default "No results." */
package/dist/index.mjs CHANGED
@@ -8625,6 +8625,53 @@ function Caret({ pos }) {
8625
8625
  const left = Math.max(12, Math.min(pos.anchorX - pos.left, pos.width - 20));
8626
8626
  return /* @__PURE__ */ jsx("span", { className: "twc-dt__caret", style: { left } });
8627
8627
  }
8628
+ function AsyncFilterValue({ col, value, onChange }) {
8629
+ const [opts, setOpts] = React51__default.useState([]);
8630
+ const [loading, setLoading] = React51__default.useState(false);
8631
+ const reqRef = React51__default.useRef(0);
8632
+ const debRef = React51__default.useRef();
8633
+ const load = React51__default.useCallback((query) => {
8634
+ const id = ++reqRef.current;
8635
+ setLoading(true);
8636
+ Promise.resolve(col.loadValueOptions(query)).then(
8637
+ (res) => {
8638
+ if (reqRef.current === id) {
8639
+ setOpts((Array.isArray(res) ? res : []).map((o) => typeof o === "string" ? { value: o, label: o } : o));
8640
+ setLoading(false);
8641
+ }
8642
+ },
8643
+ () => {
8644
+ if (reqRef.current === id) setLoading(false);
8645
+ }
8646
+ );
8647
+ }, [col]);
8648
+ React51__default.useEffect(() => {
8649
+ load("");
8650
+ return () => clearTimeout(debRef.current);
8651
+ }, [load]);
8652
+ const onInput = React51__default.useCallback((q) => {
8653
+ clearTimeout(debRef.current);
8654
+ debRef.current = setTimeout(() => load(q), 250);
8655
+ }, [load]);
8656
+ const selected = Array.isArray(value) ? value : [];
8657
+ const merged = React51__default.useMemo(() => {
8658
+ const seen2 = new Set(opts.map((o) => o.value));
8659
+ return [...selected.filter((v) => !seen2.has(v)).map((v) => ({ value: v, label: String(v) })), ...opts];
8660
+ }, [opts, selected]);
8661
+ return /* @__PURE__ */ jsx(
8662
+ MultiSelect,
8663
+ {
8664
+ portal: true,
8665
+ placeholder: "Any of\u2026",
8666
+ value: selected,
8667
+ options: merged,
8668
+ filter: false,
8669
+ loading,
8670
+ onInputChange: onInput,
8671
+ onChange
8672
+ }
8673
+ );
8674
+ }
8628
8675
  function EditCell({ col, value, options, onChange, onCommit, onCommitValue, onCancel, onKeyDown }) {
8629
8676
  const ref = React51__default.useRef(null);
8630
8677
  const isSelect = col.editType === "select" || options && col.editType !== "text";
@@ -9003,7 +9050,19 @@ function Datatable({
9003
9050
  rowGroupingKeyRef.current = rowGroupingKey;
9004
9051
  setGroupBy(rowGrouping || []);
9005
9052
  }, [rowGroupingKey]);
9006
- const widthOf = (c) => widths[c.field] ?? c.width ?? 160;
9053
+ const AUTO_CHROME = 74;
9054
+ const autoWidth = (c) => {
9055
+ const label = typeof c.headerName === "string" ? c.headerName : String(c.field ?? "");
9056
+ return AUTO_CHROME + Math.ceil(label.length * 8);
9057
+ };
9058
+ const clampWidth = (w, c) => {
9059
+ let x = w;
9060
+ if (typeof c.maxWidth === "number") x = Math.min(x, c.maxWidth);
9061
+ if (typeof c.minWidth === "number") x = Math.max(x, c.minWidth);
9062
+ return Math.max(40, x);
9063
+ };
9064
+ const intrinsicWidth = (c) => c.width === "auto" ? autoWidth(c) : typeof c.width === "number" ? c.width : 160;
9065
+ const widthOf = (c) => clampWidth(widths[c.field] ?? intrinsicWidth(c), c);
9007
9066
  const [colMenu, setColMenu] = React51__default.useState(null);
9008
9067
  const [menuPos, openMenu, closeMenu] = useFloating();
9009
9068
  const [panel, setPanel] = React51__default.useState(null);
@@ -10591,7 +10650,7 @@ function Datatable({
10591
10650
  e.preventDefault();
10592
10651
  e.stopPropagation();
10593
10652
  const delta = e.key === "ArrowRight" ? 10 : -10;
10594
- setWidths((m) => ({ ...m, [c.field]: Math.max(72, (m[c.field] ?? c.width ?? 160) + delta) }));
10653
+ setWidths((m) => ({ ...m, [c.field]: Math.max(72, (m[c.field] ?? intrinsicWidth(c)) + delta) }));
10595
10654
  },
10596
10655
  title: "Drag to resize \xB7 double-click to fit content"
10597
10656
  }
@@ -10834,10 +10893,10 @@ function Datatable({
10834
10893
  " ",
10835
10894
  groupBy.includes(c.field) ? "Stop grouping" : "Group by this column"
10836
10895
  ] }) : null,
10837
- !disableColumnReorder && c.type !== "actions" && !pins.left.includes(c.field) && !pins.right.includes(c.field) ? (() => {
10896
+ !disableColumnReorder && c.reorderable !== false && c.type !== "actions" && !pins.left.includes(c.field) && !pins.right.includes(c.field) && movableMidFields.length > 1 ? (() => {
10838
10897
  const midIdx = movableMidFields.indexOf(c.field);
10839
- const atFirst = midIdx <= 0;
10840
- const atLast = midIdx === -1 || midIdx >= movableMidFields.length - 1;
10898
+ const atFirst = midIdx === 0;
10899
+ const atLast = midIdx === movableMidFields.length - 1;
10841
10900
  return /* @__PURE__ */ jsxs(Fragment, { children: [
10842
10901
  /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", disabled: atFirst, "aria-disabled": atFirst || void 0, onClick: () => {
10843
10902
  moveCol(c.field, -1);
@@ -10873,7 +10932,7 @@ function Datatable({
10873
10932
  pins.right.includes(c.field) ? "Unpin" : "Pin to right"
10874
10933
  ] })
10875
10934
  ] }) : null,
10876
- c.type !== "actions" ? /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", "data-active": wrapped.has(c.field) || void 0, onClick: () => {
10935
+ c.type !== "actions" && c.wrappable !== false ? /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "twc-dt__mi", "data-active": wrapped.has(c.field) || void 0, onClick: () => {
10877
10936
  toggleWrap(c.field);
10878
10937
  close();
10879
10938
  }, children: [
@@ -11261,7 +11320,14 @@ function Datatable({
11261
11320
  }))
11262
11321
  }
11263
11322
  ) }),
11264
- /* @__PURE__ */ jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? /* @__PURE__ */ jsx(
11323
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsx(
11324
+ AsyncFilterValue,
11325
+ {
11326
+ col,
11327
+ value: f.value,
11328
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11329
+ }
11330
+ ) : /* @__PURE__ */ jsx(
11265
11331
  MultiSelect,
11266
11332
  {
11267
11333
  portal: true,
@@ -11377,7 +11443,7 @@ var CARDGRID_CSS = `
11377
11443
  .twc-cardgrid__toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
11378
11444
  .twc-cardgrid__search { flex: 1 1 200px; min-width: 160px; }
11379
11445
  .twc-cardgrid__spacer { flex: 1 1 auto; }
11380
- .twc-cardgrid__sort { display: inline-flex; align-items: center; gap: var(--space-1); }
11446
+ .twc-cardgrid__sort { display: inline-flex; align-items: center; gap: var(--space-1); flex: none; }
11381
11447
  .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); }
11382
11448
  .twc-cardgrid__dir:hover { background: var(--color-surface-sunken); color: var(--color-text); }
11383
11449
  .twc-cardgrid__dir svg { width: 16px; height: 16px; transition: transform var(--duration-fast) var(--ease-standard); }
@@ -11425,6 +11491,7 @@ function CardGrid({
11425
11491
  defaultSort = null,
11426
11492
  onSortChange,
11427
11493
  sortOptions,
11494
+ sortMinWidth,
11428
11495
  // slots / states
11429
11496
  toolbar,
11430
11497
  emptyState = "No results.",
@@ -11496,6 +11563,8 @@ function CardGrid({
11496
11563
  const to = paginated ? Math.min((pageVal + 1) * sizeVal, total) : total;
11497
11564
  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) }));
11498
11565
  const sortSelectOptions = sortOptions ? [{ value: "", label: "Sort by\u2026" }, ...sortOptions.map((o) => ({ value: o.field, label: o.label || o.field }))] : null;
11566
+ 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;
11567
+ const sortWidth = sortMinWidth != null ? sortMinWidth : sortAutoWidth;
11499
11568
  const hasToolbar = searchable || sortSelectOptions || toolbar;
11500
11569
  return /* @__PURE__ */ jsxs("div", { className: `twc-cardgrid ${className}`, id: fieldId, ...rest, children: [
11501
11570
  styles,
@@ -11523,6 +11592,7 @@ function CardGrid({
11523
11592
  value: sortVal?.field || "",
11524
11593
  options: sortSelectOptions,
11525
11594
  placeholder: "Sort by\u2026",
11595
+ style: { minWidth: sortWidth },
11526
11596
  "aria-label": "Sort by",
11527
11597
  onChange: (field) => commitSort(field ? { field, dir: sortVal?.dir || "asc" } : null)
11528
11598
  }