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 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", nonce }, css) : null;
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
- (flatStyle || (flatStyle = {}))[key] = value;
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
- marginInline: "auto",
15512
- paddingInline: padded ? "var(--space-5)" : void 0,
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
- margin: 0,
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
- margin: 0,
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})`,