@ai-matrx/design-system 0.21.3 → 0.21.5

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/data-table/copy-helpers.d.cts +1 -1
  3. package/dist/data-table/copy-helpers.d.ts +1 -1
  4. package/dist/data-table/data-row-inspector.cjs +3 -0
  5. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  6. package/dist/data-table/data-row-inspector.js +3 -0
  7. package/dist/data-table/data-row-inspector.js.map +1 -1
  8. package/dist/data-table/data-row-window.cjs +3 -0
  9. package/dist/data-table/data-row-window.cjs.map +1 -1
  10. package/dist/data-table/data-row-window.js +3 -0
  11. package/dist/data-table/data-row-window.js.map +1 -1
  12. package/dist/data-table/filter-engine.d.cts +1 -1
  13. package/dist/data-table/filter-engine.d.ts +1 -1
  14. package/dist/data-table/host.cjs +12 -1
  15. package/dist/data-table/host.cjs.map +1 -1
  16. package/dist/data-table/host.d.cts +19 -3
  17. package/dist/data-table/host.d.ts +19 -3
  18. package/dist/data-table/host.js +12 -1
  19. package/dist/data-table/host.js.map +1 -1
  20. package/dist/data-table/index.cjs +173 -56
  21. package/dist/data-table/index.cjs.map +1 -1
  22. package/dist/data-table/index.d.cts +12 -4
  23. package/dist/data-table/index.d.ts +12 -4
  24. package/dist/data-table/index.js +202 -85
  25. package/dist/data-table/index.js.map +1 -1
  26. package/dist/data-table/infer-filter.cjs +16 -0
  27. package/dist/data-table/infer-filter.cjs.map +1 -1
  28. package/dist/data-table/infer-filter.d.cts +1 -1
  29. package/dist/data-table/infer-filter.d.ts +1 -1
  30. package/dist/data-table/infer-filter.js +16 -0
  31. package/dist/data-table/infer-filter.js.map +1 -1
  32. package/dist/data-table/layered-filters.d.cts +1 -1
  33. package/dist/data-table/layered-filters.d.ts +1 -1
  34. package/dist/data-table/pagination.cjs +3 -0
  35. package/dist/data-table/pagination.cjs.map +1 -1
  36. package/dist/data-table/pagination.js +3 -0
  37. package/dist/data-table/pagination.js.map +1 -1
  38. package/dist/data-table/query-control.cjs +19 -0
  39. package/dist/data-table/query-control.cjs.map +1 -1
  40. package/dist/data-table/query-control.d.cts +1 -1
  41. package/dist/data-table/query-control.d.ts +1 -1
  42. package/dist/data-table/query-control.js +19 -0
  43. package/dist/data-table/query-control.js.map +1 -1
  44. package/dist/data-table/types.cjs.map +1 -1
  45. package/dist/data-table/types.d.cts +1 -1
  46. package/dist/data-table/types.d.ts +1 -1
  47. package/dist/data-table/url-state.d.cts +1 -1
  48. package/dist/data-table/url-state.d.ts +1 -1
  49. package/dist/data-table/uuid-cell.cjs +3 -0
  50. package/dist/data-table/uuid-cell.cjs.map +1 -1
  51. package/dist/data-table/uuid-cell.js +3 -0
  52. package/dist/data-table/uuid-cell.js.map +1 -1
  53. package/dist/data-table/xlsx.d.cts +1 -1
  54. package/dist/data-table/xlsx.d.ts +1 -1
  55. package/dist/{types-JFlsIv-0.d.ts → types-Dpl8OW_m.d.ts} +26 -1
  56. package/dist/{types-BarjBFyG.d.cts → types-DsmrqJHN.d.cts} +26 -1
  57. package/package.json +1 -1
@@ -48,6 +48,7 @@ __export(data_table_exports, {
48
48
  downloadTableAsXlsx: () => downloadTableAsXlsx,
49
49
  filterKindLabel: () => filterKindLabel,
50
50
  parseTableViewSnapshot: () => parseTableViewSnapshot,
51
+ resolveColumnMarker: () => resolveColumnMarker,
51
52
  resolveFilterKind: () => resolveFilterKind,
52
53
  rowsToCsvFromColumns: () => rowsToCsvFromColumns,
53
54
  rowsToMarkdownTable: () => rowsToMarkdownTable,
@@ -1925,6 +1926,12 @@ function MatrxTableRowAlchemyScope({ value, children }) {
1925
1926
  function useMatrxTableRowAlchemy() {
1926
1927
  return (0, import_react5.useContext)(RowAlchemyContext);
1927
1928
  }
1929
+ var ROW_CONTROLS_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.design-system.table-row-controls-context");
1930
+ var rowControlsRegistry = globalThis;
1931
+ var RowControlsContext = rowControlsRegistry[ROW_CONTROLS_SLOT] ??= (0, import_react5.createContext)(null);
1932
+ function MatrxTableRowControlsScope({ value, children }) {
1933
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RowControlsContext.Provider, { value, children });
1934
+ }
1928
1935
 
1929
1936
  // src/data-table/useTableNotice.tsx
1930
1937
  var import_jsx_runtime19 = require("react/jsx-runtime");
@@ -5588,7 +5595,7 @@ var import_url_state3 = require("@ai-matrx/kit/url-state");
5588
5595
  // src/data-table/RowAlchemyLauncher.tsx
5589
5596
  var import_react12 = require("react");
5590
5597
  var import_jsx_runtime29 = require("react/jsx-runtime");
5591
- function MatrxTableRowAlchemyProvider({ copy, placement, children }) {
5598
+ function MatrxTableRowAlchemyProvider({ copy, placement, controls, children }) {
5592
5599
  const controller = (0, import_react12.useRef)(null);
5593
5600
  const { CopyControls } = useMatrxDataTableHost();
5594
5601
  const [active, setActive] = (0, import_react12.useState)(false);
@@ -5605,6 +5612,7 @@ function MatrxTableRowAlchemyProvider({ copy, placement, children }) {
5605
5612
  }, [active, placement]);
5606
5613
  const value = {
5607
5614
  copy,
5615
+ controls,
5608
5616
  placement,
5609
5617
  ready: active,
5610
5618
  activate: () => setActive(true),
@@ -5767,6 +5775,34 @@ var CommandShortcut = ({
5767
5775
  };
5768
5776
  CommandShortcut.displayName = "CommandShortcut";
5769
5777
 
5778
+ // src/data-table/column-marker.ts
5779
+ var MARKER_ACCESSORS = {
5780
+ favorite: "favorite",
5781
+ is_favorite: "favorite",
5782
+ favourite: "favorite",
5783
+ is_favourite: "favorite",
5784
+ pinned: "pin",
5785
+ is_pinned: "pin"
5786
+ };
5787
+ function resolveColumnMarker(column) {
5788
+ if (column.marker === false) return void 0;
5789
+ if (column.marker) return column.marker;
5790
+ return column.accessorKey ? MARKER_ACCESSORS[column.accessorKey] : void 0;
5791
+ }
5792
+ function markerCopy(marker) {
5793
+ return marker === "favorite" ? {
5794
+ label: "Favorites",
5795
+ lowerLabel: "favorites",
5796
+ onlyLabel: "Favorites only",
5797
+ notLabel: "Not favorites"
5798
+ } : {
5799
+ label: "Pins",
5800
+ lowerLabel: "pins",
5801
+ onlyLabel: "Pins only",
5802
+ notLabel: "Not pinned"
5803
+ };
5804
+ }
5805
+
5770
5806
  // src/data-table/ColumnHeaderCell.tsx
5771
5807
  var import_jsx_runtime31 = require("react/jsx-runtime");
5772
5808
  var HeaderButton = (0, import_react13.forwardRef)(({ dragDescription, "aria-describedby": tooltipDescription, ...props }, ref) => {
@@ -5799,6 +5835,7 @@ function ColumnHeaderCell({
5799
5835
  align = "left",
5800
5836
  className,
5801
5837
  compact = false,
5838
+ marker,
5802
5839
  filterApplyLabel,
5803
5840
  filterShowCancel = false,
5804
5841
  filterFooterHint,
@@ -5850,7 +5887,9 @@ function ColumnHeaderCell({
5850
5887
  if (deferredFilter) setDraftFilter(next);
5851
5888
  else onFilterChange(next);
5852
5889
  };
5853
- const menuLabel = labelText ? `Sort or filter ${labelText}` : "Sort or filter column";
5890
+ const markerLabels = marker ? markerCopy(marker) : void 0;
5891
+ const accessibleLabel = markerLabels?.label ?? labelText;
5892
+ const menuLabel = accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column";
5854
5893
  const menuContent = /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
5855
5894
  PopoverContent,
5856
5895
  {
@@ -5879,7 +5918,7 @@ function ColumnHeaderCell({
5879
5918
  onClick: onSortAsc,
5880
5919
  children: [
5881
5920
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArrowUp, { className: "h-3.5 w-3.5" }),
5882
- "Sort ascending"
5921
+ markerLabels ? `${markerLabels.label} last` : "Sort ascending"
5883
5922
  ]
5884
5923
  }
5885
5924
  ),
@@ -5892,7 +5931,7 @@ function ColumnHeaderCell({
5892
5931
  onClick: onSortDesc,
5893
5932
  children: [
5894
5933
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArrowDown, { className: "h-3.5 w-3.5" }),
5895
- "Sort descending"
5934
+ markerLabels ? `${markerLabels.label} first` : "Sort descending"
5896
5935
  ]
5897
5936
  }
5898
5937
  ),
@@ -5905,7 +5944,7 @@ function ColumnHeaderCell({
5905
5944
  onClick: onClearSort,
5906
5945
  children: [
5907
5946
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ListX, { className: "h-3.5 w-3.5" }),
5908
- "Clear sort"
5947
+ markerLabels ? `Clear ${markerLabels.lowerLabel} sort` : "Clear sort"
5909
5948
  ]
5910
5949
  }
5911
5950
  ) : null
@@ -5924,6 +5963,13 @@ function ColumnHeaderCell({
5924
5963
  },
5925
5964
  selectOptions,
5926
5965
  selectSingle,
5966
+ ...markerLabels ? {
5967
+ booleanLabels: {
5968
+ all: "All",
5969
+ true: markerLabels.onlyLabel,
5970
+ false: markerLabels.notLabel
5971
+ }
5972
+ } : {},
5927
5973
  allowNullModes
5928
5974
  }
5929
5975
  ) : null,
@@ -6139,7 +6185,8 @@ function FilterBody({
6139
6185
  onChange,
6140
6186
  selectOptions,
6141
6187
  selectSingle = false,
6142
- allowNullModes = false
6188
+ allowNullModes = false,
6189
+ booleanLabels
6143
6190
  }) {
6144
6191
  if (kind === "text") {
6145
6192
  const text = value?.kind === "text" ? value.value : "";
@@ -6226,9 +6273,9 @@ function FilterBody({
6226
6273
  children: [
6227
6274
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectValue, {}) }),
6228
6275
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(SelectContent, { children: [
6229
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectItem, { value: "__all__", children: "All" }),
6230
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectItem, { value: "true", children: "Yes" }),
6231
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectItem, { value: "false", children: "No" })
6276
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectItem, { value: "__all__", children: booleanLabels?.all ?? "All" }),
6277
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectItem, { value: "true", children: booleanLabels?.true ?? "Yes" }),
6278
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SelectItem, { value: "false", children: booleanLabels?.false ?? "No" })
6232
6279
  ] })
6233
6280
  ]
6234
6281
  }
@@ -7471,9 +7518,13 @@ function EditableTableCell({
7471
7518
  onCommit,
7472
7519
  className,
7473
7520
  href,
7474
- editTrigger = "click"
7521
+ editTrigger = "click",
7522
+ editRequest
7475
7523
  }) {
7476
7524
  const [open, setOpen] = (0, import_react18.useState)(false);
7525
+ (0, import_react18.useEffect)(() => {
7526
+ if (editRequest !== void 0) setOpen(true);
7527
+ }, [editRequest]);
7477
7528
  const usePencil = Boolean(href) || editTrigger === "pencil";
7478
7529
  if (editType === "string") {
7479
7530
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
@@ -7485,7 +7536,8 @@ function EditableTableCell({
7485
7536
  onCommit,
7486
7537
  ...className === void 0 ? {} : { className },
7487
7538
  ...href === void 0 ? {} : { href },
7488
- usePencil
7539
+ usePencil,
7540
+ ...editRequest === void 0 ? {} : { editRequest }
7489
7541
  }
7490
7542
  );
7491
7543
  }
@@ -7560,10 +7612,18 @@ function InlineStringEditor({
7560
7612
  onCommit,
7561
7613
  className,
7562
7614
  href,
7563
- usePencil
7615
+ usePencil,
7616
+ editRequest
7564
7617
  }) {
7565
7618
  const [editing, setEditing] = (0, import_react18.useState)(false);
7566
7619
  const [draft, setDraft] = (0, import_react18.useState)(value);
7620
+ const handledEditRequest = (0, import_react18.useRef)(void 0);
7621
+ (0, import_react18.useEffect)(() => {
7622
+ if (editRequest === void 0 || handledEditRequest.current === editRequest) return;
7623
+ handledEditRequest.current = editRequest;
7624
+ setDraft(value);
7625
+ setEditing(true);
7626
+ }, [editRequest, value]);
7567
7627
  if (!editing) {
7568
7628
  if (usePencil) {
7569
7629
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
@@ -8315,6 +8375,7 @@ var SELECT_CARDINALITY_CAP = 24;
8315
8375
  function resolveFilterKind(col, sampleRows) {
8316
8376
  if (col.filter === false) return null;
8317
8377
  if (col.filter && col.filter !== "auto") return col.filter;
8378
+ if (resolveColumnMarker(col)) return "boolean";
8318
8379
  const values = sampleRows.slice(0, 200).map((row) => getCellValue(row, col)).filter((v) => v != null);
8319
8380
  if (values.length === 0) return "text";
8320
8381
  if (values.every((v) => typeof v === "boolean")) return "boolean";
@@ -8380,6 +8441,9 @@ function resolveQueryFilterMeta(column, rows, controlled) {
8380
8441
  };
8381
8442
  }
8382
8443
  if (column.filter === false) return { kind: null, options: [] };
8444
+ if (resolveColumnMarker(column) && (column.filter === void 0 || column.filter === "auto")) {
8445
+ return { kind: "boolean", options: [] };
8446
+ }
8383
8447
  const declared = column.filter;
8384
8448
  const kind = declared && declared !== "auto" ? declared : column.filterOptions?.length ? "select" : "text";
8385
8449
  return {
@@ -8557,6 +8621,9 @@ function MatrxDataTableWithUrlState({
8557
8621
  );
8558
8622
  }
8559
8623
  var COLUMN_REORDER_ACTIVATION_DISTANCE = 6;
8624
+ function TableContextPassthrough({ children }) {
8625
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_jsx_runtime41.Fragment, { children });
8626
+ }
8560
8627
  function MatrxDataTableCore({
8561
8628
  data: suppliedData,
8562
8629
  columns,
@@ -8583,6 +8650,7 @@ function MatrxDataTableCore({
8583
8650
  detail,
8584
8651
  window: windowConfig,
8585
8652
  copy,
8653
+ contextMenu,
8586
8654
  rowCopyPlacement = "inline",
8587
8655
  edit,
8588
8656
  hierarchy,
@@ -8618,6 +8686,7 @@ function MatrxDataTableCore({
8618
8686
  const isLoading = appendQuery ? appendQuery.pagination.loading : suppliedLoading;
8619
8687
  const isFetching = appendQuery ? appendQuery.pagination.isFetchingNextPage : suppliedFetching;
8620
8688
  const host = useMatrxDataTableHost();
8689
+ const ContextMenuBoundary = host.ContextMenuBoundary ?? TableContextPassthrough;
8621
8690
  const resolvedDensity = density ?? host.defaultDensity ?? "normal";
8622
8691
  const SavedViews = host.SavedViews;
8623
8692
  const Link = host.Link ?? DefaultTableLink;
@@ -8766,6 +8835,8 @@ function MatrxDataTableCore({
8766
8835
  const [editsByScope, setEditsByScope] = (0, import_react20.useState)({});
8767
8836
  const editRevisionRef = (0, import_react20.useRef)({});
8768
8837
  const [savingByScope, setSavingByScope] = (0, import_react20.useState)({});
8838
+ const savingScopesRef = (0, import_react20.useRef)(/* @__PURE__ */ new Set());
8839
+ const [editRequest, setEditRequest] = (0, import_react20.useState)(null);
8769
8840
  const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = (0, import_react20.useState)(false);
8770
8841
  const edits = editsByScope[editScopeKey]?.values ?? {};
8771
8842
  const saving = (savingByScope[editScopeKey] ?? 0) > 0;
@@ -9187,6 +9258,7 @@ function MatrxDataTableCore({
9187
9258
  [visibleColumns]
9188
9259
  );
9189
9260
  const tableCopySourceNamespace = (0, import_react20.useId)();
9261
+ const tableContextMenuInstanceId = (0, import_react20.useId)();
9190
9262
  const tableDndContextId = (0, import_react20.useId)();
9191
9263
  const defaultCopyTitle = toolbar?.title || "Table";
9192
9264
  const defaultCopy = (0, import_react20.useMemo)(() => ({
@@ -9388,13 +9460,15 @@ function MatrxDataTableCore({
9388
9460
  const showToolbarCopy = effectiveCopy !== void 0 && effectiveCopy.showToolbar !== false;
9389
9461
  const renderRowActions = (row, controls) => {
9390
9462
  const actions = rowActions?.(row, controls);
9391
- if (!effectiveCopy || !showRowCopy) return actions;
9463
+ const scopedActions = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(MatrxTableRowControlsScope, { value: controls, children: actions });
9464
+ if (!effectiveCopy || !showRowCopy) return scopedActions;
9392
9465
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
9393
9466
  MatrxTableRowAlchemyProvider,
9394
9467
  {
9395
9468
  copy: tableRowCopyProps(effectiveCopy, row, `${tableCopySourceNamespace}:row:${getRowId(row)}`, copy === void 0 ? declaredVisibleCopyColumns : void 0),
9396
9469
  placement: rowCopyInMenu ? "menu" : "inline",
9397
- children: actions
9470
+ controls,
9471
+ children: scopedActions
9398
9472
  }
9399
9473
  );
9400
9474
  };
@@ -9511,6 +9585,37 @@ function MatrxDataTableCore({
9511
9585
  return next;
9512
9586
  });
9513
9587
  };
9588
+ const beginRowEdit = (row) => {
9589
+ const column = visibleColumns.find(
9590
+ (candidate) => candidate.editable && (candidate.editableIf?.(row) ?? true)
9591
+ );
9592
+ if (!column) return;
9593
+ const field = column.accessorKey ? String(column.accessorKey) : columnId(column);
9594
+ setEditRequest((current) => ({
9595
+ rowId: getRowId(row),
9596
+ field,
9597
+ nonce: (current?.nonce ?? 0) + 1
9598
+ }));
9599
+ };
9600
+ const buildRecordControls = (row) => {
9601
+ const rowId = getRowId(row);
9602
+ const pending = edits[rowId];
9603
+ return {
9604
+ closeDetail: () => setSelectedId(null),
9605
+ openDetail: () => openDetail(row),
9606
+ openWindow: () => openWindow(row),
9607
+ closeWindow,
9608
+ hasPendingEdits: Boolean(pending),
9609
+ discardPendingEdits: () => discardRowEdits(rowId),
9610
+ ...pending ? { cancelEdits: () => discardRowEdits(rowId) } : {},
9611
+ ...pending && edit?.onSave && !edit.autoSave && !saving ? {
9612
+ saveEdits: () => handleSaveEdits(rowId)
9613
+ } : {},
9614
+ ...editEnabled && visibleColumns.some(
9615
+ (column) => column.editable && (column.editableIf?.(row) ?? true)
9616
+ ) ? { beginEdit: () => beginRowEdit(row) } : {}
9617
+ };
9618
+ };
9514
9619
  const hierarchyRows = hierarchy?.rows ?? data;
9515
9620
  const hierarchyGetParentId = hierarchy?.getParentId;
9516
9621
  const hierarchyIndex = (0, import_react20.useMemo)(() => {
@@ -9682,15 +9787,18 @@ function MatrxDataTableCore({
9682
9787
  }
9683
9788
  setDraggedRowId(null);
9684
9789
  };
9685
- const handleSaveEdits = async () => {
9790
+ const handleSaveEdits = async (rowId) => {
9686
9791
  if (!edit?.onSave || changeCount === 0) return;
9687
9792
  const scope = editScopeKey;
9793
+ if (savingScopesRef.current.has(scope)) return;
9688
9794
  const draft = editsByScope[scope];
9689
9795
  if (!draft) return;
9690
- const values = draft.values;
9691
- const revisions = draft.revisions;
9796
+ const values = rowId ? draft.values[rowId] ? { [rowId]: draft.values[rowId] } : {} : draft.values;
9797
+ if (Object.keys(values).length === 0) return;
9798
+ const revisions = rowId ? draft.revisions[rowId] ? { [rowId]: draft.revisions[rowId] } : {} : draft.revisions;
9692
9799
  const rows = appendQueryKey === null ? data : draft.rows;
9693
9800
  const save = edit.onSave;
9801
+ savingScopesRef.current.add(scope);
9694
9802
  startSaving(scope);
9695
9803
  try {
9696
9804
  await Promise.resolve().then(() => save(values, rows));
@@ -9698,16 +9806,16 @@ function MatrxDataTableCore({
9698
9806
  const currentDraft = current[scope];
9699
9807
  if (!currentDraft) return current;
9700
9808
  const nextValues = {};
9701
- for (const [rowId, rowValues] of Object.entries(currentDraft.values)) {
9702
- const savedRow = values[rowId];
9703
- const savedRevisions = revisions[rowId];
9704
- const currentRevisions = currentDraft.revisions[rowId];
9809
+ for (const [rowId2, rowValues] of Object.entries(currentDraft.values)) {
9810
+ const savedRow = values[rowId2];
9811
+ const savedRevisions = revisions[rowId2];
9812
+ const currentRevisions = currentDraft.revisions[rowId2];
9705
9813
  const remaining = Object.fromEntries(
9706
9814
  Object.entries(rowValues).filter(
9707
9815
  ([field]) => !Object.hasOwn(savedRow ?? {}, field) || currentRevisions?.[field] !== savedRevisions?.[field]
9708
9816
  )
9709
9817
  );
9710
- if (Object.keys(remaining).length > 0) nextValues[rowId] = remaining;
9818
+ if (Object.keys(remaining).length > 0) nextValues[rowId2] = remaining;
9711
9819
  }
9712
9820
  return { ...current, [scope]: { ...currentDraft, values: nextValues } };
9713
9821
  });
@@ -9717,6 +9825,7 @@ function MatrxDataTableCore({
9717
9825
  `Couldn't save: ${e instanceof Error ? e.message : String(e)}`
9718
9826
  );
9719
9827
  } finally {
9828
+ savingScopesRef.current.delete(scope);
9720
9829
  finishSaving(scope);
9721
9830
  }
9722
9831
  };
@@ -9724,6 +9833,17 @@ function MatrxDataTableCore({
9724
9833
  setEdits({});
9725
9834
  edit?.onCancel?.();
9726
9835
  };
9836
+ (0, import_react20.useEffect)(() => {
9837
+ if (!host.rowContextRegistry || !contextMenu && !host.createDefaultRowContext) return;
9838
+ return host.rowContextRegistry.register(tableContextMenuInstanceId, (rowId) => {
9839
+ const row = data.find((candidate) => getRowId(candidate) === rowId);
9840
+ if (!row) return null;
9841
+ const pending = edits[rowId];
9842
+ const displayRow = applyRowEdits(row, pending);
9843
+ const controls = buildRecordControls(displayRow);
9844
+ return contextMenu?.resolveRowContext(displayRow, controls) ?? host.createDefaultRowContext?.(displayRow, controls) ?? null;
9845
+ });
9846
+ }, [buildRecordControls, contextMenu, data, getRowId, host.createDefaultRowContext, host.rowContextRegistry, tableContextMenuInstanceId]);
9727
9847
  const showActionsCol = windowEnabled || Boolean(rowActions) || showInlineRowCopy;
9728
9848
  const selectedIdSet = (0, import_react20.useMemo)(
9729
9849
  () => new Set(selection?.selectedIds ?? []),
@@ -9804,7 +9924,8 @@ function MatrxDataTableCore({
9804
9924
  dirty: Boolean(rowEdits && field in rowEdits),
9805
9925
  onCommit: (next) => commitCell(id, field, next),
9806
9926
  ...href === void 0 ? {} : { href },
9807
- ...col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger }
9927
+ ...col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger },
9928
+ ...editRequest?.rowId === id && editRequest.field === field ? { editRequest: editRequest.nonce } : {}
9808
9929
  }
9809
9930
  );
9810
9931
  }
@@ -9914,7 +10035,7 @@ function MatrxDataTableCore({
9914
10035
  },
9915
10036
  [columns, commitColumnState, commitColumnWidths, controlledQuery, intelligentSearchEnabled, intelligentSearchScopes, toolbar]
9916
10037
  );
9917
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
10038
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ContextMenuBoundary, { label: tableId ?? "Data table", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
9918
10039
  import_core3.DndContext,
9919
10040
  {
9920
10041
  id: tableDndContextId,
@@ -10012,6 +10133,7 @@ function MatrxDataTableCore({
10012
10133
  {
10013
10134
  ref: rootRef,
10014
10135
  "data-matrx-table": true,
10136
+ "data-matrx-table-id": tableContextMenuInstanceId,
10015
10137
  "data-matrx-table-implementation": "@ai-matrx/design-system",
10016
10138
  "data-matrx-table-density": resolvedDensity,
10017
10139
  "data-url-state-table": urlState?.id,
@@ -10398,7 +10520,7 @@ function MatrxDataTableCore({
10398
10520
  const id = getRowId(row);
10399
10521
  const displayRow = applyRowEdits(row, edits[id]);
10400
10522
  const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
10401
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_react20.Fragment, { children: mobileCards(displayRow, index, {
10523
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
10402
10524
  renderCell: (columnKey) => {
10403
10525
  const column = columns.find((candidate) => columnId(candidate) === columnKey);
10404
10526
  return column ? renderRecordCell(row, column, index) : null;
@@ -10431,14 +10553,7 @@ function MatrxDataTableCore({
10431
10553
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
10432
10554
  }
10433
10555
  ) : null,
10434
- renderRowActions(displayRow, {
10435
- closeDetail: () => setSelectedId(null),
10436
- openDetail: () => openDetail(displayRow),
10437
- openWindow: () => openWindow(displayRow),
10438
- closeWindow,
10439
- hasPendingEdits: Boolean(edits[id]),
10440
- discardPendingEdits: () => discardRowEdits(id)
10441
- })
10556
+ renderRowActions(displayRow, buildRecordControls(displayRow))
10442
10557
  ]
10443
10558
  }
10444
10559
  ) : null
@@ -10570,6 +10685,10 @@ function MatrxDataTableCore({
10570
10685
  const defaultSortDirection = col.defaultSortDirection ?? "asc";
10571
10686
  const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
10572
10687
  const columnResizable = resizableColumns && col.resizable !== false;
10688
+ const marker = resolveColumnMarker(col);
10689
+ const compact = col.compact || marker !== void 0;
10690
+ const width = marker && col.width === void 0 ? 40 : col.width;
10691
+ const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
10573
10692
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
10574
10693
  "th",
10575
10694
  {
@@ -10581,7 +10700,7 @@ function MatrxDataTableCore({
10581
10700
  // ICON COLUMN (MatrxColumnDef.compact): a 40px star
10582
10701
  // column cannot honor `width: 40` while paying 16px of
10583
10702
  // padding for a 14px glyph.
10584
- col.compact ? "px-1" : "px-2",
10703
+ compact ? "px-1" : "px-2",
10585
10704
  // An intentional mobile column set (see
10586
10705
  // MatrxColumnDef.mobileHidden). CSS, not a JS
10587
10706
  // breakpoint — no hydration mismatch, and the column
@@ -10592,13 +10711,13 @@ function MatrxDataTableCore({
10592
10711
  // it, a crowded table crushes a declared 190px column to a
10593
10712
  // few characters wide instead of using the canonical
10594
10713
  // horizontal-scroll surface.
10595
- (col.width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
10714
+ (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
10596
10715
  col.headerClassName,
10597
10716
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
10598
10717
  col.align === "center" && "text-center",
10599
10718
  col.align === "right" && "text-right"
10600
10719
  ),
10601
- style: columnWidthVar(col.width, effectiveColumnWidths[id], id, columnResizable),
10720
+ style: columnWidthVar(width, effectiveColumnWidths[id], id, columnResizable),
10602
10721
  children: [
10603
10722
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
10604
10723
  ColumnHeaderCell,
@@ -10633,7 +10752,8 @@ function MatrxDataTableCore({
10633
10752
  allowNegation: filterUi?.allowNegation,
10634
10753
  allowNullModes: filterUi?.allowNullModes,
10635
10754
  align: col.align,
10636
- compact: col.compact,
10755
+ compact,
10756
+ marker,
10637
10757
  reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0
10638
10758
  }
10639
10759
  ) }),
@@ -10643,12 +10763,12 @@ function MatrxDataTableCore({
10643
10763
  columnId: id,
10644
10764
  label: headerLabel,
10645
10765
  tableRef,
10646
- minWidth: col.minWidth,
10766
+ minWidth,
10647
10767
  maxWidth: col.maxWidth,
10648
10768
  onResizeStart: captureResizeGestureSnapshot,
10649
10769
  onResizeCancel: clearResizeGestureSnapshot,
10650
- onResizeCommit: (columnId2, width) => commitColumnWidths(
10651
- { ...effectiveColumnWidths, [columnId2]: width },
10770
+ onResizeCommit: (columnId2, width2) => commitColumnWidths(
10771
+ { ...effectiveColumnWidths, [columnId2]: width2 },
10652
10772
  true
10653
10773
  )
10654
10774
  }
@@ -10678,7 +10798,7 @@ function MatrxDataTableCore({
10678
10798
  {
10679
10799
  className: cn(
10680
10800
  "py-2",
10681
- col.compact ? "px-1" : "px-2",
10801
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
10682
10802
  col.mobileHidden && "max-sm:hidden"
10683
10803
  ),
10684
10804
  children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Skeleton, { className: "h-5 w-full" })
@@ -10764,19 +10884,19 @@ function MatrxDataTableCore({
10764
10884
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
10765
10885
  className: cn(
10766
10886
  "py-1.5 align-middle lg:py-0.5",
10767
- col.compact ? "px-1" : "px-2",
10887
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
10768
10888
  // nowrap (NOT truncate — truncate clips the cell and
10769
10889
  // defeats w-max, killing the horizontal scroll).
10770
10890
  "max-sm:whitespace-nowrap",
10771
10891
  col.mobileHidden && "max-sm:hidden",
10772
- (col.width !== void 0 || resizableColumns && col.resizable !== false) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)] sm:max-w-[var(--matrx-col-w)]",
10892
+ ((resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width) !== void 0 || resizableColumns && col.resizable !== false) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)] sm:max-w-[var(--matrx-col-w)]",
10773
10893
  col.className,
10774
10894
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
10775
10895
  col.align === "center" && "text-center",
10776
10896
  col.align === "right" && "text-right"
10777
10897
  ),
10778
10898
  style: columnWidthVar(
10779
- col.width,
10899
+ resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
10780
10900
  effectiveColumnWidths[columnId(col)],
10781
10901
  columnId(col),
10782
10902
  resizableColumns && col.resizable !== false
@@ -10834,14 +10954,7 @@ function MatrxDataTableCore({
10834
10954
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
10835
10955
  }
10836
10956
  ) : null,
10837
- renderRowActions(displayRow, {
10838
- closeDetail: () => setSelectedId(null),
10839
- openDetail: () => openDetail(displayRow),
10840
- openWindow: () => openWindow(displayRow),
10841
- closeWindow,
10842
- hasPendingEdits: Boolean(rowEdits),
10843
- discardPendingEdits: () => discardRowEdits(id)
10844
- }),
10957
+ renderRowActions(displayRow, buildRecordControls(displayRow)),
10845
10958
  windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(Tooltip, { children: [
10846
10959
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
10847
10960
  Button,
@@ -10915,13 +11028,13 @@ function MatrxDataTableCore({
10915
11028
  {
10916
11029
  className: cn(
10917
11030
  "px-2 py-2 align-middle",
10918
- column.compact && "px-1",
11031
+ (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
10919
11032
  column.className,
10920
11033
  column.align === "center" && "text-center",
10921
11034
  column.align === "right" && "text-right"
10922
11035
  ),
10923
11036
  style: columnWidthVar(
10924
- column.width,
11037
+ resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
10925
11038
  effectiveColumnWidths[columnId(column)],
10926
11039
  columnId(column),
10927
11040
  resizableColumns && column.resizable !== false
@@ -11329,7 +11442,7 @@ function MatrxDataTableCore({
11329
11442
  ) : null })
11330
11443
  ]
11331
11444
  }
11332
- ) });
11445
+ ) }) });
11333
11446
  }
11334
11447
  function HierarchyDragHandle({
11335
11448
  id,
@@ -11513,7 +11626,11 @@ function normalizeColumnWidthsForColumns(widths, columns) {
11513
11626
  return Object.fromEntries(
11514
11627
  Object.entries(normalized).map(([id, width]) => {
11515
11628
  const column = byId.get(id);
11516
- const bounds = resolveColumnResizeBounds(column?.minWidth, column?.maxWidth);
11629
+ const marker = column ? resolveColumnMarker(column) : void 0;
11630
+ const bounds = resolveColumnResizeBounds(
11631
+ marker && column?.minWidth === void 0 ? 40 : column?.minWidth,
11632
+ column?.maxWidth
11633
+ );
11517
11634
  return [id, clampColumnWidth(width, bounds)];
11518
11635
  })
11519
11636
  );