twico-ui 1.30.0 → 1.31.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
@@ -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. */
@@ -2103,8 +2112,9 @@ interface DatatableState {
2103
2112
  density: "compact" | "standard" | "comfortable";
2104
2113
  /** #292: user-resized Filters popover size (px). Absent until the user drags the panel grip. */
2105
2114
  filterPanelSize?: { w?: number; h?: number };
2106
- /** #292: user-resized Filters field widths (px, panel-global — not per data column). Absent until dragged. */
2107
- filterFieldWidths?: { col?: number; op?: number; val?: number };
2115
+ /** #292: user-resized Filters field widths (px, panel-global — not per data column). Absent until dragged.
2116
+ * `logic` is the #330 And/Or connector field. */
2117
+ filterFieldWidths?: { col?: number; op?: number; val?: number; logic?: number };
2108
2118
  /** #304: user-resized sizes of the other toolbar popovers (Columns/Aggregation/Pivot/Batch-edit),
2109
2119
  * keyed by popover id → `{ w, h }` in px. Absent until a popover is dragged with `resizablePopovers`. */
2110
2120
  popoverSizes?: Record<string, { w?: number; h?: number }>;
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. */
@@ -2103,8 +2112,9 @@ interface DatatableState {
2103
2112
  density: "compact" | "standard" | "comfortable";
2104
2113
  /** #292: user-resized Filters popover size (px). Absent until the user drags the panel grip. */
2105
2114
  filterPanelSize?: { w?: number; h?: number };
2106
- /** #292: user-resized Filters field widths (px, panel-global — not per data column). Absent until dragged. */
2107
- filterFieldWidths?: { col?: number; op?: number; val?: number };
2115
+ /** #292: user-resized Filters field widths (px, panel-global — not per data column). Absent until dragged.
2116
+ * `logic` is the #330 And/Or connector field. */
2117
+ filterFieldWidths?: { col?: number; op?: number; val?: number; logic?: number };
2108
2118
  /** #304: user-resized sizes of the other toolbar popovers (Columns/Aggregation/Pivot/Batch-edit),
2109
2119
  * keyed by popover id → `{ w, h }` in px. Absent until a popover is dragged with `resizablePopovers`. */
2110
2120
  popoverSizes?: Record<string, { w?: number; h?: number }>;
package/dist/index.mjs CHANGED
@@ -8190,6 +8190,7 @@ function classifyDiff(from = [], to = [], columns = [], rowKey, opts = {}) {
8190
8190
  });
8191
8191
  return { rows, counts };
8192
8192
  }
8193
+ var DT_FILTER_PANEL_W = 620;
8193
8194
  var DT_CSS = `
8194
8195
  .twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
8195
8196
  border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
@@ -8612,13 +8613,14 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8612
8613
  .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
8614
  .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
8615
  .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); }
8616
+ /* #330: MUI DataGrid-style per-row And/Or connector. Row 1 shows a static "Where"; the first connector
8617
+ row (row 2) hosts an editable And/Or Select bound to the single filterLogic; rows 3+ echo it as static
8618
+ text. Leading cell aligns the Column/Operator/Value cells across rows. It is a first-class resizable
8619
+ filter field like col/op: auto-fit to its content (-fit, from the #289 measurer) with a user drag tier
8620
+ (-usr) winning in the clamp cascade, so it no longer truncates and the width can be adjusted manually. */
8621
+ .twc-dt__f-logic { position: relative; flex: none; min-width: 84px;
8622
+ width: clamp(84px, var(--twc-dt-flogic-usr, var(--twc-dt-flogic-fit, 84px)), var(--twc-dt-flogic-cap, 180px)); }
8623
+ .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
8624
 
8623
8625
  /* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
8624
8626
  cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
@@ -8628,7 +8630,7 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8628
8630
  equal-specificity 'position: relative' defined later would clobber it, dropping the popover into normal
8629
8631
  flow below the table. The panel stays position: fixed, which already anchors the absolutely-positioned
8630
8632
  .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);
8633
+ width: var(--twc-dt-panel-w, 620px); max-width: calc(100vw - 32px);
8632
8634
  height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
8633
8635
  display: flex; flex-direction: column;
8634
8636
  }
@@ -9240,6 +9242,9 @@ function Datatable({
9240
9242
  searchable = true,
9241
9243
  quickFilter,
9242
9244
  onQuickFilterChange,
9245
+ filterLogic: filterLogicProp,
9246
+ defaultFilterLogic = "and",
9247
+ onFilterLogicChange,
9243
9248
  toolbarActions,
9244
9249
  resizableFilters = true,
9245
9250
  resizablePopovers = true,
@@ -9333,7 +9338,14 @@ function Datatable({
9333
9338
  });
9334
9339
  const [sort, setSort] = React51__default.useState(null);
9335
9340
  const [filters, setFilters] = React51__default.useState([]);
9336
- const [filterLogic, setFilterLogic] = React51__default.useState("and");
9341
+ const filterLogicControlled = filterLogicProp !== void 0;
9342
+ const [filterLogicInternal, setFilterLogicInternal] = React51__default.useState(defaultFilterLogic === "or" ? "or" : "and");
9343
+ const filterLogic = filterLogicControlled ? filterLogicProp === "or" ? "or" : "and" : filterLogicInternal;
9344
+ const setFilterLogic = (next) => {
9345
+ const v = next === "or" ? "or" : "and";
9346
+ if (!filterLogicControlled) setFilterLogicInternal(v);
9347
+ onFilterLogicChange?.(v);
9348
+ };
9337
9349
  const [hidden, setHidden] = React51__default.useState(() => /* @__PURE__ */ new Set());
9338
9350
  const [showRowNum, setShowRowNum] = React51__default.useState(rowNumbers);
9339
9351
  const [wrapped, setWrapped] = React51__default.useState(() => new Set((columns || []).filter((c) => c.wrapText).map((c) => c.field)));
@@ -9421,7 +9433,7 @@ function Datatable({
9421
9433
  density,
9422
9434
  // #292: additive, optional — user-resized filter panel + field widths (panel-global, not per column).
9423
9435
  filterPanelSize: fPanelSize || void 0,
9424
- filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null ? fWidths : void 0,
9436
+ filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null || fWidths.logic != null ? fWidths : void 0,
9425
9437
  // #304: additive, optional — user-resized sizes of the other toolbar popovers, keyed by popover id.
9426
9438
  popoverSizes: Object.keys(popSizes).length ? popSizes : void 0
9427
9439
  });
@@ -9429,7 +9441,7 @@ function Datatable({
9429
9441
  if (!s || typeof s !== "object") return;
9430
9442
  const known = new Set(cols.map((c) => c.field).filter(Boolean));
9431
9443
  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);
9444
+ if ((s.filterLogic === "or" || s.filterLogic === "and") && !filterLogicControlled) setFilterLogicInternal(s.filterLogic);
9433
9445
  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
9446
  if (typeof s.quickFilter === "string" && quickFilter === void 0) setInternalQuick(s.quickFilter);
9435
9447
  if (typeof s.page === "number" && !pageControlled) setInternalPage(Math.max(0, Math.floor(s.page)));
@@ -9451,7 +9463,7 @@ function Datatable({
9451
9463
  if (s.density === "compact" || s.density === "standard" || s.density === "comfortable") setDensity(s.density);
9452
9464
  if (s.filterFieldWidths && typeof s.filterFieldWidths === "object") {
9453
9465
  const fw = {};
9454
- for (const k of ["col", "op", "val"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
9466
+ for (const k of ["col", "op", "val", "logic"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
9455
9467
  if (Object.keys(fw).length) setFWidths(fw);
9456
9468
  }
9457
9469
  if (s.filterPanelSize && typeof s.filterPanelSize === "object") {
@@ -10822,9 +10834,11 @@ function Datatable({
10822
10834
  const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
10823
10835
  const chrome = pad2 + 8 + 16 + 4;
10824
10836
  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));
10837
+ const logicW = Math.min(180, Math.max(84, Math.ceil(widest(["And", "Or"]) + chrome)));
10838
+ const colCap = Math.max(118, Math.min(210, el.clientWidth - logicW - 118 - 30 - 30 - 140 - 5 * 6 - 2 * 4));
10826
10839
  const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10827
10840
  const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
10841
+ if (Number.isFinite(logicW)) el.style.setProperty("--twc-dt-flogic-fit", `${logicW}px`);
10828
10842
  if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
10829
10843
  if (Number.isFinite(opW)) el.style.setProperty("--twc-dt-fop-fit", `${opW}px`);
10830
10844
  };
@@ -10838,10 +10852,10 @@ function Datatable({
10838
10852
  }
10839
10853
  return () => ro?.disconnect();
10840
10854
  }, [panel, filterableCols, filters.length]);
10841
- const F_MIN = { col: 118, op: 118, val: 140 };
10842
- const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr" };
10843
- const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val" };
10844
- const F_LABEL = { col: "column", op: "operator", val: "value" };
10855
+ const F_MIN = { col: 118, op: 118, val: 140, logic: 84 };
10856
+ const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr", logic: "--twc-dt-flogic-usr" };
10857
+ const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val", logic: ".twc-dt__f-logic" };
10858
+ const F_LABEL = { col: "column", op: "operator", val: "value", logic: "connector" };
10845
10859
  const fMaxW = () => typeof window === "undefined" ? 9999 : window.innerWidth - 32;
10846
10860
  const fMaxH = () => typeof window === "undefined" ? 9999 : window.innerHeight - 32;
10847
10861
  const clampNum = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
@@ -10850,11 +10864,12 @@ function Datatable({
10850
10864
  return el ? el.getBoundingClientRect().width : F_MIN[field];
10851
10865
  };
10852
10866
  const fCap = (field) => {
10867
+ if (field === "logic") return 180;
10853
10868
  if (field === "col") return filterFieldMaxWidth || 360;
10854
10869
  if (field === "op") return filterFieldMaxWidth || 260;
10855
10870
  const p = filterPanelRef.current;
10856
10871
  if (!p) return 640;
10857
- return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 - 24, F_MIN.val, 640);
10872
+ return clampNum(p.clientWidth - fLiveW("logic") - fLiveW("col") - fLiveW("op") - 30 - 30 - 5 * 6 - 2 * 4, F_MIN.val, 640);
10858
10873
  };
10859
10874
  const isRtl = () => {
10860
10875
  const p = filterPanelRef.current;
@@ -10987,12 +11002,12 @@ function Datatable({
10987
11002
  setFPanelSize(null);
10988
11003
  const p = filterPanelRef.current;
10989
11004
  if (p) {
10990
- ["--twc-dt-fcol-usr", "--twc-dt-fop-usr", "--twc-dt-fval-usr", "--twc-dt-panel-w", "--twc-dt-panel-h"].forEach((v) => p.style.removeProperty(v));
11005
+ ["--twc-dt-fcol-usr", "--twc-dt-fop-usr", "--twc-dt-fval-usr", "--twc-dt-flogic-usr", "--twc-dt-panel-w", "--twc-dt-panel-h"].forEach((v) => p.style.removeProperty(v));
10991
11006
  p.removeAttribute("data-val-fixed");
10992
11007
  p.removeAttribute("data-panel-sized");
10993
11008
  }
10994
11009
  };
10995
- const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fPanelSize != null;
11010
+ const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fWidths.logic != null || fPanelSize != null;
10996
11011
  const POP_MIN_W = { columns: 240, agg: 240, pivot: 260, batchedit: 260 };
10997
11012
  const popMinW = (id) => POP_MIN_W[id] || 200;
10998
11013
  const clearPop = (id, node) => {
@@ -11551,7 +11566,7 @@ function Datatable({
11551
11566
  } else {
11552
11567
  setPanel("filters");
11553
11568
  setColMenu(null);
11554
- openPanel(e.currentTarget, "left", 580);
11569
+ openPanel(e.currentTarget, "left", DT_FILTER_PANEL_W);
11555
11570
  }
11556
11571
  },
11557
11572
  children: [
@@ -12119,7 +12134,7 @@ function Datatable({
12119
12134
  closeMenu();
12120
12135
  restoreTriggerFocus();
12121
12136
  setPanel("filters");
12122
- openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", 580);
12137
+ openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", DT_FILTER_PANEL_W);
12123
12138
  }, children: [
12124
12139
  /* @__PURE__ */ jsx(Svg, { d: I.filter }),
12125
12140
  " Filter"
@@ -12542,6 +12557,7 @@ function Datatable({
12542
12557
  "--twc-dt-fcol-usr": fWidths.col != null ? `${fWidths.col}px` : void 0,
12543
12558
  "--twc-dt-fop-usr": fWidths.op != null ? `${fWidths.op}px` : void 0,
12544
12559
  "--twc-dt-fval-usr": fWidths.val != null ? `${fWidths.val}px` : void 0,
12560
+ "--twc-dt-flogic-usr": fWidths.logic != null ? `${fWidths.logic}px` : void 0,
12545
12561
  "--twc-dt-panel-w": fPanelSize?.w != null ? `${fPanelSize.w}px` : void 0,
12546
12562
  "--twc-dt-panel-h": fPanelSize?.h != null ? `${fPanelSize.h}px` : void 0,
12547
12563
  "--twc-dt-fcol-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0,
@@ -12553,10 +12569,6 @@ function Datatable({
12553
12569
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__panel-head", children: [
12554
12570
  /* @__PURE__ */ jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
12555
12571
  /* @__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
12572
  resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
12561
12573
  filters.length ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
12562
12574
  ] })
@@ -12566,6 +12578,20 @@ function Datatable({
12566
12578
  const ops = opsFor(filterTypeOf(col));
12567
12579
  const op = ops.find((o) => o.value === f.op) || ops[0];
12568
12580
  return /* @__PURE__ */ jsxs("div", { className: "twc-dt__frow", children: [
12581
+ /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-logic", children: [
12582
+ i === 0 ? /* @__PURE__ */ jsx("span", { className: "twc-dt__f-where", children: "Where" }) : i === 1 ? /* @__PURE__ */ jsx(
12583
+ Select,
12584
+ {
12585
+ size: "sm",
12586
+ portal: true,
12587
+ "aria-label": "Combine with the other filters",
12588
+ value: filterLogic,
12589
+ options: [{ value: "and", label: "And" }, { value: "or", label: "Or" }],
12590
+ onChange: (v) => setFilterLogic(v)
12591
+ }
12592
+ ) : /* @__PURE__ */ jsx("span", { className: "twc-dt__f-where", children: filterLogic === "or" ? "Or" : "And" }),
12593
+ fieldHandle("logic", i)
12594
+ ] }),
12569
12595
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-col", children: [
12570
12596
  /* @__PURE__ */ jsx(
12571
12597
  Select,
@@ -12645,7 +12671,7 @@ function Datatable({
12645
12671
  role: "slider",
12646
12672
  tabIndex: 0,
12647
12673
  "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"}`,
12674
+ "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? DT_FILTER_PANEL_W)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12649
12675
  onPointerDown: startPanelResize,
12650
12676
  onKeyDown: onPanelGripKey,
12651
12677
  onDoubleClick: (e) => {