twico-ui 1.30.0 → 1.31.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 +12 -0
- package/dist/index.cjs +36 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.mjs +36 -19
- package/dist/index.mjs.map +1 -1
- package/llms.txt +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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.0](https://github.com/EncryptionL/twico-ui/compare/v1.30.0...v1.31.0) (2026-08-04)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **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)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **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)
|
|
16
|
+
|
|
5
17
|
# [1.30.0](https://github.com/EncryptionL/twico-ui/compare/v1.29.0...v1.30.0) (2026-08-04)
|
|
6
18
|
|
|
7
19
|
|
package/dist/index.cjs
CHANGED
|
@@ -8211,6 +8211,8 @@ function classifyDiff(from = [], to = [], columns = [], rowKey, opts = {}) {
|
|
|
8211
8211
|
});
|
|
8212
8212
|
return { rows, counts };
|
|
8213
8213
|
}
|
|
8214
|
+
var DT_FILTER_PANEL_W = 620;
|
|
8215
|
+
var DT_FILTER_LOGIC_W = 68;
|
|
8214
8216
|
var DT_CSS = `
|
|
8215
8217
|
.twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
|
|
8216
8218
|
border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
|
|
@@ -8633,13 +8635,11 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8633
8635
|
.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
8636
|
.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
8637
|
.twc-dt__link:hover { background: var(--color-primary-subtle); }
|
|
8636
|
-
/* #
|
|
8637
|
-
|
|
8638
|
-
.
|
|
8639
|
-
.twc-
|
|
8640
|
-
.twc-
|
|
8641
|
-
.twc-dt__flogic-btn[data-active] { background: var(--color-primary); color: var(--color-primary-fg); }
|
|
8642
|
-
.twc-dt__flogic-btn[data-active]:hover { background: var(--color-primary); }
|
|
8638
|
+
/* #330: MUI DataGrid-style per-row And/Or connector. Row 1 shows a static "Where"; the first connector
|
|
8639
|
+
row (row 2) hosts an editable And/Or Select bound to the single filterLogic; rows 3+ echo it as static
|
|
8640
|
+
text. Fixed-width leading cell so the Column/Operator/Value cells stay aligned across rows. */
|
|
8641
|
+
.twc-dt__f-logic { position: relative; flex: none; width: 68px; }
|
|
8642
|
+
.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
8643
|
|
|
8644
8644
|
/* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
|
|
8645
8645
|
cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
|
|
@@ -8649,7 +8649,7 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8649
8649
|
equal-specificity 'position: relative' defined later would clobber it, dropping the popover into normal
|
|
8650
8650
|
flow below the table. The panel stays position: fixed, which already anchors the absolutely-positioned
|
|
8651
8651
|
.twc-dt__pop-grip; per-field handles anchor to their own position: relative .twc-dt__f-* fields. */
|
|
8652
|
-
width: var(--twc-dt-panel-w,
|
|
8652
|
+
width: var(--twc-dt-panel-w, 620px); max-width: calc(100vw - 32px);
|
|
8653
8653
|
height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
|
|
8654
8654
|
display: flex; flex-direction: column;
|
|
8655
8655
|
}
|
|
@@ -9261,6 +9261,9 @@ function Datatable({
|
|
|
9261
9261
|
searchable = true,
|
|
9262
9262
|
quickFilter,
|
|
9263
9263
|
onQuickFilterChange,
|
|
9264
|
+
filterLogic: filterLogicProp,
|
|
9265
|
+
defaultFilterLogic = "and",
|
|
9266
|
+
onFilterLogicChange,
|
|
9264
9267
|
toolbarActions,
|
|
9265
9268
|
resizableFilters = true,
|
|
9266
9269
|
resizablePopovers = true,
|
|
@@ -9354,7 +9357,14 @@ function Datatable({
|
|
|
9354
9357
|
});
|
|
9355
9358
|
const [sort, setSort] = React51__namespace.default.useState(null);
|
|
9356
9359
|
const [filters, setFilters] = React51__namespace.default.useState([]);
|
|
9357
|
-
const
|
|
9360
|
+
const filterLogicControlled = filterLogicProp !== void 0;
|
|
9361
|
+
const [filterLogicInternal, setFilterLogicInternal] = React51__namespace.default.useState(defaultFilterLogic === "or" ? "or" : "and");
|
|
9362
|
+
const filterLogic = filterLogicControlled ? filterLogicProp === "or" ? "or" : "and" : filterLogicInternal;
|
|
9363
|
+
const setFilterLogic = (next) => {
|
|
9364
|
+
const v = next === "or" ? "or" : "and";
|
|
9365
|
+
if (!filterLogicControlled) setFilterLogicInternal(v);
|
|
9366
|
+
onFilterLogicChange?.(v);
|
|
9367
|
+
};
|
|
9358
9368
|
const [hidden, setHidden] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
|
|
9359
9369
|
const [showRowNum, setShowRowNum] = React51__namespace.default.useState(rowNumbers);
|
|
9360
9370
|
const [wrapped, setWrapped] = React51__namespace.default.useState(() => new Set((columns || []).filter((c) => c.wrapText).map((c) => c.field)));
|
|
@@ -9450,7 +9460,7 @@ function Datatable({
|
|
|
9450
9460
|
if (!s || typeof s !== "object") return;
|
|
9451
9461
|
const known = new Set(cols.map((c) => c.field).filter(Boolean));
|
|
9452
9462
|
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")
|
|
9463
|
+
if ((s.filterLogic === "or" || s.filterLogic === "and") && !filterLogicControlled) setFilterLogicInternal(s.filterLogic);
|
|
9454
9464
|
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
9465
|
if (typeof s.quickFilter === "string" && quickFilter === void 0) setInternalQuick(s.quickFilter);
|
|
9456
9466
|
if (typeof s.page === "number" && !pageControlled) setInternalPage(Math.max(0, Math.floor(s.page)));
|
|
@@ -10843,7 +10853,7 @@ function Datatable({
|
|
|
10843
10853
|
const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
|
|
10844
10854
|
const chrome = pad2 + 8 + 16 + 4;
|
|
10845
10855
|
const widest = (labels) => labels.reduce((m, l) => Math.max(m, ctx.measureText(String(l)).width), 0);
|
|
10846
|
-
const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 -
|
|
10856
|
+
const colCap = Math.max(118, Math.min(210, el.clientWidth - DT_FILTER_LOGIC_W - 118 - 30 - 30 - 140 - 5 * 6 - 2 * 4));
|
|
10847
10857
|
const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
|
|
10848
10858
|
const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
|
|
10849
10859
|
if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
|
|
@@ -10875,7 +10885,7 @@ function Datatable({
|
|
|
10875
10885
|
if (field === "op") return filterFieldMaxWidth || 260;
|
|
10876
10886
|
const p = filterPanelRef.current;
|
|
10877
10887
|
if (!p) return 640;
|
|
10878
|
-
return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 -
|
|
10888
|
+
return clampNum(p.clientWidth - DT_FILTER_LOGIC_W - fLiveW("col") - fLiveW("op") - 30 - 30 - 5 * 6 - 2 * 4, F_MIN.val, 640);
|
|
10879
10889
|
};
|
|
10880
10890
|
const isRtl = () => {
|
|
10881
10891
|
const p = filterPanelRef.current;
|
|
@@ -11572,7 +11582,7 @@ function Datatable({
|
|
|
11572
11582
|
} else {
|
|
11573
11583
|
setPanel("filters");
|
|
11574
11584
|
setColMenu(null);
|
|
11575
|
-
openPanel(e.currentTarget, "left",
|
|
11585
|
+
openPanel(e.currentTarget, "left", DT_FILTER_PANEL_W);
|
|
11576
11586
|
}
|
|
11577
11587
|
},
|
|
11578
11588
|
children: [
|
|
@@ -12140,7 +12150,7 @@ function Datatable({
|
|
|
12140
12150
|
closeMenu();
|
|
12141
12151
|
restoreTriggerFocus();
|
|
12142
12152
|
setPanel("filters");
|
|
12143
|
-
openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left",
|
|
12153
|
+
openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", DT_FILTER_PANEL_W);
|
|
12144
12154
|
}, children: [
|
|
12145
12155
|
/* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.filter }),
|
|
12146
12156
|
" Filter"
|
|
@@ -12574,10 +12584,6 @@ function Datatable({
|
|
|
12574
12584
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__panel-head", children: [
|
|
12575
12585
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
|
|
12576
12586
|
/* @__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
12587
|
resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
|
|
12582
12588
|
filters.length ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
|
|
12583
12589
|
] })
|
|
@@ -12587,6 +12593,17 @@ function Datatable({
|
|
|
12587
12593
|
const ops = opsFor(filterTypeOf(col));
|
|
12588
12594
|
const op = ops.find((o) => o.value === f.op) || ops[0];
|
|
12589
12595
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__frow", children: [
|
|
12596
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__f-logic", children: i === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__f-where", children: "Where" }) : i === 1 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
12597
|
+
Select,
|
|
12598
|
+
{
|
|
12599
|
+
size: "sm",
|
|
12600
|
+
portal: true,
|
|
12601
|
+
"aria-label": "Combine with the other filters",
|
|
12602
|
+
value: filterLogic,
|
|
12603
|
+
options: [{ value: "and", label: "And" }, { value: "or", label: "Or" }],
|
|
12604
|
+
onChange: (v) => setFilterLogic(v)
|
|
12605
|
+
}
|
|
12606
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__f-where", children: filterLogic === "or" ? "Or" : "And" }) }),
|
|
12590
12607
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__f-col", children: [
|
|
12591
12608
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12592
12609
|
Select,
|
|
@@ -12666,7 +12683,7 @@ function Datatable({
|
|
|
12666
12683
|
role: "slider",
|
|
12667
12684
|
tabIndex: 0,
|
|
12668
12685
|
"aria-label": "Resize filters panel (Enter to reset)",
|
|
12669
|
-
"aria-valuetext": `Width ${Math.round(fPanelSize?.w ??
|
|
12686
|
+
"aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? DT_FILTER_PANEL_W)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
|
|
12670
12687
|
onPointerDown: startPanelResize,
|
|
12671
12688
|
onKeyDown: onPanelGripKey,
|
|
12672
12689
|
onDoubleClick: (e) => {
|