twico-ui 1.21.0 → 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,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.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
+
12
+ ## [1.21.1](https://github.com/EncryptionL/twico-ui/compare/v1.21.0...v1.21.1) (2026-07-29)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **datatable:** auto-fit filter panel field widths to the widest label ([29ca74c](https://github.com/EncryptionL/twico-ui/commit/29ca74cd89fde500ca024f574488a59feaa87ee0)), closes [#289](https://github.com/EncryptionL/twico-ui/issues/289)
18
+ * **overlay:** stack tooltips below toasts, and dropdowns above ([7236003](https://github.com/EncryptionL/twico-ui/commit/7236003bbf97f73b7440abd89b39aa846b02edc1)), closes [#288](https://github.com/EncryptionL/twico-ui/issues/288)
19
+
5
20
  # [1.21.0](https://github.com/EncryptionL/twico-ui/compare/v1.20.2...v1.21.0) (2026-07-28)
6
21
 
7
22
 
package/dist/index.cjs CHANGED
@@ -5488,7 +5488,7 @@ function ColorPicker({
5488
5488
  role: "dialog",
5489
5489
  "aria-modal": "true",
5490
5490
  "aria-label": "Color picker",
5491
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
5491
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
5492
5492
  children: [
5493
5493
  /* @__PURE__ */ jsxRuntime.jsxs(
5494
5494
  "div",
@@ -5966,7 +5966,7 @@ function Combobox({
5966
5966
  role: "listbox",
5967
5967
  ref: popRef,
5968
5968
  "data-placement": coords.flip ? "top" : "bottom",
5969
- style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-tooltip)" },
5969
+ style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-floating)" },
5970
5970
  children: popInner
5971
5971
  }
5972
5972
  ),
@@ -6853,7 +6853,7 @@ function Select({
6853
6853
  ref: popRef,
6854
6854
  "data-state": popState,
6855
6855
  "data-placement": coords.flip ? "top" : "bottom",
6856
- style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, minWidth: coords.minWidth, maxWidth: coords.maxWidth, right: "auto", zIndex: "var(--z-tooltip)" },
6856
+ style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, minWidth: coords.minWidth, maxWidth: coords.maxWidth, right: "auto", zIndex: "var(--z-floating)" },
6857
6857
  children: popInner
6858
6858
  }
6859
6859
  ),
@@ -7620,7 +7620,7 @@ function MultiSelect({
7620
7620
  className: "twc-pop twc-pop--portal",
7621
7621
  ref: popRef,
7622
7622
  "data-placement": coords.flip ? "top" : "bottom",
7623
- style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-tooltip)" },
7623
+ style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-floating)" },
7624
7624
  children: popInner
7625
7625
  }
7626
7626
  ),
@@ -8525,15 +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); }
8530
- .twc-dt__frow { display: flex; align-items: center; gap: 6px; padding: 5px 4px; }
8531
- .twc-dt__f-col { width: 118px; flex: none; }
8532
- .twc-dt__f-op { width: 118px; flex: none; }
8533
- .twc-dt__f-val { flex: 1; min-width: 140px; }
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; }
8542
+ /* #289: wrap is a safety net \u2014 a beyond-cap label or the narrow responsive panel drops the value input
8543
+ to its own line instead of clipping the remove button; no effect in the normal (capped) case. */
8544
+ .twc-dt__frow { display: flex; align-items: center; gap: 6px; padding: 5px 4px; flex-wrap: wrap; }
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; }
8534
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; }
8535
8556
  .twc-dt__frm-x:hover { background: var(--color-danger-subtle); color: var(--color-danger-subtle-fg); }
8536
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
+ }
8537
8574
  .twc-dt__empty { padding: 30px 12px; text-align: center; color: var(--color-text-subtle); font-size: var(--text-sm); }
8538
8575
 
8539
8576
  /* Action cell */
@@ -8644,6 +8681,7 @@ var NUM_OPS = [
8644
8681
  ];
8645
8682
  var opsFor = (type) => type === "number" ? NUM_OPS : STR_OPS;
8646
8683
  var isMultiOp = (op) => op === "isAnyOf";
8684
+ var ALL_OP_LABELS = [...new Set([...STR_OPS, ...NUM_OPS].map((o) => o.label))];
8647
8685
  var filterTypeOf = (col) => {
8648
8686
  const ft = col && col.filterType;
8649
8687
  if (ft === "number" || ft === "string") return ft;
@@ -9085,6 +9123,8 @@ function Datatable({
9085
9123
  quickFilter,
9086
9124
  onQuickFilterChange,
9087
9125
  toolbarActions,
9126
+ resizableFilters = true,
9127
+ filterFieldMaxWidth,
9088
9128
  rowPinning = false,
9089
9129
  rowReorder = false,
9090
9130
  rowResize = false,
@@ -9233,6 +9273,8 @@ function Datatable({
9233
9273
  const [rowGrab, setRowGrab] = React51__namespace.default.useState(null);
9234
9274
  const [reorderMsg, setReorderMsg] = React51__namespace.default.useState("");
9235
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);
9236
9278
  const serializeState = () => ({
9237
9279
  filters: filters.map(({ id, ...f }) => f),
9238
9280
  // strip the internal row id
@@ -9245,7 +9287,10 @@ function Datatable({
9245
9287
  columnVisibility: Object.fromEntries([...hidden].map((f) => [f, false])),
9246
9288
  // absent = visible
9247
9289
  columnPinning: Object.fromEntries([...pins.left.map((f) => [f, "left"]), ...pins.right.map((f) => [f, "right"])]),
9248
- 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
9249
9294
  });
9250
9295
  const applyState = (s) => {
9251
9296
  if (!s || typeof s !== "object") return;
@@ -9270,6 +9315,16 @@ function Datatable({
9270
9315
  setPins({ left: l, right: r2 });
9271
9316
  }
9272
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
+ }
9273
9328
  };
9274
9329
  const stateReadyRef = React51__namespace.default.useRef(false);
9275
9330
  const onStateChangeRef = React51__namespace.default.useRef(onStateChange);
@@ -9286,7 +9341,7 @@ function Datatable({
9286
9341
  }
9287
9342
  }
9288
9343
  return void 0;
9289
- }, [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]);
9290
9345
  React51__namespace.default.useEffect(() => {
9291
9346
  let saved = null;
9292
9347
  if (stateKey) {
@@ -9316,6 +9371,8 @@ function Datatable({
9316
9371
  };
9317
9372
  }, []);
9318
9373
  const scrollRef = React51__namespace.default.useRef(null);
9374
+ const filterPanelRef = React51__namespace.default.useRef(null);
9375
+ const measureCanvas = React51__namespace.default.useRef(null);
9319
9376
  const [scrollTop, setScrollTop] = React51__namespace.default.useState(0);
9320
9377
  const [viewportH, setViewportH] = React51__namespace.default.useState(0);
9321
9378
  const [ovTip, setOvTip] = React51__namespace.default.useState(null);
@@ -10349,7 +10406,204 @@ function Datatable({
10349
10406
  ] });
10350
10407
  }
10351
10408
  const tableMinWidth = leadW + ordered.reduce((a, c) => a + widthOf(c), 0);
10352
- const filterableCols = cols.filter((c) => c.filterable);
10409
+ const filterableCols = React51__namespace.default.useMemo(() => cols.filter((c) => c.filterable), [cols]);
10410
+ React51__namespace.default.useEffect(() => {
10411
+ const el = filterPanelRef.current;
10412
+ if (panel !== "filters" || !el || typeof document === "undefined") return void 0;
10413
+ const measure = () => {
10414
+ if (!el.isConnected) return;
10415
+ const trigger = el.querySelector(".twc-sel__trigger");
10416
+ const cs = getComputedStyle(trigger || el);
10417
+ const ctx = (measureCanvas.current || (measureCanvas.current = document.createElement("canvas"))).getContext("2d");
10418
+ if (!ctx) return;
10419
+ ctx.font = cs.font || `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize}/${cs.lineHeight} ${cs.fontFamily}`;
10420
+ const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
10421
+ const chrome = pad2 + 8 + 16 + 4;
10422
+ const widest = (labels) => labels.reduce((m, l) => Math.max(m, ctx.measureText(String(l)).width), 0);
10423
+ const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 - 3 * 6 - 2 * 4));
10424
+ const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10425
+ const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
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`);
10428
+ };
10429
+ measure();
10430
+ document.fonts?.ready?.then?.(measure).catch?.(() => {
10431
+ });
10432
+ let ro;
10433
+ if (typeof ResizeObserver !== "undefined") {
10434
+ ro = new ResizeObserver(measure);
10435
+ ro.observe(el);
10436
+ }
10437
+ return () => ro?.disconnect();
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;
10353
10607
  const orderIdxOf = (f) => {
10354
10608
  const i = order.indexOf(f);
10355
10609
  return i === -1 ? 9999 : i;
@@ -11719,78 +11973,126 @@ function Datatable({
11719
11973
  ] })
11720
11974
  ] });
11721
11975
  })() : null,
11722
- panel === "filters" && panelPos ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__filters", style: { top: panelPos.top, left: panelPos.left }, children: [
11723
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
11724
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
11725
- filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
11726
- ] }),
11727
- filters.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__empty", style: { padding: "16px 12px" }, children: "No filters applied" }) : filters.map((f) => {
11728
- const col = colByField[f.field] || cols[0];
11729
- const ops = opsFor(filterTypeOf(col));
11730
- const op = ops.find((o) => o.value === f.op) || ops[0];
11731
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__frow", children: [
11732
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-col", children: /* @__PURE__ */ jsxRuntime.jsx(
11733
- 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",
11734
12079
  {
11735
- size: "sm",
11736
- portal: true,
11737
- value: f.field,
11738
- options: filterableCols.map((c) => ({ value: c.field, label: c.headerName })),
11739
- onChange: (v) => {
11740
- const nc = colByField[v];
11741
- 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();
11742
12090
  }
11743
12091
  }
11744
- ) }),
11745
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-op", children: /* @__PURE__ */ jsxRuntime.jsx(
11746
- Select,
11747
- {
11748
- size: "sm",
11749
- portal: true,
11750
- value: f.op,
11751
- options: ops.map((o) => ({ value: o.value, label: o.label })),
11752
- onChange: (v) => setFilters((arr) => arr.map((x) => {
11753
- if (x.id !== f.id) return x;
11754
- const nextMulti = isMultiOp(v);
11755
- const wasArr = Array.isArray(x.value);
11756
- return { ...x, op: v, value: nextMulti ? wasArr ? x.value : [] : wasArr ? "" : x.value };
11757
- }))
11758
- }
11759
- ) }),
11760
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsxRuntime.jsx(
11761
- AsyncFilterValue,
11762
- {
11763
- col,
11764
- value: f.value,
11765
- onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11766
- }
11767
- ) : /* @__PURE__ */ jsxRuntime.jsx(
11768
- MultiSelect,
11769
- {
11770
- portal: true,
11771
- placeholder: "Any of\u2026",
11772
- value: Array.isArray(f.value) ? f.value : [],
11773
- options: optionsForField(f.field),
11774
- onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11775
- }
11776
- ) : /* @__PURE__ */ jsxRuntime.jsx(
11777
- Input,
11778
- {
11779
- size: "sm",
11780
- type: filterTypeOf(col) === "number" ? "number" : "text",
11781
- placeholder: "Value",
11782
- value: f.value,
11783
- onChange: (e) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: e.target.value } : x))
11784
- }
11785
- ) }),
11786
- /* @__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 }) })
11787
- ] }, f.id);
11788
- }),
11789
- /* @__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: [
11790
- /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
11791
- " Add filter"
11792
- ] }) })
11793
- ] }) : null
12092
+ ) : null
12093
+ ]
12094
+ }
12095
+ ) : null
11794
12096
  ] });
11795
12097
  }
11796
12098
  function space(v) {
@@ -12592,7 +12894,7 @@ function DatePicker({
12592
12894
  role: "dialog",
12593
12895
  "aria-modal": "true",
12594
12896
  "aria-label": "Choose date",
12595
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
12897
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
12596
12898
  children: [
12597
12899
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dp__head", children: [
12598
12900
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dp__nav", "aria-label": mode === "years" ? "Previous decade" : mode === "months" ? "Previous year" : "Previous month", onClick: () => setView(mode === "years" ? new Date(y - 10, m, 1) : mode === "months" ? new Date(y - 1, m, 1) : new Date(y, m - 1, 1)), children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 18-6-6 6-6" }) }) }),
@@ -13169,7 +13471,7 @@ function DateRangePicker({
13169
13471
  role: "dialog",
13170
13472
  "aria-modal": "true",
13171
13473
  "aria-label": "Choose date range",
13172
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
13474
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
13173
13475
  children: [
13174
13476
  presetItems.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drp__presets", children: presetItems.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-drp__preset", onClick: () => applyPreset(p), children: p.label }, i)) }) : null,
13175
13477
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drp__cal", children: [
@@ -13698,7 +14000,7 @@ function TimePicker({
13698
14000
  role: "dialog",
13699
14001
  "aria-modal": "true",
13700
14002
  "aria-label": "Choose time",
13701
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
14003
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
13702
14004
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-tp__cols", children: columns.map((col) => {
13703
14005
  const firstTab = col.items.find((it) => it.selected) || col.items.find((it) => !it.disabled);
13704
14006
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-tp__col", role: "listbox", "aria-label": col.aria, "data-col": col.key, children: [