twico-ui 1.30.0 → 1.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +53 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.mjs +53 -27
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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.31.1](https://github.com/EncryptionL/twico-ui/compare/v1.31.0...v1.31.1) (2026-08-04)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **datatable:** make the filter And/Or connector a resizable field (no more truncation) ([15698fd](https://github.com/EncryptionL/twico-ui/commit/15698fdee0191e178cfb0a65cb08775c25d20b91)), closes [#289](https://github.com/EncryptionL/twico-ui/issues/289) [#292](https://github.com/EncryptionL/twico-ui/issues/292)
|
|
11
|
+
|
|
12
|
+
# [1.31.0](https://github.com/EncryptionL/twico-ui/compare/v1.30.0...v1.31.0) (2026-08-04)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **datatable:** keep DT_CSS a plain literal so single-component bundles tree-shake ([9574c2b](https://github.com/EncryptionL/twico-ui/commit/9574c2b52be39bdfcd27241a381b463eb7002c11)), closes [#330](https://github.com/EncryptionL/twico-ui/issues/330)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **datatable:** MUI DataGrid-style per-row And/Or filter connector + controllable filterLogic ([3d79a1f](https://github.com/EncryptionL/twico-ui/commit/3d79a1f1c2e8f55d439e1bfeb37c0258b37cf5bc)), closes [#303](https://github.com/EncryptionL/twico-ui/issues/303) [#330](https://github.com/EncryptionL/twico-ui/issues/330)
|
|
23
|
+
|
|
5
24
|
# [1.30.0](https://github.com/EncryptionL/twico-ui/compare/v1.29.0...v1.30.0) (2026-08-04)
|
|
6
25
|
|
|
7
26
|
|
package/dist/index.cjs
CHANGED
|
@@ -8211,6 +8211,7 @@ function classifyDiff(from = [], to = [], columns = [], rowKey, opts = {}) {
|
|
|
8211
8211
|
});
|
|
8212
8212
|
return { rows, counts };
|
|
8213
8213
|
}
|
|
8214
|
+
var DT_FILTER_PANEL_W = 620;
|
|
8214
8215
|
var DT_CSS = `
|
|
8215
8216
|
.twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
|
|
8216
8217
|
border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
|
|
@@ -8633,13 +8634,14 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8633
8634
|
.twc-dt__panel-count { font-weight: 600; color: var(--color-text-muted); text-transform: none; letter-spacing: 0; margin-inline-start: 6px; font-variant-numeric: tabular-nums; }
|
|
8634
8635
|
.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); }
|
|
8635
8636
|
.twc-dt__link:hover { background: var(--color-primary-subtle); }
|
|
8636
|
-
/* #
|
|
8637
|
-
|
|
8638
|
-
.
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
.twc-
|
|
8642
|
-
|
|
8637
|
+
/* #330: MUI DataGrid-style per-row And/Or connector. Row 1 shows a static "Where"; the first connector
|
|
8638
|
+
row (row 2) hosts an editable And/Or Select bound to the single filterLogic; rows 3+ echo it as static
|
|
8639
|
+
text. Leading cell aligns the Column/Operator/Value cells across rows. It is a first-class resizable
|
|
8640
|
+
filter field like col/op: auto-fit to its content (-fit, from the #289 measurer) with a user drag tier
|
|
8641
|
+
(-usr) winning in the clamp cascade, so it no longer truncates and the width can be adjusted manually. */
|
|
8642
|
+
.twc-dt__f-logic { position: relative; flex: none; min-width: 84px;
|
|
8643
|
+
width: clamp(84px, var(--twc-dt-flogic-usr, var(--twc-dt-flogic-fit, 84px)), var(--twc-dt-flogic-cap, 180px)); }
|
|
8644
|
+
.twc-dt__f-where { display: inline-flex; align-items: center; height: 38px; padding-inline: 6px; color: var(--color-text-muted); font-size: var(--text-sm); font-weight: var(--font-medium); }
|
|
8643
8645
|
|
|
8644
8646
|
/* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
|
|
8645
8647
|
cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
|
|
@@ -8649,7 +8651,7 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8649
8651
|
equal-specificity 'position: relative' defined later would clobber it, dropping the popover into normal
|
|
8650
8652
|
flow below the table. The panel stays position: fixed, which already anchors the absolutely-positioned
|
|
8651
8653
|
.twc-dt__pop-grip; per-field handles anchor to their own position: relative .twc-dt__f-* fields. */
|
|
8652
|
-
width: var(--twc-dt-panel-w,
|
|
8654
|
+
width: var(--twc-dt-panel-w, 620px); max-width: calc(100vw - 32px);
|
|
8653
8655
|
height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
|
|
8654
8656
|
display: flex; flex-direction: column;
|
|
8655
8657
|
}
|
|
@@ -9261,6 +9263,9 @@ function Datatable({
|
|
|
9261
9263
|
searchable = true,
|
|
9262
9264
|
quickFilter,
|
|
9263
9265
|
onQuickFilterChange,
|
|
9266
|
+
filterLogic: filterLogicProp,
|
|
9267
|
+
defaultFilterLogic = "and",
|
|
9268
|
+
onFilterLogicChange,
|
|
9264
9269
|
toolbarActions,
|
|
9265
9270
|
resizableFilters = true,
|
|
9266
9271
|
resizablePopovers = true,
|
|
@@ -9354,7 +9359,14 @@ function Datatable({
|
|
|
9354
9359
|
});
|
|
9355
9360
|
const [sort, setSort] = React51__namespace.default.useState(null);
|
|
9356
9361
|
const [filters, setFilters] = React51__namespace.default.useState([]);
|
|
9357
|
-
const
|
|
9362
|
+
const filterLogicControlled = filterLogicProp !== void 0;
|
|
9363
|
+
const [filterLogicInternal, setFilterLogicInternal] = React51__namespace.default.useState(defaultFilterLogic === "or" ? "or" : "and");
|
|
9364
|
+
const filterLogic = filterLogicControlled ? filterLogicProp === "or" ? "or" : "and" : filterLogicInternal;
|
|
9365
|
+
const setFilterLogic = (next) => {
|
|
9366
|
+
const v = next === "or" ? "or" : "and";
|
|
9367
|
+
if (!filterLogicControlled) setFilterLogicInternal(v);
|
|
9368
|
+
onFilterLogicChange?.(v);
|
|
9369
|
+
};
|
|
9358
9370
|
const [hidden, setHidden] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
|
|
9359
9371
|
const [showRowNum, setShowRowNum] = React51__namespace.default.useState(rowNumbers);
|
|
9360
9372
|
const [wrapped, setWrapped] = React51__namespace.default.useState(() => new Set((columns || []).filter((c) => c.wrapText).map((c) => c.field)));
|
|
@@ -9442,7 +9454,7 @@ function Datatable({
|
|
|
9442
9454
|
density,
|
|
9443
9455
|
// #292: additive, optional — user-resized filter panel + field widths (panel-global, not per column).
|
|
9444
9456
|
filterPanelSize: fPanelSize || void 0,
|
|
9445
|
-
filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null ? fWidths : void 0,
|
|
9457
|
+
filterFieldWidths: fWidths.col != null || fWidths.op != null || fWidths.val != null || fWidths.logic != null ? fWidths : void 0,
|
|
9446
9458
|
// #304: additive, optional — user-resized sizes of the other toolbar popovers, keyed by popover id.
|
|
9447
9459
|
popoverSizes: Object.keys(popSizes).length ? popSizes : void 0
|
|
9448
9460
|
});
|
|
@@ -9450,7 +9462,7 @@ function Datatable({
|
|
|
9450
9462
|
if (!s || typeof s !== "object") return;
|
|
9451
9463
|
const known = new Set(cols.map((c) => c.field).filter(Boolean));
|
|
9452
9464
|
if (Array.isArray(s.filters)) setFilters(s.filters.filter((f) => f && known.has(f.field)).map((f, i) => ({ id: Date.now() + Math.random() + i, field: f.field, op: f.op, value: f.value })));
|
|
9453
|
-
if (s.filterLogic === "or" || s.filterLogic === "and")
|
|
9465
|
+
if ((s.filterLogic === "or" || s.filterLogic === "and") && !filterLogicControlled) setFilterLogicInternal(s.filterLogic);
|
|
9454
9466
|
if ("sort" in s) setSort(s.sort && known.has(s.sort.field) ? { field: s.sort.field, dir: s.sort.dir === "desc" ? "desc" : "asc" } : null);
|
|
9455
9467
|
if (typeof s.quickFilter === "string" && quickFilter === void 0) setInternalQuick(s.quickFilter);
|
|
9456
9468
|
if (typeof s.page === "number" && !pageControlled) setInternalPage(Math.max(0, Math.floor(s.page)));
|
|
@@ -9472,7 +9484,7 @@ function Datatable({
|
|
|
9472
9484
|
if (s.density === "compact" || s.density === "standard" || s.density === "comfortable") setDensity(s.density);
|
|
9473
9485
|
if (s.filterFieldWidths && typeof s.filterFieldWidths === "object") {
|
|
9474
9486
|
const fw = {};
|
|
9475
|
-
for (const k of ["col", "op", "val"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
|
|
9487
|
+
for (const k of ["col", "op", "val", "logic"]) if (typeof s.filterFieldWidths[k] === "number") fw[k] = s.filterFieldWidths[k];
|
|
9476
9488
|
if (Object.keys(fw).length) setFWidths(fw);
|
|
9477
9489
|
}
|
|
9478
9490
|
if (s.filterPanelSize && typeof s.filterPanelSize === "object") {
|
|
@@ -10843,9 +10855,11 @@ function Datatable({
|
|
|
10843
10855
|
const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
|
|
10844
10856
|
const chrome = pad2 + 8 + 16 + 4;
|
|
10845
10857
|
const widest = (labels) => labels.reduce((m, l) => Math.max(m, ctx.measureText(String(l)).width), 0);
|
|
10846
|
-
const
|
|
10858
|
+
const logicW = Math.min(180, Math.max(84, Math.ceil(widest(["And", "Or"]) + chrome)));
|
|
10859
|
+
const colCap = Math.max(118, Math.min(210, el.clientWidth - logicW - 118 - 30 - 30 - 140 - 5 * 6 - 2 * 4));
|
|
10847
10860
|
const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
|
|
10848
10861
|
const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
|
|
10862
|
+
if (Number.isFinite(logicW)) el.style.setProperty("--twc-dt-flogic-fit", `${logicW}px`);
|
|
10849
10863
|
if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
|
|
10850
10864
|
if (Number.isFinite(opW)) el.style.setProperty("--twc-dt-fop-fit", `${opW}px`);
|
|
10851
10865
|
};
|
|
@@ -10859,10 +10873,10 @@ function Datatable({
|
|
|
10859
10873
|
}
|
|
10860
10874
|
return () => ro?.disconnect();
|
|
10861
10875
|
}, [panel, filterableCols, filters.length]);
|
|
10862
|
-
const F_MIN = { col: 118, op: 118, val: 140 };
|
|
10863
|
-
const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr" };
|
|
10864
|
-
const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val" };
|
|
10865
|
-
const F_LABEL = { col: "column", op: "operator", val: "value" };
|
|
10876
|
+
const F_MIN = { col: 118, op: 118, val: 140, logic: 84 };
|
|
10877
|
+
const F_VAR = { col: "--twc-dt-fcol-usr", op: "--twc-dt-fop-usr", val: "--twc-dt-fval-usr", logic: "--twc-dt-flogic-usr" };
|
|
10878
|
+
const F_SEL = { col: ".twc-dt__f-col", op: ".twc-dt__f-op", val: ".twc-dt__f-val", logic: ".twc-dt__f-logic" };
|
|
10879
|
+
const F_LABEL = { col: "column", op: "operator", val: "value", logic: "connector" };
|
|
10866
10880
|
const fMaxW = () => typeof window === "undefined" ? 9999 : window.innerWidth - 32;
|
|
10867
10881
|
const fMaxH = () => typeof window === "undefined" ? 9999 : window.innerHeight - 32;
|
|
10868
10882
|
const clampNum = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
|
|
@@ -10871,11 +10885,12 @@ function Datatable({
|
|
|
10871
10885
|
return el ? el.getBoundingClientRect().width : F_MIN[field];
|
|
10872
10886
|
};
|
|
10873
10887
|
const fCap = (field) => {
|
|
10888
|
+
if (field === "logic") return 180;
|
|
10874
10889
|
if (field === "col") return filterFieldMaxWidth || 360;
|
|
10875
10890
|
if (field === "op") return filterFieldMaxWidth || 260;
|
|
10876
10891
|
const p = filterPanelRef.current;
|
|
10877
10892
|
if (!p) return 640;
|
|
10878
|
-
return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 -
|
|
10893
|
+
return clampNum(p.clientWidth - fLiveW("logic") - fLiveW("col") - fLiveW("op") - 30 - 30 - 5 * 6 - 2 * 4, F_MIN.val, 640);
|
|
10879
10894
|
};
|
|
10880
10895
|
const isRtl = () => {
|
|
10881
10896
|
const p = filterPanelRef.current;
|
|
@@ -11008,12 +11023,12 @@ function Datatable({
|
|
|
11008
11023
|
setFPanelSize(null);
|
|
11009
11024
|
const p = filterPanelRef.current;
|
|
11010
11025
|
if (p) {
|
|
11011
|
-
["--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));
|
|
11026
|
+
["--twc-dt-fcol-usr", "--twc-dt-fop-usr", "--twc-dt-fval-usr", "--twc-dt-flogic-usr", "--twc-dt-panel-w", "--twc-dt-panel-h"].forEach((v) => p.style.removeProperty(v));
|
|
11012
11027
|
p.removeAttribute("data-val-fixed");
|
|
11013
11028
|
p.removeAttribute("data-panel-sized");
|
|
11014
11029
|
}
|
|
11015
11030
|
};
|
|
11016
|
-
const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fPanelSize != null;
|
|
11031
|
+
const hasFilterSizeOverride = fWidths.col != null || fWidths.op != null || fWidths.val != null || fWidths.logic != null || fPanelSize != null;
|
|
11017
11032
|
const POP_MIN_W = { columns: 240, agg: 240, pivot: 260, batchedit: 260 };
|
|
11018
11033
|
const popMinW = (id) => POP_MIN_W[id] || 200;
|
|
11019
11034
|
const clearPop = (id, node) => {
|
|
@@ -11572,7 +11587,7 @@ function Datatable({
|
|
|
11572
11587
|
} else {
|
|
11573
11588
|
setPanel("filters");
|
|
11574
11589
|
setColMenu(null);
|
|
11575
|
-
openPanel(e.currentTarget, "left",
|
|
11590
|
+
openPanel(e.currentTarget, "left", DT_FILTER_PANEL_W);
|
|
11576
11591
|
}
|
|
11577
11592
|
},
|
|
11578
11593
|
children: [
|
|
@@ -12140,7 +12155,7 @@ function Datatable({
|
|
|
12140
12155
|
closeMenu();
|
|
12141
12156
|
restoreTriggerFocus();
|
|
12142
12157
|
setPanel("filters");
|
|
12143
|
-
openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left",
|
|
12158
|
+
openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", DT_FILTER_PANEL_W);
|
|
12144
12159
|
}, children: [
|
|
12145
12160
|
/* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.filter }),
|
|
12146
12161
|
" Filter"
|
|
@@ -12563,6 +12578,7 @@ function Datatable({
|
|
|
12563
12578
|
"--twc-dt-fcol-usr": fWidths.col != null ? `${fWidths.col}px` : void 0,
|
|
12564
12579
|
"--twc-dt-fop-usr": fWidths.op != null ? `${fWidths.op}px` : void 0,
|
|
12565
12580
|
"--twc-dt-fval-usr": fWidths.val != null ? `${fWidths.val}px` : void 0,
|
|
12581
|
+
"--twc-dt-flogic-usr": fWidths.logic != null ? `${fWidths.logic}px` : void 0,
|
|
12566
12582
|
"--twc-dt-panel-w": fPanelSize?.w != null ? `${fPanelSize.w}px` : void 0,
|
|
12567
12583
|
"--twc-dt-panel-h": fPanelSize?.h != null ? `${fPanelSize.h}px` : void 0,
|
|
12568
12584
|
"--twc-dt-fcol-cap": filterFieldMaxWidth ? `${filterFieldMaxWidth}px` : void 0,
|
|
@@ -12574,10 +12590,6 @@ function Datatable({
|
|
|
12574
12590
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
|
|
12575
12591
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
|
|
12576
12592
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
|
|
12577
|
-
filters.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-dt__flogic", role: "group", "aria-label": "Combine filters with", children: [
|
|
12578
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "and" || void 0, "aria-pressed": filterLogic === "and", onClick: () => setFilterLogic("and"), children: "AND" }),
|
|
12579
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "or" || void 0, "aria-pressed": filterLogic === "or", onClick: () => setFilterLogic("or"), children: "OR" })
|
|
12580
|
-
] }) : null,
|
|
12581
12593
|
resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
|
|
12582
12594
|
filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
|
|
12583
12595
|
] })
|
|
@@ -12587,6 +12599,20 @@ function Datatable({
|
|
|
12587
12599
|
const ops = opsFor(filterTypeOf(col));
|
|
12588
12600
|
const op = ops.find((o) => o.value === f.op) || ops[0];
|
|
12589
12601
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__frow", children: [
|
|
12602
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-logic", children: [
|
|
12603
|
+
i === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__f-where", children: "Where" }) : i === 1 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
12604
|
+
Select,
|
|
12605
|
+
{
|
|
12606
|
+
size: "sm",
|
|
12607
|
+
portal: true,
|
|
12608
|
+
"aria-label": "Combine with the other filters",
|
|
12609
|
+
value: filterLogic,
|
|
12610
|
+
options: [{ value: "and", label: "And" }, { value: "or", label: "Or" }],
|
|
12611
|
+
onChange: (v) => setFilterLogic(v)
|
|
12612
|
+
}
|
|
12613
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__f-where", children: filterLogic === "or" ? "Or" : "And" }),
|
|
12614
|
+
fieldHandle("logic", i)
|
|
12615
|
+
] }),
|
|
12590
12616
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-col", children: [
|
|
12591
12617
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12592
12618
|
Select,
|
|
@@ -12666,7 +12692,7 @@ function Datatable({
|
|
|
12666
12692
|
role: "slider",
|
|
12667
12693
|
tabIndex: 0,
|
|
12668
12694
|
"aria-label": "Resize filters panel (Enter to reset)",
|
|
12669
|
-
"aria-valuetext": `Width ${Math.round(fPanelSize?.w ??
|
|
12695
|
+
"aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? DT_FILTER_PANEL_W)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
|
|
12670
12696
|
onPointerDown: startPanelResize,
|
|
12671
12697
|
onKeyDown: onPanelGripKey,
|
|
12672
12698
|
onDoubleClick: (e) => {
|