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/dist/index.d.cts CHANGED
@@ -1814,6 +1814,14 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1814
1814
  * cluster (#286) — for host controls like an "Add row" button or bulk actions. Mirrors `CardGrid`'s
1815
1815
  * `toolbar` prop, so no internal-class/absolute-position hacks are needed. */
1816
1816
  toolbarActions?: React.ReactNode;
1817
+ /** Let the user drag-resize the **Filters** popover and its column/operator/value fields (#292), on top
1818
+ * of the #289 auto-fit (a dragged width overrides the measured one; reset returns to it). Widths + panel
1819
+ * size persist via `stateKey` and are keyboard-accessible. Set `false` to disable the handles/grip
1820
+ * (fields still auto-fit). @default true */
1821
+ resizableFilters?: boolean;
1822
+ /** Raise the drag cap for the filter panel's column/operator fields, in px (default col 360 / op 260) —
1823
+ * the #289-deferred "optional prop to raise the cap". Only affects `resizableFilters`. */
1824
+ filterFieldMaxWidth?: number;
1817
1825
  /**
1818
1826
  * Actions shown in the toolbar when one or more rows are selected (requires
1819
1827
  * `checkboxSelection`). Each handler receives the selected keys, the resolved
@@ -2037,6 +2045,10 @@ interface DatatableState {
2037
2045
  columnPinning: Record<string, "left" | "right">;
2038
2046
  /** Row-density preset. */
2039
2047
  density: "compact" | "standard" | "comfortable";
2048
+ /** #292: user-resized Filters popover size (px). Absent until the user drags the panel grip. */
2049
+ filterPanelSize?: { w?: number; h?: number };
2050
+ /** #292: user-resized Filters field widths (px, panel-global — not per data column). Absent until dragged. */
2051
+ filterFieldWidths?: { col?: number; op?: number; val?: number };
2040
2052
  }
2041
2053
 
2042
2054
  interface DatatableColumn<T = any> {
package/dist/index.d.ts CHANGED
@@ -1814,6 +1814,14 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1814
1814
  * cluster (#286) — for host controls like an "Add row" button or bulk actions. Mirrors `CardGrid`'s
1815
1815
  * `toolbar` prop, so no internal-class/absolute-position hacks are needed. */
1816
1816
  toolbarActions?: React.ReactNode;
1817
+ /** Let the user drag-resize the **Filters** popover and its column/operator/value fields (#292), on top
1818
+ * of the #289 auto-fit (a dragged width overrides the measured one; reset returns to it). Widths + panel
1819
+ * size persist via `stateKey` and are keyboard-accessible. Set `false` to disable the handles/grip
1820
+ * (fields still auto-fit). @default true */
1821
+ resizableFilters?: boolean;
1822
+ /** Raise the drag cap for the filter panel's column/operator fields, in px (default col 360 / op 260) —
1823
+ * the #289-deferred "optional prop to raise the cap". Only affects `resizableFilters`. */
1824
+ filterFieldMaxWidth?: number;
1817
1825
  /**
1818
1826
  * Actions shown in the toolbar when one or more rows are selected (requires
1819
1827
  * `checkboxSelection`). Each handler receives the selected keys, the resolved
@@ -2037,6 +2045,10 @@ interface DatatableState {
2037
2045
  columnPinning: Record<string, "left" | "right">;
2038
2046
  /** Row-density preset. */
2039
2047
  density: "compact" | "standard" | "comfortable";
2048
+ /** #292: user-resized Filters popover size (px). Absent until the user drags the panel grip. */
2049
+ filterPanelSize?: { w?: number; h?: number };
2050
+ /** #292: user-resized Filters field widths (px, panel-global — not per data column). Absent until dragged. */
2051
+ filterFieldWidths?: { col?: number; op?: number; val?: number };
2040
2052
  }
2041
2053
 
2042
2054
  interface DatatableColumn<T = any> {
package/dist/index.mjs CHANGED
@@ -5467,7 +5467,7 @@ function ColorPicker({
5467
5467
  role: "dialog",
5468
5468
  "aria-modal": "true",
5469
5469
  "aria-label": "Color picker",
5470
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
5470
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
5471
5471
  children: [
5472
5472
  /* @__PURE__ */ jsxs(
5473
5473
  "div",
@@ -5945,7 +5945,7 @@ function Combobox({
5945
5945
  role: "listbox",
5946
5946
  ref: popRef,
5947
5947
  "data-placement": coords.flip ? "top" : "bottom",
5948
- style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-tooltip)" },
5948
+ style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-floating)" },
5949
5949
  children: popInner
5950
5950
  }
5951
5951
  ),
@@ -6832,7 +6832,7 @@ function Select({
6832
6832
  ref: popRef,
6833
6833
  "data-state": popState,
6834
6834
  "data-placement": coords.flip ? "top" : "bottom",
6835
- 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)" },
6835
+ 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)" },
6836
6836
  children: popInner
6837
6837
  }
6838
6838
  ),
@@ -7599,7 +7599,7 @@ function MultiSelect({
7599
7599
  className: "twc-pop twc-pop--portal",
7600
7600
  ref: popRef,
7601
7601
  "data-placement": coords.flip ? "top" : "bottom",
7602
- style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-tooltip)" },
7602
+ style: { position: "fixed", left: coords.left, top: coords.top, bottom: coords.bottom, width: coords.width, right: "auto", zIndex: "var(--z-floating)" },
7603
7603
  children: popInner
7604
7604
  }
7605
7605
  ),
@@ -8504,15 +8504,52 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8504
8504
  .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); }
8505
8505
  .twc-dt__link:hover { background: var(--color-primary-subtle); }
8506
8506
 
8507
- /* Filter panel */
8508
- .twc-dt__filters { width: 580px; max-width: calc(100vw - 32px); }
8509
- .twc-dt__frow { display: flex; align-items: center; gap: 6px; padding: 5px 4px; }
8510
- .twc-dt__f-col { width: 118px; flex: none; }
8511
- .twc-dt__f-op { width: 118px; flex: none; }
8512
- .twc-dt__f-val { flex: 1; min-width: 140px; }
8507
+ /* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
8508
+ cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
8509
+ neither writer reads the other (reset = remove the -usr var and auto-fit instantly re-wins). */
8510
+ .twc-dt__filters {
8511
+ position: relative;
8512
+ width: var(--twc-dt-panel-w, 580px); max-width: calc(100vw - 32px);
8513
+ height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
8514
+ display: flex; flex-direction: column;
8515
+ }
8516
+ /* Rows list scrolls only once the user pins a height; Add-filter stays sticky. Safe because the filter
8517
+ Selects portal to <body> \u2014 an inline (non-portaled) menu would be clipped by this scroll box. */
8518
+ .twc-dt__frows { min-height: 0; }
8519
+ .twc-dt__filters[data-panel-sized] .twc-dt__frows { flex: 1 1 auto; overflow-y: auto; }
8520
+ .twc-dt__filters[data-panel-sized] .twc-dt__f-add { position: sticky; bottom: 0; background: var(--color-surface-raised); padding-inline-end: 20px; }
8521
+ /* #289: wrap is a safety net \u2014 a beyond-cap label or the narrow responsive panel drops the value input
8522
+ to its own line instead of clipping the remove button; no effect in the normal (capped) case. */
8523
+ .twc-dt__frow { display: flex; align-items: center; gap: 6px; padding: 5px 4px; flex-wrap: wrap; }
8524
+ /* col/op: user > measured (-fit, written by the #289 measurer) > 118px, clamped to a drag cap that
8525
+ filterFieldMaxWidth can raise. Position:relative hosts the resize handle. */
8526
+ .twc-dt__f-col { position: relative; flex: none; min-width: 118px;
8527
+ width: clamp(118px, var(--twc-dt-fcol-usr, var(--twc-dt-fcol-fit, 118px)), var(--twc-dt-fcol-cap, 360px)); }
8528
+ .twc-dt__f-op { position: relative; flex: none; min-width: 118px;
8529
+ width: clamp(118px, var(--twc-dt-fop-usr, var(--twc-dt-fop-fit, 118px)), var(--twc-dt-fop-cap, 260px)); }
8530
+ /* value: the flex shock-absorber by default; the first drag flips it to a fixed width (data-val-fixed). */
8531
+ .twc-dt__f-val { position: relative; flex: 1 1 140px; min-width: 140px; }
8532
+ .twc-dt__filters[data-val-fixed] .twc-dt__f-val { flex: 0 0 clamp(140px, var(--twc-dt-fval-usr, 240px), 640px); }
8533
+ .twc-dt__filters[data-val-fixed] .twc-dt__frm-x { margin-inline-start: auto; }
8513
8534
  .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; }
8514
8535
  .twc-dt__frm-x:hover { background: var(--color-danger-subtle); color: var(--color-danger-subtle-fg); }
8515
8536
  .twc-dt__frm-x svg { width: 15px; height: 15px; }
8537
+ /* per-field resize handle \u2014 cloned from the grid's .twc-dt__resizer; always-on baseline opacity so it's
8538
+ discoverable/hittable on touch (no hover there), with a non-color-only focus ring. */
8539
+ .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); }
8540
+ .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); }
8541
+ .twc-dt__frow:hover .twc-dt__f-rz, .twc-dt__f-rz:focus-visible, .twc-dt__f-rz[data-active="true"] { opacity: 1; }
8542
+ .twc-dt__f-rz:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--color-primary); border-radius: 2px; }
8543
+ .twc-dt__f-rz:focus-visible::after, .twc-dt__f-rz[data-active="true"]::after { width: 3px; background: var(--color-primary); }
8544
+ /* panel corner grip (2-axis) at the bottom-inline-end. */
8545
+ .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); }
8546
+ .twc-dt__pop-grip:hover, .twc-dt__pop-grip:focus-visible, .twc-dt__pop-grip[data-active="true"] { opacity: 1; }
8547
+ .twc-dt__pop-grip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
8548
+ [dir="rtl"] .twc-dt__pop-grip { cursor: nesw-resize; }
8549
+ @media (pointer: coarse) {
8550
+ .twc-dt__f-rz { width: 16px; inset-inline-end: -6px; opacity: 0.4; }
8551
+ .twc-dt__pop-grip { width: 20px; height: 20px; opacity: 0.6; }
8552
+ }
8516
8553
  .twc-dt__empty { padding: 30px 12px; text-align: center; color: var(--color-text-subtle); font-size: var(--text-sm); }
8517
8554
 
8518
8555
  /* Action cell */
@@ -8623,6 +8660,7 @@ var NUM_OPS = [
8623
8660
  ];
8624
8661
  var opsFor = (type) => type === "number" ? NUM_OPS : STR_OPS;
8625
8662
  var isMultiOp = (op) => op === "isAnyOf";
8663
+ var ALL_OP_LABELS = [...new Set([...STR_OPS, ...NUM_OPS].map((o) => o.label))];
8626
8664
  var filterTypeOf = (col) => {
8627
8665
  const ft = col && col.filterType;
8628
8666
  if (ft === "number" || ft === "string") return ft;
@@ -9064,6 +9102,8 @@ function Datatable({
9064
9102
  quickFilter,
9065
9103
  onQuickFilterChange,
9066
9104
  toolbarActions,
9105
+ resizableFilters = true,
9106
+ filterFieldMaxWidth,
9067
9107
  rowPinning = false,
9068
9108
  rowReorder = false,
9069
9109
  rowResize = false,
@@ -9212,6 +9252,8 @@ function Datatable({
9212
9252
  const [rowGrab, setRowGrab] = React51__default.useState(null);
9213
9253
  const [reorderMsg, setReorderMsg] = React51__default.useState("");
9214
9254
  const rowFocusRef = React51__default.useRef(null);
9255
+ const [fWidths, setFWidths] = React51__default.useState({});
9256
+ const [fPanelSize, setFPanelSize] = React51__default.useState(null);
9215
9257
  const serializeState = () => ({
9216
9258
  filters: filters.map(({ id, ...f }) => f),
9217
9259
  // strip the internal row id
@@ -9224,7 +9266,10 @@ function Datatable({
9224
9266
  columnVisibility: Object.fromEntries([...hidden].map((f) => [f, false])),
9225
9267
  // absent = visible
9226
9268
  columnPinning: Object.fromEntries([...pins.left.map((f) => [f, "left"]), ...pins.right.map((f) => [f, "right"])]),
9227
- density
9269
+ density,
9270
+ // #292: additive, optional — user-resized filter panel + field widths (panel-global, not per column).
9271
+ filterPanelSize: fPanelSize || void 0,
9272
+ filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null ? fWidths : void 0
9228
9273
  });
9229
9274
  const applyState = (s) => {
9230
9275
  if (!s || typeof s !== "object") return;
@@ -9249,6 +9294,16 @@ function Datatable({
9249
9294
  setPins({ left: l, right: r2 });
9250
9295
  }
9251
9296
  if (s.density === "compact" || s.density === "standard" || s.density === "comfortable") setDensity(s.density);
9297
+ if (s.filterFieldWidths && typeof s.filterFieldWidths === "object") {
9298
+ const fw = {};
9299
+ for (const k of ["col", "op", "val"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
9300
+ if (Object.keys(fw).length) setFWidths(fw);
9301
+ }
9302
+ if (s.filterPanelSize && typeof s.filterPanelSize === "object") {
9303
+ const w = typeof s.filterPanelSize.w === "number" ? clampNum(s.filterPanelSize.w, 220, fMaxW()) : void 0;
9304
+ const h = typeof s.filterPanelSize.h === "number" ? clampNum(s.filterPanelSize.h, 120, fMaxH()) : void 0;
9305
+ if (w != null || h != null) setFPanelSize({ ...w != null ? { w } : {}, ...h != null ? { h } : {} });
9306
+ }
9252
9307
  };
9253
9308
  const stateReadyRef = React51__default.useRef(false);
9254
9309
  const onStateChangeRef = React51__default.useRef(onStateChange);
@@ -9265,7 +9320,7 @@ function Datatable({
9265
9320
  }
9266
9321
  }
9267
9322
  return void 0;
9268
- }, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, stateKey]);
9323
+ }, [filters, sort, quick, pageVal, sizeVal, order, widths, hidden, pins, density, fWidths, fPanelSize, stateKey]);
9269
9324
  React51__default.useEffect(() => {
9270
9325
  let saved = null;
9271
9326
  if (stateKey) {
@@ -9295,6 +9350,8 @@ function Datatable({
9295
9350
  };
9296
9351
  }, []);
9297
9352
  const scrollRef = React51__default.useRef(null);
9353
+ const filterPanelRef = React51__default.useRef(null);
9354
+ const measureCanvas = React51__default.useRef(null);
9298
9355
  const [scrollTop, setScrollTop] = React51__default.useState(0);
9299
9356
  const [viewportH, setViewportH] = React51__default.useState(0);
9300
9357
  const [ovTip, setOvTip] = React51__default.useState(null);
@@ -10328,7 +10385,204 @@ function Datatable({
10328
10385
  ] });
10329
10386
  }
10330
10387
  const tableMinWidth = leadW + ordered.reduce((a, c) => a + widthOf(c), 0);
10331
- const filterableCols = cols.filter((c) => c.filterable);
10388
+ const filterableCols = React51__default.useMemo(() => cols.filter((c) => c.filterable), [cols]);
10389
+ React51__default.useEffect(() => {
10390
+ const el = filterPanelRef.current;
10391
+ if (panel !== "filters" || !el || typeof document === "undefined") return void 0;
10392
+ const measure = () => {
10393
+ if (!el.isConnected) return;
10394
+ const trigger = el.querySelector(".twc-sel__trigger");
10395
+ const cs = getComputedStyle(trigger || el);
10396
+ const ctx = (measureCanvas.current || (measureCanvas.current = document.createElement("canvas"))).getContext("2d");
10397
+ if (!ctx) return;
10398
+ ctx.font = cs.font || `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize}/${cs.lineHeight} ${cs.fontFamily}`;
10399
+ const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
10400
+ const chrome = pad2 + 8 + 16 + 4;
10401
+ const widest = (labels) => labels.reduce((m, l) => Math.max(m, ctx.measureText(String(l)).width), 0);
10402
+ const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 - 3 * 6 - 2 * 4));
10403
+ const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10404
+ const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
10405
+ if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
10406
+ if (Number.isFinite(opW)) el.style.setProperty("--twc-dt-fop-fit", `${opW}px`);
10407
+ };
10408
+ measure();
10409
+ document.fonts?.ready?.then?.(measure).catch?.(() => {
10410
+ });
10411
+ let ro;
10412
+ if (typeof ResizeObserver !== "undefined") {
10413
+ ro = new ResizeObserver(measure);
10414
+ ro.observe(el);
10415
+ }
10416
+ return () => ro?.disconnect();
10417
+ }, [panel, filterableCols, filters.length]);
10418
+ const F_MIN = { col: 118, op: 118, val: 140 };
10419
+ const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr" };
10420
+ const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val" };
10421
+ const F_LABEL = { col: "column", op: "operator", val: "value" };
10422
+ const fMaxW = () => typeof window === "undefined" ? 9999 : window.innerWidth - 32;
10423
+ const fMaxH = () => typeof window === "undefined" ? 9999 : window.innerHeight - 32;
10424
+ const clampNum = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
10425
+ const fLiveW = (field) => {
10426
+ const p = filterPanelRef.current, el = p && p.querySelector(F_SEL[field]);
10427
+ return el ? el.getBoundingClientRect().width : F_MIN[field];
10428
+ };
10429
+ const fCap = (field) => {
10430
+ if (field === "col") return filterFieldMaxWidth || 360;
10431
+ if (field === "op") return filterFieldMaxWidth || 260;
10432
+ const p = filterPanelRef.current;
10433
+ if (!p) return 640;
10434
+ return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 - 24, F_MIN.val, 640);
10435
+ };
10436
+ const isRtl = () => {
10437
+ const p = filterPanelRef.current;
10438
+ return p ? getComputedStyle(p).direction === "rtl" : false;
10439
+ };
10440
+ const startFieldResize = (e, field) => {
10441
+ if (e.button != null && e.button !== 0) return;
10442
+ e.preventDefault();
10443
+ e.stopPropagation();
10444
+ const panel2 = filterPanelRef.current;
10445
+ if (!panel2) return;
10446
+ const handle = e.currentTarget;
10447
+ const startX = e.clientX;
10448
+ const startW = fLiveW(field);
10449
+ const cap = fCap(field);
10450
+ const dir = isRtl() ? -1 : 1;
10451
+ handle.setAttribute("data-active", "true");
10452
+ if (field === "val") panel2.setAttribute("data-val-fixed", "");
10453
+ let last = startW;
10454
+ const onMove = (ev) => {
10455
+ last = clampNum(startW + (ev.clientX - startX) * dir, F_MIN[field], cap);
10456
+ panel2.style.setProperty(F_VAR[field], `${last}px`);
10457
+ };
10458
+ const onUp = () => {
10459
+ setFWidths((m) => ({ ...m, [field]: last }));
10460
+ handle.removeAttribute("data-active");
10461
+ window.removeEventListener("pointermove", onMove);
10462
+ window.removeEventListener("pointerup", onUp);
10463
+ };
10464
+ window.addEventListener("pointermove", onMove);
10465
+ window.addEventListener("pointerup", onUp);
10466
+ };
10467
+ const onFieldResizeKey = (e, field) => {
10468
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10469
+ e.preventDefault();
10470
+ e.stopPropagation();
10471
+ resetFWidth(field);
10472
+ return;
10473
+ }
10474
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
10475
+ e.preventDefault();
10476
+ e.stopPropagation();
10477
+ const cur = fLiveW(field), cap = fCap(field), step = e.shiftKey ? 2 : 10, dir = isRtl() ? -1 : 1;
10478
+ let next = cur;
10479
+ if (e.key === "ArrowRight") next = cur + step * dir;
10480
+ else if (e.key === "ArrowLeft") next = cur - step * dir;
10481
+ else if (e.key === "Home") next = F_MIN[field];
10482
+ else if (e.key === "End") next = cap;
10483
+ setFWidths((m) => ({ ...m, [field]: clampNum(next, F_MIN[field], cap) }));
10484
+ };
10485
+ const resetFWidth = (field) => {
10486
+ setFWidths((m) => {
10487
+ const n = { ...m };
10488
+ delete n[field];
10489
+ return n;
10490
+ });
10491
+ const p = filterPanelRef.current;
10492
+ if (p) {
10493
+ p.style.removeProperty(F_VAR[field]);
10494
+ if (field === "val") p.removeAttribute("data-val-fixed");
10495
+ }
10496
+ };
10497
+ const startPanelResize = (e) => {
10498
+ if (e.button != null && e.button !== 0) return;
10499
+ e.preventDefault();
10500
+ e.stopPropagation();
10501
+ const panel2 = filterPanelRef.current;
10502
+ if (!panel2) return;
10503
+ const grip = e.currentTarget;
10504
+ const startX = e.clientX, startY = e.clientY;
10505
+ const r2 = panel2.getBoundingClientRect();
10506
+ const startW = r2.width, startH = r2.height;
10507
+ const dir = isRtl() ? -1 : 1;
10508
+ grip.setAttribute("data-active", "true");
10509
+ panel2.setAttribute("data-panel-sized", "");
10510
+ let lw = startW, lh = startH;
10511
+ const onMove = (ev) => {
10512
+ lw = clampNum(startW + (ev.clientX - startX) * dir, 220, fMaxW());
10513
+ lh = clampNum(startH + (ev.clientY - startY), 120, fMaxH());
10514
+ panel2.style.setProperty("--twc-dt-panel-w", `${lw}px`);
10515
+ panel2.style.setProperty("--twc-dt-panel-h", `${lh}px`);
10516
+ };
10517
+ const onUp = () => {
10518
+ setFPanelSize({ w: lw, h: lh });
10519
+ grip.removeAttribute("data-active");
10520
+ window.removeEventListener("pointermove", onMove);
10521
+ window.removeEventListener("pointerup", onUp);
10522
+ };
10523
+ window.addEventListener("pointermove", onMove);
10524
+ window.addEventListener("pointerup", onUp);
10525
+ };
10526
+ const onPanelGripKey = (e) => {
10527
+ if (e.key === "Enter" || e.key === " " || e.key === "Backspace") {
10528
+ e.preventDefault();
10529
+ resetPanelSize();
10530
+ return;
10531
+ }
10532
+ if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) return;
10533
+ e.preventDefault();
10534
+ const panel2 = filterPanelRef.current;
10535
+ if (!panel2) return;
10536
+ const r2 = panel2.getBoundingClientRect();
10537
+ const step = e.shiftKey ? 4 : 20;
10538
+ const dir = isRtl() ? -1 : 1;
10539
+ let w = r2.width, h = r2.height;
10540
+ if (e.key === "ArrowRight") w += step * dir;
10541
+ else if (e.key === "ArrowLeft") w -= step * dir;
10542
+ else if (e.key === "ArrowDown") h += step;
10543
+ else if (e.key === "ArrowUp") h -= step;
10544
+ else if (e.key === "Home") {
10545
+ w = 220;
10546
+ h = 120;
10547
+ } else if (e.key === "End") {
10548
+ w = fMaxW();
10549
+ h = fMaxH();
10550
+ }
10551
+ setFPanelSize({ w: clampNum(w, 220, fMaxW()), h: clampNum(h, 120, fMaxH()) });
10552
+ };
10553
+ const resetPanelSize = () => {
10554
+ setFPanelSize(null);
10555
+ const p = filterPanelRef.current;
10556
+ if (p) {
10557
+ p.style.removeProperty("--twc-dt-panel-w");
10558
+ p.style.removeProperty("--twc-dt-panel-h");
10559
+ p.removeAttribute("data-panel-sized");
10560
+ }
10561
+ };
10562
+ const resetFilterSizes = () => {
10563
+ setFWidths({});
10564
+ setFPanelSize(null);
10565
+ const p = filterPanelRef.current;
10566
+ if (p) {
10567
+ ["--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));
10568
+ p.removeAttribute("data-val-fixed");
10569
+ p.removeAttribute("data-panel-sized");
10570
+ }
10571
+ };
10572
+ const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fPanelSize != null;
10573
+ const fieldHandle = (field, rowIdx) => resizableFilters && rowIdx === 0 ? /* @__PURE__ */ jsx(
10574
+ "span",
10575
+ {
10576
+ className: "twc-dt__f-rz",
10577
+ role: "separator",
10578
+ "aria-orientation": "vertical",
10579
+ tabIndex: 0,
10580
+ "aria-label": `Resize ${F_LABEL[field]} field (Enter to reset)`,
10581
+ onPointerDown: (e) => startFieldResize(e, field),
10582
+ onClick: (e) => e.stopPropagation(),
10583
+ onKeyDown: (e) => onFieldResizeKey(e, field)
10584
+ }
10585
+ ) : null;
10332
10586
  const orderIdxOf = (f) => {
10333
10587
  const i = order.indexOf(f);
10334
10588
  return i === -1 ? 9999 : i;
@@ -11698,78 +11952,126 @@ function Datatable({
11698
11952
  ] })
11699
11953
  ] });
11700
11954
  })() : null,
11701
- panel === "filters" && panelPos ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__pop twc-dt__filters", style: { top: panelPos.top, left: panelPos.left }, children: [
11702
- /* @__PURE__ */ jsxs("div", { className: "twc-dt__panel-head", children: [
11703
- /* @__PURE__ */ jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
11704
- filters.length ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
11705
- ] }),
11706
- filters.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-dt__empty", style: { padding: "16px 12px" }, children: "No filters applied" }) : filters.map((f) => {
11707
- const col = colByField[f.field] || cols[0];
11708
- const ops = opsFor(filterTypeOf(col));
11709
- const op = ops.find((o) => o.value === f.op) || ops[0];
11710
- return /* @__PURE__ */ jsxs("div", { className: "twc-dt__frow", children: [
11711
- /* @__PURE__ */ jsx("div", { className: "twc-dt__f-col", children: /* @__PURE__ */ jsx(
11712
- Select,
11955
+ panel === "filters" && panelPos ? /* @__PURE__ */ jsxs(
11956
+ "div",
11957
+ {
11958
+ ref: filterPanelRef,
11959
+ className: "twc-dt__pop twc-dt__filters",
11960
+ style: {
11961
+ top: panelPos.top,
11962
+ left: panelPos.left,
11963
+ "--twc-dt-fcol-usr": fWidths.col != null ? `${fWidths.col}px` : void 0,
11964
+ "--twc-dt-fop-usr": fWidths.op != null ? `${fWidths.op}px` : void 0,
11965
+ "--twc-dt-fval-usr": fWidths.val != null ? `${fWidths.val}px` : void 0,
11966
+ "--twc-dt-panel-w": fPanelSize?.w != null ? `${fPanelSize.w}px` : void 0,
11967
+ "--twc-dt-panel-h": fPanelSize?.h != null ? `${fPanelSize.h}px` : void 0,
11968
+ "--twc-dt-fcol-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0,
11969
+ "--twc-dt-fop-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0
11970
+ },
11971
+ "data-val-fixed": fWidths.val != null ? "" : void 0,
11972
+ "data-panel-sized": fPanelSize?.h != null ? "" : void 0,
11973
+ children: [
11974
+ /* @__PURE__ */ jsxs("div", { className: "twc-dt__panel-head", children: [
11975
+ /* @__PURE__ */ jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
11976
+ /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", gap: 2 }, children: [
11977
+ resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
11978
+ filters.length ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
11979
+ ] })
11980
+ ] }),
11981
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__frows", children: filters.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-dt__empty", style: { padding: "16px 12px" }, children: "No filters applied" }) : filters.map((f, i) => {
11982
+ const col = colByField[f.field] || cols[0];
11983
+ const ops = opsFor(filterTypeOf(col));
11984
+ const op = ops.find((o) => o.value === f.op) || ops[0];
11985
+ return /* @__PURE__ */ jsxs("div", { className: "twc-dt__frow", children: [
11986
+ /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-col", children: [
11987
+ /* @__PURE__ */ jsx(
11988
+ Select,
11989
+ {
11990
+ size: "sm",
11991
+ portal: true,
11992
+ value: f.field,
11993
+ options: filterableCols.map((c) => ({ value: c.field, label: c.headerName })),
11994
+ onChange: (v) => {
11995
+ const nc = colByField[v];
11996
+ setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, field: v, op: opsFor(filterTypeOf(nc))[0].value, value: "" } : x));
11997
+ }
11998
+ }
11999
+ ),
12000
+ fieldHandle("col", i)
12001
+ ] }),
12002
+ /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-op", children: [
12003
+ /* @__PURE__ */ jsx(
12004
+ Select,
12005
+ {
12006
+ size: "sm",
12007
+ portal: true,
12008
+ value: f.op,
12009
+ options: ops.map((o) => ({ value: o.value, label: o.label })),
12010
+ onChange: (v) => setFilters((arr) => arr.map((x) => {
12011
+ if (x.id !== f.id) return x;
12012
+ const nextMulti = isMultiOp(v);
12013
+ const wasArr = Array.isArray(x.value);
12014
+ return { ...x, op: v, value: nextMulti ? wasArr ? x.value : [] : wasArr ? "" : x.value };
12015
+ }))
12016
+ }
12017
+ ),
12018
+ fieldHandle("op", i)
12019
+ ] }),
12020
+ /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-val", children: [
12021
+ op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsx(
12022
+ AsyncFilterValue,
12023
+ {
12024
+ col,
12025
+ value: f.value,
12026
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
12027
+ }
12028
+ ) : /* @__PURE__ */ jsx(
12029
+ MultiSelect,
12030
+ {
12031
+ portal: true,
12032
+ placeholder: "Any of\u2026",
12033
+ value: Array.isArray(f.value) ? f.value : [],
12034
+ options: optionsForField(f.field),
12035
+ onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
12036
+ }
12037
+ ) : /* @__PURE__ */ jsx(
12038
+ Input,
12039
+ {
12040
+ size: "sm",
12041
+ type: filterTypeOf(col) === "number" ? "number" : "text",
12042
+ placeholder: "Value",
12043
+ value: f.value,
12044
+ onChange: (e) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: e.target.value } : x))
12045
+ }
12046
+ ),
12047
+ fieldHandle("val", i)
12048
+ ] }),
12049
+ /* @__PURE__ */ 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__ */ jsx(Svg, { d: I.x }) })
12050
+ ] }, f.id);
12051
+ }) }),
12052
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__f-add", style: { padding: "6px 4px 2px" }, children: /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-dt__mi", style: { color: "var(--color-primary)" }, onClick: () => addFilter(cols[0].field), children: [
12053
+ /* @__PURE__ */ jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
12054
+ " Add filter"
12055
+ ] }) }),
12056
+ resizableFilters ? /* @__PURE__ */ jsx(
12057
+ "span",
11713
12058
  {
11714
- size: "sm",
11715
- portal: true,
11716
- value: f.field,
11717
- options: filterableCols.map((c) => ({ value: c.field, label: c.headerName })),
11718
- onChange: (v) => {
11719
- const nc = colByField[v];
11720
- setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, field: v, op: opsFor(filterTypeOf(nc))[0].value, value: "" } : x));
12059
+ className: "twc-dt__pop-grip",
12060
+ role: "slider",
12061
+ tabIndex: 0,
12062
+ "aria-label": "Resize filters panel (Enter to reset)",
12063
+ "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? 580)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12064
+ onPointerDown: startPanelResize,
12065
+ onKeyDown: onPanelGripKey,
12066
+ onDoubleClick: (e) => {
12067
+ e.preventDefault();
12068
+ resetPanelSize();
11721
12069
  }
11722
12070
  }
11723
- ) }),
11724
- /* @__PURE__ */ jsx("div", { className: "twc-dt__f-op", children: /* @__PURE__ */ jsx(
11725
- Select,
11726
- {
11727
- size: "sm",
11728
- portal: true,
11729
- value: f.op,
11730
- options: ops.map((o) => ({ value: o.value, label: o.label })),
11731
- onChange: (v) => setFilters((arr) => arr.map((x) => {
11732
- if (x.id !== f.id) return x;
11733
- const nextMulti = isMultiOp(v);
11734
- const wasArr = Array.isArray(x.value);
11735
- return { ...x, op: v, value: nextMulti ? wasArr ? x.value : [] : wasArr ? "" : x.value };
11736
- }))
11737
- }
11738
- ) }),
11739
- /* @__PURE__ */ jsx("div", { className: "twc-dt__f-val", children: op.noInput ? null : op.multi ? col.loadValueOptions ? /* @__PURE__ */ jsx(
11740
- AsyncFilterValue,
11741
- {
11742
- col,
11743
- value: f.value,
11744
- onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11745
- }
11746
- ) : /* @__PURE__ */ jsx(
11747
- MultiSelect,
11748
- {
11749
- portal: true,
11750
- placeholder: "Any of\u2026",
11751
- value: Array.isArray(f.value) ? f.value : [],
11752
- options: optionsForField(f.field),
11753
- onChange: (vals) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: vals } : x))
11754
- }
11755
- ) : /* @__PURE__ */ jsx(
11756
- Input,
11757
- {
11758
- size: "sm",
11759
- type: filterTypeOf(col) === "number" ? "number" : "text",
11760
- placeholder: "Value",
11761
- value: f.value,
11762
- onChange: (e) => setFilters((arr) => arr.map((x) => x.id === f.id ? { ...x, value: e.target.value } : x))
11763
- }
11764
- ) }),
11765
- /* @__PURE__ */ 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__ */ jsx(Svg, { d: I.x }) })
11766
- ] }, f.id);
11767
- }),
11768
- /* @__PURE__ */ jsx("div", { style: { padding: "6px 4px 2px" }, children: /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-dt__mi", style: { color: "var(--color-primary)" }, onClick: () => addFilter(cols[0].field), children: [
11769
- /* @__PURE__ */ jsx(Svg, { d: I.plus, style: { color: "var(--color-primary)" } }),
11770
- " Add filter"
11771
- ] }) })
11772
- ] }) : null
12071
+ ) : null
12072
+ ]
12073
+ }
12074
+ ) : null
11773
12075
  ] });
11774
12076
  }
11775
12077
  function space(v) {
@@ -12571,7 +12873,7 @@ function DatePicker({
12571
12873
  role: "dialog",
12572
12874
  "aria-modal": "true",
12573
12875
  "aria-label": "Choose date",
12574
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
12876
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
12575
12877
  children: [
12576
12878
  /* @__PURE__ */ jsxs("div", { className: "twc-dp__head", children: [
12577
12879
  /* @__PURE__ */ 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__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) }) }),
@@ -13148,7 +13450,7 @@ function DateRangePicker({
13148
13450
  role: "dialog",
13149
13451
  "aria-modal": "true",
13150
13452
  "aria-label": "Choose date range",
13151
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
13453
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
13152
13454
  children: [
13153
13455
  presetItems.length ? /* @__PURE__ */ jsx("div", { className: "twc-drp__presets", children: presetItems.map((p, i) => /* @__PURE__ */ jsx("button", { type: "button", className: "twc-drp__preset", onClick: () => applyPreset(p), children: p.label }, i)) }) : null,
13154
13456
  /* @__PURE__ */ jsxs("div", { className: "twc-drp__cal", children: [
@@ -13677,7 +13979,7 @@ function TimePicker({
13677
13979
  role: "dialog",
13678
13980
  "aria-modal": "true",
13679
13981
  "aria-label": "Choose time",
13680
- style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-tooltip)" },
13982
+ style: { position: "fixed", left: coords.left, right: "auto", top: coords.top, bottom: coords.bottom, zIndex: "var(--z-floating)" },
13681
13983
  children: /* @__PURE__ */ jsx("div", { className: "twc-tp__cols", children: columns.map((col) => {
13682
13984
  const firstTab = col.items.find((it) => it.selected) || col.items.find((it) => !it.disabled);
13683
13985
  return /* @__PURE__ */ jsxs("div", { className: "twc-tp__col", role: "listbox", "aria-label": col.aria, "data-col": col.key, children: [