twico-ui 1.10.0 → 1.11.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 +22 -0
- package/dist/index.cjs +154 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.mjs +154 -19
- package/dist/index.mjs.map +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.11.1](https://github.com/EncryptionL/twico-ui/compare/v1.11.0...v1.11.1) (2026-07-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **datatable:** pointer cursor on the Columns-panel visibility toggle ([9d30343](https://github.com/EncryptionL/twico-ui/commit/9d303434f51c4a4131cf393e56f8534d53f0a7bb))
|
|
11
|
+
* **tooltip:** grow horizontally + point the arrow at the trigger ([19aa76a](https://github.com/EncryptionL/twico-ui/commit/19aa76a6ffe597d81aff00a4144182661db0971b))
|
|
12
|
+
|
|
13
|
+
# [1.11.0](https://github.com/EncryptionL/twico-ui/compare/v1.10.0...v1.11.0) (2026-07-07)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **app-shell:** suppress the focus ring on the tabindex=-1 skip target ([#190](https://github.com/EncryptionL/twico-ui/issues/190)) ([88fd874](https://github.com/EncryptionL/twico-ui/commit/88fd874d659115407492d7ab78997f5b88ee46fa))
|
|
19
|
+
* **styles:** omit CSP nonce from React 19 precedence styles ([#189](https://github.com/EncryptionL/twico-ui/issues/189)) ([c86c7e8](https://github.com/EncryptionL/twico-ui/commit/c86c7e8c09d559f1749431cc8c5075d382f6ca8f))
|
|
20
|
+
* **sx:** normalize box-model shorthands to longhands ([#188](https://github.com/EncryptionL/twico-ui/issues/188)) ([3807718](https://github.com/EncryptionL/twico-ui/commit/38077184aeb58cfe5e4a4989b8cacb10e6068475))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* **datatable:** expose column visibility to server mode ([#191](https://github.com/EncryptionL/twico-ui/issues/191)) ([1412c13](https://github.com/EncryptionL/twico-ui/commit/1412c135095f091557697ea04a9f700ee0b472cc))
|
|
26
|
+
|
|
5
27
|
# [1.10.0](https://github.com/EncryptionL/twico-ui/compare/v1.9.0...v1.10.0) (2026-07-05)
|
|
6
28
|
|
|
7
29
|
|
package/dist/index.cjs
CHANGED
|
@@ -46,7 +46,7 @@ function useScopedStyles(id, css) {
|
|
|
46
46
|
}
|
|
47
47
|
if (el.textContent !== css) el.textContent = css;
|
|
48
48
|
}, [id, css, nonce]);
|
|
49
|
-
return SUPPORTS_STYLE_HOIST && css ? React51__namespace.createElement("style", { href: id, precedence: "twc-ui"
|
|
49
|
+
return SUPPORTS_STYLE_HOIST && css ? React51__namespace.createElement("style", { href: id, precedence: "twc-ui" }, css) : null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// components/TwicoProvider.jsx
|
|
@@ -1308,6 +1308,62 @@ function compileSx(obj, selector) {
|
|
|
1308
1308
|
out += rules;
|
|
1309
1309
|
return out;
|
|
1310
1310
|
}
|
|
1311
|
+
var BOX_EDGE4 = {
|
|
1312
|
+
padding: ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"],
|
|
1313
|
+
margin: ["marginTop", "marginRight", "marginBottom", "marginLeft"],
|
|
1314
|
+
inset: ["top", "right", "bottom", "left"]
|
|
1315
|
+
};
|
|
1316
|
+
var BOX_EDGE2 = {
|
|
1317
|
+
paddingBlock: ["paddingBlockStart", "paddingBlockEnd"],
|
|
1318
|
+
paddingInline: ["paddingInlineStart", "paddingInlineEnd"],
|
|
1319
|
+
marginBlock: ["marginBlockStart", "marginBlockEnd"],
|
|
1320
|
+
marginInline: ["marginInlineStart", "marginInlineEnd"]
|
|
1321
|
+
};
|
|
1322
|
+
function splitShorthand(value) {
|
|
1323
|
+
const s = String(value).trim();
|
|
1324
|
+
if (!s) return [];
|
|
1325
|
+
const out = [];
|
|
1326
|
+
let depth = 0, cur = "";
|
|
1327
|
+
for (let i = 0; i < s.length; i++) {
|
|
1328
|
+
const c = s[i];
|
|
1329
|
+
if (c === "(") {
|
|
1330
|
+
depth++;
|
|
1331
|
+
cur += c;
|
|
1332
|
+
} else if (c === ")") {
|
|
1333
|
+
depth = Math.max(0, depth - 1);
|
|
1334
|
+
cur += c;
|
|
1335
|
+
} else if (depth === 0 && (c === " " || c === " " || c === "\n" || c === "\r")) {
|
|
1336
|
+
if (cur) {
|
|
1337
|
+
out.push(cur);
|
|
1338
|
+
cur = "";
|
|
1339
|
+
}
|
|
1340
|
+
} else cur += c;
|
|
1341
|
+
}
|
|
1342
|
+
if (cur) out.push(cur);
|
|
1343
|
+
return out;
|
|
1344
|
+
}
|
|
1345
|
+
function expandBoxShorthand(key, value) {
|
|
1346
|
+
const four = BOX_EDGE4[key];
|
|
1347
|
+
const two = BOX_EDGE2[key];
|
|
1348
|
+
if (!four && !two || value == null) return null;
|
|
1349
|
+
if (typeof value === "number") return (four || two).map((edge) => [edge, value]);
|
|
1350
|
+
const p = splitShorthand(value);
|
|
1351
|
+
if (p.length === 0) return null;
|
|
1352
|
+
if (two) return [[two[0], p[0]], [two[1], p.length > 1 ? p[1] : p[0]]];
|
|
1353
|
+
let top, right, bottom, left;
|
|
1354
|
+
if (p.length === 1) top = right = bottom = left = p[0];
|
|
1355
|
+
else if (p.length === 2) {
|
|
1356
|
+
top = bottom = p[0];
|
|
1357
|
+
right = left = p[1];
|
|
1358
|
+
} else if (p.length === 3) {
|
|
1359
|
+
top = p[0];
|
|
1360
|
+
right = left = p[1];
|
|
1361
|
+
bottom = p[2];
|
|
1362
|
+
} else {
|
|
1363
|
+
[top, right, bottom, left] = p;
|
|
1364
|
+
}
|
|
1365
|
+
return [[four[0], top], [four[1], right], [four[2], bottom], [four[3], left]];
|
|
1366
|
+
}
|
|
1311
1367
|
function buildSx(sx, uid) {
|
|
1312
1368
|
if (!isObject(sx)) return { flatStyle: void 0, css: "" };
|
|
1313
1369
|
const scope = `[data-twc-sx="${uid}"]`;
|
|
@@ -1325,7 +1381,13 @@ function buildSx(sx, uid) {
|
|
|
1325
1381
|
css += compileSx(value, resolveSelector(key, scope));
|
|
1326
1382
|
}
|
|
1327
1383
|
} else {
|
|
1328
|
-
|
|
1384
|
+
const expanded = expandBoxShorthand(key, value);
|
|
1385
|
+
if (expanded) {
|
|
1386
|
+
flatStyle || (flatStyle = {});
|
|
1387
|
+
for (const [k, v] of expanded) flatStyle[k] = v;
|
|
1388
|
+
} else {
|
|
1389
|
+
(flatStyle || (flatStyle = {}))[key] = value;
|
|
1390
|
+
}
|
|
1329
1391
|
}
|
|
1330
1392
|
}
|
|
1331
1393
|
return { flatStyle, css };
|
|
@@ -7776,7 +7838,12 @@ var TOOLTIP_CSS = `
|
|
|
7776
7838
|
padding: 6px 10px; border-radius: var(--radius-md);
|
|
7777
7839
|
background: var(--color-text); color: var(--color-surface);
|
|
7778
7840
|
font-family: var(--font-sans); font-size: var(--text-xs); font-weight: var(--font-medium);
|
|
7779
|
-
line-height: 1.3; white-space: normal;
|
|
7841
|
+
line-height: 1.3; white-space: normal;
|
|
7842
|
+
/* Grow horizontally to max-width, then wrap. max-content sizes to the content (capped by
|
|
7843
|
+
max-width) and \u2014 unlike shrink-to-fit width:auto \u2014 is NOT constrained by the space between
|
|
7844
|
+
the fixed left edge and the viewport edge, so a tooltip near the right edge no longer
|
|
7845
|
+
collapses to ~1 word per line and stretch vertically. */
|
|
7846
|
+
width: max-content; max-width: var(--_tw-maxw, 320px); box-shadow: var(--shadow-md);
|
|
7780
7847
|
opacity: 0; transform: scale(0.9); transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-spring);
|
|
7781
7848
|
}
|
|
7782
7849
|
.twc-tooltip[data-show="true"] { pointer-events: auto; }
|
|
@@ -7787,10 +7854,13 @@ var TOOLTIP_CSS = `
|
|
|
7787
7854
|
.twc-tooltip[data-place="right"] { transform-origin: left center; translate: 0 -50%; }
|
|
7788
7855
|
.twc-tooltip[data-show="true"][data-place="top"], .twc-tooltip[data-show="true"][data-place="bottom"] { transform: scale(1); }
|
|
7789
7856
|
.twc-tooltip__arrow { position: absolute; width: 7px; height: 7px; background: var(--color-text); transform: rotate(45deg); }
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
.twc-tooltip[data-place="
|
|
7857
|
+
/* Arrow tracks the trigger via --_tw-arrow-x/y (px from the tooltip edge) instead of being
|
|
7858
|
+
locked to the centre, so it still points at the trigger when the bubble is clamped near a
|
|
7859
|
+
viewport edge. Falls back to 50% before measurement / SSR. */
|
|
7860
|
+
.twc-tooltip[data-place="top"] .twc-tooltip__arrow { bottom: -3px; left: var(--_tw-arrow-x, 50%); margin-left: -3.5px; }
|
|
7861
|
+
.twc-tooltip[data-place="bottom"] .twc-tooltip__arrow { top: -3px; left: var(--_tw-arrow-x, 50%); margin-left: -3.5px; }
|
|
7862
|
+
.twc-tooltip[data-place="left"] .twc-tooltip__arrow { right: -3px; top: var(--_tw-arrow-y, 50%); margin-top: -3.5px; }
|
|
7863
|
+
.twc-tooltip[data-place="right"] .twc-tooltip__arrow { left: -3px; top: var(--_tw-arrow-y, 50%); margin-top: -3.5px; }
|
|
7794
7864
|
@media (prefers-reduced-motion: reduce) { .twc-tooltip { transition: opacity var(--duration-fast) linear; transform: none; } }
|
|
7795
7865
|
`;
|
|
7796
7866
|
function Tooltip({
|
|
@@ -7844,10 +7914,22 @@ function Tooltip({
|
|
|
7844
7914
|
else if (placement === "left" && spaceLeft < tw && spaceRight > spaceLeft) side = "right";
|
|
7845
7915
|
else if (placement === "right" && spaceRight < tw && spaceLeft > spaceRight) side = "left";
|
|
7846
7916
|
const base = { left: "auto", right: "auto", top: "auto", bottom: "auto", place: side };
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7917
|
+
const AP = 12;
|
|
7918
|
+
const arrowX = (center) => tw ? Math.round(Math.min(Math.max(cx - (center - tw / 2), AP), tw - AP)) : void 0;
|
|
7919
|
+
const arrowY = (center) => th ? Math.round(Math.min(Math.max(cy - (center - th / 2), AP), th - AP)) : void 0;
|
|
7920
|
+
if (side === "bottom") {
|
|
7921
|
+
const c = clampX(cx);
|
|
7922
|
+
setCoords({ ...base, left: c, top: Math.round(r2.bottom + gap), arrowX: arrowX(c) });
|
|
7923
|
+
} else if (side === "left") {
|
|
7924
|
+
const c = clampY(cy);
|
|
7925
|
+
setCoords({ ...base, top: c, right: Math.round(vw - r2.left + gap), arrowY: arrowY(c) });
|
|
7926
|
+
} else if (side === "right") {
|
|
7927
|
+
const c = clampY(cy);
|
|
7928
|
+
setCoords({ ...base, top: c, left: Math.round(r2.right + gap), arrowY: arrowY(c) });
|
|
7929
|
+
} else {
|
|
7930
|
+
const c = clampX(cx);
|
|
7931
|
+
setCoords({ ...base, left: c, bottom: Math.round(vh - r2.top + gap), arrowX: arrowX(c) });
|
|
7932
|
+
}
|
|
7851
7933
|
}, [placement]);
|
|
7852
7934
|
React51__namespace.default.useEffect(() => {
|
|
7853
7935
|
place();
|
|
@@ -7904,7 +7986,15 @@ function Tooltip({
|
|
|
7904
7986
|
"aria-hidden": show ? void 0 : "true",
|
|
7905
7987
|
onMouseEnter: cancelClose,
|
|
7906
7988
|
onMouseLeave: close,
|
|
7907
|
-
style: {
|
|
7989
|
+
style: {
|
|
7990
|
+
position: "fixed",
|
|
7991
|
+
left: coords.left,
|
|
7992
|
+
right: coords.right,
|
|
7993
|
+
top: coords.top,
|
|
7994
|
+
bottom: coords.bottom,
|
|
7995
|
+
"--_tw-arrow-x": coords.arrowX != null ? `${coords.arrowX}px` : void 0,
|
|
7996
|
+
"--_tw-arrow-y": coords.arrowY != null ? `${coords.arrowY}px` : void 0
|
|
7997
|
+
},
|
|
7908
7998
|
children: [
|
|
7909
7999
|
label,
|
|
7910
8000
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-tooltip__arrow", "aria-hidden": "true" })
|
|
@@ -8284,7 +8374,8 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
|
|
|
8284
8374
|
.twc-dt__col-pin:hover { background: var(--color-surface); color: var(--color-text); }
|
|
8285
8375
|
.twc-dt__col-pin[data-on="true"] { color: var(--color-primary); background: var(--color-primary-subtle); }
|
|
8286
8376
|
.twc-dt__col-pin svg { width: 14px; height: 14px; }
|
|
8287
|
-
.twc-dt__sw { width: 32px; height: 18px; border-radius: var(--radius-full); background: var(--color-border-strong); position: relative; flex: none; transition: background-color var(--duration-base); }
|
|
8377
|
+
.twc-dt__sw { width: 32px; height: 18px; border-radius: var(--radius-full); background: var(--color-border-strong); position: relative; flex: none; cursor: pointer; transition: background-color var(--duration-base); }
|
|
8378
|
+
.twc-dt__sw[aria-disabled="true"] { cursor: default; }
|
|
8288
8379
|
.twc-dt__sw[data-on="true"] { background: var(--color-primary); }
|
|
8289
8380
|
.twc-dt__sw::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: var(--radius-full); background: var(--color-primary-fg); box-shadow: var(--shadow-sm); transition: transform var(--duration-base) var(--ease-spring); }
|
|
8290
8381
|
.twc-dt__sw:focus-visible { outline: none; box-shadow: var(--ring); }
|
|
@@ -8699,6 +8790,7 @@ function Datatable({
|
|
|
8699
8790
|
serverMode = false,
|
|
8700
8791
|
rowCount,
|
|
8701
8792
|
onServerChange,
|
|
8793
|
+
onColumnVisibilityChange,
|
|
8702
8794
|
batchActions = [],
|
|
8703
8795
|
showExport = false,
|
|
8704
8796
|
showDensity = false,
|
|
@@ -9005,6 +9097,19 @@ function Datatable({
|
|
|
9005
9097
|
}, [exportOpen]);
|
|
9006
9098
|
const colByField = React51__namespace.default.useMemo(() => Object.fromEntries(cols.map((c) => [c.field, c])), [cols]);
|
|
9007
9099
|
const visibleCols = cols.filter((c) => !hidden.has(c.field));
|
|
9100
|
+
const colFieldsKey = JSON.stringify(
|
|
9101
|
+
cols.filter((c) => typeof c.field === "string" && c.type !== "actions").map((c) => c.field)
|
|
9102
|
+
);
|
|
9103
|
+
const colFields = React51__namespace.default.useMemo(() => JSON.parse(colFieldsKey), [colFieldsKey]);
|
|
9104
|
+
const hiddenKey = JSON.stringify([...hidden].filter((f) => typeof f === "string").sort());
|
|
9105
|
+
const visibleColumns = React51__namespace.default.useMemo(() => {
|
|
9106
|
+
const h = new Set(JSON.parse(hiddenKey));
|
|
9107
|
+
return colFields.filter((f) => !h.has(f));
|
|
9108
|
+
}, [colFields, hiddenKey]);
|
|
9109
|
+
const hiddenColumns = React51__namespace.default.useMemo(() => {
|
|
9110
|
+
const h = new Set(JSON.parse(hiddenKey));
|
|
9111
|
+
return colFields.filter((f) => h.has(f));
|
|
9112
|
+
}, [colFields, hiddenKey]);
|
|
9008
9113
|
const ordered = React51__namespace.default.useMemo(() => {
|
|
9009
9114
|
const orderIdx = (f) => {
|
|
9010
9115
|
const i = order.indexOf(f);
|
|
@@ -9347,11 +9452,27 @@ function Datatable({
|
|
|
9347
9452
|
pageSize: sizeVal,
|
|
9348
9453
|
sort,
|
|
9349
9454
|
filters: filters.map(({ id, ...f }) => f),
|
|
9350
|
-
quickFilter: quick.trim()
|
|
9455
|
+
quickFilter: quick.trim(),
|
|
9456
|
+
visibleColumns,
|
|
9457
|
+
hiddenColumns
|
|
9458
|
+
// #191: server can project only the visible columns
|
|
9351
9459
|
});
|
|
9352
9460
|
}, 250);
|
|
9353
9461
|
return () => clearTimeout(t);
|
|
9354
|
-
}, [serverMode, pageVal, sizeVal, sort, filters, quick]);
|
|
9462
|
+
}, [serverMode, pageVal, sizeVal, sort, filters, quick, visibleColumns, hiddenColumns]);
|
|
9463
|
+
const onColVisRef = React51__namespace.default.useRef(onColumnVisibilityChange);
|
|
9464
|
+
onColVisRef.current = onColumnVisibilityChange;
|
|
9465
|
+
const lastVisibleKey = React51__namespace.default.useRef(null);
|
|
9466
|
+
React51__namespace.default.useEffect(() => {
|
|
9467
|
+
const key = JSON.stringify(visibleColumns);
|
|
9468
|
+
if (lastVisibleKey.current === null) {
|
|
9469
|
+
lastVisibleKey.current = key;
|
|
9470
|
+
return;
|
|
9471
|
+
}
|
|
9472
|
+
if (lastVisibleKey.current === key) return;
|
|
9473
|
+
lastVisibleKey.current = key;
|
|
9474
|
+
onColVisRef.current?.(visibleColumns);
|
|
9475
|
+
}, [visibleColumns]);
|
|
9355
9476
|
function cycleSort(field) {
|
|
9356
9477
|
setSort((s) => !s || s.field !== field ? { field, dir: "asc" } : s.dir === "asc" ? { field, dir: "desc" } : null);
|
|
9357
9478
|
}
|
|
@@ -15374,6 +15495,12 @@ var SHELL_CSS = `
|
|
|
15374
15495
|
min-height: 60px; padding-inline: var(--space-5); border-bottom: var(--border-thin) solid var(--color-border); background: var(--color-surface); }
|
|
15375
15496
|
.twc-shell__content { flex: 1 1 auto; min-height: 0; overflow: auto; }
|
|
15376
15497
|
.twc-shell__content[data-padded="true"] { padding: var(--space-6) var(--space-5); }
|
|
15498
|
+
/* #190: <main> is the skip-link target (tabindex="-1") \u2014 a programmatic focus target,
|
|
15499
|
+
not tab-reachable and not an interactive control. Clicking content mouse-focuses it,
|
|
15500
|
+
then the first keypress flips to keyboard modality and :focus-visible would draw a UA
|
|
15501
|
+
outline around the ENTIRE content area. Suppress it \u2014 the skip LINK keeps its own
|
|
15502
|
+
visible ring (.twc-shell__skip:focus-visible) and SR focus announcement is unaffected. */
|
|
15503
|
+
.twc-shell__content:focus, .twc-shell__content:focus-visible { outline: none; }
|
|
15377
15504
|
/* Mobile: stack the shell so the sidebar (rendered as an off-canvas overlay) no
|
|
15378
15505
|
longer reserves a column \u2014 the main content spans the full width. */
|
|
15379
15506
|
@media (max-width: 720px) {
|
|
@@ -15508,8 +15635,10 @@ var Container = React51__namespace.default.forwardRef(function Container2({
|
|
|
15508
15635
|
style: {
|
|
15509
15636
|
width: "100%",
|
|
15510
15637
|
maxWidth: typeof max === "number" ? `${max}px` : max,
|
|
15511
|
-
|
|
15512
|
-
|
|
15638
|
+
marginInlineStart: "auto",
|
|
15639
|
+
marginInlineEnd: "auto",
|
|
15640
|
+
paddingInlineStart: padded ? "var(--space-5)" : void 0,
|
|
15641
|
+
paddingInlineEnd: padded ? "var(--space-5)" : void 0,
|
|
15513
15642
|
...style,
|
|
15514
15643
|
...flatStyle
|
|
15515
15644
|
},
|
|
@@ -15759,7 +15888,10 @@ var Heading = React51__namespace.default.forwardRef(function Heading2({
|
|
|
15759
15888
|
className: `twc-heading ${className}`.trim(),
|
|
15760
15889
|
"data-twc-sx": sxAttr,
|
|
15761
15890
|
style: {
|
|
15762
|
-
|
|
15891
|
+
marginTop: 0,
|
|
15892
|
+
marginRight: 0,
|
|
15893
|
+
marginBottom: 0,
|
|
15894
|
+
marginLeft: 0,
|
|
15763
15895
|
fontFamily: "var(--font-sans)",
|
|
15764
15896
|
fontWeight: "var(--font-bold)",
|
|
15765
15897
|
color: "var(--color-text)",
|
|
@@ -15824,7 +15956,10 @@ var Text = React51__namespace.default.forwardRef(function Text2({
|
|
|
15824
15956
|
className: `twc-text ${className}`.trim(),
|
|
15825
15957
|
"data-twc-sx": sxAttr,
|
|
15826
15958
|
style: {
|
|
15827
|
-
|
|
15959
|
+
marginTop: 0,
|
|
15960
|
+
marginRight: 0,
|
|
15961
|
+
marginBottom: 0,
|
|
15962
|
+
marginLeft: 0,
|
|
15828
15963
|
fontFamily: "var(--font-sans)",
|
|
15829
15964
|
lineHeight: 1.6,
|
|
15830
15965
|
fontSize: `var(--text-${size})`,
|