twico-ui 1.30.0 → 1.31.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
@@ -1822,6 +1822,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1822
1822
  quickFilter?: string;
1823
1823
  /** Fires with the new quick-search string as the user types (and when a host sets it). */
1824
1824
  onQuickFilterChange?: (value: string) => void;
1825
+ /** #330: how the filter clauses combine — `"and"` (all must match) or `"or"` (any). A single, set-wide
1826
+ * connective (matching MUI DataGrid's `logicOperator`), surfaced as the per-row And/Or connector in the
1827
+ * Filters panel and included as `filterLogic` in the `onServerChange` query. Pass this (with
1828
+ * `onFilterLogicChange`) to control it; omit for uncontrolled (seeded by `defaultFilterLogic`). */
1829
+ filterLogic?: "and" | "or";
1830
+ /** #330: initial And/Or connective when `filterLogic` is uncontrolled. @default "and" */
1831
+ defaultFilterLogic?: "and" | "or";
1832
+ /** #330: fires with the new connective (`"and"` | `"or"`) when the user changes the per-row And/Or select. */
1833
+ onFilterLogicChange?: (logic: "and" | "or") => void;
1825
1834
  /** Custom node rendered in a **leading slot** of the toolbar row, before the built-in Columns/Filters
1826
1835
  * cluster (#286) — for host controls like an "Add row" button or bulk actions. Mirrors `CardGrid`'s
1827
1836
  * `toolbar` prop, so no internal-class/absolute-position hacks are needed. */
package/dist/index.d.ts CHANGED
@@ -1822,6 +1822,15 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1822
1822
  quickFilter?: string;
1823
1823
  /** Fires with the new quick-search string as the user types (and when a host sets it). */
1824
1824
  onQuickFilterChange?: (value: string) => void;
1825
+ /** #330: how the filter clauses combine — `"and"` (all must match) or `"or"` (any). A single, set-wide
1826
+ * connective (matching MUI DataGrid's `logicOperator`), surfaced as the per-row And/Or connector in the
1827
+ * Filters panel and included as `filterLogic` in the `onServerChange` query. Pass this (with
1828
+ * `onFilterLogicChange`) to control it; omit for uncontrolled (seeded by `defaultFilterLogic`). */
1829
+ filterLogic?: "and" | "or";
1830
+ /** #330: initial And/Or connective when `filterLogic` is uncontrolled. @default "and" */
1831
+ defaultFilterLogic?: "and" | "or";
1832
+ /** #330: fires with the new connective (`"and"` | `"or"`) when the user changes the per-row And/Or select. */
1833
+ onFilterLogicChange?: (logic: "and" | "or") => void;
1825
1834
  /** Custom node rendered in a **leading slot** of the toolbar row, before the built-in Columns/Filters
1826
1835
  * cluster (#286) — for host controls like an "Add row" button or bulk actions. Mirrors `CardGrid`'s
1827
1836
  * `toolbar` prop, so no internal-class/absolute-position hacks are needed. */
package/dist/index.mjs CHANGED
@@ -8190,6 +8190,8 @@ function classifyDiff(from = [], to = [], columns = [], rowKey, opts = {}) {
8190
8190
  });
8191
8191
  return { rows, counts };
8192
8192
  }
8193
+ var DT_FILTER_PANEL_W = 620;
8194
+ var DT_FILTER_LOGIC_W = 68;
8193
8195
  var DT_CSS = `
8194
8196
  .twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
8195
8197
  border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
@@ -8612,13 +8614,11 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8612
8614
  .twc-dt__panel-count { font-weight: 600; color: var(--color-text-muted); text-transform: none; letter-spacing: 0; margin-inline-start: 6px; font-variant-numeric: tabular-nums; }
8613
8615
  .twc-dt__link { border: none; background: transparent; color: var(--color-primary); font-family: inherit; font-size: var(--text-xs); font-weight: 600; cursor: pointer; padding: 2px 4px; border-radius: var(--radius-sm); }
8614
8616
  .twc-dt__link:hover { background: var(--color-primary-subtle); }
8615
- /* #303: AND/OR connective toggle (segmented pill) in the Filters panel head. */
8616
- .twc-dt__flogic { display: inline-flex; border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; }
8617
- .twc-dt__flogic-btn { border: none; background: transparent; color: var(--color-text-muted); font-family: inherit; font-size: 10px; font-weight: 700; letter-spacing: var(--tracking-wide); cursor: pointer; padding: 2px 8px; }
8618
- .twc-dt__flogic-btn + .twc-dt__flogic-btn { border-inline-start: var(--border-thin) solid var(--color-border); }
8619
- .twc-dt__flogic-btn:hover { background: var(--color-surface-sunken); }
8620
- .twc-dt__flogic-btn[data-active] { background: var(--color-primary); color: var(--color-primary-fg); }
8621
- .twc-dt__flogic-btn[data-active]:hover { background: var(--color-primary); }
8617
+ /* #330: MUI DataGrid-style per-row And/Or connector. Row 1 shows a static "Where"; the first connector
8618
+ row (row 2) hosts an editable And/Or Select bound to the single filterLogic; rows 3+ echo it as static
8619
+ text. Fixed-width leading cell so the Column/Operator/Value cells stay aligned across rows. */
8620
+ .twc-dt__f-logic { position: relative; flex: none; width: 68px; }
8621
+ .twc-dt__f-where { display: inline-flex; align-items: center; height: 38px; padding-inline: 6px; color: var(--color-text-muted); font-size: var(--text-sm); font-weight: var(--font-medium); }
8622
8622
 
8623
8623
  /* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
8624
8624
  cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
@@ -8628,7 +8628,7 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8628
8628
  equal-specificity 'position: relative' defined later would clobber it, dropping the popover into normal
8629
8629
  flow below the table. The panel stays position: fixed, which already anchors the absolutely-positioned
8630
8630
  .twc-dt__pop-grip; per-field handles anchor to their own position: relative .twc-dt__f-* fields. */
8631
- width: var(--twc-dt-panel-w, 580px); max-width: calc(100vw - 32px);
8631
+ width: var(--twc-dt-panel-w, 620px); max-width: calc(100vw - 32px);
8632
8632
  height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
8633
8633
  display: flex; flex-direction: column;
8634
8634
  }
@@ -9240,6 +9240,9 @@ function Datatable({
9240
9240
  searchable = true,
9241
9241
  quickFilter,
9242
9242
  onQuickFilterChange,
9243
+ filterLogic: filterLogicProp,
9244
+ defaultFilterLogic = "and",
9245
+ onFilterLogicChange,
9243
9246
  toolbarActions,
9244
9247
  resizableFilters = true,
9245
9248
  resizablePopovers = true,
@@ -9333,7 +9336,14 @@ function Datatable({
9333
9336
  });
9334
9337
  const [sort, setSort] = React51__default.useState(null);
9335
9338
  const [filters, setFilters] = React51__default.useState([]);
9336
- const [filterLogic, setFilterLogic] = React51__default.useState("and");
9339
+ const filterLogicControlled = filterLogicProp !== void 0;
9340
+ const [filterLogicInternal, setFilterLogicInternal] = React51__default.useState(defaultFilterLogic === "or" ? "or" : "and");
9341
+ const filterLogic = filterLogicControlled ? filterLogicProp === "or" ? "or" : "and" : filterLogicInternal;
9342
+ const setFilterLogic = (next) => {
9343
+ const v = next === "or" ? "or" : "and";
9344
+ if (!filterLogicControlled) setFilterLogicInternal(v);
9345
+ onFilterLogicChange?.(v);
9346
+ };
9337
9347
  const [hidden, setHidden] = React51__default.useState(() => /* @__PURE__ */ new Set());
9338
9348
  const [showRowNum, setShowRowNum] = React51__default.useState(rowNumbers);
9339
9349
  const [wrapped, setWrapped] = React51__default.useState(() => new Set((columns || []).filter((c) => c.wrapText).map((c) => c.field)));
@@ -9429,7 +9439,7 @@ function Datatable({
9429
9439
  if (!s || typeof s !== "object") return;
9430
9440
  const known = new Set(cols.map((c) => c.field).filter(Boolean));
9431
9441
  if (Array.isArray(s.filters)) setFilters(s.filters.filter((f) => f && known.has(f.field)).map((f, i) => ({ id: Date.now() + Math.random() + i, field: f.field, op: f.op, value: f.value })));
9432
- if (s.filterLogic === "or" || s.filterLogic === "and") setFilterLogic(s.filterLogic);
9442
+ if ((s.filterLogic === "or" || s.filterLogic === "and") && !filterLogicControlled) setFilterLogicInternal(s.filterLogic);
9433
9443
  if ("sort" in s) setSort(s.sort && known.has(s.sort.field) ? { field: s.sort.field, dir: s.sort.dir === "desc" ? "desc" : "asc" } : null);
9434
9444
  if (typeof s.quickFilter === "string" && quickFilter === void 0) setInternalQuick(s.quickFilter);
9435
9445
  if (typeof s.page === "number" && !pageControlled) setInternalPage(Math.max(0, Math.floor(s.page)));
@@ -10822,7 +10832,7 @@ function Datatable({
10822
10832
  const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
10823
10833
  const chrome = pad2 + 8 + 16 + 4;
10824
10834
  const widest = (labels) => labels.reduce((m, l) => Math.max(m, ctx.measureText(String(l)).width), 0);
10825
- const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 - 3 * 6 - 2 * 4));
10835
+ const colCap = Math.max(118, Math.min(210, el.clientWidth - DT_FILTER_LOGIC_W - 118 - 30 - 30 - 140 - 5 * 6 - 2 * 4));
10826
10836
  const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10827
10837
  const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
10828
10838
  if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
@@ -10854,7 +10864,7 @@ function Datatable({
10854
10864
  if (field === "op") return filterFieldMaxWidth || 260;
10855
10865
  const p = filterPanelRef.current;
10856
10866
  if (!p) return 640;
10857
- return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 - 24, F_MIN.val, 640);
10867
+ return clampNum(p.clientWidth - DT_FILTER_LOGIC_W - fLiveW("col") - fLiveW("op") - 30 - 30 - 5 * 6 - 2 * 4, F_MIN.val, 640);
10858
10868
  };
10859
10869
  const isRtl = () => {
10860
10870
  const p = filterPanelRef.current;
@@ -11551,7 +11561,7 @@ function Datatable({
11551
11561
  } else {
11552
11562
  setPanel("filters");
11553
11563
  setColMenu(null);
11554
- openPanel(e.currentTarget, "left", 580);
11564
+ openPanel(e.currentTarget, "left", DT_FILTER_PANEL_W);
11555
11565
  }
11556
11566
  },
11557
11567
  children: [
@@ -12119,7 +12129,7 @@ function Datatable({
12119
12129
  closeMenu();
12120
12130
  restoreTriggerFocus();
12121
12131
  setPanel("filters");
12122
- openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", 580);
12132
+ openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", DT_FILTER_PANEL_W);
12123
12133
  }, children: [
12124
12134
  /* @__PURE__ */ jsx(Svg, { d: I.filter }),
12125
12135
  " Filter"
@@ -12553,10 +12563,6 @@ function Datatable({
12553
12563
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__panel-head", children: [
12554
12564
  /* @__PURE__ */ jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
12555
12565
  /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
12556
- filters.length > 1 ? /* @__PURE__ */ jsxs("span", { className: "twc-dt__flogic", role: "group", "aria-label": "Combine filters with", children: [
12557
- /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "and" || void 0, "aria-pressed": filterLogic === "and", onClick: () => setFilterLogic("and"), children: "AND" }),
12558
- /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "or" || void 0, "aria-pressed": filterLogic === "or", onClick: () => setFilterLogic("or"), children: "OR" })
12559
- ] }) : null,
12560
12566
  resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
12561
12567
  filters.length ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
12562
12568
  ] })
@@ -12566,6 +12572,17 @@ function Datatable({
12566
12572
  const ops = opsFor(filterTypeOf(col));
12567
12573
  const op = ops.find((o) => o.value === f.op) || ops[0];
12568
12574
  return /* @__PURE__ */ jsxs("div", { className: "twc-dt__frow", children: [
12575
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__f-logic", children: i === 0 ? /* @__PURE__ */ jsx("span", { className: "twc-dt__f-where", children: "Where" }) : i === 1 ? /* @__PURE__ */ jsx(
12576
+ Select,
12577
+ {
12578
+ size: "sm",
12579
+ portal: true,
12580
+ "aria-label": "Combine with the other filters",
12581
+ value: filterLogic,
12582
+ options: [{ value: "and", label: "And" }, { value: "or", label: "Or" }],
12583
+ onChange: (v) => setFilterLogic(v)
12584
+ }
12585
+ ) : /* @__PURE__ */ jsx("span", { className: "twc-dt__f-where", children: filterLogic === "or" ? "Or" : "And" }) }),
12569
12586
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-col", children: [
12570
12587
  /* @__PURE__ */ jsx(
12571
12588
  Select,
@@ -12645,7 +12662,7 @@ function Datatable({
12645
12662
  role: "slider",
12646
12663
  tabIndex: 0,
12647
12664
  "aria-label": "Resize filters panel (Enter to reset)",
12648
- "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? 580)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12665
+ "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? DT_FILTER_PANEL_W)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12649
12666
  onPointerDown: startPanelResize,
12650
12667
  onKeyDown: onPanelGripKey,
12651
12668
  onDoubleClick: (e) => {