twico-ui 1.21.1 → 1.22.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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
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.22.0](https://github.com/EncryptionL/twico-ui/compare/v1.21.1...v1.22.0) (2026-07-29)
6
+
7
+
8
+ ### Features
9
+
10
+ * **datatable:** user-resizable filters popover + fields ([4a7aeab](https://github.com/EncryptionL/twico-ui/commit/4a7aeab2e7f08c452b3547f1406df811c9333f03)), closes [#292](https://github.com/EncryptionL/twico-ui/issues/292)
11
+
5
12
  ## [1.21.1](https://github.com/EncryptionL/twico-ui/compare/v1.21.0...v1.21.1) (2026-07-29)
6
13
 
7
14
 
package/dist/index.cjs CHANGED
@@ -8525,20 +8525,52 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8525
8525
  .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); }
8526
8526
  .twc-dt__link:hover { background: var(--color-primary-subtle); }
8527
8527
 
8528
- /* Filter panel */
8529
- .twc-dt__filters { width: 580px; max-width: calc(100vw - 32px); }
8528
+ /* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
8529
+ cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
8530
+ neither writer reads the other (reset = remove the -usr var and auto-fit instantly re-wins). */
8531
+ .twc-dt__filters {
8532
+ position: relative;
8533
+ width: var(--twc-dt-panel-w, 580px); max-width: calc(100vw - 32px);
8534
+ height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
8535
+ display: flex; flex-direction: column;
8536
+ }
8537
+ /* Rows list scrolls only once the user pins a height; Add-filter stays sticky. Safe because the filter
8538
+ Selects portal to <body> \u2014 an inline (non-portaled) menu would be clipped by this scroll box. */
8539
+ .twc-dt__frows { min-height: 0; }
8540
+ .twc-dt__filters[data-panel-sized] .twc-dt__frows { flex: 1 1 auto; overflow-y: auto; }
8541
+ .twc-dt__filters[data-panel-sized] .twc-dt__f-add { position: sticky; bottom: 0; background: var(--color-surface-raised); padding-inline-end: 20px; }
8530
8542
  /* #289: wrap is a safety net \u2014 a beyond-cap label or the narrow responsive panel drops the value input
8531
8543
  to its own line instead of clipping the remove button; no effect in the normal (capped) case. */
8532
8544
  .twc-dt__frow { display: flex; align-items: center; gap: 6px; padding: 5px 4px; flex-wrap: wrap; }
8533
- /* #289: column-field auto-fits the widest header label \u2014 JS writes --twc-dt-fcol-w (measured, clamped),
8534
- the max-width is the hard backstop that keeps the value input above its 140px floor inside the 580px
8535
- panel even if the var is stale/too large. Fallback 118px = the previous fixed behavior. */
8536
- .twc-dt__f-col { flex: none; width: var(--twc-dt-fcol-w, 118px); min-width: 118px; max-width: 210px; }
8537
- .twc-dt__f-op { flex: none; width: var(--twc-dt-fop-w, 118px); min-width: 118px; max-width: 170px; }
8538
- .twc-dt__f-val { flex: 1; min-width: 140px; }
8545
+ /* col/op: user > measured (-fit, written by the #289 measurer) > 118px, clamped to a drag cap that
8546
+ filterFieldMaxWidth can raise. Position:relative hosts the resize handle. */
8547
+ .twc-dt__f-col { position: relative; flex: none; min-width: 118px;
8548
+ width: clamp(118px, var(--twc-dt-fcol-usr, var(--twc-dt-fcol-fit, 118px)), var(--twc-dt-fcol-cap, 360px)); }
8549
+ .twc-dt__f-op { position: relative; flex: none; min-width: 118px;
8550
+ width: clamp(118px, var(--twc-dt-fop-usr, var(--twc-dt-fop-fit, 118px)), var(--twc-dt-fop-cap, 260px)); }
8551
+ /* value: the flex shock-absorber by default; the first drag flips it to a fixed width (data-val-fixed). */
8552
+ .twc-dt__f-val { position: relative; flex: 1 1 140px; min-width: 140px; }
8553
+ .twc-dt__filters[data-val-fixed] .twc-dt__f-val { flex: 0 0 clamp(140px, var(--twc-dt-fval-usr, 240px), 640px); }
8554
+ .twc-dt__filters[data-val-fixed] .twc-dt__frm-x { margin-inline-start: auto; }
8539
8555
  .twc-dt__frm-x { display: inline-grid; place-items: center; width: 30px; height: 38px; border: none; background: transparent; color: var(--color-text-subtle); border-radius: var(--radius-md); cursor: pointer; flex: none; align-self: flex-start; }
8540
8556
  .twc-dt__frm-x:hover { background: var(--color-danger-subtle); color: var(--color-danger-subtle-fg); }
8541
8557
  .twc-dt__frm-x svg { width: 15px; height: 15px; }
8558
+ /* per-field resize handle \u2014 cloned from the grid's .twc-dt__resizer; always-on baseline opacity so it's
8559
+ discoverable/hittable on touch (no hover there), with a non-color-only focus ring. */
8560
+ .twc-dt__f-rz { position: absolute; top: 0; inset-inline-end: -3px; width: 8px; height: 100%; cursor: col-resize; touch-action: none; z-index: 6; opacity: 0.28; transition: opacity var(--duration-fast); }
8561
+ .twc-dt__f-rz::after { content: ""; position: absolute; top: 22%; inset-inline-end: 2px; width: 2px; height: 56%; background: var(--color-border-strong); border-radius: 2px; transition: width var(--duration-fast), background-color var(--duration-fast); }
8562
+ .twc-dt__frow:hover .twc-dt__f-rz, .twc-dt__f-rz:focus-visible, .twc-dt__f-rz[data-active="true"] { opacity: 1; }
8563
+ .twc-dt__f-rz:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--color-primary); border-radius: 2px; }
8564
+ .twc-dt__f-rz:focus-visible::after, .twc-dt__f-rz[data-active="true"]::after { width: 3px; background: var(--color-primary); }
8565
+ /* panel corner grip (2-axis) at the bottom-inline-end. */
8566
+ .twc-dt__pop-grip { position: absolute; inset-block-end: 2px; inset-inline-end: 2px; width: 14px; height: 14px; cursor: nwse-resize; touch-action: none; z-index: 8; opacity: 0.5; border-radius: 3px; background: linear-gradient(135deg, transparent 0 55%, var(--color-border-strong) 55% 64%, transparent 64% 73%, var(--color-border-strong) 73% 82%, transparent 82%); transition: opacity var(--duration-fast); }
8567
+ .twc-dt__pop-grip:hover, .twc-dt__pop-grip:focus-visible, .twc-dt__pop-grip[data-active="true"] { opacity: 1; }
8568
+ .twc-dt__pop-grip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
8569
+ [dir="rtl"] .twc-dt__pop-grip { cursor: nesw-resize; }
8570
+ @media (pointer: coarse) {
8571
+ .twc-dt__f-rz { width: 16px; inset-inline-end: -6px; opacity: 0.4; }
8572
+ .twc-dt__pop-grip { width: 20px; height: 20px; opacity: 0.6; }
8573
+ }
8542
8574
  .twc-dt__empty { padding: 30px 12px; text-align: center; color: var(--color-text-subtle); font-size: var(--text-sm); }
8543
8575
 
8544
8576
  /* Action cell */
@@ -9091,6 +9123,8 @@ function Datatable({
9091
9123
  quickFilter,
9092
9124
  onQuickFilterChange,
9093
9125
  toolbarActions,
9126
+ resizableFilters = true,
9127
+ filterFieldMaxWidth,
9094
9128
  rowPinning = false,
9095
9129
  rowReorder = false,
9096
9130
  rowResize = false,
@@ -9239,6 +9273,8 @@ function Datatable({
9239
9273
  const [rowGrab, setRowGrab] = React51__namespace.default.useState(null);
9240
9274
  const [reorderMsg, setReorderMsg] = React51__namespace.default.useState("");
9241
9275
  const rowFocusRef = React51__namespace.default.useRef(null);
9276
+ const [fWidths, setFWidths] = React51__namespace.default.useState({});
9277
+ const [fPanelSize, setFPanelSize] = React51__namespace.default.useState(null);
9242
9278
  const serializeState = () => ({
9243
9279
  filters: filters.map(({ id, ...f }) => f),
9244
9280
  // strip the internal row id
@@ -9251,7 +9287,10 @@ function Datatable({
9251
9287
  columnVisibility: Object.fromEntries([...hidden].map((f) => [f, false])),
9252
9288
  // absent = visible
9253
9289
  columnPinning: Object.fromEntries([...pins.left.map((f) => [f, "left"]), ...pins.right.map((f) => [f, "right"])]),
9254
- density
9290
+ density,
9291
+ // #292: additive, optional — user-resized filter panel + field widths (panel-global, not per column).
9292
+ filterPanelSize: fPanelSize || void 0,
9293
+ filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null ? fWidths : void 0
9255
9294
  });
9256
9295
  const applyState = (s) => {
9257
9296
  if (!s || typeof s !== "object") return;
@@ -9276,6 +9315,16 @@ function Datatable({
9276
9315
  setPins({ left: l, right: r2 });
9277
9316
  }
9278
9317
  if (s.density === "compact" || s.density === "standard" || s.density === "comfortable") setDensity(s.density);
9318
+ if (s.filterFieldWidths && typeof s.filterFieldWidths === "object") {
9319
+ const fw = {};
9320
+ for (const k of ["col", "op", "val"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
9321
+ if (Object.keys(fw).length) setFWidths(fw);
9322
+ }
9323
+ if (s.filterPanelSize && typeof s.filterPanelSize === "object") {
9324
+ const w = typeof s.filterPanelSize.w === "number" ? clampNum(s.filterPanelSize.w, 220, fMaxW()) : void 0;
9325
+ const h = typeof s.filterPanelSize.h === "number" ? clampNum(s.filterPanelSize.h, 120, fMaxH()) : void 0;
9326
+ if (w != null || h != null) setFPanelSize({ ...w != null ? { w } : {}, ...h != null ? { h } : {} });
9327
+ }
9279
9328
  };
9280
9329
  const stateReadyRef = React51__namespace.default.useRef(false);
9281
9330
  const onStateChangeRef = React51__namespace.default.useRef(onStateChange);
@@ -9292,7 +9341,7 @@ function Datatable({
9292
9341
  }
9293
9342
  }
9294
9343
  return void 0;
9295
- }, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, stateKey]);
9344
+ }, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, stateKey]);
9296
9345
  React51__namespace.default.useEffect(() => {
9297
9346
  let saved = null;
9298
9347
  if (stateKey) {
@@ -10374,8 +10423,8 @@ function Datatable({
10374
10423
  const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 - 3 * 6 - 2 * 4));
10375
10424
  const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10376
10425
  const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
10377
- if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-w", `${colW}px`);
10378
- if (Number.isFinite(opW)) el.style.setProperty("--twc-dt-fop-w", `${opW}px`);
10426
+ if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
10427
+ if (Number.isFinite(opW)) el.style.setProperty("--twc-dt-fop-fit", `${opW}px`);
10379
10428
  };
10380
10429
  measure();
10381
10430
  document.fonts?.ready?.then?.(measure).catch?.(() => {
@@ -10387,6 +10436,174 @@ function Datatable({
10387
10436
  }
10388
10437
  return () => ro?.disconnect();
10389
10438
  }, [panel, filterableCols, filters.length]);
10439
+ const F_MIN = { col: 118, op: 118, val: 140 };
10440
+ const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr" };
10441
+ const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val" };
10442
+ const F_LABEL = { col: "column", op: "operator", val: "value" };
10443
+ const fMaxW = () => typeof window === "undefined" ? 9999 : window.innerWidth - 32;
10444
+ const fMaxH = () => typeof window === "undefined" ? 9999 : window.innerHeight - 32;
10445
+ const clampNum = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
10446
+ const fLiveW = (field) => {
10447
+ const p = filterPanelRef.current, el = p && p.querySelector(F_SEL[field]);
10448
+ return el ? el.getBoundingClientRect().width : F_MIN[field];
10449
+ };
10450
+ const fCap = (field) => {
10451
+ if (field === "col") return filterFieldMaxWidth || 360;
10452
+ if (field === "op") return filterFieldMaxWidth || 260;
10453
+ const p = filterPanelRef.current;
10454
+ if (!p) return 640;
10455
+ return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 - 24, F_MIN.val, 640);
10456
+ };
10457
+ const isRtl = () => {
10458
+ const p = filterPanelRef.current;
10459
+ return p ? getComputedStyle(p).direction === "rtl" : false;
10460
+ };
10461
+ const startFieldResize = (e, field) => {
10462
+ if (e.button != null && e.button !== 0) return;
10463
+ e.preventDefault();
10464
+ e.stopPropagation();
10465
+ const panel2 = filterPanelRef.current;
10466
+ if (!panel2) return;
10467
+ const handle = e.currentTarget;
10468
+ const startX = e.clientX;
10469
+ const startW = fLiveW(field);
10470
+ const cap = fCap(field);
10471
+ const dir = isRtl() ? -1 : 1;
10472
+ handle.setAttribute("data-active", "true");
10473
+ if (field === "val") panel2.setAttribute("data-val-fixed", "");
10474
+ let last = startW;
10475
+ const onMove = (ev) => {
10476
+ last = clampNum(startW + (ev.clientX - startX) * dir, F_MIN[field], cap);
10477
+ panel2.style.setProperty(F_VAR[field], `${last}px`);
10478
+ };
10479
+ const onUp = () => {
10480
+ setFWidths((m) => ({ ...m, [field]: last }));
10481
+ handle.removeAttribute("data-active");
10482
+ window.removeEventListener("pointermove", onMove);
10483
+ window.removeEventListener("pointerup", onUp);
10484
+ };
10485
+ window.addEventListener("pointermove", onMove);
10486
+ window.addEventListener("pointerup", onUp);
10487
+ };
10488
+ const onFieldResizeKey = (e, field) => {
10489
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10490
+ e.preventDefault();
10491
+ e.stopPropagation();
10492
+ resetFWidth(field);
10493
+ return;
10494
+ }
10495
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
10496
+ e.preventDefault();
10497
+ e.stopPropagation();
10498
+ const cur = fLiveW(field), cap = fCap(field), step = e.shiftKey ? 2 : 10, dir = isRtl() ? -1 : 1;
10499
+ let next = cur;
10500
+ if (e.key === "ArrowRight") next = cur + step * dir;
10501
+ else if (e.key === "ArrowLeft") next = cur - step * dir;
10502
+ else if (e.key === "Home") next = F_MIN[field];
10503
+ else if (e.key === "End") next = cap;
10504
+ setFWidths((m) => ({ ...m, [field]: clampNum(next, F_MIN[field], cap) }));
10505
+ };
10506
+ const resetFWidth = (field) => {
10507
+ setFWidths((m) => {
10508
+ const n = { ...m };
10509
+ delete n[field];
10510
+ return n;
10511
+ });
10512
+ const p = filterPanelRef.current;
10513
+ if (p) {
10514
+ p.style.removeProperty(F_VAR[field]);
10515
+ if (field === "val") p.removeAttribute("data-val-fixed");
10516
+ }
10517
+ };
10518
+ const startPanelResize = (e) => {
10519
+ if (e.button != null && e.button !== 0) return;
10520
+ e.preventDefault();
10521
+ e.stopPropagation();
10522
+ const panel2 = filterPanelRef.current;
10523
+ if (!panel2) return;
10524
+ const grip = e.currentTarget;
10525
+ const startX = e.clientX, startY = e.clientY;
10526
+ const r2 = panel2.getBoundingClientRect();
10527
+ const startW = r2.width, startH = r2.height;
10528
+ const dir = isRtl() ? -1 : 1;
10529
+ grip.setAttribute("data-active", "true");
10530
+ panel2.setAttribute("data-panel-sized", "");
10531
+ let lw = startW, lh = startH;
10532
+ const onMove = (ev) => {
10533
+ lw = clampNum(startW + (ev.clientX - startX) * dir, 220, fMaxW());
10534
+ lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
10535
+ panel2.style.setProperty("--twc-dt-panel-w", `${lw}px`);
10536
+ panel2.style.setProperty("--twc-dt-panel-h", `${lh}px`);
10537
+ };
10538
+ const onUp = () => {
10539
+ setFPanelSize({ w: lw, h: lh });
10540
+ grip.removeAttribute("data-active");
10541
+ window.removeEventListener("pointermove", onMove);
10542
+ window.removeEventListener("pointerup", onUp);
10543
+ };
10544
+ window.addEventListener("pointermove", onMove);
10545
+ window.addEventListener("pointerup", onUp);
10546
+ };
10547
+ const onPanelGripKey = (e) => {
10548
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10549
+ e.preventDefault();
10550
+ resetPanelSize();
10551
+ return;
10552
+ }
10553
+ if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) return;
10554
+ e.preventDefault();
10555
+ const panel2 = filterPanelRef.current;
10556
+ if (!panel2) return;
10557
+ const r2 = panel2.getBoundingClientRect();
10558
+ const step = e.shiftKey ? 4 : 20;
10559
+ const dir = isRtl() ? -1 : 1;
10560
+ let w = r2.width, h = r2.height;
10561
+ if (e.key === "ArrowRight") w += step * dir;
10562
+ else if (e.key === "ArrowLeft") w -= step * dir;
10563
+ else if (e.key === "ArrowDown") h += step;
10564
+ else if (e.key === "ArrowUp") h -= step;
10565
+ else if (e.key === "Home") {
10566
+ w = 220;
10567
+ h = 120;
10568
+ } else if (e.key === "End") {
10569
+ w = fMaxW();
10570
+ h = fMaxH();
10571
+ }
10572
+ setFPanelSize({ w: clampNum(w, 220, fMaxW()), h: clampNum(h, 120, fMaxH()) });
10573
+ };
10574
+ const resetPanelSize = () => {
10575
+ setFPanelSize(null);
10576
+ const p = filterPanelRef.current;
10577
+ if (p) {
10578
+ p.style.removeProperty("--twc-dt-panel-w");
10579
+ p.style.removeProperty("--twc-dt-panel-h");
10580
+ p.removeAttribute("data-panel-sized");
10581
+ }
10582
+ };
10583
+ const resetFilterSizes = () => {
10584
+ setFWidths({});
10585
+ setFPanelSize(null);
10586
+ const p = filterPanelRef.current;
10587
+ if (p) {
10588
+ ["--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));
10589
+ p.removeAttribute("data-val-fixed");
10590
+ p.removeAttribute("data-panel-sized");
10591
+ }
10592
+ };
10593
+ const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fPanelSize != null;
10594
+ const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
10595
+ "span",
10596
+ {
10597
+ className: "twc-dt__f-rz",
10598
+ role: "separator",
10599
+ "aria-orientation": "vertical",
10600
+ tabIndex: 0,
10601
+ "aria-label": `Resize ${F_LABEL[field]} field (Enter to reset)`,
10602
+ onPointerDown: (e) => startFieldResize(e, field),
10603
+ onClick: (e) => e.stopPropagation(),
10604
+ onKeyDown: (e) => onFieldResizeKey(e, field)
10605
+ }
10606
+ ) : null;
10390
10607
  const orderIdxOf = (f) => {
10391
10608
  const i = order.indexOf(f);
10392
10609
  return i === -1 ? 9999 : i;
@@ -11756,78 +11973,126 @@ function Datatable({
11756
11973
  ] })
11757
11974
  ] });
11758
11975
  })() : null,
11759
- panel === "filters" && panelPos ? /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: filterPanelRef, className: "twc-dt__pop twc-dt__filters", style: { top: panelPos.top, left: panelPos.left }, children: [
11760
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
11761
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
11762
- filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
11763
- ] }),
11764
- filters.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__empty", style: { padding: "16px 12px" }, children: "No filters applied" }) : filters.map((f) => {
11765
- const col = colByField[f.field] || cols[0];
11766
- const ops = opsFor(filterTypeOf(col));
11767
- const op = ops.find((o) => o.value === f.op) || ops[0];
11768
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__frow", children: [
11769
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-col", children: /* @__PURE__ */ jsxRuntime.jsx(
11770
- Select,
11976
+ panel === "filters" && panelPos ? /* @__PURE__ */ jsxRuntime.jsxs(
11977
+ "div",
11978
+ {
11979
+ ref: filterPanelRef,
11980
+ className: "twc-dt__pop twc-dt__filters",
11981
+ style: {
11982
+ top: panelPos.top,
11983
+ left: panelPos.left,
11984
+ "--twc-dt-fcol-usr": fWidths.col != null ? `${fWidths.col}px` : void 0,
11985
+ "--twc-dt-fop-usr": fWidths.op != null ? `${fWidths.op}px` : void 0,
11986
+ "--twc-dt-fval-usr": fWidths.val != null ? `${fWidths.val}px` : void 0,
11987
+ "--twc-dt-panel-w": fPanelSize?.w != null ? `${fPanelSize.w}px` : void 0,
11988
+ "--twc-dt-panel-h": fPanelSize?.h != null ? `${fPanelSize.h}px` : void 0,
11989
+ "--twc-dt-fcol-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0,
11990
+ "--twc-dt-fop-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0
11991
+ },
11992
+ "data-val-fixed": fWidths.val != null ? "" : void 0,
11993
+ "data-panel-sized": fPanelSize?.h != null ? "" : void 0,
11994
+ children: [
11995
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
11996
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
11997
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", gap: 2 }, children: [
11998
+ resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
11999
+ filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
12000
+ ] })
12001
+ ] }),
12002
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__frows", children: filters.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__empty", style: { padding: "16px 12px" }, children: "No filters applied" }) : filters.map((f, i) => {
12003
+ const col = colByField[f.field] || cols[0];
12004
+ const ops = opsFor(filterTypeOf(col));
12005
+ const op = ops.find((o) => o.value === f.op) || ops[0];
12006
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__frow", children: [
12007
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-col", children: [
12008
+ /* @__PURE__ */ jsxRuntime.jsx(
12009
+ Select,
12010
+ {
12011
+ size: "sm",
12012
+ portal: true,
12013
+ value: f.field,
12014
+ options: filterableCols.map((c) => ({ value: c.field, label: c.headerName })),
12015
+ onChange: (v) => {
12016
+ const nc = colByField[v];
12017
+ setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, field: v, op: opsFor(filterTypeOf(nc))[0].value, value: "" } : x));
12018
+ }
12019
+ }
12020
+ ),
12021
+ fieldHandle("col", i)
12022
+ ] }),
12023
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-op", children: [
12024
+ /* @__PURE__ */ jsxRuntime.jsx(
12025
+ Select,
12026
+ {
12027
+ size: "sm",
12028
+ portal: true,
12029
+ value: f.op,
12030
+ options: ops.map((o) => ({ value: o.value, label: o.label })),
12031
+ onChange: (v) => setFilters((arr) => arr.map((x) => {
12032
+ if (x.id !== f.id) return x;
12033
+ const nextMulti = isMultiOp(v);
12034
+ const wasArr = Array.isArray(x.value);
12035
+ return { ...x, op: v, value: nextMulti ? wasArr ? x.value : [] : wasArr ? "" : x.value };
12036
+ }))
12037
+ }
12038
+ ),
12039
+ fieldHandle("op", i)
12040
+ ] }),
12041
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-val", children: [
12042
+ op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsxRuntime.jsx(
12043
+ AsyncFilterValue,
12044
+ {
12045
+ col,
12046
+ value: f.value,
12047
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
12048
+ }
12049
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12050
+ MultiSelect,
12051
+ {
12052
+ portal: true,
12053
+ placeholder: "Any of\u2026",
12054
+ value: Array.isArray(f.value) ? f.value : [],
12055
+ options: optionsForField(f.field),
12056
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
12057
+ }
12058
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12059
+ Input,
12060
+ {
12061
+ size: "sm",
12062
+ type: filterTypeOf(col) === "number" ? "number" : "text",
12063
+ placeholder: "Value",
12064
+ value: f.value,
12065
+ onChange: (e) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: e.target.value } : x))
12066
+ }
12067
+ ),
12068
+ fieldHandle("val", i)
12069
+ ] }),
12070
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__frm-x", "aria-label": "Remove filter", onClick: () => setFilters((arr) => arr.filter((x) => x.id !== f.id)), children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.x }) })
12071
+ ] }, f.id);
12072
+ }) }),
12073
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-add", style: { padding: "6px 4px 2px" }, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__mi", style: { color: "var(--color-primary)" }, onClick: () => addFilter(cols[0].field), children: [
12074
+ /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
12075
+ " Add filter"
12076
+ ] }) }),
12077
+ resizableFilters ? /* @__PURE__ */ jsxRuntime.jsx(
12078
+ "span",
11771
12079
  {
11772
- size: "sm",
11773
- portal: true,
11774
- value: f.field,
11775
- options: filterableCols.map((c) => ({ value: c.field, label: c.headerName })),
11776
- onChange: (v) => {
11777
- const nc = colByField[v];
11778
- setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, field: v, op: opsFor(filterTypeOf(nc))[0].value, value: "" } : x));
12080
+ className: "twc-dt__pop-grip",
12081
+ role: "slider",
12082
+ tabIndex: 0,
12083
+ "aria-label": "Resize filters panel (Enter to reset)",
12084
+ "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? 580)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12085
+ onPointerDown: startPanelResize,
12086
+ onKeyDown: onPanelGripKey,
12087
+ onDoubleClick: (e) => {
12088
+ e.preventDefault();
12089
+ resetPanelSize();
11779
12090
  }
11780
12091
  }
11781
- ) }),
11782
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-op", children: /* @__PURE__ */ jsxRuntime.jsx(
11783
- Select,
11784
- {
11785
- size: "sm",
11786
- portal: true,
11787
- value: f.op,
11788
- options: ops.map((o) => ({ value: o.value, label: o.label })),
11789
- onChange: (v) => setFilters((arr) => arr.map((x) => {
11790
- if (x.id !== f.id) return x;
11791
- const nextMulti = isMultiOp(v);
11792
- const wasArr = Array.isArray(x.value);
11793
- return { ...x, op: v, value: nextMulti ? wasArr ? x.value : [] : wasArr ? "" : x.value };
11794
- }))
11795
- }
11796
- ) }),
11797
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsxRuntime.jsx(
11798
- AsyncFilterValue,
11799
- {
11800
- col,
11801
- value: f.value,
11802
- onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11803
- }
11804
- ) : /* @__PURE__ */ jsxRuntime.jsx(
11805
- MultiSelect,
11806
- {
11807
- portal: true,
11808
- placeholder: "Any of\u2026",
11809
- value: Array.isArray(f.value) ? f.value : [],
11810
- options: optionsForField(f.field),
11811
- onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11812
- }
11813
- ) : /* @__PURE__ */ jsxRuntime.jsx(
11814
- Input,
11815
- {
11816
- size: "sm",
11817
- type: filterTypeOf(col) === "number" ? "number" : "text",
11818
- placeholder: "Value",
11819
- value: f.value,
11820
- onChange: (e) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: e.target.value } : x))
11821
- }
11822
- ) }),
11823
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__frm-x", "aria-label": "Remove filter", onClick: () => setFilters((arr) => arr.filter((x) => x.id !== f.id)), children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.x }) })
11824
- ] }, f.id);
11825
- }),
11826
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "6px 4px 2px" }, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__mi", style: { color: "var(--color-primary)" }, onClick: () => addFilter(cols[0].field), children: [
11827
- /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
11828
- " Add filter"
11829
- ] }) })
11830
- ] }) : null
12092
+ ) : null
12093
+ ]
12094
+ }
12095
+ ) : null
11831
12096
  ] });
11832
12097
  }
11833
12098
  function space(v) {