twico-ui 1.10.0 → 1.11.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 +14 -0
- package/dist/index.cjs +114 -8
- 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 +114 -8
- package/dist/index.mjs.map +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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.0](https://github.com/EncryptionL/twico-ui/compare/v1.10.0...v1.11.0) (2026-07-07)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **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))
|
|
11
|
+
* **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))
|
|
12
|
+
* **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))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **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))
|
|
18
|
+
|
|
5
19
|
# [1.10.0](https://github.com/EncryptionL/twico-ui/compare/v1.9.0...v1.10.0) (2026-07-05)
|
|
6
20
|
|
|
7
21
|
|
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 };
|
|
@@ -8699,6 +8761,7 @@ function Datatable({
|
|
|
8699
8761
|
serverMode = false,
|
|
8700
8762
|
rowCount,
|
|
8701
8763
|
onServerChange,
|
|
8764
|
+
onColumnVisibilityChange,
|
|
8702
8765
|
batchActions = [],
|
|
8703
8766
|
showExport = false,
|
|
8704
8767
|
showDensity = false,
|
|
@@ -9005,6 +9068,19 @@ function Datatable({
|
|
|
9005
9068
|
}, [exportOpen]);
|
|
9006
9069
|
const colByField = React51__namespace.default.useMemo(() => Object.fromEntries(cols.map((c) => [c.field, c])), [cols]);
|
|
9007
9070
|
const visibleCols = cols.filter((c) => !hidden.has(c.field));
|
|
9071
|
+
const colFieldsKey = JSON.stringify(
|
|
9072
|
+
cols.filter((c) => typeof c.field === "string" && c.type !== "actions").map((c) => c.field)
|
|
9073
|
+
);
|
|
9074
|
+
const colFields = React51__namespace.default.useMemo(() => JSON.parse(colFieldsKey), [colFieldsKey]);
|
|
9075
|
+
const hiddenKey = JSON.stringify([...hidden].filter((f) => typeof f === "string").sort());
|
|
9076
|
+
const visibleColumns = React51__namespace.default.useMemo(() => {
|
|
9077
|
+
const h = new Set(JSON.parse(hiddenKey));
|
|
9078
|
+
return colFields.filter((f) => !h.has(f));
|
|
9079
|
+
}, [colFields, hiddenKey]);
|
|
9080
|
+
const hiddenColumns = React51__namespace.default.useMemo(() => {
|
|
9081
|
+
const h = new Set(JSON.parse(hiddenKey));
|
|
9082
|
+
return colFields.filter((f) => h.has(f));
|
|
9083
|
+
}, [colFields, hiddenKey]);
|
|
9008
9084
|
const ordered = React51__namespace.default.useMemo(() => {
|
|
9009
9085
|
const orderIdx = (f) => {
|
|
9010
9086
|
const i = order.indexOf(f);
|
|
@@ -9347,11 +9423,27 @@ function Datatable({
|
|
|
9347
9423
|
pageSize: sizeVal,
|
|
9348
9424
|
sort,
|
|
9349
9425
|
filters: filters.map(({ id, ...f }) => f),
|
|
9350
|
-
quickFilter: quick.trim()
|
|
9426
|
+
quickFilter: quick.trim(),
|
|
9427
|
+
visibleColumns,
|
|
9428
|
+
hiddenColumns
|
|
9429
|
+
// #191: server can project only the visible columns
|
|
9351
9430
|
});
|
|
9352
9431
|
}, 250);
|
|
9353
9432
|
return () => clearTimeout(t);
|
|
9354
|
-
}, [serverMode, pageVal, sizeVal, sort, filters, quick]);
|
|
9433
|
+
}, [serverMode, pageVal, sizeVal, sort, filters, quick, visibleColumns, hiddenColumns]);
|
|
9434
|
+
const onColVisRef = React51__namespace.default.useRef(onColumnVisibilityChange);
|
|
9435
|
+
onColVisRef.current = onColumnVisibilityChange;
|
|
9436
|
+
const lastVisibleKey = React51__namespace.default.useRef(null);
|
|
9437
|
+
React51__namespace.default.useEffect(() => {
|
|
9438
|
+
const key = JSON.stringify(visibleColumns);
|
|
9439
|
+
if (lastVisibleKey.current === null) {
|
|
9440
|
+
lastVisibleKey.current = key;
|
|
9441
|
+
return;
|
|
9442
|
+
}
|
|
9443
|
+
if (lastVisibleKey.current === key) return;
|
|
9444
|
+
lastVisibleKey.current = key;
|
|
9445
|
+
onColVisRef.current?.(visibleColumns);
|
|
9446
|
+
}, [visibleColumns]);
|
|
9355
9447
|
function cycleSort(field) {
|
|
9356
9448
|
setSort((s) => !s || s.field !== field ? { field, dir: "asc" } : s.dir === "asc" ? { field, dir: "desc" } : null);
|
|
9357
9449
|
}
|
|
@@ -15374,6 +15466,12 @@ var SHELL_CSS = `
|
|
|
15374
15466
|
min-height: 60px; padding-inline: var(--space-5); border-bottom: var(--border-thin) solid var(--color-border); background: var(--color-surface); }
|
|
15375
15467
|
.twc-shell__content { flex: 1 1 auto; min-height: 0; overflow: auto; }
|
|
15376
15468
|
.twc-shell__content[data-padded="true"] { padding: var(--space-6) var(--space-5); }
|
|
15469
|
+
/* #190: <main> is the skip-link target (tabindex="-1") \u2014 a programmatic focus target,
|
|
15470
|
+
not tab-reachable and not an interactive control. Clicking content mouse-focuses it,
|
|
15471
|
+
then the first keypress flips to keyboard modality and :focus-visible would draw a UA
|
|
15472
|
+
outline around the ENTIRE content area. Suppress it \u2014 the skip LINK keeps its own
|
|
15473
|
+
visible ring (.twc-shell__skip:focus-visible) and SR focus announcement is unaffected. */
|
|
15474
|
+
.twc-shell__content:focus, .twc-shell__content:focus-visible { outline: none; }
|
|
15377
15475
|
/* Mobile: stack the shell so the sidebar (rendered as an off-canvas overlay) no
|
|
15378
15476
|
longer reserves a column \u2014 the main content spans the full width. */
|
|
15379
15477
|
@media (max-width: 720px) {
|
|
@@ -15508,8 +15606,10 @@ var Container = React51__namespace.default.forwardRef(function Container2({
|
|
|
15508
15606
|
style: {
|
|
15509
15607
|
width: "100%",
|
|
15510
15608
|
maxWidth: typeof max === "number" ? `${max}px` : max,
|
|
15511
|
-
|
|
15512
|
-
|
|
15609
|
+
marginInlineStart: "auto",
|
|
15610
|
+
marginInlineEnd: "auto",
|
|
15611
|
+
paddingInlineStart: padded ? "var(--space-5)" : void 0,
|
|
15612
|
+
paddingInlineEnd: padded ? "var(--space-5)" : void 0,
|
|
15513
15613
|
...style,
|
|
15514
15614
|
...flatStyle
|
|
15515
15615
|
},
|
|
@@ -15759,7 +15859,10 @@ var Heading = React51__namespace.default.forwardRef(function Heading2({
|
|
|
15759
15859
|
className: `twc-heading ${className}`.trim(),
|
|
15760
15860
|
"data-twc-sx": sxAttr,
|
|
15761
15861
|
style: {
|
|
15762
|
-
|
|
15862
|
+
marginTop: 0,
|
|
15863
|
+
marginRight: 0,
|
|
15864
|
+
marginBottom: 0,
|
|
15865
|
+
marginLeft: 0,
|
|
15763
15866
|
fontFamily: "var(--font-sans)",
|
|
15764
15867
|
fontWeight: "var(--font-bold)",
|
|
15765
15868
|
color: "var(--color-text)",
|
|
@@ -15824,7 +15927,10 @@ var Text = React51__namespace.default.forwardRef(function Text2({
|
|
|
15824
15927
|
className: `twc-text ${className}`.trim(),
|
|
15825
15928
|
"data-twc-sx": sxAttr,
|
|
15826
15929
|
style: {
|
|
15827
|
-
|
|
15930
|
+
marginTop: 0,
|
|
15931
|
+
marginRight: 0,
|
|
15932
|
+
marginBottom: 0,
|
|
15933
|
+
marginLeft: 0,
|
|
15828
15934
|
fontFamily: "var(--font-sans)",
|
|
15829
15935
|
lineHeight: 1.6,
|
|
15830
15936
|
fontSize: `var(--text-${size})`,
|