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/dist/index.d.cts CHANGED
@@ -1783,8 +1783,13 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1783
1783
  serverMode?: boolean;
1784
1784
  /** Total row count on the server (required for pagination in server mode). */
1785
1785
  rowCount?: number;
1786
- /** Server-mode callback fired (debounced) whenever the query changes. */
1786
+ /** Server-mode callback fired (debounced) whenever the query changes. The query now
1787
+ * includes `visibleColumns`/`hiddenColumns` so you can project only the shown columns. */
1787
1788
  onServerChange?: (state: DatatableQuery) => void;
1789
+ /** Fired when the built-in Columns menu shows/hides a column, with the currently visible
1790
+ * column `field`s (in column order). Lets a server-mode grid drive column projection from
1791
+ * the built-in menu instead of a duplicate control. Not fired on initial mount. */
1792
+ onColumnVisibilityChange?: (visible: string[]) => void;
1788
1793
  /** Show the Export toolbar button (split button: click = CSV, chevron = format menu). @default false */
1789
1794
  showExport?: boolean;
1790
1795
  /** Show the row-density toolbar button (cycles compact / standard / comfortable). The `density` prop
@@ -1903,6 +1908,14 @@ interface DatatableQuery {
1903
1908
  filters: DatatableFilter[];
1904
1909
  /** Quick-search text. */
1905
1910
  quickFilter: string;
1911
+ /** `field`s of the columns currently shown (in column order) — the built-in Columns menu
1912
+ * is the source of truth. Use it to fetch/project only the visible columns server-side.
1913
+ * Always populated by the grid in `onServerChange`; optional so a hand-built query passed
1914
+ * to `runDatatableQuery` (which ignores it) need not supply it. */
1915
+ visibleColumns?: string[];
1916
+ /** `field`s of the columns currently hidden via the Columns menu. Always populated in
1917
+ * `onServerChange`; optional for the same reason as `visibleColumns`. */
1918
+ hiddenColumns?: string[];
1906
1919
  }
1907
1920
 
1908
1921
  interface DatatableColumn<T = any> {
package/dist/index.d.ts CHANGED
@@ -1783,8 +1783,13 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
1783
1783
  serverMode?: boolean;
1784
1784
  /** Total row count on the server (required for pagination in server mode). */
1785
1785
  rowCount?: number;
1786
- /** Server-mode callback fired (debounced) whenever the query changes. */
1786
+ /** Server-mode callback fired (debounced) whenever the query changes. The query now
1787
+ * includes `visibleColumns`/`hiddenColumns` so you can project only the shown columns. */
1787
1788
  onServerChange?: (state: DatatableQuery) => void;
1789
+ /** Fired when the built-in Columns menu shows/hides a column, with the currently visible
1790
+ * column `field`s (in column order). Lets a server-mode grid drive column projection from
1791
+ * the built-in menu instead of a duplicate control. Not fired on initial mount. */
1792
+ onColumnVisibilityChange?: (visible: string[]) => void;
1788
1793
  /** Show the Export toolbar button (split button: click = CSV, chevron = format menu). @default false */
1789
1794
  showExport?: boolean;
1790
1795
  /** Show the row-density toolbar button (cycles compact / standard / comfortable). The `density` prop
@@ -1903,6 +1908,14 @@ interface DatatableQuery {
1903
1908
  filters: DatatableFilter[];
1904
1909
  /** Quick-search text. */
1905
1910
  quickFilter: string;
1911
+ /** `field`s of the columns currently shown (in column order) — the built-in Columns menu
1912
+ * is the source of truth. Use it to fetch/project only the visible columns server-side.
1913
+ * Always populated by the grid in `onServerChange`; optional so a hand-built query passed
1914
+ * to `runDatatableQuery` (which ignores it) need not supply it. */
1915
+ visibleColumns?: string[];
1916
+ /** `field`s of the columns currently hidden via the Columns menu. Always populated in
1917
+ * `onServerChange`; optional for the same reason as `visibleColumns`. */
1918
+ hiddenColumns?: string[];
1906
1919
  }
1907
1920
 
1908
1921
  interface DatatableColumn<T = any> {
package/dist/index.mjs CHANGED
@@ -25,7 +25,7 @@ function useScopedStyles(id, css) {
25
25
  }
26
26
  if (el.textContent !== css) el.textContent = css;
27
27
  }, [id, css, nonce]);
28
- return SUPPORTS_STYLE_HOIST && css ? React51.createElement("style", { href: id, precedence: "twc-ui", nonce }, css) : null;
28
+ return SUPPORTS_STYLE_HOIST && css ? React51.createElement("style", { href: id, precedence: "twc-ui" }, css) : null;
29
29
  }
30
30
 
31
31
  // components/TwicoProvider.jsx
@@ -1287,6 +1287,62 @@ function compileSx(obj, selector) {
1287
1287
  out += rules;
1288
1288
  return out;
1289
1289
  }
1290
+ var BOX_EDGE4 = {
1291
+ padding: ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"],
1292
+ margin: ["marginTop", "marginRight", "marginBottom", "marginLeft"],
1293
+ inset: ["top", "right", "bottom", "left"]
1294
+ };
1295
+ var BOX_EDGE2 = {
1296
+ paddingBlock: ["paddingBlockStart", "paddingBlockEnd"],
1297
+ paddingInline: ["paddingInlineStart", "paddingInlineEnd"],
1298
+ marginBlock: ["marginBlockStart", "marginBlockEnd"],
1299
+ marginInline: ["marginInlineStart", "marginInlineEnd"]
1300
+ };
1301
+ function splitShorthand(value) {
1302
+ const s = String(value).trim();
1303
+ if (!s) return [];
1304
+ const out = [];
1305
+ let depth = 0, cur = "";
1306
+ for (let i = 0; i < s.length; i++) {
1307
+ const c = s[i];
1308
+ if (c === "(") {
1309
+ depth++;
1310
+ cur += c;
1311
+ } else if (c === ")") {
1312
+ depth = Math.max(0, depth - 1);
1313
+ cur += c;
1314
+ } else if (depth === 0 && (c === " " || c === " " || c === "\n" || c === "\r")) {
1315
+ if (cur) {
1316
+ out.push(cur);
1317
+ cur = "";
1318
+ }
1319
+ } else cur += c;
1320
+ }
1321
+ if (cur) out.push(cur);
1322
+ return out;
1323
+ }
1324
+ function expandBoxShorthand(key, value) {
1325
+ const four = BOX_EDGE4[key];
1326
+ const two = BOX_EDGE2[key];
1327
+ if (!four && !two || value == null) return null;
1328
+ if (typeof value === "number") return (four || two).map((edge) => [edge, value]);
1329
+ const p = splitShorthand(value);
1330
+ if (p.length === 0) return null;
1331
+ if (two) return [[two[0], p[0]], [two[1], p.length > 1 ? p[1] : p[0]]];
1332
+ let top, right, bottom, left;
1333
+ if (p.length === 1) top = right = bottom = left = p[0];
1334
+ else if (p.length === 2) {
1335
+ top = bottom = p[0];
1336
+ right = left = p[1];
1337
+ } else if (p.length === 3) {
1338
+ top = p[0];
1339
+ right = left = p[1];
1340
+ bottom = p[2];
1341
+ } else {
1342
+ [top, right, bottom, left] = p;
1343
+ }
1344
+ return [[four[0], top], [four[1], right], [four[2], bottom], [four[3], left]];
1345
+ }
1290
1346
  function buildSx(sx, uid) {
1291
1347
  if (!isObject(sx)) return { flatStyle: void 0, css: "" };
1292
1348
  const scope = `[data-twc-sx="${uid}"]`;
@@ -1304,7 +1360,13 @@ function buildSx(sx, uid) {
1304
1360
  css += compileSx(value, resolveSelector(key, scope));
1305
1361
  }
1306
1362
  } else {
1307
- (flatStyle || (flatStyle = {}))[key] = value;
1363
+ const expanded = expandBoxShorthand(key, value);
1364
+ if (expanded) {
1365
+ flatStyle || (flatStyle = {});
1366
+ for (const [k, v] of expanded) flatStyle[k] = v;
1367
+ } else {
1368
+ (flatStyle || (flatStyle = {}))[key] = value;
1369
+ }
1308
1370
  }
1309
1371
  }
1310
1372
  return { flatStyle, css };
@@ -8678,6 +8740,7 @@ function Datatable({
8678
8740
  serverMode = false,
8679
8741
  rowCount,
8680
8742
  onServerChange,
8743
+ onColumnVisibilityChange,
8681
8744
  batchActions = [],
8682
8745
  showExport = false,
8683
8746
  showDensity = false,
@@ -8984,6 +9047,19 @@ function Datatable({
8984
9047
  }, [exportOpen]);
8985
9048
  const colByField = React51__default.useMemo(() => Object.fromEntries(cols.map((c) => [c.field, c])), [cols]);
8986
9049
  const visibleCols = cols.filter((c) => !hidden.has(c.field));
9050
+ const colFieldsKey = JSON.stringify(
9051
+ cols.filter((c) => typeof c.field === "string" && c.type !== "actions").map((c) => c.field)
9052
+ );
9053
+ const colFields = React51__default.useMemo(() => JSON.parse(colFieldsKey), [colFieldsKey]);
9054
+ const hiddenKey = JSON.stringify([...hidden].filter((f) => typeof f === "string").sort());
9055
+ const visibleColumns = React51__default.useMemo(() => {
9056
+ const h = new Set(JSON.parse(hiddenKey));
9057
+ return colFields.filter((f) => !h.has(f));
9058
+ }, [colFields, hiddenKey]);
9059
+ const hiddenColumns = React51__default.useMemo(() => {
9060
+ const h = new Set(JSON.parse(hiddenKey));
9061
+ return colFields.filter((f) => h.has(f));
9062
+ }, [colFields, hiddenKey]);
8987
9063
  const ordered = React51__default.useMemo(() => {
8988
9064
  const orderIdx = (f) => {
8989
9065
  const i = order.indexOf(f);
@@ -9326,11 +9402,27 @@ function Datatable({
9326
9402
  pageSize: sizeVal,
9327
9403
  sort,
9328
9404
  filters: filters.map(({ id, ...f }) => f),
9329
- quickFilter: quick.trim()
9405
+ quickFilter: quick.trim(),
9406
+ visibleColumns,
9407
+ hiddenColumns
9408
+ // #191: server can project only the visible columns
9330
9409
  });
9331
9410
  }, 250);
9332
9411
  return () => clearTimeout(t);
9333
- }, [serverMode, pageVal, sizeVal, sort, filters, quick]);
9412
+ }, [serverMode, pageVal, sizeVal, sort, filters, quick, visibleColumns, hiddenColumns]);
9413
+ const onColVisRef = React51__default.useRef(onColumnVisibilityChange);
9414
+ onColVisRef.current = onColumnVisibilityChange;
9415
+ const lastVisibleKey = React51__default.useRef(null);
9416
+ React51__default.useEffect(() => {
9417
+ const key = JSON.stringify(visibleColumns);
9418
+ if (lastVisibleKey.current === null) {
9419
+ lastVisibleKey.current = key;
9420
+ return;
9421
+ }
9422
+ if (lastVisibleKey.current === key) return;
9423
+ lastVisibleKey.current = key;
9424
+ onColVisRef.current?.(visibleColumns);
9425
+ }, [visibleColumns]);
9334
9426
  function cycleSort(field) {
9335
9427
  setSort((s) => !s || s.field !== field ? { field, dir: "asc" } : s.dir === "asc" ? { field, dir: "desc" } : null);
9336
9428
  }
@@ -15353,6 +15445,12 @@ var SHELL_CSS = `
15353
15445
  min-height: 60px; padding-inline: var(--space-5); border-bottom: var(--border-thin) solid var(--color-border); background: var(--color-surface); }
15354
15446
  .twc-shell__content { flex: 1 1 auto; min-height: 0; overflow: auto; }
15355
15447
  .twc-shell__content[data-padded="true"] { padding: var(--space-6) var(--space-5); }
15448
+ /* #190: <main> is the skip-link target (tabindex="-1") \u2014 a programmatic focus target,
15449
+ not tab-reachable and not an interactive control. Clicking content mouse-focuses it,
15450
+ then the first keypress flips to keyboard modality and :focus-visible would draw a UA
15451
+ outline around the ENTIRE content area. Suppress it \u2014 the skip LINK keeps its own
15452
+ visible ring (.twc-shell__skip:focus-visible) and SR focus announcement is unaffected. */
15453
+ .twc-shell__content:focus, .twc-shell__content:focus-visible { outline: none; }
15356
15454
  /* Mobile: stack the shell so the sidebar (rendered as an off-canvas overlay) no
15357
15455
  longer reserves a column \u2014 the main content spans the full width. */
15358
15456
  @media (max-width: 720px) {
@@ -15487,8 +15585,10 @@ var Container = React51__default.forwardRef(function Container2({
15487
15585
  style: {
15488
15586
  width: "100%",
15489
15587
  maxWidth: typeof max === "number" ? `${max}px` : max,
15490
- marginInline: "auto",
15491
- paddingInline: padded ? "var(--space-5)" : void 0,
15588
+ marginInlineStart: "auto",
15589
+ marginInlineEnd: "auto",
15590
+ paddingInlineStart: padded ? "var(--space-5)" : void 0,
15591
+ paddingInlineEnd: padded ? "var(--space-5)" : void 0,
15492
15592
  ...style,
15493
15593
  ...flatStyle
15494
15594
  },
@@ -15738,7 +15838,10 @@ var Heading = React51__default.forwardRef(function Heading2({
15738
15838
  className: `twc-heading ${className}`.trim(),
15739
15839
  "data-twc-sx": sxAttr,
15740
15840
  style: {
15741
- margin: 0,
15841
+ marginTop: 0,
15842
+ marginRight: 0,
15843
+ marginBottom: 0,
15844
+ marginLeft: 0,
15742
15845
  fontFamily: "var(--font-sans)",
15743
15846
  fontWeight: "var(--font-bold)",
15744
15847
  color: "var(--color-text)",
@@ -15803,7 +15906,10 @@ var Text = React51__default.forwardRef(function Text2({
15803
15906
  className: `twc-text ${className}`.trim(),
15804
15907
  "data-twc-sx": sxAttr,
15805
15908
  style: {
15806
- margin: 0,
15909
+ marginTop: 0,
15910
+ marginRight: 0,
15911
+ marginBottom: 0,
15912
+ marginLeft: 0,
15807
15913
  fontFamily: "var(--font-sans)",
15808
15914
  lineHeight: 1.6,
15809
15915
  fontSize: `var(--text-${size})`,