@ai-matrx/design-system 0.56.2 → 0.57.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
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.57.0
4
+
5
+ - **One actions column.** A table that declares its own `customActions` column no longer also
6
+ draws the built-in row-actions column just to hold the row-copy icon: the copy rides inline at
7
+ the start of the first visible custom column. Before, records-ui's merged grid showed two
8
+ actions columns, and the second one, 225 px wide with only the copy icon in it, pushed the
9
+ grid's own Delete off the right edge. The built-in column is still drawn when the table has
10
+ standard icon `rowActions` or a window button, and it still holds the copy whenever no custom
11
+ column is visible. Under `fitToWidth` a custom column now takes its content width (its widest
12
+ row strip or its header), not a share of the space, and a custom strip gets compact rows'
13
+ 24 px buttons. With no built-in column drawn, a custom column that ends the row takes the
14
+ built-in column's pin (sticky right from `sm:` up, `data-matrx-table-pinned-actions`), and the
15
+ sideways scroll control sits clear of it. Guards: `TableIconActions.test.tsx` and `MatrxDataTable.layout-look.test.tsx`
16
+ (red on 0.56.2).
17
+
18
+ ### Consumer action
19
+
20
+ - None. A consumer that relied on a copy-only built-in column beside its own custom column sees
21
+ that column go away; its copy is in the custom column.
22
+
3
23
  ## 0.56.2
4
24
 
5
25
  - **`BottomSheetHeader`'s Back button has a name.** It was an unnamed icon button (screen
@@ -1239,7 +1259,7 @@ on the server; drop any app-side copy of the palette.
1239
1259
 
1240
1260
  ## 0.38.0 — 2026-09-23
1241
1261
 
1242
- **On desktop a Dialog is a window, not a wall.** (register ARE-006, `common-docs/projects/ai-reachable-everywhere/REGISTER.md`.)
1262
+ **On desktop a Dialog is a window, not a wall.** (register ARE-006, `common-docs/systems/platform/ui-shell/projects/ai-reachable-everywhere/REGISTER.md`.)
1243
1263
 
1244
1264
  Arman, on a blocking settings dialog: *"none of the built-in page ai integrations are accessible because the page is totally blocked."* Unless the caller passes `modal`, a desktop `Dialog` no longer blocks the page: no overlay, and the page, its menus and every AI control beside it stay usable. It behaves like a light window:
1245
1265
 
package/README.md CHANGED
@@ -242,4 +242,8 @@ button event for callers that need modifier keys. A menu, editor or other React
242
242
  be declared in its own column: `{ id: "workflow", header: "Workflow", customActions: (row,
243
243
  controls) => <WorkflowMenu row={row} controls={controls} /> }`. Custom columns carry live
244
244
  record controls, never participate in sort/filter/copy/spreadsheet data, and are not draggable
245
- (their headers may contain controls). Hidden custom columns stay hidden in mobile cards too.
245
+ (their headers may contain controls). Hidden custom columns stay hidden in mobile cards too. A table with a visible custom column draws ONE actions column: the row copy rides inline at the
246
+ start of the first visible custom column, and the built-in row-actions column is drawn only for
247
+ standard icon `rowActions` or the window button. Under `fitToWidth` a custom column is as wide as
248
+ its widest row strip or its header, never a scaled share; when it ends the row it is pinned right
249
+ like the built-in column.
@@ -52988,7 +52988,7 @@ function MatrxDataTableCore(coreProps) {
52988
52988
  if (!el) return;
52989
52989
  hintScrollLeft.current = el.scrollLeft;
52990
52990
  const hasRows = Boolean(el.querySelector("tbody tr[data-row-id]"));
52991
- const actions = el.querySelector("th[data-matrx-table-actions-column]");
52991
+ const actions = el.querySelector("th[data-matrx-table-actions-column], th[data-matrx-table-pinned-actions]");
52992
52992
  setScrollHintRight(hasRows && el.scrollLeft + el.clientWidth < el.scrollWidth - 2);
52993
52993
  setScrollHintLeft(hasRows && el.scrollLeft > 2);
52994
52994
  setScrollHintRightInset(actions ? Math.ceil(actions.getBoundingClientRect().width) + 6 : 6);
@@ -53308,13 +53308,24 @@ function MatrxDataTableCore(coreProps) {
53308
53308
  ...strips.map((strip) => Math.ceil(strip.scrollWidth) + 16),
53309
53309
  actionsHeader ? Math.ceil(actionsHeader.scrollWidth) : 0
53310
53310
  );
53311
+ const custom = {};
53312
+ for (const strip of Array.from(el.querySelectorAll("[data-matrx-table-custom-actions]"))) {
53313
+ const id = strip.closest("td")?.getAttribute("data-matrx-table-column-id");
53314
+ if (id) custom[id] = Math.max(custom[id] ?? 0, Math.ceil(strip.scrollWidth) + 16);
53315
+ }
53316
+ for (const id of Object.keys(custom)) {
53317
+ const header = Array.from(el.querySelectorAll("thead th[data-matrx-table-column-id]")).find((th) => th.getAttribute("data-matrx-table-column-id") === id);
53318
+ const inner = header?.firstElementChild;
53319
+ if (inner) custom[id] = Math.max(custom[id], Math.ceil(inner.scrollWidth) + 16);
53320
+ }
53311
53321
  const table2 = el.querySelector(":scope > table");
53312
53322
  const spill = table2 && table2.hasAttribute("data-matrx-table-fit") ? Math.max(0, el.scrollWidth - el.clientWidth) : 0;
53313
53323
  setFitSpace((now) => {
53314
53324
  const sameWidth = now !== null && now.width === el.clientWidth;
53315
53325
  const slack = sameWidth ? now.slack + spill : spill;
53316
- const next = { width: el.clientWidth, actions, slack };
53317
- return now && now.width === next.width && now.actions === next.actions && now.slack === next.slack ? now : next;
53326
+ const next = { width: el.clientWidth, actions, slack, custom };
53327
+ const sameCustom = now !== null && Object.keys(now.custom).length === Object.keys(custom).length && Object.entries(custom).every(([id, w]) => now.custom[id] === w);
53328
+ return now && sameCustom && now.width === next.width && now.actions === next.actions && now.slack === next.slack ? now : next;
53318
53329
  });
53319
53330
  };
53320
53331
  read2();
@@ -53327,11 +53338,12 @@ function MatrxDataTableCore(coreProps) {
53327
53338
  }, [fitToWidth, data.length]);
53328
53339
  const fittedWidths = (0, import_react28.useMemo)(() => {
53329
53340
  if (!fitToWidth || !fitSpace) return null;
53330
- const keepsItsWidth = (col) => resolveColumnMarker(col) !== void 0 || col.resizable === false && pixelWidth(col.width) !== void 0;
53341
+ const contentWidth = (col) => col.customActions ? fitSpace.custom[columnId(col)] : void 0;
53342
+ const keepsItsWidth = (col) => resolveColumnMarker(col) !== void 0 || col.resizable === false && pixelWidth(col.width) !== void 0 || contentWidth(col) !== void 0;
53331
53343
  const scaling = visibleColumns.filter((col) => !keepsItsWidth(col));
53332
- const fixed = visibleColumns.filter(keepsItsWidth).reduce((sum, col) => sum + (pixelWidth(col.width) ?? 40), 0);
53344
+ const fixed = visibleColumns.filter(keepsItsWidth).reduce((sum, col) => sum + (contentWidth(col) ?? pixelWidth(col.width) ?? 40), 0);
53333
53345
  const utilities = (selection ? selectionColumnWidth ?? SELECTION_COLUMN_PX : 0) + fitSpace.actions;
53334
- return fitColumnWidths(
53346
+ const shares = fitColumnWidths(
53335
53347
  scaling.map(columnId),
53336
53348
  (id) => {
53337
53349
  const col = scaling.find((c) => columnId(c) === id);
@@ -53339,6 +53351,13 @@ function MatrxDataTableCore(coreProps) {
53339
53351
  },
53340
53352
  fitSpace.width - utilities - fixed - fitSpace.slack
53341
53353
  );
53354
+ if (!shares) return null;
53355
+ const out = { ...shares };
53356
+ for (const col of visibleColumns) {
53357
+ const w = contentWidth(col);
53358
+ if (w !== void 0) out[columnId(col)] = w;
53359
+ }
53360
+ return out;
53342
53361
  }, [fitToWidth, fitSpace, visibleColumns, selection, selectionColumnWidth, effectiveColumnWidths]);
53343
53362
  const fitActive = fittedWidths !== null;
53344
53363
  const actionsColumnWidth = fitActive && fitSpace ? fitSpace.actions : fixedTableGeometry?.actionsWidth;
@@ -54021,7 +54040,8 @@ function MatrxDataTableCore(coreProps) {
54021
54040
  const showRowCopy = effectiveCopy !== void 0 && effectiveCopy.showRow !== false;
54022
54041
  const menuActionsColumn = visibleColumns.find((column) => column.customActions);
54023
54042
  const rowCopyInMenu = rowCopyPlacement === "menu" && menuActionsColumn !== void 0;
54024
- const showInlineRowCopy = showRowCopy && !rowCopyInMenu;
54043
+ const rowCopyInCustomColumn = showRowCopy && !rowCopyInMenu && menuActionsColumn !== void 0;
54044
+ const showInlineRowCopy = showRowCopy && !rowCopyInMenu && !rowCopyInCustomColumn;
54025
54045
  const showToolbarCopy = effectiveCopy !== void 0 && effectiveCopy.showToolbar !== false;
54026
54046
  const placeToolbar = (node) => {
54027
54047
  if (toolbar && "portalInto" in toolbar) {
@@ -54709,6 +54729,8 @@ function MatrxDataTableCore(coreProps) {
54709
54729
  );
54710
54730
  }, [host.rowContextRegistry, tableContextMenuInstanceId]);
54711
54731
  const showActionsCol = windowEnabled || Boolean(rowActions) || showInlineRowCopy;
54732
+ const lastVisibleColumn = visibleColumns[visibleColumns.length - 1];
54733
+ const pinnedActionsColumnId = !showActionsCol && lastVisibleColumn?.customActions ? columnId(lastVisibleColumn) : void 0;
54712
54734
  const getSelectionId = selection?.getSelectionId ?? getRowId;
54713
54735
  const selectedIdSet = (0, import_react28.useMemo)(
54714
54736
  () => new Set(selection?.selectedIds ?? []),
@@ -54781,15 +54803,28 @@ function MatrxDataTableCore(coreProps) {
54781
54803
  if (col.customActions) {
54782
54804
  const controls = suppliedControls ?? liveRecordControls(displayRow);
54783
54805
  const content = /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(MatrxTableRowControlsScope, { value: controls, children: col.customActions(displayRow, controls) });
54784
- return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { "data-matrx-table-custom-actions": true, "data-matrx-cell-control": true, className: "inline-flex items-center justify-end gap-0", onClick: (event) => event.stopPropagation(), children: effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
54785
- MatrxTableRowAlchemyProvider,
54786
- {
54787
- copy: tableRowCopyProps(effectiveCopy, displayRow, `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`, copy === void 0 ? declaredVisibleCopyColumns : void 0),
54788
- placement: rowCopyInMenu && columnId(col) === (menuActionsColumn ? columnId(menuActionsColumn) : void 0) ? "menu" : "inline",
54789
- controls,
54790
- children: content
54791
- }
54792
- ) : content });
54806
+ const copyHere = rowCopyInCustomColumn && effectiveCopy && menuActionsColumn !== void 0 && columnId(col) === columnId(menuActionsColumn);
54807
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { "data-matrx-table-custom-actions": true, "data-matrx-cell-control": true, className: cn("inline-flex items-center justify-end gap-0", compactRows && "[&_button]:h-6 [&_button]:min-h-6"), onClick: (event) => event.stopPropagation(), children: [
54808
+ copyHere && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
54809
+ TableRowCopyControl,
54810
+ {
54811
+ size: "toolbar",
54812
+ copy: effectiveCopy,
54813
+ row: displayRow,
54814
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
54815
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
54816
+ }
54817
+ ) : null,
54818
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
54819
+ MatrxTableRowAlchemyProvider,
54820
+ {
54821
+ copy: tableRowCopyProps(effectiveCopy, displayRow, `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`, copy === void 0 ? declaredVisibleCopyColumns : void 0),
54822
+ placement: rowCopyInMenu && columnId(col) === (menuActionsColumn ? columnId(menuActionsColumn) : void 0) ? "menu" : "inline",
54823
+ controls,
54824
+ children: content
54825
+ }
54826
+ ) : content
54827
+ ] });
54793
54828
  }
54794
54829
  const display = renderCell(displayRow, col, index);
54795
54830
  const href = col.href?.(row) ?? void 0;
@@ -55293,6 +55328,7 @@ function MatrxDataTableCore(coreProps) {
55293
55328
  "td",
55294
55329
  {
55295
55330
  "data-matrx-table-column-id": columnId(col),
55331
+ "data-matrx-table-pinned-actions": pinnedActionsColumnId === columnId(col) ? "" : void 0,
55296
55332
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
55297
55333
  ...sheetCell ? {
55298
55334
  "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
@@ -55331,6 +55367,11 @@ function MatrxDataTableCore(coreProps) {
55331
55367
  frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
55332
55368
  frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
55333
55369
  frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
55370
+ pinnedActionsColumnId === columnId(col) && [
55371
+ "text-right sm:sticky sm:right-0 sm:z-20 sm:shadow-[inset_1px_0_0_var(--color-border)]",
55372
+ frozenRowBackgroundClass,
55373
+ canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
55374
+ ],
55334
55375
  // A cell tint paints OVER the row tint beneath
55335
55376
  // it, and the consumer's own hook comes last so
55336
55377
  // it can always win.
@@ -56313,6 +56354,7 @@ function MatrxDataTableCore(coreProps) {
56313
56354
  {
56314
56355
  "data-matrx-table-column-id": id,
56315
56356
  "data-matrx-header-column-id": id,
56357
+ "data-matrx-table-pinned-actions": pinnedActionsColumnId === id ? "" : void 0,
56316
56358
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
56317
56359
  "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
56318
56360
  className: cn(
@@ -56340,7 +56382,9 @@ function MatrxDataTableCore(coreProps) {
56340
56382
  frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
56341
56383
  phoneFrozenId === id && FROZEN_PHONE_HEADER_CLASS,
56342
56384
  embedded && (frozenColumns.offsets[id] !== void 0 || phoneFrozenId === id) && "sm:bg-card max-sm:bg-card",
56343
- frozenEdgeId === id && FROZEN_EDGE_CLASS
56385
+ frozenEdgeId === id && FROZEN_EDGE_CLASS,
56386
+ pinnedActionsColumnId === id && "sm:sticky sm:right-0 sm:z-30 sm:bg-muted sm:shadow-[inset_1px_0_0_var(--color-border)]",
56387
+ pinnedActionsColumnId === id && embedded && "sm:bg-card"
56344
56388
  ),
56345
56389
  style: {
56346
56390
  ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),