@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
@@ -6,11 +6,11 @@ import {
6
6
  Fragment as Fragment6,
7
7
  isValidElement as isValidElement2,
8
8
  useCallback as useCallback6,
9
- useEffect as useEffect14,
9
+ useEffect as useEffect15,
10
10
  useId as useId2,
11
11
  useLayoutEffect as useLayoutEffect2,
12
12
  useMemo as useMemo7,
13
- useRef as useRef12,
13
+ useRef as useRef13,
14
14
  useState as useState21
15
15
  } from "react";
16
16
  import {
@@ -1886,6 +1886,12 @@ function MatrxTableRowAlchemyScope({ value, children }) {
1886
1886
  function useMatrxTableRowAlchemy() {
1887
1887
  return useContext4(RowAlchemyContext);
1888
1888
  }
1889
+ var ROW_CONTROLS_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.design-system.table-row-controls-context");
1890
+ var rowControlsRegistry = globalThis;
1891
+ var RowControlsContext = rowControlsRegistry[ROW_CONTROLS_SLOT] ??= createContext4(null);
1892
+ function MatrxTableRowControlsScope({ value, children }) {
1893
+ return /* @__PURE__ */ jsx18(RowControlsContext.Provider, { value, children });
1894
+ }
1889
1895
 
1890
1896
  // src/data-table/useTableNotice.tsx
1891
1897
  import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
@@ -5592,7 +5598,7 @@ import {
5592
5598
  // src/data-table/RowAlchemyLauncher.tsx
5593
5599
  import { useEffect as useEffect8, useRef as useRef9, useState as useState13 } from "react";
5594
5600
  import { jsx as jsx29, jsxs as jsxs20 } from "react/jsx-runtime";
5595
- function MatrxTableRowAlchemyProvider({ copy, placement, children }) {
5601
+ function MatrxTableRowAlchemyProvider({ copy, placement, controls, children }) {
5596
5602
  const controller = useRef9(null);
5597
5603
  const { CopyControls } = useMatrxDataTableHost();
5598
5604
  const [active, setActive] = useState13(false);
@@ -5609,6 +5615,7 @@ function MatrxTableRowAlchemyProvider({ copy, placement, children }) {
5609
5615
  }, [active, placement]);
5610
5616
  const value = {
5611
5617
  copy,
5618
+ controls,
5612
5619
  placement,
5613
5620
  ready: active,
5614
5621
  activate: () => setActive(true),
@@ -5776,6 +5783,34 @@ var CommandShortcut = ({
5776
5783
  };
5777
5784
  CommandShortcut.displayName = "CommandShortcut";
5778
5785
 
5786
+ // src/data-table/column-marker.ts
5787
+ var MARKER_ACCESSORS = {
5788
+ favorite: "favorite",
5789
+ is_favorite: "favorite",
5790
+ favourite: "favorite",
5791
+ is_favourite: "favorite",
5792
+ pinned: "pin",
5793
+ is_pinned: "pin"
5794
+ };
5795
+ function resolveColumnMarker(column) {
5796
+ if (column.marker === false) return void 0;
5797
+ if (column.marker) return column.marker;
5798
+ return column.accessorKey ? MARKER_ACCESSORS[column.accessorKey] : void 0;
5799
+ }
5800
+ function markerCopy(marker) {
5801
+ return marker === "favorite" ? {
5802
+ label: "Favorites",
5803
+ lowerLabel: "favorites",
5804
+ onlyLabel: "Favorites only",
5805
+ notLabel: "Not favorites"
5806
+ } : {
5807
+ label: "Pins",
5808
+ lowerLabel: "pins",
5809
+ onlyLabel: "Pins only",
5810
+ notLabel: "Not pinned"
5811
+ };
5812
+ }
5813
+
5779
5814
  // src/data-table/ColumnHeaderCell.tsx
5780
5815
  import { Fragment as Fragment4, jsx as jsx31, jsxs as jsxs22 } from "react/jsx-runtime";
5781
5816
  var HeaderButton = forwardRef12(({ dragDescription, "aria-describedby": tooltipDescription, ...props }, ref) => {
@@ -5808,6 +5843,7 @@ function ColumnHeaderCell({
5808
5843
  align = "left",
5809
5844
  className,
5810
5845
  compact = false,
5846
+ marker,
5811
5847
  filterApplyLabel,
5812
5848
  filterShowCancel = false,
5813
5849
  filterFooterHint,
@@ -5859,7 +5895,9 @@ function ColumnHeaderCell({
5859
5895
  if (deferredFilter) setDraftFilter(next);
5860
5896
  else onFilterChange(next);
5861
5897
  };
5862
- const menuLabel = labelText ? `Sort or filter ${labelText}` : "Sort or filter column";
5898
+ const markerLabels = marker ? markerCopy(marker) : void 0;
5899
+ const accessibleLabel = markerLabels?.label ?? labelText;
5900
+ const menuLabel = accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column";
5863
5901
  const menuContent = /* @__PURE__ */ jsxs22(
5864
5902
  PopoverContent,
5865
5903
  {
@@ -5888,7 +5926,7 @@ function ColumnHeaderCell({
5888
5926
  onClick: onSortAsc,
5889
5927
  children: [
5890
5928
  /* @__PURE__ */ jsx31(ArrowUp, { className: "h-3.5 w-3.5" }),
5891
- "Sort ascending"
5929
+ markerLabels ? `${markerLabels.label} last` : "Sort ascending"
5892
5930
  ]
5893
5931
  }
5894
5932
  ),
@@ -5901,7 +5939,7 @@ function ColumnHeaderCell({
5901
5939
  onClick: onSortDesc,
5902
5940
  children: [
5903
5941
  /* @__PURE__ */ jsx31(ArrowDown, { className: "h-3.5 w-3.5" }),
5904
- "Sort descending"
5942
+ markerLabels ? `${markerLabels.label} first` : "Sort descending"
5905
5943
  ]
5906
5944
  }
5907
5945
  ),
@@ -5914,7 +5952,7 @@ function ColumnHeaderCell({
5914
5952
  onClick: onClearSort,
5915
5953
  children: [
5916
5954
  /* @__PURE__ */ jsx31(ListX, { className: "h-3.5 w-3.5" }),
5917
- "Clear sort"
5955
+ markerLabels ? `Clear ${markerLabels.lowerLabel} sort` : "Clear sort"
5918
5956
  ]
5919
5957
  }
5920
5958
  ) : null
@@ -5933,6 +5971,13 @@ function ColumnHeaderCell({
5933
5971
  },
5934
5972
  selectOptions,
5935
5973
  selectSingle,
5974
+ ...markerLabels ? {
5975
+ booleanLabels: {
5976
+ all: "All",
5977
+ true: markerLabels.onlyLabel,
5978
+ false: markerLabels.notLabel
5979
+ }
5980
+ } : {},
5936
5981
  allowNullModes
5937
5982
  }
5938
5983
  ) : null,
@@ -6148,7 +6193,8 @@ function FilterBody({
6148
6193
  onChange,
6149
6194
  selectOptions,
6150
6195
  selectSingle = false,
6151
- allowNullModes = false
6196
+ allowNullModes = false,
6197
+ booleanLabels
6152
6198
  }) {
6153
6199
  if (kind === "text") {
6154
6200
  const text = value?.kind === "text" ? value.value : "";
@@ -6235,9 +6281,9 @@ function FilterBody({
6235
6281
  children: [
6236
6282
  /* @__PURE__ */ jsx31(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ jsx31(SelectValue, {}) }),
6237
6283
  /* @__PURE__ */ jsxs22(SelectContent, { children: [
6238
- /* @__PURE__ */ jsx31(SelectItem, { value: "__all__", children: "All" }),
6239
- /* @__PURE__ */ jsx31(SelectItem, { value: "true", children: "Yes" }),
6240
- /* @__PURE__ */ jsx31(SelectItem, { value: "false", children: "No" })
6284
+ /* @__PURE__ */ jsx31(SelectItem, { value: "__all__", children: booleanLabels?.all ?? "All" }),
6285
+ /* @__PURE__ */ jsx31(SelectItem, { value: "true", children: booleanLabels?.true ?? "Yes" }),
6286
+ /* @__PURE__ */ jsx31(SelectItem, { value: "false", children: booleanLabels?.false ?? "No" })
6241
6287
  ] })
6242
6288
  ]
6243
6289
  }
@@ -7442,7 +7488,7 @@ function DirtySavePill({
7442
7488
  }
7443
7489
 
7444
7490
  // src/data-table/EditableTableCell.tsx
7445
- import { useState as useState19 } from "react";
7491
+ import { useEffect as useEffect14, useRef as useRef12, useState as useState19 } from "react";
7446
7492
  import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
7447
7493
  function CellEditShell({
7448
7494
  href,
@@ -7489,9 +7535,13 @@ function EditableTableCell({
7489
7535
  onCommit,
7490
7536
  className,
7491
7537
  href,
7492
- editTrigger = "click"
7538
+ editTrigger = "click",
7539
+ editRequest
7493
7540
  }) {
7494
7541
  const [open, setOpen] = useState19(false);
7542
+ useEffect14(() => {
7543
+ if (editRequest !== void 0) setOpen(true);
7544
+ }, [editRequest]);
7495
7545
  const usePencil = Boolean(href) || editTrigger === "pencil";
7496
7546
  if (editType === "string") {
7497
7547
  return /* @__PURE__ */ jsx38(
@@ -7503,7 +7553,8 @@ function EditableTableCell({
7503
7553
  onCommit,
7504
7554
  ...className === void 0 ? {} : { className },
7505
7555
  ...href === void 0 ? {} : { href },
7506
- usePencil
7556
+ usePencil,
7557
+ ...editRequest === void 0 ? {} : { editRequest }
7507
7558
  }
7508
7559
  );
7509
7560
  }
@@ -7578,10 +7629,18 @@ function InlineStringEditor({
7578
7629
  onCommit,
7579
7630
  className,
7580
7631
  href,
7581
- usePencil
7632
+ usePencil,
7633
+ editRequest
7582
7634
  }) {
7583
7635
  const [editing, setEditing] = useState19(false);
7584
7636
  const [draft, setDraft] = useState19(value);
7637
+ const handledEditRequest = useRef12(void 0);
7638
+ useEffect14(() => {
7639
+ if (editRequest === void 0 || handledEditRequest.current === editRequest) return;
7640
+ handledEditRequest.current = editRequest;
7641
+ setDraft(value);
7642
+ setEditing(true);
7643
+ }, [editRequest, value]);
7585
7644
  if (!editing) {
7586
7645
  if (usePencil) {
7587
7646
  return /* @__PURE__ */ jsx38(
@@ -8333,6 +8392,7 @@ var SELECT_CARDINALITY_CAP = 24;
8333
8392
  function resolveFilterKind(col, sampleRows) {
8334
8393
  if (col.filter === false) return null;
8335
8394
  if (col.filter && col.filter !== "auto") return col.filter;
8395
+ if (resolveColumnMarker(col)) return "boolean";
8336
8396
  const values = sampleRows.slice(0, 200).map((row) => getCellValue(row, col)).filter((v) => v != null);
8337
8397
  if (values.length === 0) return "text";
8338
8398
  if (values.every((v) => typeof v === "boolean")) return "boolean";
@@ -8398,6 +8458,9 @@ function resolveQueryFilterMeta(column, rows, controlled) {
8398
8458
  };
8399
8459
  }
8400
8460
  if (column.filter === false) return { kind: null, options: [] };
8461
+ if (resolveColumnMarker(column) && (column.filter === void 0 || column.filter === "auto")) {
8462
+ return { kind: "boolean", options: [] };
8463
+ }
8401
8464
  const declared = column.filter;
8402
8465
  const kind = declared && declared !== "auto" ? declared : column.filterOptions?.length ? "select" : "text";
8403
8466
  return {
@@ -8505,7 +8568,7 @@ function MatrxDataTableWithUrlState({
8505
8568
  ...urlState.history === void 0 ? {} : { history: urlState.history },
8506
8569
  ...urlState.textHistory === void 0 ? {} : { textHistory: urlState.textHistory }
8507
8570
  });
8508
- useEffect14(() => {
8571
+ useEffect15(() => {
8509
8572
  if (activeUrlTableIds.has(urlState.id)) {
8510
8573
  throw new Error(
8511
8574
  `Duplicate MatrxDataTable urlState.id "${urlState.id}" on one page. Give every table a unique stable id.`
@@ -8575,6 +8638,9 @@ function MatrxDataTableWithUrlState({
8575
8638
  );
8576
8639
  }
8577
8640
  var COLUMN_REORDER_ACTIVATION_DISTANCE = 6;
8641
+ function TableContextPassthrough({ children }) {
8642
+ return /* @__PURE__ */ jsx41(Fragment7, { children });
8643
+ }
8578
8644
  function MatrxDataTableCore({
8579
8645
  data: suppliedData,
8580
8646
  columns,
@@ -8601,6 +8667,7 @@ function MatrxDataTableCore({
8601
8667
  detail,
8602
8668
  window: windowConfig,
8603
8669
  copy,
8670
+ contextMenu,
8604
8671
  rowCopyPlacement = "inline",
8605
8672
  edit,
8606
8673
  hierarchy,
@@ -8630,12 +8697,13 @@ function MatrxDataTableCore({
8630
8697
  }) {
8631
8698
  const appendQuery = query?.mode === "controlled-append" ? query : null;
8632
8699
  const appendQueryKey = appendQuery?.pagination.queryKey ?? null;
8633
- const committedAppendQueryKey = useRef12(appendQueryKey);
8700
+ const committedAppendQueryKey = useRef13(appendQueryKey);
8634
8701
  const appendSourceChanged = appendQuery !== null && committedAppendQueryKey.current !== appendQueryKey;
8635
8702
  const data = appendQuery ? appendQuery.pagination.rows : suppliedData;
8636
8703
  const isLoading = appendQuery ? appendQuery.pagination.loading : suppliedLoading;
8637
8704
  const isFetching = appendQuery ? appendQuery.pagination.isFetchingNextPage : suppliedFetching;
8638
8705
  const host = useMatrxDataTableHost();
8706
+ const ContextMenuBoundary = host.ContextMenuBoundary ?? TableContextPassthrough;
8639
8707
  const resolvedDensity = density ?? host.defaultDensity ?? "normal";
8640
8708
  const SavedViews = host.SavedViews;
8641
8709
  const Link = host.Link ?? DefaultTableLink;
@@ -8644,7 +8712,7 @@ function MatrxDataTableCore({
8644
8712
  const [savedViewError, setSavedViewError] = useState21(null);
8645
8713
  const [toolbarActionError, setToolbarActionError] = useState21(null);
8646
8714
  const [toolbarActionPending, setToolbarActionPending] = useState21({ refresh: false, add: false });
8647
- const toolbarActionPendingRef = useRef12({ refresh: false, add: false });
8715
+ const toolbarActionPendingRef = useRef13({ refresh: false, add: false });
8648
8716
  const CHECKBOX_TAP_AREA = "h-4 w-4 touch-manipulation";
8649
8717
  const mobileCardsClass = mobileCardsBreakpoint === "lg" ? "lg:hidden" : "sm:hidden";
8650
8718
  const tableWithCardsClass = mobileCardsBreakpoint === "lg" ? "max-lg:hidden" : "max-sm:hidden";
@@ -8782,8 +8850,10 @@ function MatrxDataTableCore({
8782
8850
  const [windowRowSnapshot, setWindowRowSnapshot] = useState21(null);
8783
8851
  const editScopeKey = appendQueryKey === null ? "\0local" : `append:${appendQueryKey}`;
8784
8852
  const [editsByScope, setEditsByScope] = useState21({});
8785
- const editRevisionRef = useRef12({});
8853
+ const editRevisionRef = useRef13({});
8786
8854
  const [savingByScope, setSavingByScope] = useState21({});
8855
+ const savingScopesRef = useRef13(/* @__PURE__ */ new Set());
8856
+ const [editRequest, setEditRequest] = useState21(null);
8787
8857
  const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = useState21(false);
8788
8858
  const edits = editsByScope[editScopeKey]?.values ?? {};
8789
8859
  const saving = (savingByScope[editScopeKey] ?? 0) > 0;
@@ -8851,7 +8921,7 @@ function MatrxDataTableCore({
8851
8921
  rows: Object.entries(draft.values).map(([rowId, columns2]) => ({ rowId, columns: columns2 }))
8852
8922
  }));
8853
8923
  const selection = appendSourceChanged && selectionConfig ? { ...selectionConfig, selectedIds: [] } : selectionConfig;
8854
- useEffect14(() => {
8924
+ useEffect15(() => {
8855
8925
  const previous = committedAppendQueryKey.current;
8856
8926
  committedAppendQueryKey.current = appendQueryKey;
8857
8927
  if (previous === null || appendQueryKey === null || previous === appendQueryKey) {
@@ -8869,13 +8939,13 @@ function MatrxDataTableCore({
8869
8939
  onWindowRowIdChange,
8870
8940
  selectionConfig
8871
8941
  ]);
8872
- const scrollRef = useRef12(null);
8873
- const tableRef = useRef12(null);
8874
- const tableHeaderRef = useRef12(null);
8875
- const cardsScrollRef = useRef12(null);
8942
+ const scrollRef = useRef13(null);
8943
+ const tableRef = useRef13(null);
8944
+ const tableHeaderRef = useRef13(null);
8945
+ const cardsScrollRef = useRef13(null);
8876
8946
  const [documentStickyHeader, setDocumentStickyHeader] = useState21(false);
8877
- const documentStickyHeaderRef = useRef12(false);
8878
- const previousAppendKeyForScrollReset = useRef12(appendQueryKey);
8947
+ const documentStickyHeaderRef = useRef13(false);
8948
+ const previousAppendKeyForScrollReset = useRef13(appendQueryKey);
8879
8949
  useLayoutEffect2(() => {
8880
8950
  const previous = previousAppendKeyForScrollReset.current;
8881
8951
  previousAppendKeyForScrollReset.current = appendQueryKey;
@@ -8901,7 +8971,7 @@ function MatrxDataTableCore({
8901
8971
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.thresholdPx !== void 0 ? { thresholdPx: appendScrollPolicy.thresholdPx } : {},
8902
8972
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.intentTimeoutMs !== void 0 ? { intentTimeoutMs: appendScrollPolicy.intentTimeoutMs } : {}
8903
8973
  });
8904
- const rootRef = useRef12(null);
8974
+ const rootRef = useRef13(null);
8905
8975
  useClippedContentGuard(rootRef, {
8906
8976
  label: typeof copy === "object" && copy.listLabel ? `Table: ${copy.listLabel}` : "MatrxDataTable"
8907
8977
  });
@@ -8921,7 +8991,7 @@ function MatrxDataTableCore({
8921
8991
  behavior: "smooth"
8922
8992
  });
8923
8993
  };
8924
- useEffect14(() => {
8994
+ useEffect15(() => {
8925
8995
  updateScrollHint();
8926
8996
  window.addEventListener("resize", updateScrollHint);
8927
8997
  const el = scrollRef.current;
@@ -9003,18 +9073,18 @@ function MatrxDataTableCore({
9003
9073
  () => normalizeColumnWidthsForColumns(columnState?.widths, columns)
9004
9074
  );
9005
9075
  const [fixedTableGeometry, setFixedTableGeometry] = useState21(null);
9006
- const knownColumnIds = useRef12(new Set(columns.map(columnId)));
9007
- const draggedColumnId = useRef12(null);
9008
- const columnDragActive = useRef12(false);
9009
- const columnDragRaf = useRef12(null);
9010
- const columnDragScrollDirection = useRef12(0);
9076
+ const knownColumnIds = useRef13(new Set(columns.map(columnId)));
9077
+ const draggedColumnId = useRef13(null);
9078
+ const columnDragActive = useRef13(false);
9079
+ const columnDragRaf = useRef13(null);
9080
+ const columnDragScrollDirection = useRef13(0);
9011
9081
  const [columnDragPreview, setColumnDragPreview] = useState21(null);
9012
9082
  const stopColumnDragScroll = useCallback6(() => {
9013
9083
  if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
9014
9084
  columnDragRaf.current = null;
9015
9085
  columnDragScrollDirection.current = 0;
9016
9086
  }, []);
9017
- useEffect14(() => () => {
9087
+ useEffect15(() => () => {
9018
9088
  columnDragActive.current = false;
9019
9089
  stopColumnDragScroll();
9020
9090
  }, [stopColumnDragScroll]);
@@ -9062,7 +9132,7 @@ function MatrxDataTableCore({
9062
9132
  () => normalizeColumnWidthsForColumns(columnState?.widths ?? columnWidths, columns),
9063
9133
  [columns, columnState?.widths, columnWidths]
9064
9134
  );
9065
- const resizeGestureSnapshotRef = useRef12(null);
9135
+ const resizeGestureSnapshotRef = useRef13(null);
9066
9136
  const measureVisibleColumnWidths = useCallback6(() => {
9067
9137
  const measured = {};
9068
9138
  const headers = tableRef.current ? Array.from(tableRef.current.querySelectorAll(
@@ -9135,7 +9205,7 @@ function MatrxDataTableCore({
9135
9205
  }
9136
9206
  setColumnWidths(normalized);
9137
9207
  }, [columnIds, columnState, columns, measureFixedTableGeometry, measureVisibleColumnWidths]);
9138
- useEffect14(() => {
9208
+ useEffect15(() => {
9139
9209
  const ids = columns.map(columnId);
9140
9210
  if (columnState) return;
9141
9211
  const previousIds = knownColumnIds.current;
@@ -9205,6 +9275,7 @@ function MatrxDataTableCore({
9205
9275
  [visibleColumns]
9206
9276
  );
9207
9277
  const tableCopySourceNamespace = useId2();
9278
+ const tableContextMenuInstanceId = useId2();
9208
9279
  const tableDndContextId = useId2();
9209
9280
  const defaultCopyTitle = toolbar?.title || "Table";
9210
9281
  const defaultCopy = useMemo7(() => ({
@@ -9320,8 +9391,8 @@ function MatrxDataTableCore({
9320
9391
  intelligentSearchEnabled,
9321
9392
  searchScope
9322
9393
  ]);
9323
- const lastNotifiedViewRows = useRef12(null);
9324
- useEffect14(() => {
9394
+ const lastNotifiedViewRows = useRef13(null);
9395
+ useEffect15(() => {
9325
9396
  if (!onViewChange) {
9326
9397
  lastNotifiedViewRows.current = null;
9327
9398
  return;
@@ -9330,7 +9401,7 @@ function MatrxDataTableCore({
9330
9401
  lastNotifiedViewRows.current = processed;
9331
9402
  if (changed) onViewChange(processed);
9332
9403
  }, [processed, onViewChange]);
9333
- useEffect14(() => {
9404
+ useEffect15(() => {
9334
9405
  if (controlledQuery) return;
9335
9406
  setInternalPage(1);
9336
9407
  }, [
@@ -9382,7 +9453,7 @@ function MatrxDataTableCore({
9382
9453
  const start = (safePage - 1) * effectivePageSize;
9383
9454
  return processed.slice(start, start + effectivePageSize);
9384
9455
  }, [processed, safePage, effectivePageSize, pageSize, remoteQuery, hidePagination, localProgressiveEnabled, progressiveRows.rows]);
9385
- useEffect14(() => {
9456
+ useEffect15(() => {
9386
9457
  if (!controlledQuery || controlledQuery.state.page === safePage) return;
9387
9458
  emitControlledQueryChange({ page: safePage });
9388
9459
  }, [controlledQuery, emitControlledQueryChange, safePage]);
@@ -9406,13 +9477,15 @@ function MatrxDataTableCore({
9406
9477
  const showToolbarCopy = effectiveCopy !== void 0 && effectiveCopy.showToolbar !== false;
9407
9478
  const renderRowActions = (row, controls) => {
9408
9479
  const actions = rowActions?.(row, controls);
9409
- if (!effectiveCopy || !showRowCopy) return actions;
9480
+ const scopedActions = /* @__PURE__ */ jsx41(MatrxTableRowControlsScope, { value: controls, children: actions });
9481
+ if (!effectiveCopy || !showRowCopy) return scopedActions;
9410
9482
  return /* @__PURE__ */ jsx41(
9411
9483
  MatrxTableRowAlchemyProvider,
9412
9484
  {
9413
9485
  copy: tableRowCopyProps(effectiveCopy, row, `${tableCopySourceNamespace}:row:${getRowId(row)}`, copy === void 0 ? declaredVisibleCopyColumns : void 0),
9414
9486
  placement: rowCopyInMenu ? "menu" : "inline",
9415
- children: actions
9487
+ controls,
9488
+ children: scopedActions
9416
9489
  }
9417
9490
  );
9418
9491
  };
@@ -9529,6 +9602,37 @@ function MatrxDataTableCore({
9529
9602
  return next;
9530
9603
  });
9531
9604
  };
9605
+ const beginRowEdit = (row) => {
9606
+ const column = visibleColumns.find(
9607
+ (candidate) => candidate.editable && (candidate.editableIf?.(row) ?? true)
9608
+ );
9609
+ if (!column) return;
9610
+ const field = column.accessorKey ? String(column.accessorKey) : columnId(column);
9611
+ setEditRequest((current) => ({
9612
+ rowId: getRowId(row),
9613
+ field,
9614
+ nonce: (current?.nonce ?? 0) + 1
9615
+ }));
9616
+ };
9617
+ const buildRecordControls = (row) => {
9618
+ const rowId = getRowId(row);
9619
+ const pending = edits[rowId];
9620
+ return {
9621
+ closeDetail: () => setSelectedId(null),
9622
+ openDetail: () => openDetail(row),
9623
+ openWindow: () => openWindow(row),
9624
+ closeWindow,
9625
+ hasPendingEdits: Boolean(pending),
9626
+ discardPendingEdits: () => discardRowEdits(rowId),
9627
+ ...pending ? { cancelEdits: () => discardRowEdits(rowId) } : {},
9628
+ ...pending && edit?.onSave && !edit.autoSave && !saving ? {
9629
+ saveEdits: () => handleSaveEdits(rowId)
9630
+ } : {},
9631
+ ...editEnabled && visibleColumns.some(
9632
+ (column) => column.editable && (column.editableIf?.(row) ?? true)
9633
+ ) ? { beginEdit: () => beginRowEdit(row) } : {}
9634
+ };
9635
+ };
9532
9636
  const hierarchyRows = hierarchy?.rows ?? data;
9533
9637
  const hierarchyGetParentId = hierarchy?.getParentId;
9534
9638
  const hierarchyIndex = useMemo7(() => {
@@ -9700,15 +9804,18 @@ function MatrxDataTableCore({
9700
9804
  }
9701
9805
  setDraggedRowId(null);
9702
9806
  };
9703
- const handleSaveEdits = async () => {
9807
+ const handleSaveEdits = async (rowId) => {
9704
9808
  if (!edit?.onSave || changeCount === 0) return;
9705
9809
  const scope = editScopeKey;
9810
+ if (savingScopesRef.current.has(scope)) return;
9706
9811
  const draft = editsByScope[scope];
9707
9812
  if (!draft) return;
9708
- const values = draft.values;
9709
- const revisions = draft.revisions;
9813
+ const values = rowId ? draft.values[rowId] ? { [rowId]: draft.values[rowId] } : {} : draft.values;
9814
+ if (Object.keys(values).length === 0) return;
9815
+ const revisions = rowId ? draft.revisions[rowId] ? { [rowId]: draft.revisions[rowId] } : {} : draft.revisions;
9710
9816
  const rows = appendQueryKey === null ? data : draft.rows;
9711
9817
  const save = edit.onSave;
9818
+ savingScopesRef.current.add(scope);
9712
9819
  startSaving(scope);
9713
9820
  try {
9714
9821
  await Promise.resolve().then(() => save(values, rows));
@@ -9716,16 +9823,16 @@ function MatrxDataTableCore({
9716
9823
  const currentDraft = current[scope];
9717
9824
  if (!currentDraft) return current;
9718
9825
  const nextValues = {};
9719
- for (const [rowId, rowValues] of Object.entries(currentDraft.values)) {
9720
- const savedRow = values[rowId];
9721
- const savedRevisions = revisions[rowId];
9722
- const currentRevisions = currentDraft.revisions[rowId];
9826
+ for (const [rowId2, rowValues] of Object.entries(currentDraft.values)) {
9827
+ const savedRow = values[rowId2];
9828
+ const savedRevisions = revisions[rowId2];
9829
+ const currentRevisions = currentDraft.revisions[rowId2];
9723
9830
  const remaining = Object.fromEntries(
9724
9831
  Object.entries(rowValues).filter(
9725
9832
  ([field]) => !Object.hasOwn(savedRow ?? {}, field) || currentRevisions?.[field] !== savedRevisions?.[field]
9726
9833
  )
9727
9834
  );
9728
- if (Object.keys(remaining).length > 0) nextValues[rowId] = remaining;
9835
+ if (Object.keys(remaining).length > 0) nextValues[rowId2] = remaining;
9729
9836
  }
9730
9837
  return { ...current, [scope]: { ...currentDraft, values: nextValues } };
9731
9838
  });
@@ -9735,6 +9842,7 @@ function MatrxDataTableCore({
9735
9842
  `Couldn't save: ${e instanceof Error ? e.message : String(e)}`
9736
9843
  );
9737
9844
  } finally {
9845
+ savingScopesRef.current.delete(scope);
9738
9846
  finishSaving(scope);
9739
9847
  }
9740
9848
  };
@@ -9742,6 +9850,17 @@ function MatrxDataTableCore({
9742
9850
  setEdits({});
9743
9851
  edit?.onCancel?.();
9744
9852
  };
9853
+ useEffect15(() => {
9854
+ if (!host.rowContextRegistry || !contextMenu && !host.createDefaultRowContext) return;
9855
+ return host.rowContextRegistry.register(tableContextMenuInstanceId, (rowId) => {
9856
+ const row = data.find((candidate) => getRowId(candidate) === rowId);
9857
+ if (!row) return null;
9858
+ const pending = edits[rowId];
9859
+ const displayRow = applyRowEdits(row, pending);
9860
+ const controls = buildRecordControls(displayRow);
9861
+ return contextMenu?.resolveRowContext(displayRow, controls) ?? host.createDefaultRowContext?.(displayRow, controls) ?? null;
9862
+ });
9863
+ }, [buildRecordControls, contextMenu, data, getRowId, host.createDefaultRowContext, host.rowContextRegistry, tableContextMenuInstanceId]);
9745
9864
  const showActionsCol = windowEnabled || Boolean(rowActions) || showInlineRowCopy;
9746
9865
  const selectedIdSet = useMemo7(
9747
9866
  () => new Set(selection?.selectedIds ?? []),
@@ -9759,7 +9878,7 @@ function MatrxDataTableCore({
9759
9878
  () => data.filter((row) => selectedIdSet.has(getRowId(row))),
9760
9879
  [data, selectedIdSet, getRowId]
9761
9880
  );
9762
- const lastToggledIndex = useRef12(null);
9881
+ const lastToggledIndex = useRef13(null);
9763
9882
  const setSelectedIds = (next) => selection?.onSelectedIdsChange([...next]);
9764
9883
  const toggleRowSelected = (index, shiftKey) => {
9765
9884
  if (!selection) return;
@@ -9822,7 +9941,8 @@ function MatrxDataTableCore({
9822
9941
  dirty: Boolean(rowEdits && field in rowEdits),
9823
9942
  onCommit: (next) => commitCell(id, field, next),
9824
9943
  ...href === void 0 ? {} : { href },
9825
- ...col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger }
9944
+ ...col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger },
9945
+ ...editRequest?.rowId === id && editRequest.field === field ? { editRequest: editRequest.nonce } : {}
9826
9946
  }
9827
9947
  );
9828
9948
  }
@@ -9932,7 +10052,7 @@ function MatrxDataTableCore({
9932
10052
  },
9933
10053
  [columns, commitColumnState, commitColumnWidths, controlledQuery, intelligentSearchEnabled, intelligentSearchScopes, toolbar]
9934
10054
  );
9935
- return /* @__PURE__ */ jsx41(TooltipProvider, { children: /* @__PURE__ */ jsxs31(
10055
+ return /* @__PURE__ */ jsx41(TooltipProvider, { children: /* @__PURE__ */ jsx41(ContextMenuBoundary, { label: tableId ?? "Data table", children: /* @__PURE__ */ jsxs31(
9936
10056
  DndContext,
9937
10057
  {
9938
10058
  id: tableDndContextId,
@@ -10030,6 +10150,7 @@ function MatrxDataTableCore({
10030
10150
  {
10031
10151
  ref: rootRef,
10032
10152
  "data-matrx-table": true,
10153
+ "data-matrx-table-id": tableContextMenuInstanceId,
10033
10154
  "data-matrx-table-implementation": "@ai-matrx/design-system",
10034
10155
  "data-matrx-table-density": resolvedDensity,
10035
10156
  "data-url-state-table": urlState?.id,
@@ -10416,7 +10537,7 @@ function MatrxDataTableCore({
10416
10537
  const id = getRowId(row);
10417
10538
  const displayRow = applyRowEdits(row, edits[id]);
10418
10539
  const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
10419
- return /* @__PURE__ */ jsx41(Fragment6, { children: mobileCards(displayRow, index, {
10540
+ return /* @__PURE__ */ jsx41("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
10420
10541
  renderCell: (columnKey) => {
10421
10542
  const column = columns.find((candidate) => columnId(candidate) === columnKey);
10422
10543
  return column ? renderRecordCell(row, column, index) : null;
@@ -10449,14 +10570,7 @@ function MatrxDataTableCore({
10449
10570
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
10450
10571
  }
10451
10572
  ) : null,
10452
- renderRowActions(displayRow, {
10453
- closeDetail: () => setSelectedId(null),
10454
- openDetail: () => openDetail(displayRow),
10455
- openWindow: () => openWindow(displayRow),
10456
- closeWindow,
10457
- hasPendingEdits: Boolean(edits[id]),
10458
- discardPendingEdits: () => discardRowEdits(id)
10459
- })
10573
+ renderRowActions(displayRow, buildRecordControls(displayRow))
10460
10574
  ]
10461
10575
  }
10462
10576
  ) : null
@@ -10588,6 +10702,10 @@ function MatrxDataTableCore({
10588
10702
  const defaultSortDirection = col.defaultSortDirection ?? "asc";
10589
10703
  const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
10590
10704
  const columnResizable = resizableColumns && col.resizable !== false;
10705
+ const marker = resolveColumnMarker(col);
10706
+ const compact = col.compact || marker !== void 0;
10707
+ const width = marker && col.width === void 0 ? 40 : col.width;
10708
+ const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
10591
10709
  return /* @__PURE__ */ jsxs31(
10592
10710
  "th",
10593
10711
  {
@@ -10599,7 +10717,7 @@ function MatrxDataTableCore({
10599
10717
  // ICON COLUMN (MatrxColumnDef.compact): a 40px star
10600
10718
  // column cannot honor `width: 40` while paying 16px of
10601
10719
  // padding for a 14px glyph.
10602
- col.compact ? "px-1" : "px-2",
10720
+ compact ? "px-1" : "px-2",
10603
10721
  // An intentional mobile column set (see
10604
10722
  // MatrxColumnDef.mobileHidden). CSS, not a JS
10605
10723
  // breakpoint — no hydration mismatch, and the column
@@ -10610,13 +10728,13 @@ function MatrxDataTableCore({
10610
10728
  // it, a crowded table crushes a declared 190px column to a
10611
10729
  // few characters wide instead of using the canonical
10612
10730
  // horizontal-scroll surface.
10613
- (col.width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
10731
+ (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
10614
10732
  col.headerClassName,
10615
10733
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
10616
10734
  col.align === "center" && "text-center",
10617
10735
  col.align === "right" && "text-right"
10618
10736
  ),
10619
- style: columnWidthVar(col.width, effectiveColumnWidths[id], id, columnResizable),
10737
+ style: columnWidthVar(width, effectiveColumnWidths[id], id, columnResizable),
10620
10738
  children: [
10621
10739
  /* @__PURE__ */ jsx41(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ jsx41(
10622
10740
  ColumnHeaderCell,
@@ -10651,7 +10769,8 @@ function MatrxDataTableCore({
10651
10769
  allowNegation: filterUi?.allowNegation,
10652
10770
  allowNullModes: filterUi?.allowNullModes,
10653
10771
  align: col.align,
10654
- compact: col.compact,
10772
+ compact,
10773
+ marker,
10655
10774
  reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0
10656
10775
  }
10657
10776
  ) }),
@@ -10661,12 +10780,12 @@ function MatrxDataTableCore({
10661
10780
  columnId: id,
10662
10781
  label: headerLabel,
10663
10782
  tableRef,
10664
- minWidth: col.minWidth,
10783
+ minWidth,
10665
10784
  maxWidth: col.maxWidth,
10666
10785
  onResizeStart: captureResizeGestureSnapshot,
10667
10786
  onResizeCancel: clearResizeGestureSnapshot,
10668
- onResizeCommit: (columnId2, width) => commitColumnWidths(
10669
- { ...effectiveColumnWidths, [columnId2]: width },
10787
+ onResizeCommit: (columnId2, width2) => commitColumnWidths(
10788
+ { ...effectiveColumnWidths, [columnId2]: width2 },
10670
10789
  true
10671
10790
  )
10672
10791
  }
@@ -10696,7 +10815,7 @@ function MatrxDataTableCore({
10696
10815
  {
10697
10816
  className: cn(
10698
10817
  "py-2",
10699
- col.compact ? "px-1" : "px-2",
10818
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
10700
10819
  col.mobileHidden && "max-sm:hidden"
10701
10820
  ),
10702
10821
  children: /* @__PURE__ */ jsx41(Skeleton, { className: "h-5 w-full" })
@@ -10782,19 +10901,19 @@ function MatrxDataTableCore({
10782
10901
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
10783
10902
  className: cn(
10784
10903
  "py-1.5 align-middle lg:py-0.5",
10785
- col.compact ? "px-1" : "px-2",
10904
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
10786
10905
  // nowrap (NOT truncate — truncate clips the cell and
10787
10906
  // defeats w-max, killing the horizontal scroll).
10788
10907
  "max-sm:whitespace-nowrap",
10789
10908
  col.mobileHidden && "max-sm:hidden",
10790
- (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)]",
10909
+ ((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)]",
10791
10910
  col.className,
10792
10911
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
10793
10912
  col.align === "center" && "text-center",
10794
10913
  col.align === "right" && "text-right"
10795
10914
  ),
10796
10915
  style: columnWidthVar(
10797
- col.width,
10916
+ resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
10798
10917
  effectiveColumnWidths[columnId(col)],
10799
10918
  columnId(col),
10800
10919
  resizableColumns && col.resizable !== false
@@ -10852,14 +10971,7 @@ function MatrxDataTableCore({
10852
10971
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
10853
10972
  }
10854
10973
  ) : null,
10855
- renderRowActions(displayRow, {
10856
- closeDetail: () => setSelectedId(null),
10857
- openDetail: () => openDetail(displayRow),
10858
- openWindow: () => openWindow(displayRow),
10859
- closeWindow,
10860
- hasPendingEdits: Boolean(rowEdits),
10861
- discardPendingEdits: () => discardRowEdits(id)
10862
- }),
10974
+ renderRowActions(displayRow, buildRecordControls(displayRow)),
10863
10975
  windowEnabled ? /* @__PURE__ */ jsxs31(Tooltip, { children: [
10864
10976
  /* @__PURE__ */ jsx41(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx41(
10865
10977
  Button,
@@ -10933,13 +11045,13 @@ function MatrxDataTableCore({
10933
11045
  {
10934
11046
  className: cn(
10935
11047
  "px-2 py-2 align-middle",
10936
- column.compact && "px-1",
11048
+ (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
10937
11049
  column.className,
10938
11050
  column.align === "center" && "text-center",
10939
11051
  column.align === "right" && "text-right"
10940
11052
  ),
10941
11053
  style: columnWidthVar(
10942
- column.width,
11054
+ resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
10943
11055
  effectiveColumnWidths[columnId(column)],
10944
11056
  columnId(column),
10945
11057
  resizableColumns && column.resizable !== false
@@ -11347,7 +11459,7 @@ function MatrxDataTableCore({
11347
11459
  ) : null })
11348
11460
  ]
11349
11461
  }
11350
- ) });
11462
+ ) }) });
11351
11463
  }
11352
11464
  function HierarchyDragHandle({
11353
11465
  id,
@@ -11531,7 +11643,11 @@ function normalizeColumnWidthsForColumns(widths, columns) {
11531
11643
  return Object.fromEntries(
11532
11644
  Object.entries(normalized).map(([id, width]) => {
11533
11645
  const column = byId.get(id);
11534
- const bounds = resolveColumnResizeBounds(column?.minWidth, column?.maxWidth);
11646
+ const marker = column ? resolveColumnMarker(column) : void 0;
11647
+ const bounds = resolveColumnResizeBounds(
11648
+ marker && column?.minWidth === void 0 ? 40 : column?.minWidth,
11649
+ column?.maxWidth
11650
+ );
11535
11651
  return [id, clampColumnWidth(width, bounds)];
11536
11652
  })
11537
11653
  );
@@ -11895,6 +12011,7 @@ export {
11895
12011
  downloadTableAsXlsx,
11896
12012
  filterKindLabel,
11897
12013
  parseTableViewSnapshot,
12014
+ resolveColumnMarker,
11898
12015
  resolveFilterKind,
11899
12016
  rowsToCsvFromColumns,
11900
12017
  rowsToMarkdownTable,