twico-ui 1.21.1 → 1.22.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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
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.1](https://github.com/EncryptionL/twico-ui/compare/v1.22.0...v1.22.1) (2026-07-29)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **datatable:** keep the Filters popover position:fixed (regression from [#292](https://github.com/EncryptionL/twico-ui/issues/292)) ([0d53d05](https://github.com/EncryptionL/twico-ui/commit/0d53d057e861f5327af7b43730b3f143a937205e)), closes [#294](https://github.com/EncryptionL/twico-ui/issues/294)
11
+ * **inputs:** portaled dropdown menu re-tracks its trigger's resize ([b6e6843](https://github.com/EncryptionL/twico-ui/commit/b6e6843f2345f60db0719f7e12bef504393e3189)), closes [#292](https://github.com/EncryptionL/twico-ui/issues/292)
12
+
13
+ # [1.22.0](https://github.com/EncryptionL/twico-ui/compare/v1.21.1...v1.22.0) (2026-07-29)
14
+
15
+
16
+ ### Features
17
+
18
+ * **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)
19
+
5
20
  ## [1.21.1](https://github.com/EncryptionL/twico-ui/compare/v1.21.0...v1.21.1) (2026-07-29)
6
21
 
7
22
 
package/dist/index.cjs CHANGED
@@ -5345,9 +5345,16 @@ function ColorPicker({
5345
5345
  const onMove = () => place();
5346
5346
  window.addEventListener("scroll", onMove, true);
5347
5347
  window.addEventListener("resize", onMove);
5348
+ let ro;
5349
+ const rt = triggerRef.current;
5350
+ if (rt && typeof ResizeObserver !== "undefined") {
5351
+ ro = new ResizeObserver(onMove);
5352
+ ro.observe(rt);
5353
+ }
5348
5354
  return () => {
5349
5355
  window.removeEventListener("scroll", onMove, true);
5350
5356
  window.removeEventListener("resize", onMove);
5357
+ ro?.disconnect();
5351
5358
  };
5352
5359
  }, [open, place]);
5353
5360
  const labelId = React51__namespace.default.useId();
@@ -5807,9 +5814,16 @@ function Combobox({
5807
5814
  const onMove = () => place();
5808
5815
  window.addEventListener("scroll", onMove, true);
5809
5816
  window.addEventListener("resize", onMove);
5817
+ let ro;
5818
+ const rt = controlRef.current;
5819
+ if (rt && typeof ResizeObserver !== "undefined") {
5820
+ ro = new ResizeObserver(onMove);
5821
+ ro.observe(rt);
5822
+ }
5810
5823
  return () => {
5811
5824
  window.removeEventListener("scroll", onMove, true);
5812
5825
  window.removeEventListener("resize", onMove);
5826
+ ro?.disconnect();
5813
5827
  };
5814
5828
  }, [open, portal, place]);
5815
5829
  React51__namespace.default.useEffect(() => {
@@ -6634,9 +6648,16 @@ function Select({
6634
6648
  const onMove = () => place();
6635
6649
  window.addEventListener("scroll", onMove, true);
6636
6650
  window.addEventListener("resize", onMove);
6651
+ let ro;
6652
+ const rt = triggerRef.current;
6653
+ if (rt && typeof ResizeObserver !== "undefined") {
6654
+ ro = new ResizeObserver(onMove);
6655
+ ro.observe(rt);
6656
+ }
6637
6657
  return () => {
6638
6658
  window.removeEventListener("scroll", onMove, true);
6639
6659
  window.removeEventListener("resize", onMove);
6660
+ ro?.disconnect();
6640
6661
  };
6641
6662
  }, [open, portal, place]);
6642
6663
  React51__namespace.default.useEffect(() => {
@@ -7463,9 +7484,16 @@ function MultiSelect({
7463
7484
  const onMove = () => place();
7464
7485
  window.addEventListener("scroll", onMove, true);
7465
7486
  window.addEventListener("resize", onMove);
7487
+ let ro;
7488
+ const rt = controlRef.current;
7489
+ if (rt && typeof ResizeObserver !== "undefined") {
7490
+ ro = new ResizeObserver(onMove);
7491
+ ro.observe(rt);
7492
+ }
7466
7493
  return () => {
7467
7494
  window.removeEventListener("scroll", onMove, true);
7468
7495
  window.removeEventListener("resize", onMove);
7496
+ ro?.disconnect();
7469
7497
  };
7470
7498
  }, [open, portal, place]);
7471
7499
  React51__namespace.default.useEffect(() => {
@@ -8525,20 +8553,55 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8525
8553
  .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
8554
  .twc-dt__link:hover { background: var(--color-primary-subtle); }
8527
8555
 
8528
- /* Filter panel */
8529
- .twc-dt__filters { width: 580px; max-width: calc(100vw - 32px); }
8556
+ /* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
8557
+ cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
8558
+ neither writer reads the other (reset = remove the -usr var and auto-fit instantly re-wins). */
8559
+ .twc-dt__filters {
8560
+ /* #294: do NOT set 'position' here \u2014 this element also carries .twc-dt__pop (position: fixed), and an
8561
+ equal-specificity 'position: relative' defined later would clobber it, dropping the popover into normal
8562
+ flow below the table. The panel stays position: fixed, which already anchors the absolutely-positioned
8563
+ .twc-dt__pop-grip; per-field handles anchor to their own position: relative .twc-dt__f-* fields. */
8564
+ width: var(--twc-dt-panel-w, 580px); max-width: calc(100vw - 32px);
8565
+ height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
8566
+ display: flex; flex-direction: column;
8567
+ }
8568
+ /* Rows list scrolls only once the user pins a height; Add-filter stays sticky. Safe because the filter
8569
+ Selects portal to <body> \u2014 an inline (non-portaled) menu would be clipped by this scroll box. */
8570
+ .twc-dt__frows { min-height: 0; }
8571
+ .twc-dt__filters[data-panel-sized] .twc-dt__frows { flex: 1 1 auto; overflow-y: auto; }
8572
+ .twc-dt__filters[data-panel-sized] .twc-dt__f-add { position: sticky; bottom: 0; background: var(--color-surface-raised); padding-inline-end: 20px; }
8530
8573
  /* #289: wrap is a safety net \u2014 a beyond-cap label or the narrow responsive panel drops the value input
8531
8574
  to its own line instead of clipping the remove button; no effect in the normal (capped) case. */
8532
8575
  .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; }
8576
+ /* col/op: user > measured (-fit, written by the #289 measurer) > 118px, clamped to a drag cap that
8577
+ filterFieldMaxWidth can raise. Position:relative hosts the resize handle. */
8578
+ .twc-dt__f-col { position: relative; flex: none; min-width: 118px;
8579
+ width: clamp(118px, var(--twc-dt-fcol-usr, var(--twc-dt-fcol-fit, 118px)), var(--twc-dt-fcol-cap, 360px)); }
8580
+ .twc-dt__f-op { position: relative; flex: none; min-width: 118px;
8581
+ width: clamp(118px, var(--twc-dt-fop-usr, var(--twc-dt-fop-fit, 118px)), var(--twc-dt-fop-cap, 260px)); }
8582
+ /* value: the flex shock-absorber by default; the first drag flips it to a fixed width (data-val-fixed). */
8583
+ .twc-dt__f-val { position: relative; flex: 1 1 140px; min-width: 140px; }
8584
+ .twc-dt__filters[data-val-fixed] .twc-dt__f-val { flex: 0 0 clamp(140px, var(--twc-dt-fval-usr, 240px), 640px); }
8585
+ .twc-dt__filters[data-val-fixed] .twc-dt__frm-x { margin-inline-start: auto; }
8539
8586
  .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
8587
  .twc-dt__frm-x:hover { background: var(--color-danger-subtle); color: var(--color-danger-subtle-fg); }
8541
8588
  .twc-dt__frm-x svg { width: 15px; height: 15px; }
8589
+ /* per-field resize handle \u2014 cloned from the grid's .twc-dt__resizer; always-on baseline opacity so it's
8590
+ discoverable/hittable on touch (no hover there), with a non-color-only focus ring. */
8591
+ .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); }
8592
+ .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); }
8593
+ .twc-dt__frow:hover .twc-dt__f-rz, .twc-dt__f-rz:focus-visible, .twc-dt__f-rz[data-active="true"] { opacity: 1; }
8594
+ .twc-dt__f-rz:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--color-primary); border-radius: 2px; }
8595
+ .twc-dt__f-rz:focus-visible::after, .twc-dt__f-rz[data-active="true"]::after { width: 3px; background: var(--color-primary); }
8596
+ /* panel corner grip (2-axis) at the bottom-inline-end. */
8597
+ .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); }
8598
+ .twc-dt__pop-grip:hover, .twc-dt__pop-grip:focus-visible, .twc-dt__pop-grip[data-active="true"] { opacity: 1; }
8599
+ .twc-dt__pop-grip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
8600
+ [dir="rtl"] .twc-dt__pop-grip { cursor: nesw-resize; }
8601
+ @media (pointer: coarse) {
8602
+ .twc-dt__f-rz { width: 16px; inset-inline-end: -6px; opacity: 0.4; }
8603
+ .twc-dt__pop-grip { width: 20px; height: 20px; opacity: 0.6; }
8604
+ }
8542
8605
  .twc-dt__empty { padding: 30px 12px; text-align: center; color: var(--color-text-subtle); font-size: var(--text-sm); }
8543
8606
 
8544
8607
  /* Action cell */
@@ -9091,6 +9154,8 @@ function Datatable({
9091
9154
  quickFilter,
9092
9155
  onQuickFilterChange,
9093
9156
  toolbarActions,
9157
+ resizableFilters = true,
9158
+ filterFieldMaxWidth,
9094
9159
  rowPinning = false,
9095
9160
  rowReorder = false,
9096
9161
  rowResize = false,
@@ -9239,6 +9304,8 @@ function Datatable({
9239
9304
  const [rowGrab, setRowGrab] = React51__namespace.default.useState(null);
9240
9305
  const [reorderMsg, setReorderMsg] = React51__namespace.default.useState("");
9241
9306
  const rowFocusRef = React51__namespace.default.useRef(null);
9307
+ const [fWidths, setFWidths] = React51__namespace.default.useState({});
9308
+ const [fPanelSize, setFPanelSize] = React51__namespace.default.useState(null);
9242
9309
  const serializeState = () => ({
9243
9310
  filters: filters.map(({ id, ...f }) => f),
9244
9311
  // strip the internal row id
@@ -9251,7 +9318,10 @@ function Datatable({
9251
9318
  columnVisibility: Object.fromEntries([...hidden].map((f) => [f, false])),
9252
9319
  // absent = visible
9253
9320
  columnPinning: Object.fromEntries([...pins.left.map((f) => [f, "left"]), ...pins.right.map((f) => [f, "right"])]),
9254
- density
9321
+ density,
9322
+ // #292: additive, optional — user-resized filter panel + field widths (panel-global, not per column).
9323
+ filterPanelSize: fPanelSize || void 0,
9324
+ filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null ? fWidths : void 0
9255
9325
  });
9256
9326
  const applyState = (s) => {
9257
9327
  if (!s || typeof s !== "object") return;
@@ -9276,6 +9346,16 @@ function Datatable({
9276
9346
  setPins({ left: l, right: r2 });
9277
9347
  }
9278
9348
  if (s.density === "compact" || s.density === "standard" || s.density === "comfortable") setDensity(s.density);
9349
+ if (s.filterFieldWidths && typeof s.filterFieldWidths === "object") {
9350
+ const fw = {};
9351
+ for (const k of ["col", "op", "val"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
9352
+ if (Object.keys(fw).length) setFWidths(fw);
9353
+ }
9354
+ if (s.filterPanelSize && typeof s.filterPanelSize === "object") {
9355
+ const w = typeof s.filterPanelSize.w === "number" ? clampNum(s.filterPanelSize.w, 220, fMaxW()) : void 0;
9356
+ const h = typeof s.filterPanelSize.h === "number" ? clampNum(s.filterPanelSize.h, 120, fMaxH()) : void 0;
9357
+ if (w != null || h != null) setFPanelSize({ ...w != null ? { w } : {}, ...h != null ? { h } : {} });
9358
+ }
9279
9359
  };
9280
9360
  const stateReadyRef = React51__namespace.default.useRef(false);
9281
9361
  const onStateChangeRef = React51__namespace.default.useRef(onStateChange);
@@ -9292,7 +9372,7 @@ function Datatable({
9292
9372
  }
9293
9373
  }
9294
9374
  return void 0;
9295
- }, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, stateKey]);
9375
+ }, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, stateKey]);
9296
9376
  React51__namespace.default.useEffect(() => {
9297
9377
  let saved = null;
9298
9378
  if (stateKey) {
@@ -10374,8 +10454,8 @@ function Datatable({
10374
10454
  const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 - 3 * 6 - 2 * 4));
10375
10455
  const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10376
10456
  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`);
10457
+ if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
10458
+ if (Number.isFinite(opW)) el.style.setProperty("--twc-dt-fop-fit", `${opW}px`);
10379
10459
  };
10380
10460
  measure();
10381
10461
  document.fonts?.ready?.then?.(measure).catch?.(() => {
@@ -10387,6 +10467,174 @@ function Datatable({
10387
10467
  }
10388
10468
  return () => ro?.disconnect();
10389
10469
  }, [panel, filterableCols, filters.length]);
10470
+ const F_MIN = { col: 118, op: 118, val: 140 };
10471
+ const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr" };
10472
+ const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val" };
10473
+ const F_LABEL = { col: "column", op: "operator", val: "value" };
10474
+ const fMaxW = () => typeof window === "undefined" ? 9999 : window.innerWidth - 32;
10475
+ const fMaxH = () => typeof window === "undefined" ? 9999 : window.innerHeight - 32;
10476
+ const clampNum = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
10477
+ const fLiveW = (field) => {
10478
+ const p = filterPanelRef.current, el = p && p.querySelector(F_SEL[field]);
10479
+ return el ? el.getBoundingClientRect().width : F_MIN[field];
10480
+ };
10481
+ const fCap = (field) => {
10482
+ if (field === "col") return filterFieldMaxWidth || 360;
10483
+ if (field === "op") return filterFieldMaxWidth || 260;
10484
+ const p = filterPanelRef.current;
10485
+ if (!p) return 640;
10486
+ return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 - 24, F_MIN.val, 640);
10487
+ };
10488
+ const isRtl = () => {
10489
+ const p = filterPanelRef.current;
10490
+ return p ? getComputedStyle(p).direction === "rtl" : false;
10491
+ };
10492
+ const startFieldResize = (e, field) => {
10493
+ if (e.button != null && e.button !== 0) return;
10494
+ e.preventDefault();
10495
+ e.stopPropagation();
10496
+ const panel2 = filterPanelRef.current;
10497
+ if (!panel2) return;
10498
+ const handle = e.currentTarget;
10499
+ const startX = e.clientX;
10500
+ const startW = fLiveW(field);
10501
+ const cap = fCap(field);
10502
+ const dir = isRtl() ? -1 : 1;
10503
+ handle.setAttribute("data-active", "true");
10504
+ if (field === "val") panel2.setAttribute("data-val-fixed", "");
10505
+ let last = startW;
10506
+ const onMove = (ev) => {
10507
+ last = clampNum(startW + (ev.clientX - startX) * dir, F_MIN[field], cap);
10508
+ panel2.style.setProperty(F_VAR[field], `${last}px`);
10509
+ };
10510
+ const onUp = () => {
10511
+ setFWidths((m) => ({ ...m, [field]: last }));
10512
+ handle.removeAttribute("data-active");
10513
+ window.removeEventListener("pointermove", onMove);
10514
+ window.removeEventListener("pointerup", onUp);
10515
+ };
10516
+ window.addEventListener("pointermove", onMove);
10517
+ window.addEventListener("pointerup", onUp);
10518
+ };
10519
+ const onFieldResizeKey = (e, field) => {
10520
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10521
+ e.preventDefault();
10522
+ e.stopPropagation();
10523
+ resetFWidth(field);
10524
+ return;
10525
+ }
10526
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
10527
+ e.preventDefault();
10528
+ e.stopPropagation();
10529
+ const cur = fLiveW(field), cap = fCap(field), step = e.shiftKey ? 2 : 10, dir = isRtl() ? -1 : 1;
10530
+ let next = cur;
10531
+ if (e.key === "ArrowRight") next = cur + step * dir;
10532
+ else if (e.key === "ArrowLeft") next = cur - step * dir;
10533
+ else if (e.key === "Home") next = F_MIN[field];
10534
+ else if (e.key === "End") next = cap;
10535
+ setFWidths((m) => ({ ...m, [field]: clampNum(next, F_MIN[field], cap) }));
10536
+ };
10537
+ const resetFWidth = (field) => {
10538
+ setFWidths((m) => {
10539
+ const n = { ...m };
10540
+ delete n[field];
10541
+ return n;
10542
+ });
10543
+ const p = filterPanelRef.current;
10544
+ if (p) {
10545
+ p.style.removeProperty(F_VAR[field]);
10546
+ if (field === "val") p.removeAttribute("data-val-fixed");
10547
+ }
10548
+ };
10549
+ const startPanelResize = (e) => {
10550
+ if (e.button != null && e.button !== 0) return;
10551
+ e.preventDefault();
10552
+ e.stopPropagation();
10553
+ const panel2 = filterPanelRef.current;
10554
+ if (!panel2) return;
10555
+ const grip = e.currentTarget;
10556
+ const startX = e.clientX, startY = e.clientY;
10557
+ const r2 = panel2.getBoundingClientRect();
10558
+ const startW = r2.width, startH = r2.height;
10559
+ const dir = isRtl() ? -1 : 1;
10560
+ grip.setAttribute("data-active", "true");
10561
+ panel2.setAttribute("data-panel-sized", "");
10562
+ let lw = startW, lh = startH;
10563
+ const onMove = (ev) => {
10564
+ lw = clampNum(startW + (ev.clientX - startX) * dir, 220, fMaxW());
10565
+ lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
10566
+ panel2.style.setProperty("--twc-dt-panel-w", `${lw}px`);
10567
+ panel2.style.setProperty("--twc-dt-panel-h", `${lh}px`);
10568
+ };
10569
+ const onUp = () => {
10570
+ setFPanelSize({ w: lw, h: lh });
10571
+ grip.removeAttribute("data-active");
10572
+ window.removeEventListener("pointermove", onMove);
10573
+ window.removeEventListener("pointerup", onUp);
10574
+ };
10575
+ window.addEventListener("pointermove", onMove);
10576
+ window.addEventListener("pointerup", onUp);
10577
+ };
10578
+ const onPanelGripKey = (e) => {
10579
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10580
+ e.preventDefault();
10581
+ resetPanelSize();
10582
+ return;
10583
+ }
10584
+ if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) return;
10585
+ e.preventDefault();
10586
+ const panel2 = filterPanelRef.current;
10587
+ if (!panel2) return;
10588
+ const r2 = panel2.getBoundingClientRect();
10589
+ const step = e.shiftKey ? 4 : 20;
10590
+ const dir = isRtl() ? -1 : 1;
10591
+ let w = r2.width, h = r2.height;
10592
+ if (e.key === "ArrowRight") w += step * dir;
10593
+ else if (e.key === "ArrowLeft") w -= step * dir;
10594
+ else if (e.key === "ArrowDown") h += step;
10595
+ else if (e.key === "ArrowUp") h -= step;
10596
+ else if (e.key === "Home") {
10597
+ w = 220;
10598
+ h = 120;
10599
+ } else if (e.key === "End") {
10600
+ w = fMaxW();
10601
+ h = fMaxH();
10602
+ }
10603
+ setFPanelSize({ w: clampNum(w, 220, fMaxW()), h: clampNum(h, 120, fMaxH()) });
10604
+ };
10605
+ const resetPanelSize = () => {
10606
+ setFPanelSize(null);
10607
+ const p = filterPanelRef.current;
10608
+ if (p) {
10609
+ p.style.removeProperty("--twc-dt-panel-w");
10610
+ p.style.removeProperty("--twc-dt-panel-h");
10611
+ p.removeAttribute("data-panel-sized");
10612
+ }
10613
+ };
10614
+ const resetFilterSizes = () => {
10615
+ setFWidths({});
10616
+ setFPanelSize(null);
10617
+ const p = filterPanelRef.current;
10618
+ if (p) {
10619
+ ["--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));
10620
+ p.removeAttribute("data-val-fixed");
10621
+ p.removeAttribute("data-panel-sized");
10622
+ }
10623
+ };
10624
+ const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fPanelSize != null;
10625
+ const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
10626
+ "span",
10627
+ {
10628
+ className: "twc-dt__f-rz",
10629
+ role: "separator",
10630
+ "aria-orientation": "vertical",
10631
+ tabIndex: 0,
10632
+ "aria-label": `Resize ${F_LABEL[field]} field (Enter to reset)`,
10633
+ onPointerDown: (e) => startFieldResize(e, field),
10634
+ onClick: (e) => e.stopPropagation(),
10635
+ onKeyDown: (e) => onFieldResizeKey(e, field)
10636
+ }
10637
+ ) : null;
10390
10638
  const orderIdxOf = (f) => {
10391
10639
  const i = order.indexOf(f);
10392
10640
  return i === -1 ? 9999 : i;
@@ -11756,78 +12004,126 @@ function Datatable({
11756
12004
  ] })
11757
12005
  ] });
11758
12006
  })() : 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,
12007
+ panel === "filters" && panelPos ? /* @__PURE__ */ jsxRuntime.jsxs(
12008
+ "div",
12009
+ {
12010
+ ref: filterPanelRef,
12011
+ className: "twc-dt__pop twc-dt__filters",
12012
+ style: {
12013
+ top: panelPos.top,
12014
+ left: panelPos.left,
12015
+ "--twc-dt-fcol-usr": fWidths.col != null ? `${fWidths.col}px` : void 0,
12016
+ "--twc-dt-fop-usr": fWidths.op != null ? `${fWidths.op}px` : void 0,
12017
+ "--twc-dt-fval-usr": fWidths.val != null ? `${fWidths.val}px` : void 0,
12018
+ "--twc-dt-panel-w": fPanelSize?.w != null ? `${fPanelSize.w}px` : void 0,
12019
+ "--twc-dt-panel-h": fPanelSize?.h != null ? `${fPanelSize.h}px` : void 0,
12020
+ "--twc-dt-fcol-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0,
12021
+ "--twc-dt-fop-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0
12022
+ },
12023
+ "data-val-fixed": fWidths.val != null ? "" : void 0,
12024
+ "data-panel-sized": fPanelSize?.h != null ? "" : void 0,
12025
+ children: [
12026
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
12027
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
12028
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", gap: 2 }, children: [
12029
+ resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
12030
+ filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
12031
+ ] })
12032
+ ] }),
12033
+ /* @__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) => {
12034
+ const col = colByField[f.field] || cols[0];
12035
+ const ops = opsFor(filterTypeOf(col));
12036
+ const op = ops.find((o) => o.value === f.op) || ops[0];
12037
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__frow", children: [
12038
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-col", children: [
12039
+ /* @__PURE__ */ jsxRuntime.jsx(
12040
+ Select,
12041
+ {
12042
+ size: "sm",
12043
+ portal: true,
12044
+ value: f.field,
12045
+ options: filterableCols.map((c) => ({ value: c.field, label: c.headerName })),
12046
+ onChange: (v) => {
12047
+ const nc = colByField[v];
12048
+ setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, field: v, op: opsFor(filterTypeOf(nc))[0].value, value: "" } : x));
12049
+ }
12050
+ }
12051
+ ),
12052
+ fieldHandle("col", i)
12053
+ ] }),
12054
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-op", children: [
12055
+ /* @__PURE__ */ jsxRuntime.jsx(
12056
+ Select,
12057
+ {
12058
+ size: "sm",
12059
+ portal: true,
12060
+ value: f.op,
12061
+ options: ops.map((o) => ({ value: o.value, label: o.label })),
12062
+ onChange: (v) => setFilters((arr) => arr.map((x) => {
12063
+ if (x.id !== f.id) return x;
12064
+ const nextMulti = isMultiOp(v);
12065
+ const wasArr = Array.isArray(x.value);
12066
+ return { ...x, op: v, value: nextMulti ? wasArr ? x.value : [] : wasArr ? "" : x.value };
12067
+ }))
12068
+ }
12069
+ ),
12070
+ fieldHandle("op", i)
12071
+ ] }),
12072
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-val", children: [
12073
+ op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsxRuntime.jsx(
12074
+ AsyncFilterValue,
12075
+ {
12076
+ col,
12077
+ value: f.value,
12078
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
12079
+ }
12080
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12081
+ MultiSelect,
12082
+ {
12083
+ portal: true,
12084
+ placeholder: "Any of\u2026",
12085
+ value: Array.isArray(f.value) ? f.value : [],
12086
+ options: optionsForField(f.field),
12087
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
12088
+ }
12089
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12090
+ Input,
12091
+ {
12092
+ size: "sm",
12093
+ type: filterTypeOf(col) === "number" ? "number" : "text",
12094
+ placeholder: "Value",
12095
+ value: f.value,
12096
+ onChange: (e) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: e.target.value } : x))
12097
+ }
12098
+ ),
12099
+ fieldHandle("val", i)
12100
+ ] }),
12101
+ /* @__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 }) })
12102
+ ] }, f.id);
12103
+ }) }),
12104
+ /* @__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: [
12105
+ /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
12106
+ " Add filter"
12107
+ ] }) }),
12108
+ resizableFilters ? /* @__PURE__ */ jsxRuntime.jsx(
12109
+ "span",
11771
12110
  {
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));
12111
+ className: "twc-dt__pop-grip",
12112
+ role: "slider",
12113
+ tabIndex: 0,
12114
+ "aria-label": "Resize filters panel (Enter to reset)",
12115
+ "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? 580)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12116
+ onPointerDown: startPanelResize,
12117
+ onKeyDown: onPanelGripKey,
12118
+ onDoubleClick: (e) => {
12119
+ e.preventDefault();
12120
+ resetPanelSize();
11779
12121
  }
11780
12122
  }
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
12123
+ ) : null
12124
+ ]
12125
+ }
12126
+ ) : null
11831
12127
  ] });
11832
12128
  }
11833
12129
  function space(v) {
@@ -12341,9 +12637,16 @@ function DatePicker({
12341
12637
  const onMove = () => place();
12342
12638
  window.addEventListener("scroll", onMove, true);
12343
12639
  window.addEventListener("resize", onMove);
12640
+ let ro;
12641
+ const rt = triggerRef.current;
12642
+ if (rt && typeof ResizeObserver !== "undefined") {
12643
+ ro = new ResizeObserver(onMove);
12644
+ ro.observe(rt);
12645
+ }
12344
12646
  return () => {
12345
12647
  window.removeEventListener("scroll", onMove, true);
12346
12648
  window.removeEventListener("resize", onMove);
12649
+ ro?.disconnect();
12347
12650
  };
12348
12651
  }, [open, place]);
12349
12652
  React51__namespace.default.useEffect(() => {
@@ -12885,9 +13188,16 @@ function DateRangePicker({
12885
13188
  const onMove = () => place();
12886
13189
  window.addEventListener("scroll", onMove, true);
12887
13190
  window.addEventListener("resize", onMove);
13191
+ let ro;
13192
+ const rt = triggerRef.current;
13193
+ if (rt && typeof ResizeObserver !== "undefined") {
13194
+ ro = new ResizeObserver(onMove);
13195
+ ro.observe(rt);
13196
+ }
12888
13197
  return () => {
12889
13198
  window.removeEventListener("scroll", onMove, true);
12890
13199
  window.removeEventListener("resize", onMove);
13200
+ ro?.disconnect();
12891
13201
  };
12892
13202
  }, [open, place]);
12893
13203
  React51__namespace.default.useEffect(() => {
@@ -13438,9 +13748,16 @@ function TimePicker({
13438
13748
  const onMove = () => place();
13439
13749
  window.addEventListener("scroll", onMove, true);
13440
13750
  window.addEventListener("resize", onMove);
13751
+ let ro;
13752
+ const rt = triggerRef.current;
13753
+ if (rt && typeof ResizeObserver !== "undefined") {
13754
+ ro = new ResizeObserver(onMove);
13755
+ ro.observe(rt);
13756
+ }
13441
13757
  return () => {
13442
13758
  window.removeEventListener("scroll", onMove, true);
13443
13759
  window.removeEventListener("resize", onMove);
13760
+ ro?.disconnect();
13444
13761
  };
13445
13762
  }, [open, place]);
13446
13763
  React51__namespace.default.useEffect(() => {