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