@ai-matrx/design-system 0.40.0 → 0.42.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/data-table/copy-helpers.d.cts +3 -2
  3. package/dist/data-table/copy-helpers.d.ts +3 -2
  4. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  5. package/dist/data-table/data-row-inspector.js.map +1 -1
  6. package/dist/data-table/data-row-window.cjs.map +1 -1
  7. package/dist/data-table/data-row-window.js.map +1 -1
  8. package/dist/data-table/filter-engine.d.cts +3 -2
  9. package/dist/data-table/filter-engine.d.ts +3 -2
  10. package/dist/data-table/host.cjs.map +1 -1
  11. package/dist/data-table/host.d.cts +19 -5
  12. package/dist/data-table/host.d.ts +19 -5
  13. package/dist/data-table/host.js.map +1 -1
  14. package/dist/data-table/index.cjs +2080 -1655
  15. package/dist/data-table/index.cjs.map +1 -1
  16. package/dist/data-table/index.d.cts +5 -5
  17. package/dist/data-table/index.d.ts +5 -5
  18. package/dist/data-table/index.js +2080 -1655
  19. package/dist/data-table/index.js.map +1 -1
  20. package/dist/data-table/infer-filter.d.cts +3 -2
  21. package/dist/data-table/infer-filter.d.ts +3 -2
  22. package/dist/data-table/layered-filters.d.cts +3 -2
  23. package/dist/data-table/layered-filters.d.ts +3 -2
  24. package/dist/data-table/menu-targets.cjs +27 -0
  25. package/dist/data-table/menu-targets.cjs.map +1 -1
  26. package/dist/data-table/menu-targets.d.cts +49 -1
  27. package/dist/data-table/menu-targets.d.ts +49 -1
  28. package/dist/data-table/menu-targets.js +27 -0
  29. package/dist/data-table/menu-targets.js.map +1 -1
  30. package/dist/data-table/pagination.cjs.map +1 -1
  31. package/dist/data-table/pagination.js.map +1 -1
  32. package/dist/data-table/query-control.d.cts +3 -2
  33. package/dist/data-table/query-control.d.ts +3 -2
  34. package/dist/data-table/spreadsheet.cjs +24 -8
  35. package/dist/data-table/spreadsheet.cjs.map +1 -1
  36. package/dist/data-table/spreadsheet.d.cts +20 -3
  37. package/dist/data-table/spreadsheet.d.ts +20 -3
  38. package/dist/data-table/spreadsheet.js +24 -8
  39. package/dist/data-table/spreadsheet.js.map +1 -1
  40. package/dist/data-table/types.cjs.map +1 -1
  41. package/dist/data-table/types.d.cts +3 -2
  42. package/dist/data-table/types.d.ts +3 -2
  43. package/dist/data-table/url-state.d.cts +3 -2
  44. package/dist/data-table/url-state.d.ts +3 -2
  45. package/dist/data-table/uuid-cell.cjs.map +1 -1
  46. package/dist/data-table/uuid-cell.js.map +1 -1
  47. package/dist/data-table/validation.d.cts +1 -1
  48. package/dist/data-table/validation.d.ts +1 -1
  49. package/dist/data-table/xlsx.d.cts +3 -2
  50. package/dist/data-table/xlsx.d.ts +3 -2
  51. package/dist/field-formats/index.cjs +269 -5
  52. package/dist/field-formats/index.cjs.map +1 -1
  53. package/dist/field-formats/index.d.cts +2 -2
  54. package/dist/field-formats/index.d.ts +2 -2
  55. package/dist/field-formats/index.js +269 -5
  56. package/dist/field-formats/index.js.map +1 -1
  57. package/dist/formulas/index.cjs +38 -5
  58. package/dist/formulas/index.cjs.map +1 -1
  59. package/dist/formulas/index.d.cts +39 -2
  60. package/dist/formulas/index.d.ts +39 -2
  61. package/dist/formulas/index.js +38 -5
  62. package/dist/formulas/index.js.map +1 -1
  63. package/dist/{types-CJWU1Gae.d.cts → types-JnZD8wM0.d.cts} +27 -2
  64. package/dist/{types-CJWU1Gae.d.ts → types-JnZD8wM0.d.ts} +27 -2
  65. package/dist/{types-Z32pxexx.d.ts → types-afUz93xo.d.ts} +34 -2
  66. package/dist/{types-oVyZbfwn.d.cts → types-q5ficDSo.d.cts} +34 -2
  67. package/package.json +1 -1
@@ -44,6 +44,7 @@ __export(data_table_exports, {
44
44
  FORMAT_MISMATCH_CLASS: () => FORMAT_MISMATCH_CLASS,
45
45
  FormattedCell: () => FormattedCell,
46
46
  GenericTablePagination: () => GenericTablePagination,
47
+ MATRX_TABLE_MENU_ICON_NAMES: () => MATRX_TABLE_MENU_ICON_NAMES,
47
48
  MatrxDataTable: () => MatrxDataTable,
48
49
  MatrxDataTableProvider: () => MatrxDataTableProvider,
49
50
  MatrxTableRowAlchemyProvider: () => MatrxTableRowAlchemyProvider,
@@ -6266,6 +6267,52 @@ function markerCopy(marker) {
6266
6267
 
6267
6268
  // src/data-table/ColumnHeaderCell.tsx
6268
6269
  var import_jsx_runtime31 = require("react/jsx-runtime");
6270
+ function HeaderMenuItems({
6271
+ items,
6272
+ MenuIcon,
6273
+ onDone,
6274
+ depth = 0
6275
+ }) {
6276
+ const [openSubmenu, setOpenSubmenu] = (0, import_react13.useState)(null);
6277
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("flex flex-col gap-0.5", depth > 0 && "ml-3 border-l border-border pl-1"), role: "group", children: items.map((item) => {
6278
+ const disabled = Boolean(item.disabledReason);
6279
+ const hasSubmenu = Boolean(item.submenu && item.submenu.length > 0);
6280
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col", children: [
6281
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
6282
+ Button,
6283
+ {
6284
+ type: "button",
6285
+ variant: "ghost",
6286
+ size: "sm",
6287
+ disabled,
6288
+ "aria-disabled": disabled || void 0,
6289
+ ...hasSubmenu ? { "aria-expanded": openSubmenu === item.id } : {},
6290
+ "data-matrx-header-menu-item": item.id,
6291
+ className: cn(
6292
+ "h-8 justify-start gap-2 px-2 text-xs font-normal",
6293
+ item.destructive && "text-destructive hover:text-destructive"
6294
+ ),
6295
+ onClick: () => {
6296
+ if (disabled) return;
6297
+ if (hasSubmenu) {
6298
+ setOpenSubmenu((current) => current === item.id ? null : item.id);
6299
+ return;
6300
+ }
6301
+ onDone();
6302
+ item.onSelect?.();
6303
+ },
6304
+ children: [
6305
+ item.icon && MenuIcon ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MenuIcon, { name: item.icon, className: "h-3.5 w-3.5" }) : null,
6306
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label }),
6307
+ item.shortcut ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "ml-3 text-[10px] text-muted-foreground", children: item.shortcut }) : null
6308
+ ]
6309
+ }
6310
+ ),
6311
+ disabled ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "px-2 pb-1 text-[11px] leading-tight text-muted-foreground", children: item.disabledReason }) : null,
6312
+ hasSubmenu && openSubmenu === item.id ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(HeaderMenuItems, { items: item.submenu, MenuIcon, onDone, depth: depth + 1 }) : null
6313
+ ] }, item.id);
6314
+ }) });
6315
+ }
6269
6316
  var HeaderButton = (0, import_react13.forwardRef)(({ dragDescription, "aria-describedby": tooltipDescription, ...props }, ref) => {
6270
6317
  const descriptions = [tooltipDescription, dragDescription].filter((value) => Boolean(value)).join(" ");
6271
6318
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -6307,8 +6354,11 @@ function ColumnHeaderCell({
6307
6354
  facetsLoading = false,
6308
6355
  facetsError,
6309
6356
  onFilterOpen,
6310
- reorderColumn
6357
+ reorderColumn,
6358
+ menuItems,
6359
+ MenuIcon
6311
6360
  }) {
6361
+ const hasMenuItems = Boolean(menuItems && menuItems.length > 0);
6312
6362
  const reorderEnabled = reorderColumn !== void 0;
6313
6363
  const { attributes: reorderAttributes, listeners: reorderListeners, setNodeRef } = (0, import_core.useDraggable)(
6314
6364
  reorderColumn ? {
@@ -6365,7 +6415,7 @@ function ColumnHeaderCell({
6365
6415
  };
6366
6416
  const markerLabels = marker ? markerCopy(marker) : void 0;
6367
6417
  const accessibleLabel = markerLabels?.label ?? labelText;
6368
- const menuLabel = filterKind ? accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column" : accessibleLabel ? `Sort ${accessibleLabel}` : "Sort column";
6418
+ const menuLabel = filterKind ? accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column" : sortable ? accessibleLabel ? `Sort ${accessibleLabel}` : "Sort column" : accessibleLabel ? `${accessibleLabel} column options` : "Column options";
6369
6419
  const menuContent = /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
6370
6420
  PopoverContent,
6371
6421
  {
@@ -6491,12 +6541,20 @@ function ColumnHeaderCell({
6491
6541
  filterShowCancel ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Button, { type: "button", variant: "ghost", size: "sm", onClick: cancelFilter, children: "Cancel" }) : null,
6492
6542
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Button, { type: "button", size: "sm", onClick: applyFilter, children: filterApplyLabel ?? "Apply" })
6493
6543
  ] })
6494
- ] }) : null
6544
+ ] }) : null,
6545
+ hasMenuItems ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6546
+ "div",
6547
+ {
6548
+ className: cn((sortable || filterKind) && "mt-2 border-t border-border pt-2"),
6549
+ "data-matrx-header-menu": "consumer",
6550
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(HeaderMenuItems, { items: menuItems, MenuIcon, onDone: () => setFilterOpen(false) })
6551
+ }
6552
+ ) : null
6495
6553
  ]
6496
6554
  }
6497
6555
  );
6498
6556
  if (compact) {
6499
- if (!sortable && !filterKind) {
6557
+ if (!sortable && !filterKind && !hasMenuItems) {
6500
6558
  if (!reorderEnabled) {
6501
6559
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("flex items-center justify-center", className), children: label });
6502
6560
  }
@@ -6617,7 +6675,7 @@ function ColumnHeaderCell({
6617
6675
  "."
6618
6676
  ] })
6619
6677
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
6620
- filterKind || sortable ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "inline-flex items-center", children: [
6678
+ filterKind || sortable || hasMenuItems ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "inline-flex items-center", children: [
6621
6679
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Popover, { open: filterOpen, onOpenChange: setFilterOpen, children: [
6622
6680
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Tooltip, { children: [
6623
6681
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -6632,7 +6690,7 @@ function ColumnHeaderCell({
6632
6690
  "rounded p-0.5 transition-colors",
6633
6691
  filterActive ? "text-primary hover:text-primary/80" : "text-muted-foreground/40 hover:text-muted-foreground"
6634
6692
  ),
6635
- children: filterKind ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ListFilter, { className: cn("h-3 w-3", filterActive && "fill-primary/20") }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArrowUpDown, { className: "h-3 w-3" })
6693
+ children: filterKind ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ListFilter, { className: cn("h-3 w-3", filterActive && "fill-primary/20") }) : sortable ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArrowUpDown, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ChevronDown, { className: "h-3 w-3" })
6636
6694
  }
6637
6695
  ) }) }),
6638
6696
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipContent, { children: menuLabel })
@@ -8523,6 +8581,7 @@ function camePastAPortalIntoTheContainer(target) {
8523
8581
  if (!element) return false;
8524
8582
  return element.closest('[data-matrx-table-spreadsheet="true"]') === null;
8525
8583
  }
8584
+ var READ_ONLY_REASON = "This table is view-only \u2014 you can select and copy, but not change cells.";
8526
8585
  function useSpreadsheetGrid(options) {
8527
8586
  const [range, setRange] = (0, import_react15.useState)(null);
8528
8587
  const [fillTarget, setFillTarget] = (0, import_react15.useState)(null);
@@ -8589,10 +8648,18 @@ function useSpreadsheetGrid(options) {
8589
8648
  }));
8590
8649
  setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));
8591
8650
  }
8651
+ if (!current.onPatch) return;
8592
8652
  await current.onPatch(patches, intent);
8593
8653
  },
8594
8654
  []
8595
8655
  );
8656
+ const refuseWrite = (0, import_react15.useCallback)(() => {
8657
+ const current = optionsRef.current;
8658
+ if (!current) return true;
8659
+ if (!current.readOnly && current.onPatch) return false;
8660
+ current.notify?.(current.readOnlyReason ?? READ_ONLY_REASON);
8661
+ return true;
8662
+ }, []);
8596
8663
  const readValueAt = (0, import_react15.useCallback)((rowIndex, columnIndex) => {
8597
8664
  const current = optionsRef.current;
8598
8665
  const rowId = current?.rowIds[rowIndex];
@@ -8626,6 +8693,7 @@ function useSpreadsheetGrid(options) {
8626
8693
  []
8627
8694
  );
8628
8695
  const doFillDown = (0, import_react15.useCallback)(async () => {
8696
+ if (refuseWrite()) return;
8629
8697
  const base = fillRequestBase();
8630
8698
  if (!base || !bounds) return;
8631
8699
  const result = fillDown({
@@ -8635,8 +8703,9 @@ function useSpreadsheetGrid(options) {
8635
8703
  const screened = screenPatches(result.patches);
8636
8704
  await applyPatches(screened.accepted, "fill-down", "Filled down");
8637
8705
  announceSkips([...result.skipped, ...screened.refusals], result.summary);
8638
- }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches]);
8706
+ }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);
8639
8707
  const doClear = (0, import_react15.useCallback)(async () => {
8708
+ if (refuseWrite()) return;
8640
8709
  const current = optionsRef.current;
8641
8710
  if (!current || !bounds) return;
8642
8711
  const patches = [];
@@ -8663,20 +8732,22 @@ function useSpreadsheetGrid(options) {
8663
8732
  [...skipped, ...screened.refusals],
8664
8733
  `Cleared ${screened.accepted.length} cells.`
8665
8734
  );
8666
- }, [bounds, applyPatches, announceSkips, screenPatches]);
8735
+ }, [bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);
8667
8736
  const doUndo = (0, import_react15.useCallback)(async () => {
8737
+ if (refuseWrite()) return;
8668
8738
  const taken = popUndo(stack);
8669
8739
  if (!taken.entry) return;
8670
8740
  setStack(taken.stack);
8671
8741
  await applyPatches(taken.entry.undo, "undo", taken.entry.label);
8672
8742
  optionsRef.current?.notify?.(taken.entry.label);
8673
- }, [stack, applyPatches]);
8743
+ }, [stack, applyPatches, refuseWrite]);
8674
8744
  const doRedo = (0, import_react15.useCallback)(async () => {
8745
+ if (refuseWrite()) return;
8675
8746
  const taken = popRedo(stack);
8676
8747
  if (!taken.entry) return;
8677
8748
  setStack(taken.stack);
8678
8749
  await applyPatches(taken.entry.redo, "redo", taken.entry.label);
8679
- }, [stack, applyPatches]);
8750
+ }, [stack, applyPatches, refuseWrite]);
8680
8751
  const move = (0, import_react15.useCallback)(
8681
8752
  (direction, extend) => {
8682
8753
  setRange((previous) => {
@@ -8752,7 +8823,7 @@ function useSpreadsheetGrid(options) {
8752
8823
  case "edit":
8753
8824
  case "editSeeded": {
8754
8825
  const focused = range?.focus;
8755
- if (!focused || !current.onEditRequest) return;
8826
+ if (!focused || !current.onEditRequest || current.readOnly) return;
8756
8827
  const seed = action.kind === "editSeeded" ? action.seed : null;
8757
8828
  if (current.onEditRequest(focused, seed)) event.preventDefault();
8758
8829
  return;
@@ -8809,6 +8880,7 @@ function useSpreadsheetGrid(options) {
8809
8880
  const text = event.clipboardData?.getData("text/plain") ?? "";
8810
8881
  if (!text) return;
8811
8882
  event.preventDefault();
8883
+ if (refuseWrite()) return;
8812
8884
  const block = parseClipboardGrid(text);
8813
8885
  const start = range.focus;
8814
8886
  const startRow = rowIds.indexOf(start.rowId);
@@ -8871,7 +8943,7 @@ function useSpreadsheetGrid(options) {
8871
8943
  const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : "";
8872
8944
  announceSkips(refusals, `Pasted ${screened.accepted.length} cells.${tail}`);
8873
8945
  },
8874
- [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches]
8946
+ [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches, refuseWrite]
8875
8947
  );
8876
8948
  const cellProps = (0, import_react15.useCallback)(
8877
8949
  (rowId, columnId2) => {
@@ -8922,7 +8994,7 @@ function useSpreadsheetGrid(options) {
8922
8994
  const isFillHandleCell = (0, import_react15.useCallback)(
8923
8995
  (rowId, columnId2) => {
8924
8996
  const current = optionsRef.current;
8925
- if (!current || current.fillHandle === false || !bounds) return false;
8997
+ if (!current || current.fillHandle === false || current.readOnly || !bounds) return false;
8926
8998
  return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId2;
8927
8999
  },
8928
9000
  [bounds, rowIds, columnIds]
@@ -8949,6 +9021,7 @@ function useSpreadsheetGrid(options) {
8949
9021
  const source = boundsRef.current;
8950
9022
  setFillTarget(null);
8951
9023
  if (!target || !source) return;
9024
+ if (refuseWrite()) return;
8952
9025
  const base = fillRequestBase();
8953
9026
  if (!base) return;
8954
9027
  const result = fillRange({
@@ -8962,7 +9035,7 @@ function useSpreadsheetGrid(options) {
8962
9035
  };
8963
9036
  window.addEventListener("mouseup", commit);
8964
9037
  return () => window.removeEventListener("mouseup", commit);
8965
- }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches]);
9038
+ }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches, refuseWrite]);
8966
9039
  return {
8967
9040
  range,
8968
9041
  bounds,
@@ -10253,6 +10326,35 @@ function toText(value) {
10253
10326
  return String(value);
10254
10327
  return null;
10255
10328
  }
10329
+ function parseTimeOfDay(value) {
10330
+ if (value instanceof Date) {
10331
+ return { h: value.getHours(), m: value.getMinutes(), s: value.getSeconds() };
10332
+ }
10333
+ if (typeof value !== "string") return null;
10334
+ const text = value.trim();
10335
+ if (!text) return null;
10336
+ const m = /^(\d{1,2})(?::(\d{2}))?(?::(\d{2}))?\s*([aApP]\.?[mM]\.?)?$/.exec(text);
10337
+ if (m) {
10338
+ let h = Number(m[1]);
10339
+ const min = m[2] ? Number(m[2]) : 0;
10340
+ const sec = m[3] ? Number(m[3]) : 0;
10341
+ const ampm = m[4]?.toLowerCase().replace(/\./g, "");
10342
+ if (ampm) {
10343
+ if (h < 1 || h > 12) return null;
10344
+ if (ampm === "pm" && h !== 12) h += 12;
10345
+ if (ampm === "am" && h === 12) h = 0;
10346
+ }
10347
+ if (h > 23 || min > 59 || sec > 59) return null;
10348
+ return { h, m: min, s: sec };
10349
+ }
10350
+ if (/\d{4}-\d{2}-\d{2}T/.test(text)) {
10351
+ const d = new Date(text);
10352
+ if (!Number.isNaN(d.getTime())) {
10353
+ return { h: d.getHours(), m: d.getMinutes(), s: d.getSeconds() };
10354
+ }
10355
+ }
10356
+ return null;
10357
+ }
10256
10358
  var BARE_CALENDAR_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]00:00(?::00(?:\.0+)?)?)?$/;
10257
10359
  function toDate(value) {
10258
10360
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
@@ -10373,6 +10475,23 @@ var DEFS = [
10373
10475
  },
10374
10476
  parse: (raw) => raw === "" || raw == null ? null : String(raw).trim()
10375
10477
  },
10478
+ {
10479
+ // A postal address. Free text by design (addresses have no one shape
10480
+ // worldwide); the cell opens it in maps, which is the thing people do
10481
+ // with an address in a table.
10482
+ id: "address",
10483
+ label: "Address",
10484
+ description: "Street address \u2014 click to open in maps",
10485
+ group: "Text",
10486
+ base: "string",
10487
+ editor: "text",
10488
+ rich: true,
10489
+ format: (v) => {
10490
+ const t = toText(v)?.trim();
10491
+ return t ? t : null;
10492
+ },
10493
+ parse: (raw) => raw === "" || raw == null ? null : String(raw)
10494
+ },
10376
10495
  {
10377
10496
  id: "url",
10378
10497
  label: "Link",
@@ -10506,6 +10625,26 @@ var DEFS = [
10506
10625
  },
10507
10626
  parse: (raw) => toNumber(raw)
10508
10627
  },
10628
+ {
10629
+ // A percentage DRAWN as a bar — task completion, quota used, a score out of
10630
+ // 100. Same storage and scale rule as `percent`; only the rendering differs.
10631
+ id: "progress",
10632
+ label: "Progress bar",
10633
+ description: "A percentage shown as a bar",
10634
+ group: "Numbers",
10635
+ base: "number",
10636
+ alsoAccepts: ["integer"],
10637
+ editor: "number",
10638
+ rich: true,
10639
+ optionKeys: ["percentScale"],
10640
+ format: (v, o) => {
10641
+ const n2 = toNumber(v);
10642
+ if (n2 === null) return null;
10643
+ const shown = o.percentScale === "fraction" ? n2 * 100 : n2;
10644
+ return `${groupedNumber(shown, { precision: 0 })}%`;
10645
+ },
10646
+ parse: (raw) => toNumber(raw)
10647
+ },
10509
10648
  {
10510
10649
  id: "duration",
10511
10650
  label: "Duration",
@@ -10574,13 +10713,16 @@ var DEFS = [
10574
10713
  alsoAccepts: ["number"],
10575
10714
  editor: "number",
10576
10715
  numericAlign: true,
10716
+ // The name says BYTES on purpose: this cell holds a genuine byte length,
10717
+ // never a character or item count, and `formatFileSize` is only honest
10718
+ // about a quantity that is actually bytes.
10577
10719
  format: (v) => {
10578
- const n2 = toNumber(v);
10579
- return n2 === null ? null : (0, import_format3.formatFileSize)(n2);
10720
+ const byteLength = toNumber(v);
10721
+ return byteLength === null ? null : (0, import_format3.formatFileSize)(byteLength);
10580
10722
  },
10581
10723
  parse: (raw) => {
10582
- const n2 = toNumber(raw);
10583
- return n2 === null ? null : Math.trunc(n2);
10724
+ const byteLength = toNumber(raw);
10725
+ return byteLength === null ? null : Math.trunc(byteLength);
10584
10726
  }
10585
10727
  },
10586
10728
  // ── Choice ────────────────────────────────────────────────────────────────
@@ -10641,6 +10783,87 @@ var DEFS = [
10641
10783
  },
10642
10784
  parse: (raw) => raw === "" || raw == null ? null : String(raw)
10643
10785
  },
10786
+ {
10787
+ // A TIME OF DAY with no date: opening hours, a shift start, a meeting slot.
10788
+ // Stored as the string the <input type="time"> gives ("HH:MM" or
10789
+ // "HH:MM:SS"), shown in the viewer's locale ("2:30 PM" / "14:30"). The
10790
+ // storage type stays `string`; sorting is lexical on the 24-hour form,
10791
+ // which is chronological — the one reason the stored form is 24-hour.
10792
+ id: "time",
10793
+ label: "Time",
10794
+ description: "Time of day, without a date",
10795
+ group: "Dates",
10796
+ base: "string",
10797
+ editor: "time",
10798
+ optionKeys: ["timeSeconds"],
10799
+ format: (v, o) => {
10800
+ const parts = parseTimeOfDay(v);
10801
+ if (!parts) return null;
10802
+ const d = new Date(2e3, 0, 1, parts.h, parts.m, parts.s);
10803
+ return d.toLocaleTimeString(void 0, {
10804
+ hour: "numeric",
10805
+ minute: "2-digit",
10806
+ ...o.timeSeconds ? { second: "2-digit" } : {}
10807
+ });
10808
+ },
10809
+ // Normalise to the 24-hour "HH:MM[:SS]" form; a value that is not a time
10810
+ // is handed back unchanged so the validation layer can refuse it by name.
10811
+ parse: (raw) => {
10812
+ if (raw == null || raw === "") return null;
10813
+ const parts = parseTimeOfDay(raw);
10814
+ if (!parts) return raw;
10815
+ const two = (n2) => String(n2).padStart(2, "0");
10816
+ const fields = parts.s > 0 ? [parts.h, parts.m, parts.s] : [parts.h, parts.m];
10817
+ return fields.map(two).join(":");
10818
+ }
10819
+ },
10820
+ {
10821
+ // Assigned BY THE DATABASE when the row is inserted (trigger
10822
+ // `_udt_autonumber`), so it is the same number whichever door made the row
10823
+ // and never shifts when another row is deleted. Stored in the cell as an
10824
+ // integer; every editor refuses it. `prefix` turns 42 into "INV-42".
10825
+ id: "autonumber",
10826
+ label: "Autonumber",
10827
+ description: "A number the table assigns to each new row \u2014 never typed",
10828
+ group: "Numbers",
10829
+ base: "integer",
10830
+ alsoAccepts: ["number"],
10831
+ editor: "computed",
10832
+ numericAlign: true,
10833
+ optionKeys: ["prefix"],
10834
+ format: (v, o) => {
10835
+ const n2 = toNumber(v);
10836
+ if (n2 === null) return null;
10837
+ return `${o.prefix ?? ""}${Math.trunc(n2)}`;
10838
+ },
10839
+ parse: () => null
10840
+ },
10841
+ // ── System columns ────────────────────────────────────────────────────────
10842
+ // Filled from the ROW'S OWN RECORD on read (`withComputedColumns` in
10843
+ // `@ai-matrx/design-system/formulas`), never typed and never stored in the
10844
+ // cell. `editor: "computed"` makes every editor refuse them.
10845
+ {
10846
+ id: "created_time",
10847
+ label: "Created time",
10848
+ description: "When the row was added \u2014 filled in automatically",
10849
+ group: "Dates",
10850
+ base: "string",
10851
+ editor: "computed",
10852
+ optionKeys: ["dateStyle"],
10853
+ format: (v, o) => FIELD_FORMATS.datetime.format(v, o),
10854
+ parse: () => null
10855
+ },
10856
+ {
10857
+ id: "modified_time",
10858
+ label: "Last modified time",
10859
+ description: "When the row last changed \u2014 filled in automatically",
10860
+ group: "Dates",
10861
+ base: "string",
10862
+ editor: "computed",
10863
+ optionKeys: ["dateStyle"],
10864
+ format: (v, o) => FIELD_FORMATS.datetime.format(v, o),
10865
+ parse: () => null
10866
+ },
10644
10867
  {
10645
10868
  id: "relative_time",
10646
10869
  label: "Relative time",
@@ -10697,6 +10920,44 @@ var DEFS = [
10697
10920
  return toList(raw) ?? raw;
10698
10921
  }
10699
10922
  },
10923
+ {
10924
+ // FILES ATTACHED TO THE ROW. The cell stores the files feature's file ids
10925
+ // (`files.files.id`), never a URL or a name: a rename or a move in Files
10926
+ // changes nothing here, and access is the file's own (RLS) — a reader who
10927
+ // may see the row but not the file gets the chip's honest "no access", not
10928
+ // a broken link. Drawn by `AttachmentChips`, edited by `AttachmentInput`
10929
+ // through the ONE file picker.
10930
+ id: "attachment",
10931
+ label: "Attachments",
10932
+ description: "Files from your Files, shown as chips",
10933
+ group: "Structured",
10934
+ base: "array",
10935
+ // Not `string`: on a text column it must appear under "Other kinds" so
10936
+ // picking it retypes the column to a list, rather than storing a JSON
10937
+ // string of ids in a text cell.
10938
+ alsoAccepts: ["json"],
10939
+ editor: "attachment",
10940
+ rich: true,
10941
+ format: (v) => {
10942
+ const list = toList(v);
10943
+ if (list === null) return null;
10944
+ const n2 = list.filter((i) => String(i).trim()).length;
10945
+ return n2 === 0 ? "" : n2 === 1 ? "1 file" : `${n2} files`;
10946
+ },
10947
+ parse: (raw) => {
10948
+ if (raw == null || raw === "") return null;
10949
+ if (Array.isArray(raw)) return raw.map((i) => String(i).trim()).filter(Boolean);
10950
+ const text = String(raw).trim();
10951
+ if (text.startsWith("[")) {
10952
+ try {
10953
+ const parsed = JSON.parse(text);
10954
+ if (Array.isArray(parsed)) return parsed.map((i) => String(i).trim()).filter(Boolean);
10955
+ } catch {
10956
+ }
10957
+ }
10958
+ return text.split(",").map((s) => s.trim()).filter(Boolean);
10959
+ }
10960
+ },
10700
10961
  {
10701
10962
  id: "tags",
10702
10963
  label: "Tags",
@@ -10760,6 +11021,82 @@ var DEFS = [
10760
11021
  return trimmed === "" ? null : trimmed;
10761
11022
  }
10762
11023
  },
11024
+ {
11025
+ // A PERSON: one member of the organization the table belongs to, stored as
11026
+ // the user id. It is a choice column whose options are the members —
11027
+ // supplied at runtime by whoever renders it (features/data-tables/
11028
+ // person-choices.tsx), the same door a structured-list-bound column uses —
11029
+ // so the cell picker, the chip, filters, color-by and the agent scope are
11030
+ // all the choice system's, with no second implementation.
11031
+ id: "person",
11032
+ label: "Person",
11033
+ description: "Someone in your organization \u2014 pick from its members",
11034
+ group: "Choice",
11035
+ base: "string",
11036
+ editor: "select",
11037
+ rich: true,
11038
+ format: (v, o) => FIELD_FORMATS.choice.format(v, o),
11039
+ parse: (raw) => FIELD_FORMATS.choice.parse(raw, {})
11040
+ },
11041
+ {
11042
+ // A RECORD OF ANOTHER TABLE. The cell stores that record's ID; the screen
11043
+ // shows its WORDS. `person` is the precedent and the template — it has
11044
+ // stored a user id and displayed a name through `choiceMap` since
11045
+ // 2026-09-21 — so `relation` is a member of CHOICE_FORMAT_IDS and every
11046
+ // one of the ten readers that print a cell resolves it through the path
11047
+ // they already use for `person`. There is no eleventh place to remember.
11048
+ //
11049
+ // THREE STATES, THREE RENDERINGS, NO FOURTH (OLD-TABLES-CUTOVER §3.3):
11050
+ // resolved the target's words
11051
+ // unresolvable the amber fallback WITH the id, as an identifier — never
11052
+ // a blank, and never the bare uuid as if it were text, so a
11053
+ // person can act on it and support can trace it
11054
+ // withheld platform.relation_withheld_label(), with NO id — a cell
11055
+ // the reader may not see never leaks the identifier through
11056
+ // the gap between the two stores' refusals
11057
+ //
11058
+ // `format` therefore returns null (the fallback law's "cannot interpret")
11059
+ // for anything the resolver did not turn into words, and the renderer is
11060
+ // what distinguishes unresolvable from withheld. It never prints a bare id.
11061
+ id: "relation",
11062
+ label: "Relation",
11063
+ description: "A record of another table \u2014 pick it, and its name is shown",
11064
+ group: "Choice",
11065
+ base: "string",
11066
+ alsoAccepts: ["array", "json"],
11067
+ editor: "select",
11068
+ rich: true,
11069
+ optionKeys: ["relation_target", "relation_max", "on_delete", "display"],
11070
+ // IT DOES NOT DELEGATE TO `choice`, AND THAT IS THE WHOLE POINT.
11071
+ // `choice.format` deliberately returns the RAW VALUE when no options have
11072
+ // resolved yet — a structured list still loading should not make every cell
11073
+ // look off-list. For a label column that is right: the raw value IS the
11074
+ // label. For an identifier column it is the defect §3.3 names, and it is
11075
+ // how the first cut of this wave put four raw uuids on a real grid: the
11076
+ // fallback arm never ran, because returning the uuid counted as SUCCESS.
11077
+ //
11078
+ // Here, a value that did not resolve to words is a MISMATCH — `null` — so
11079
+ // the renderer's three-state arm owns it and shows the amber fallback with
11080
+ // the id marked as an identifier, or the withheld sentence. The id never
11081
+ // reaches the eye as if it were the cell's contents.
11082
+ format: (v, options) => {
11083
+ const list = toList(v);
11084
+ const values = list !== null ? list : v == null ? [] : [v];
11085
+ if (values.length === 0) return "";
11086
+ const words2 = [];
11087
+ for (const entry of values) {
11088
+ const text = toText(entry);
11089
+ if (text === null) return null;
11090
+ const trimmed = text.trim();
11091
+ if (trimmed === "") continue;
11092
+ const match = findChoice(options.choices, trimmed);
11093
+ if (!match) return null;
11094
+ words2.push(match.label ?? match.value);
11095
+ }
11096
+ return words2.length === 0 ? "" : words2.join(", ");
11097
+ },
11098
+ parse: (raw) => FIELD_FORMATS.choice.parse(raw, {})
11099
+ },
10763
11100
  {
10764
11101
  id: "multi_choice",
10765
11102
  label: "Multi-choice",
@@ -10804,7 +11141,7 @@ var DEFS = [
10804
11141
  // A formula column stores NOTHING. `metadata.format.options.formula` holds
10805
11142
  // the expression; the stored cell is always null; the value is computed on
10806
11143
  // read from the other columns of the same row
10807
- // (`features/data-tables/formulas.ts`). Grouped under Structured on purpose
11144
+ // (`@ai-matrx/design-system/formulas`). Grouped under Structured on purpose
10808
11145
  // — the picker's groups are a fixed vocabulary, and a formula is structure
10809
11146
  // over a plain string column, not a sixth family.
10810
11147
  //
@@ -13380,12 +13717,15 @@ function MatrxDataTableCore({
13380
13717
  return getCellValue(applyRowEdits(row, edits[rowId]), col);
13381
13718
  },
13382
13719
  isColumnWritable: (colId) => {
13720
+ if (spreadsheet.readOnly) return false;
13383
13721
  if (spreadsheet.writableColumnIds) return spreadsheet.writableColumnIds.includes(colId);
13384
13722
  const col = spreadsheetColumnById.get(colId);
13385
13723
  return Boolean(col && col.editable);
13386
13724
  },
13387
13725
  ...spreadsheet.columnRefusal ? { columnRefusal: spreadsheet.columnRefusal } : {},
13388
- onPatch: spreadsheet.onPatch,
13726
+ ...spreadsheet.onPatch ? { onPatch: spreadsheet.onPatch } : {},
13727
+ ...spreadsheet.readOnly ? { readOnly: true } : {},
13728
+ ...spreadsheet.readOnlyReason ? { readOnlyReason: spreadsheet.readOnlyReason } : {},
13389
13729
  ...spreadsheet.undoDepth === void 0 ? {} : { undoDepth: spreadsheet.undoDepth },
13390
13730
  ...spreadsheet.fillHandle === void 0 ? {} : { fillHandle: spreadsheet.fillHandle },
13391
13731
  ...spreadsheet.paste === void 0 ? {} : { paste: spreadsheet.paste },
@@ -13945,7 +14285,7 @@ function MatrxDataTableCore({
13945
14285
  setEdits({});
13946
14286
  edit?.onCancel?.();
13947
14287
  };
13948
- const resolveMenuLevel = (target) => {
14288
+ const menuPayloadFor = (target) => {
13949
14289
  const rowFor = (rowId) => {
13950
14290
  const row = data.find((candidate) => getRowId(candidate) === rowId);
13951
14291
  if (!row) return null;
@@ -13953,14 +14293,13 @@ function MatrxDataTableCore({
13953
14293
  };
13954
14294
  const columnFor = (id) => visibleColumns.find((col) => columnId(col) === id);
13955
14295
  const labelFor = (col, id) => col?.label ?? (typeof col?.header === "string" ? col.header : id);
13956
- const fromHost = (payload) => host.createDefaultMenuContext?.(payload) ?? null;
13957
14296
  switch (target.level) {
13958
14297
  case "row": {
13959
14298
  if (!target.rowId) return null;
13960
14299
  const displayRow = rowFor(target.rowId);
13961
14300
  if (!displayRow) return null;
13962
14301
  const controls = buildRecordControls(displayRow);
13963
- return contextMenu?.resolveRowContext?.(displayRow, controls) ?? fromHost({ level: "row", row: displayRow, rowId: target.rowId, controls });
14302
+ return { payload: { level: "row", row: displayRow, rowId: target.rowId, controls }, controls };
13964
14303
  }
13965
14304
  case "cell": {
13966
14305
  if (!target.rowId || !target.columnId) return null;
@@ -13969,45 +14308,94 @@ function MatrxDataTableCore({
13969
14308
  if (!displayRow || !col) return null;
13970
14309
  const controls = buildRecordControls(displayRow);
13971
14310
  const value = spreadsheet?.readCell ? spreadsheet.readCell(displayRow, target.columnId) : getCellValue(displayRow, col);
13972
- const args = {
13973
- row: displayRow,
13974
- rowId: target.rowId,
13975
- columnId: target.columnId,
13976
- columnLabel: labelFor(col, target.columnId),
13977
- value,
13978
- text: stringifyCellValue(value),
14311
+ return {
14312
+ payload: {
14313
+ level: "cell",
14314
+ row: displayRow,
14315
+ rowId: target.rowId,
14316
+ columnId: target.columnId,
14317
+ columnLabel: labelFor(col, target.columnId),
14318
+ value,
14319
+ text: stringifyCellValue(value),
14320
+ controls
14321
+ },
13979
14322
  controls
13980
14323
  };
13981
- return contextMenu?.resolveCellContext?.(args) ?? fromHost({ level: "cell", ...args });
13982
14324
  }
13983
14325
  case "column": {
13984
14326
  if (!target.columnId) return null;
13985
14327
  const col = columnFor(target.columnId);
13986
14328
  if (!col) return null;
13987
- const args = {
13988
- columnId: target.columnId,
13989
- columnLabel: labelFor(col, target.columnId),
13990
- values: paginated.map(
13991
- (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
13992
- )
14329
+ return {
14330
+ payload: {
14331
+ level: "column",
14332
+ columnId: target.columnId,
14333
+ columnLabel: labelFor(col, target.columnId),
14334
+ values: paginated.map(
14335
+ (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
14336
+ )
14337
+ }
13993
14338
  };
13994
- return contextMenu?.resolveColumnContext?.(args) ?? fromHost({ level: "column", ...args });
13995
14339
  }
13996
14340
  case "selection": {
13997
14341
  const addresses = sheetRef.current.selectionAddresses();
13998
14342
  const cellCount = addresses.rowIds.length * addresses.columnIds.length;
13999
- if (cellCount <= 1) {
14000
- return resolveMenuLevel({ ...target, level: "cell" });
14001
- }
14002
- const args = { ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount };
14003
- return contextMenu?.resolveSelectionContext?.(args) ?? fromHost({ level: "selection", ...args });
14343
+ if (cellCount <= 1) return menuPayloadFor({ ...target, level: "cell" });
14344
+ return {
14345
+ payload: { level: "selection", ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount }
14346
+ };
14347
+ }
14348
+ case "table":
14349
+ return {
14350
+ payload: { level: "table", rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) }
14351
+ };
14352
+ }
14353
+ };
14354
+ const resolveMenuLevel = (target) => {
14355
+ const built = menuPayloadFor(target);
14356
+ if (!built) return null;
14357
+ const { payload } = built;
14358
+ const fromHost = () => host.createDefaultMenuContext?.(payload) ?? null;
14359
+ switch (payload.level) {
14360
+ case "row":
14361
+ return contextMenu?.resolveRowContext?.(payload.row, built.controls) ?? fromHost();
14362
+ case "cell": {
14363
+ const { level: _level, ...args } = payload;
14364
+ return contextMenu?.resolveCellContext?.({ ...args, controls: built.controls }) ?? fromHost();
14365
+ }
14366
+ case "column": {
14367
+ const { level: _level, ...args } = payload;
14368
+ return contextMenu?.resolveColumnContext?.(args) ?? fromHost();
14369
+ }
14370
+ case "selection": {
14371
+ const { level: _level, ...args } = payload;
14372
+ return contextMenu?.resolveSelectionContext?.(args) ?? fromHost();
14004
14373
  }
14005
14374
  case "table": {
14006
- const args = { rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) };
14007
- return contextMenu?.resolveTableContext?.(args) ?? fromHost({ level: "table", ...args });
14375
+ const { level: _level, ...args } = payload;
14376
+ return contextMenu?.resolveTableContext?.(args) ?? fromHost();
14008
14377
  }
14009
14378
  }
14010
14379
  };
14380
+ const menuSectionsFor = (target) => {
14381
+ const resolve2 = contextMenu?.sections;
14382
+ if (!resolve2 || target.tableId !== tableContextMenuInstanceId) return [];
14383
+ const built = menuPayloadFor(target);
14384
+ if (!built) return [];
14385
+ try {
14386
+ return resolve2(built.payload);
14387
+ } catch (error) {
14388
+ console.error("[MatrxDataTable] contextMenu.sections threw; the menu opens without them.", error);
14389
+ return [];
14390
+ }
14391
+ };
14392
+ const menuSectionsForRef = (0, import_react21.useRef)(menuSectionsFor);
14393
+ menuSectionsForRef.current = menuSectionsFor;
14394
+ const hasMenuSections = Boolean(contextMenu?.sections);
14395
+ const boundarySections = (0, import_react21.useCallback)(
14396
+ (target) => menuSectionsForRef.current(target),
14397
+ []
14398
+ );
14011
14399
  const resolveMenuLevelRef = (0, import_react21.useRef)(resolveMenuLevel);
14012
14400
  resolveMenuLevelRef.current = resolveMenuLevel;
14013
14401
  (0, import_react21.useEffect)(() => {
@@ -14220,959 +14608,640 @@ function MatrxDataTableCore({
14220
14608
  },
14221
14609
  [columns, commitColumnState, commitColumnWidths, controlledQuery, intelligentSearchEnabled, intelligentSearchScopes, toolbar]
14222
14610
  );
14223
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ContextMenuBoundary, { label: tableId ?? "Data table", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14224
- import_core3.DndContext,
14611
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14612
+ ContextMenuBoundary,
14225
14613
  {
14226
- id: tableDndContextId,
14227
- sensors: hierarchySensors,
14228
- collisionDetection: import_core3.pointerWithin,
14229
- accessibility: {
14230
- screenReaderInstructions: {
14231
- draggable: "Drag with a pointer. To reorder columns with a keyboard, open Columns and use Move earlier or Move later."
14232
- }
14233
- },
14234
- measuring: { droppable: { strategy: import_core3.MeasuringStrategy.Always } },
14235
- onDragStart: (event) => {
14236
- const kind = event.active.data.current?.matrxDragKind;
14237
- if (kind === "column-header") {
14238
- const draggedId = String(event.active.data.current?.columnId);
14239
- draggedColumnId.current = draggedId;
14240
- columnDragActive.current = true;
14241
- setColumnDragPreview({ draggedId, targetId: null, after: false });
14242
- return;
14243
- }
14244
- if (kind === "column-manager") return;
14245
- setDraggedRowId(String(event.active.id));
14246
- },
14247
- onDragMove: (event) => {
14248
- if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
14249
- const overData = event.over?.data.current;
14250
- const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
14251
- const activeRect = event.active.rect.current.translated;
14252
- const overRect = event.over?.rect;
14253
- if (!overId || !activeRect || !overRect) {
14254
- setColumnDragPreview(
14255
- (current) => current?.targetId === null ? current : current ? { ...current, targetId: null } : current
14256
- );
14257
- columnDragScrollDirection.current = 0;
14258
- stopColumnDragScroll();
14259
- return;
14260
- }
14261
- const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14262
- setColumnDragPreview((current) => {
14263
- if (!current) return current;
14264
- return current.targetId === overId && current.after === after ? current : { ...current, targetId: String(overId), after };
14265
- });
14266
- const scroll = scrollRef.current;
14267
- if (!scroll) return;
14268
- const bounds = scroll.getBoundingClientRect();
14269
- const center = activeRect.left + activeRect.width / 2;
14270
- columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
14271
- if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
14272
- else startColumnDragScroll();
14273
- },
14274
- onDragCancel: () => {
14275
- columnDragActive.current = false;
14276
- draggedColumnId.current = null;
14277
- setColumnDragPreview(null);
14278
- stopColumnDragScroll();
14279
- setDraggedRowId(null);
14280
- },
14281
- onDragEnd: (event) => {
14282
- const kind = event.active.data.current?.matrxDragKind;
14283
- if (kind === "column-manager") return;
14284
- if (kind !== "column-header") {
14285
- endHierarchyDrag(event);
14286
- return;
14287
- }
14288
- const from = String(event.active.data.current?.columnId);
14289
- const targetData = event.over?.data.current;
14290
- const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
14291
- const activeRect = event.active.rect.current.translated;
14292
- const overRect = event.over?.rect;
14293
- columnDragActive.current = false;
14294
- draggedColumnId.current = null;
14295
- setColumnDragPreview(null);
14296
- stopColumnDragScroll();
14297
- if (columnDragPreview?.draggedId === from && columnDragPreview.targetId !== null && columnDragPreview.targetId !== from) {
14298
- commitColumnState(
14299
- projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
14300
- effectiveHiddenColumnIds
14301
- );
14302
- return;
14303
- }
14304
- if (!target || from === target || !activeRect || !overRect) return;
14305
- const order = [...effectiveColumnOrder];
14306
- const fromIndex = order.indexOf(from);
14307
- const targetIndex = order.indexOf(String(target));
14308
- if (fromIndex < 0 || targetIndex < 0) return;
14309
- order.splice(fromIndex, 1);
14310
- const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14311
- const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
14312
- order.splice(insertion, 0, from);
14313
- commitColumnState(order, effectiveHiddenColumnIds);
14314
- },
14315
- children: [
14316
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14317
- "div",
14318
- {
14319
- ref: rootRef,
14320
- "data-matrx-table": true,
14321
- "data-matrx-table-id": tableContextMenuInstanceId,
14322
- "data-matrx-table-implementation": "@ai-matrx/design-system",
14323
- "data-matrx-table-density": resolvedDensity,
14324
- "data-url-state-table": urlState?.id,
14325
- className: cn(
14326
- "flex h-full min-h-0 flex-col gap-2 px-0 py-2",
14327
- className
14328
- ),
14329
- children: [
14330
- notice,
14331
- savedViewError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: savedViewError }) : null,
14332
- toolbarActionError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: toolbarActionError }) : null,
14333
- placeToolbar(!hideToolbar && (viewTabs || toolbar?.title || toolbar?.titleCount || showSearch || toolbar?.customSearch || toolbar?.anyOf || toolbar?.layeredFilters || toolbar?.facets && toolbar.facets.length > 0 || toolbar?.leading || toolbar?.actions || toolbar?.refresh || toolbar?.add || tableId && SavedViews || hasActiveFilters || showToolbarCopy || columns.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14334
- TableTitleRow,
14335
- {
14336
- ...toolbar?.singleRow ? { singleRow: true } : {},
14337
- tabs: viewTabs ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TableViewWorkspace, { snapshot: currentViewSnapshot, onApply: applySavedView, related: relatedTableActions }, tableId ?? "table") : void 0,
14338
- ...toolbar?.title === void 0 ? {} : { title: toolbar.title },
14339
- ...toolbar?.titleCount === void 0 ? {} : { count: toolbar.titleCount },
14340
- context: tableId && SavedViews ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-view-control": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14341
- SavedViews,
14342
- {
14343
- tableId,
14344
- snapshot: currentViewSnapshot,
14345
- defaultSnapshot: declaredDefaultViewSnapshot,
14346
- onApply: applySavedView
14347
- }
14348
- ) }) : void 0,
14349
- search: toolbar?.customSearch ?? (showSearch ? intelligentSearchEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14350
- TableSearch,
14351
- {
14352
- value: searchValue,
14353
- onChange: setSearchValue,
14354
- scope: searchScope,
14355
- onScopeChange: setSearchScope,
14356
- scopes: intelligentSearchScopes,
14357
- ...toolbar?.searchPlaceholder === void 0 ? {} : { placeholder: toolbar.searchPlaceholder }
14358
- }
14359
- ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14360
- "div",
14614
+ label: tableId ?? "Data table",
14615
+ ...hasMenuSections ? { sections: boundarySections } : {},
14616
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14617
+ import_core3.DndContext,
14618
+ {
14619
+ id: tableDndContextId,
14620
+ sensors: hierarchySensors,
14621
+ collisionDetection: import_core3.pointerWithin,
14622
+ accessibility: {
14623
+ screenReaderInstructions: {
14624
+ draggable: "Drag with a pointer. To reorder columns with a keyboard, open Columns and use Move earlier or Move later."
14625
+ }
14626
+ },
14627
+ measuring: { droppable: { strategy: import_core3.MeasuringStrategy.Always } },
14628
+ onDragStart: (event) => {
14629
+ const kind = event.active.data.current?.matrxDragKind;
14630
+ if (kind === "column-header") {
14631
+ const draggedId = String(event.active.data.current?.columnId);
14632
+ draggedColumnId.current = draggedId;
14633
+ columnDragActive.current = true;
14634
+ setColumnDragPreview({ draggedId, targetId: null, after: false });
14635
+ return;
14636
+ }
14637
+ if (kind === "column-manager") return;
14638
+ setDraggedRowId(String(event.active.id));
14639
+ },
14640
+ onDragMove: (event) => {
14641
+ if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
14642
+ const overData = event.over?.data.current;
14643
+ const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
14644
+ const activeRect = event.active.rect.current.translated;
14645
+ const overRect = event.over?.rect;
14646
+ if (!overId || !activeRect || !overRect) {
14647
+ setColumnDragPreview(
14648
+ (current) => current?.targetId === null ? current : current ? { ...current, targetId: null } : current
14649
+ );
14650
+ columnDragScrollDirection.current = 0;
14651
+ stopColumnDragScroll();
14652
+ return;
14653
+ }
14654
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14655
+ setColumnDragPreview((current) => {
14656
+ if (!current) return current;
14657
+ return current.targetId === overId && current.after === after ? current : { ...current, targetId: String(overId), after };
14658
+ });
14659
+ const scroll = scrollRef.current;
14660
+ if (!scroll) return;
14661
+ const bounds = scroll.getBoundingClientRect();
14662
+ const center = activeRect.left + activeRect.width / 2;
14663
+ columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
14664
+ if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
14665
+ else startColumnDragScroll();
14666
+ },
14667
+ onDragCancel: () => {
14668
+ columnDragActive.current = false;
14669
+ draggedColumnId.current = null;
14670
+ setColumnDragPreview(null);
14671
+ stopColumnDragScroll();
14672
+ setDraggedRowId(null);
14673
+ },
14674
+ onDragEnd: (event) => {
14675
+ const kind = event.active.data.current?.matrxDragKind;
14676
+ if (kind === "column-manager") return;
14677
+ if (kind !== "column-header") {
14678
+ endHierarchyDrag(event);
14679
+ return;
14680
+ }
14681
+ const from = String(event.active.data.current?.columnId);
14682
+ const targetData = event.over?.data.current;
14683
+ const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
14684
+ const activeRect = event.active.rect.current.translated;
14685
+ const overRect = event.over?.rect;
14686
+ columnDragActive.current = false;
14687
+ draggedColumnId.current = null;
14688
+ setColumnDragPreview(null);
14689
+ stopColumnDragScroll();
14690
+ if (columnDragPreview?.draggedId === from && columnDragPreview.targetId !== null && columnDragPreview.targetId !== from) {
14691
+ commitColumnState(
14692
+ projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
14693
+ effectiveHiddenColumnIds
14694
+ );
14695
+ return;
14696
+ }
14697
+ if (!target || from === target || !activeRect || !overRect) return;
14698
+ const order = [...effectiveColumnOrder];
14699
+ const fromIndex = order.indexOf(from);
14700
+ const targetIndex = order.indexOf(String(target));
14701
+ if (fromIndex < 0 || targetIndex < 0) return;
14702
+ order.splice(fromIndex, 1);
14703
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14704
+ const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
14705
+ order.splice(insertion, 0, from);
14706
+ commitColumnState(order, effectiveHiddenColumnIds);
14707
+ },
14708
+ children: [
14709
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14710
+ "div",
14711
+ {
14712
+ ref: rootRef,
14713
+ "data-matrx-table": true,
14714
+ "data-matrx-table-id": tableContextMenuInstanceId,
14715
+ "data-matrx-table-implementation": "@ai-matrx/design-system",
14716
+ "data-matrx-table-density": resolvedDensity,
14717
+ "data-url-state-table": urlState?.id,
14718
+ className: cn(
14719
+ "flex h-full min-h-0 flex-col gap-2 px-0 py-2",
14720
+ className
14721
+ ),
14722
+ children: [
14723
+ notice,
14724
+ savedViewError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: savedViewError }) : null,
14725
+ toolbarActionError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: toolbarActionError }) : null,
14726
+ placeToolbar(!hideToolbar && (viewTabs || toolbar?.title || toolbar?.titleCount || showSearch || toolbar?.customSearch || toolbar?.anyOf || toolbar?.layeredFilters || toolbar?.facets && toolbar.facets.length > 0 || toolbar?.leading || toolbar?.actions || toolbar?.refresh || toolbar?.add || tableId && SavedViews || hasActiveFilters || showToolbarCopy || columns.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14727
+ TableTitleRow,
14361
14728
  {
14362
- "data-matrx-table-search": true,
14363
- className: cn(
14364
- "flex min-w-0 flex-1 sm:w-full sm:flex-none",
14365
- toolbar?.searchMatch ? "sm:max-w-md" : "sm:max-w-xs"
14366
- ),
14729
+ ...toolbar?.singleRow ? { singleRow: true } : {},
14730
+ tabs: viewTabs ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TableViewWorkspace, { snapshot: currentViewSnapshot, onApply: applySavedView, related: relatedTableActions }, tableId ?? "table") : void 0,
14731
+ ...toolbar?.title === void 0 ? {} : { title: toolbar.title },
14732
+ ...toolbar?.titleCount === void 0 ? {} : { count: toolbar.titleCount },
14733
+ context: tableId && SavedViews ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-view-control": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14734
+ SavedViews,
14735
+ {
14736
+ tableId,
14737
+ snapshot: currentViewSnapshot,
14738
+ defaultSnapshot: declaredDefaultViewSnapshot,
14739
+ onApply: applySavedView
14740
+ }
14741
+ ) }) : void 0,
14742
+ search: toolbar?.customSearch ?? (showSearch ? intelligentSearchEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14743
+ TableSearch,
14744
+ {
14745
+ value: searchValue,
14746
+ onChange: setSearchValue,
14747
+ scope: searchScope,
14748
+ onScopeChange: setSearchScope,
14749
+ scopes: intelligentSearchScopes,
14750
+ ...toolbar?.searchPlaceholder === void 0 ? {} : { placeholder: toolbar.searchPlaceholder }
14751
+ }
14752
+ ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14753
+ "div",
14754
+ {
14755
+ "data-matrx-table-search": true,
14756
+ className: cn(
14757
+ "flex min-w-0 flex-1 sm:w-full sm:flex-none",
14758
+ toolbar?.searchMatch ? "sm:max-w-md" : "sm:max-w-xs"
14759
+ ),
14760
+ children: [
14761
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
14762
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14763
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14764
+ Input,
14765
+ {
14766
+ value: searchValue,
14767
+ onChange: (e) => setSearchValue(e.target.value),
14768
+ placeholder: toolbar?.searchPlaceholder ?? "Search\u2026",
14769
+ className: cn(
14770
+ "h-8 pl-7 pr-7 text-sm",
14771
+ toolbar?.searchMatch && "rounded-r-none"
14772
+ ),
14773
+ style: { fontSize: "16px" }
14774
+ }
14775
+ ),
14776
+ searchValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14777
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14778
+ "button",
14779
+ {
14780
+ type: "button",
14781
+ "aria-label": "Clear search",
14782
+ className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14783
+ onClick: () => setSearchValue(""),
14784
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-5 w-5" })
14785
+ }
14786
+ ) }),
14787
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear search" })
14788
+ ] }) : null
14789
+ ] }),
14790
+ toolbar?.searchMatch ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14791
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14792
+ Button,
14793
+ {
14794
+ type: "button",
14795
+ variant: searchMatchMode === "whole_words" ? "secondary" : "outline",
14796
+ size: "sm",
14797
+ className: "h-8 shrink-0 gap-1 rounded-l-none border-l-0 px-2 text-xs",
14798
+ "aria-pressed": searchMatchMode === "whole_words",
14799
+ "aria-label": `Search match: ${searchMatchMode === "whole_words" ? "whole words" : "contains text"}`,
14800
+ onClick: () => setSearchMatchMode(
14801
+ searchMatchMode === "whole_words" ? "contains" : "whole_words"
14802
+ ),
14803
+ children: [
14804
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(WholeWord, { className: "h-5 w-5" }),
14805
+ searchMatchMode === "whole_words" ? "Whole words" : "Contains"
14806
+ ]
14807
+ }
14808
+ ) }),
14809
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: searchMatchMode === "whole_words" ? "Whole words \u2014 click to match text anywhere" : "Contains text \u2014 click to require whole words" })
14810
+ ] }) : null
14811
+ ]
14812
+ }
14813
+ ) : null),
14367
14814
  children: [
14368
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
14815
+ toolbar?.layeredFilters ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14816
+ LayeredFilterBuilder,
14817
+ {
14818
+ fields: toolbar.layeredFilters.fields,
14819
+ rules: layeredFilters,
14820
+ onChange: setLayeredFilters,
14821
+ maxRules: toolbar.layeredFilters.maxRules,
14822
+ label: toolbar.layeredFilters.label
14823
+ }
14824
+ ) : null,
14825
+ toolbar?.anyOf ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full max-w-xs", children: [
14369
14826
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14370
14827
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14371
14828
  Input,
14372
14829
  {
14373
- value: searchValue,
14374
- onChange: (e) => setSearchValue(e.target.value),
14375
- placeholder: toolbar?.searchPlaceholder ?? "Search\u2026",
14376
- className: cn(
14377
- "h-8 pl-7 pr-7 text-sm",
14378
- toolbar?.searchMatch && "rounded-r-none"
14379
- ),
14830
+ value: anyOfValue,
14831
+ onChange: (e) => setAnyOfValue(e.target.value),
14832
+ placeholder: toolbar.anyOf.placeholder ?? "Match any of\u2026",
14833
+ className: "h-8 pl-7 pr-7 text-sm",
14380
14834
  style: { fontSize: "16px" }
14381
14835
  }
14382
14836
  ),
14383
- searchValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14837
+ anyOfValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14384
14838
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14385
14839
  "button",
14386
14840
  {
14387
14841
  type: "button",
14388
- "aria-label": "Clear search",
14842
+ "aria-label": "Clear match-any search",
14389
14843
  className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14390
- onClick: () => setSearchValue(""),
14844
+ onClick: () => setAnyOfValue(""),
14391
14845
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-5 w-5" })
14392
14846
  }
14393
14847
  ) }),
14394
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear search" })
14848
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear match-any search" })
14395
14849
  ] }) : null
14396
- ] }),
14397
- toolbar?.searchMatch ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14398
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14399
- Button,
14400
- {
14401
- type: "button",
14402
- variant: searchMatchMode === "whole_words" ? "secondary" : "outline",
14403
- size: "sm",
14404
- className: "h-8 shrink-0 gap-1 rounded-l-none border-l-0 px-2 text-xs",
14405
- "aria-pressed": searchMatchMode === "whole_words",
14406
- "aria-label": `Search match: ${searchMatchMode === "whole_words" ? "whole words" : "contains text"}`,
14407
- onClick: () => setSearchMatchMode(
14408
- searchMatchMode === "whole_words" ? "contains" : "whole_words"
14409
- ),
14410
- children: [
14411
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(WholeWord, { className: "h-5 w-5" }),
14412
- searchMatchMode === "whole_words" ? "Whole words" : "Contains"
14413
- ]
14414
- }
14415
- ) }),
14416
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: searchMatchMode === "whole_words" ? "Whole words \u2014 click to match text anywhere" : "Contains text \u2014 click to require whole words" })
14417
- ] }) : null
14418
- ]
14419
- }
14420
- ) : null),
14421
- children: [
14422
- toolbar?.layeredFilters ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14423
- LayeredFilterBuilder,
14424
- {
14425
- fields: toolbar.layeredFilters.fields,
14426
- rules: layeredFilters,
14427
- onChange: setLayeredFilters,
14428
- maxRules: toolbar.layeredFilters.maxRules,
14429
- label: toolbar.layeredFilters.label
14430
- }
14431
- ) : null,
14432
- toolbar?.anyOf ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full max-w-xs", children: [
14433
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14434
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14435
- Input,
14436
- {
14437
- value: anyOfValue,
14438
- onChange: (e) => setAnyOfValue(e.target.value),
14439
- placeholder: toolbar.anyOf.placeholder ?? "Match any of\u2026",
14440
- className: "h-8 pl-7 pr-7 text-sm",
14441
- style: { fontSize: "16px" }
14442
- }
14443
- ),
14444
- anyOfValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14445
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14446
- "button",
14850
+ ] }) : null,
14851
+ renderedFacets ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ToolbarFacets, { facets: renderedFacets }) : null,
14852
+ toolbar?.leading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14853
+ "div",
14447
14854
  {
14448
- type: "button",
14449
- "aria-label": "Clear match-any search",
14450
- className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14451
- onClick: () => setAnyOfValue(""),
14452
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-5 w-5" })
14855
+ "data-matrx-table-toolbar-extras": true,
14856
+ className: "order-last flex w-full min-w-0 flex-wrap items-center gap-2",
14857
+ children: toolbar.leading
14453
14858
  }
14454
- ) }),
14455
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear match-any search" })
14456
- ] }) : null
14457
- ] }) : null,
14458
- renderedFacets ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ToolbarFacets, { facets: renderedFacets }) : null,
14459
- toolbar?.leading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14859
+ ) : null,
14860
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14861
+ "div",
14862
+ {
14863
+ "data-matrx-table-actions": true,
14864
+ className: cn(
14865
+ "ml-auto flex min-w-0 items-center gap-0",
14866
+ toolbar?.singleRow ? "shrink-0 flex-nowrap" : "flex-wrap"
14867
+ ),
14868
+ children: [
14869
+ toolbar?.singleRow && coverageResult && coverageResult.notice ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14870
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14871
+ "span",
14872
+ {
14873
+ "data-matrx-table-coverage": true,
14874
+ "data-matrx-table-coverage-scope": coverageResult.scope,
14875
+ role: "status",
14876
+ tabIndex: 0,
14877
+ className: "mr-1 inline-flex shrink-0 items-center whitespace-nowrap rounded-md border border-amber-300/70 bg-amber-50 px-2 py-0.5 text-[11px] font-semibold text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
14878
+ children: [
14879
+ coverageResult.badge,
14880
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "sr-only", children: [
14881
+ ": ",
14882
+ coverageResult.notice
14883
+ ] })
14884
+ ]
14885
+ }
14886
+ ) }),
14887
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { className: "max-w-xs", children: coverageResult.notice })
14888
+ ] }) : null,
14889
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14890
+ TableToolbarAction,
14891
+ {
14892
+ HostAction: host.ToolbarAction,
14893
+ ariaLabel: "Clear all filters",
14894
+ tooltip: hasActiveFilters ? "Clear search and filters; keep columns and sort" : "No search or filters to clear",
14895
+ disabled: !hasActiveFilters,
14896
+ onClick: clearAllFilters,
14897
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Eraser, {})
14898
+ }
14899
+ ),
14900
+ showToolbarCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14901
+ CopyButtons,
14902
+ {
14903
+ size: "toolbar",
14904
+ sourceId: `${tableCopySourceNamespace}:view`,
14905
+ label: effectiveCopy.listLabel ?? `${effectiveCopy.label} view`,
14906
+ ...effectiveCopy.references ? { references: effectiveCopy.references(processed, data) } : {},
14907
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14908
+ ...copy === void 0 ? {} : {
14909
+ human: () => effectiveCopy.listHuman?.(processed, data) ?? buildViewHuman(effectiveCopy, processed, effectiveCopyColumns),
14910
+ json: () => effectiveCopy.listJson?.(processed, data) ?? processed.map((r) => effectiveCopy.agentRow ? effectiveCopy.agentRow(r) : r)
14911
+ },
14912
+ agent: () => copy !== void 0 && effectiveCopy.listAgent ? effectiveCopy.listAgent(processed, data) : copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, processed, data, declaredVisibleCopyColumns, {
14913
+ search: searchValue,
14914
+ searchMatchMode,
14915
+ anyOf: anyOfValue,
14916
+ filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14917
+ sort: sort ? `${sort.id}:${sort.direction}` : null
14918
+ }) : buildViewAgentInput(effectiveCopy, processed, data, {
14919
+ search: searchValue,
14920
+ searchMatchMode,
14921
+ anyOf: anyOfValue,
14922
+ filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14923
+ sort: sort ? `${sort.id}:${sort.direction}` : null
14924
+ }),
14925
+ aiVariants: [
14926
+ ...effectiveCopy.aiVariants?.(processed, data) ?? [],
14927
+ ...copySubsetVariantFor(() => processed, "view")
14928
+ ],
14929
+ ...effectiveCopy.aiCustom ? { aiCustom: effectiveCopy.aiCustom(processed, data) } : {},
14930
+ export: {
14931
+ items: [],
14932
+ sheetRows: () => processed.map(
14933
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14934
+ ),
14935
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14936
+ id: columnId(column),
14937
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14938
+ })),
14939
+ ...effectiveCopy.export?.(processed, data)
14940
+ }
14941
+ }
14942
+ ) : null,
14943
+ grouping?.onColumnIdChange ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14944
+ Select,
14945
+ {
14946
+ value: groupingColumnId ?? NO_GROUPING_VALUE,
14947
+ onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14948
+ children: [
14949
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14950
+ SelectTrigger,
14951
+ {
14952
+ "data-matrx-table-group-by": true,
14953
+ "aria-label": "Group rows by a column",
14954
+ className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14955
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, { placeholder: "No groups" })
14956
+ }
14957
+ ),
14958
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(SelectContent, { children: [
14959
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14960
+ groupableColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14961
+ ] })
14962
+ ]
14963
+ }
14964
+ ) : null,
14965
+ columns.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14966
+ TableToolbarAction,
14967
+ {
14968
+ HostAction: host.ToolbarAction,
14969
+ ariaLabel: "Columns",
14970
+ tooltip: "Show, hide, or reorder columns",
14971
+ onClick: () => setColumnManagerOpen(true),
14972
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SlidersHorizontal, {})
14973
+ }
14974
+ ) : null,
14975
+ toolbar?.refresh ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14976
+ TableToolbarAction,
14977
+ {
14978
+ HostAction: host.ToolbarAction,
14979
+ ariaLabel: toolbar.refresh.label ?? "Refresh",
14980
+ tooltip: isFetching ? "Refreshing table data" : toolbar.refresh.label ?? "Refresh",
14981
+ disabled: isFetching || toolbarActionPending.refresh,
14982
+ busy: isFetching || toolbarActionPending.refresh,
14983
+ onClick: () => {
14984
+ if (toolbarActionPendingRef.current.refresh) return;
14985
+ toolbarActionPendingRef.current.refresh = true;
14986
+ setToolbarActionPending((current) => ({ ...current, refresh: true }));
14987
+ setToolbarActionError(null);
14988
+ void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
14989
+ const message = cause instanceof Error ? cause.message : String(cause);
14990
+ setToolbarActionError(`Refresh failed: ${message}`);
14991
+ toast.error(`Refresh failed: ${message}`);
14992
+ }).finally(() => {
14993
+ toolbarActionPendingRef.current.refresh = false;
14994
+ setToolbarActionPending((current) => ({ ...current, refresh: false }));
14995
+ });
14996
+ },
14997
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefreshControlIcon, { spinning: isFetching || toolbarActionPending.refresh })
14998
+ }
14999
+ ) : null,
15000
+ toolbar?.add ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15001
+ TableToolbarAction,
15002
+ {
15003
+ HostAction: host.ToolbarAction,
15004
+ ariaLabel: "Add",
15005
+ tooltip: toolbar.add.disabled ? toolbar.add.disabledReason ?? "Add unavailable" : "Add",
15006
+ disabled: toolbar.add.disabled || toolbarActionPending.add,
15007
+ busy: toolbarActionPending.add,
15008
+ onClick: () => {
15009
+ if (toolbarActionPendingRef.current.add) return;
15010
+ toolbarActionPendingRef.current.add = true;
15011
+ setToolbarActionPending((current) => ({ ...current, add: true }));
15012
+ setToolbarActionError(null);
15013
+ void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
15014
+ const message = cause instanceof Error ? cause.message : String(cause);
15015
+ setToolbarActionError(`Add failed: ${message}`);
15016
+ toast.error(`Add failed: ${message}`);
15017
+ }).finally(() => {
15018
+ toolbarActionPendingRef.current.add = false;
15019
+ setToolbarActionPending((current) => ({ ...current, add: false }));
15020
+ });
15021
+ },
15022
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Plus, {})
15023
+ }
15024
+ ) : null,
15025
+ toolbar?.actions
15026
+ ]
15027
+ }
15028
+ )
15029
+ ]
15030
+ }
15031
+ )),
15032
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
15033
+ coverageResult && coverageResult.notice && !toolbar?.singleRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14460
15034
  "div",
14461
15035
  {
14462
- "data-matrx-table-toolbar-extras": true,
14463
- className: "order-last flex w-full min-w-0 flex-wrap items-center gap-2",
14464
- children: toolbar.leading
15036
+ "data-matrx-table-coverage": true,
15037
+ "data-matrx-table-coverage-scope": coverageResult.scope,
15038
+ role: "status",
15039
+ className: "flex shrink-0 items-start gap-2 rounded-md border border-amber-300/70 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
15040
+ children: [
15041
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 whitespace-nowrap font-semibold", children: coverageResult.badge }),
15042
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0", children: coverageResult.notice })
15043
+ ]
14465
15044
  }
14466
15045
  ) : null,
14467
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14468
- "div",
14469
- {
14470
- "data-matrx-table-actions": true,
14471
- className: cn(
14472
- "ml-auto flex min-w-0 items-center gap-0",
14473
- toolbar?.singleRow ? "shrink-0 flex-nowrap" : "flex-wrap"
14474
- ),
14475
- children: [
14476
- toolbar?.singleRow && coverageResult && coverageResult.notice ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14477
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14478
- "span",
15046
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
15047
+ mobileCards ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15048
+ "div",
15049
+ {
15050
+ ref: cardsScrollRef,
15051
+ "data-matrx-table-cards-scroll": true,
15052
+ "aria-busy": isLoading || isFetching,
15053
+ ...appendQuery === null ? {} : { style: { containerType: "inline-size" } },
15054
+ className: cn(
15055
+ "relative flex h-full min-h-0 flex-col gap-2 overflow-y-auto rounded-md border border-border bg-card p-2",
15056
+ mobileCardsClass
15057
+ ),
15058
+ children: [
15059
+ isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15060
+ "div",
14479
15061
  {
14480
- "data-matrx-table-coverage": true,
14481
- "data-matrx-table-coverage-scope": coverageResult.scope,
14482
15062
  role: "status",
14483
- tabIndex: 0,
14484
- className: "mr-1 inline-flex shrink-0 items-center whitespace-nowrap rounded-md border border-amber-300/70 bg-amber-50 px-2 py-0.5 text-[11px] font-semibold text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
15063
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14485
15064
  children: [
14486
- coverageResult.badge,
14487
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "sr-only", children: [
14488
- ": ",
14489
- coverageResult.notice
14490
- ] })
15065
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
15066
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
14491
15067
  ]
14492
15068
  }
14493
- ) }),
14494
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { className: "max-w-xs", children: coverageResult.notice })
14495
- ] }) : null,
14496
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14497
- TableToolbarAction,
14498
- {
14499
- HostAction: host.ToolbarAction,
14500
- ariaLabel: "Clear all filters",
14501
- tooltip: hasActiveFilters ? "Clear search and filters; keep columns and sort" : "No search or filters to clear",
14502
- disabled: !hasActiveFilters,
14503
- onClick: clearAllFilters,
14504
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Eraser, {})
14505
- }
14506
- ),
14507
- showToolbarCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14508
- CopyButtons,
14509
- {
14510
- size: "toolbar",
14511
- sourceId: `${tableCopySourceNamespace}:view`,
14512
- label: effectiveCopy.listLabel ?? `${effectiveCopy.label} view`,
14513
- ...effectiveCopy.references ? { references: effectiveCopy.references(processed, data) } : {},
14514
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14515
- ...copy === void 0 ? {} : {
14516
- human: () => effectiveCopy.listHuman?.(processed, data) ?? buildViewHuman(effectiveCopy, processed, effectiveCopyColumns),
14517
- json: () => effectiveCopy.listJson?.(processed, data) ?? processed.map((r) => effectiveCopy.agentRow ? effectiveCopy.agentRow(r) : r)
15069
+ ) : null,
15070
+ isLoading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15071
+ Skeleton,
15072
+ {
15073
+ className: "h-52 w-full shrink-0 rounded-lg"
14518
15074
  },
14519
- agent: () => copy !== void 0 && effectiveCopy.listAgent ? effectiveCopy.listAgent(processed, data) : copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, processed, data, declaredVisibleCopyColumns, {
14520
- search: searchValue,
14521
- searchMatchMode,
14522
- anyOf: anyOfValue,
14523
- filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14524
- sort: sort ? `${sort.id}:${sort.direction}` : null
14525
- }) : buildViewAgentInput(effectiveCopy, processed, data, {
14526
- search: searchValue,
14527
- searchMatchMode,
14528
- anyOf: anyOfValue,
14529
- filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14530
- sort: sort ? `${sort.id}:${sort.direction}` : null
14531
- }),
14532
- aiVariants: [
14533
- ...effectiveCopy.aiVariants?.(processed, data) ?? [],
14534
- ...copySubsetVariantFor(() => processed, "view")
14535
- ],
14536
- ...effectiveCopy.aiCustom ? { aiCustom: effectiveCopy.aiCustom(processed, data) } : {},
14537
- export: {
14538
- items: [],
14539
- sheetRows: () => processed.map(
14540
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14541
- ),
14542
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14543
- id: columnId(column),
14544
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14545
- })),
14546
- ...effectiveCopy.export?.(processed, data)
14547
- }
14548
- }
14549
- ) : null,
14550
- grouping?.onColumnIdChange ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14551
- Select,
14552
- {
14553
- value: groupingColumnId ?? NO_GROUPING_VALUE,
14554
- onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14555
- children: [
14556
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14557
- SelectTrigger,
15075
+ `mobile-card-sk-${index}`
15076
+ )) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex min-h-48 flex-1 items-center justify-center px-4 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex max-w-sm flex-col items-center gap-2", children: [
15077
+ renderedEmptyState?.icon,
15078
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15079
+ renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15080
+ renderedEmptyState?.action
15081
+ ] }) }) : paginated.map((row, index) => {
15082
+ const id = getRowId(row);
15083
+ const displayRow = applyRowEdits(row, edits[id]);
15084
+ const controls = buildRecordControls(displayRow);
15085
+ const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
15086
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
15087
+ renderCell: (columnKey) => {
15088
+ const column = columns.find((candidate) => columnId(candidate) === columnKey);
15089
+ return column ? renderRecordCell(row, column, index) : null;
15090
+ },
15091
+ selected: selectedIdSet.has(getSelectionId(row)),
15092
+ selectable,
15093
+ onSelectedChange: (nextSelected) => {
15094
+ if (!selection || !selectable || nextSelected === selectedIdSet.has(getSelectionId(row))) {
15095
+ return;
15096
+ }
15097
+ const next = new Set(selectedIdSet);
15098
+ const selectionId = getSelectionId(row);
15099
+ if (nextSelected) next.add(selectionId);
15100
+ else next.delete(selectionId);
15101
+ setSelectedIds(next);
15102
+ },
15103
+ actions: showInlineRowCopy || rowActions ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15104
+ "div",
14558
15105
  {
14559
- "data-matrx-table-group-by": true,
14560
- "aria-label": "Group rows by a column",
14561
- className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14562
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, { placeholder: "No groups" })
15106
+ "data-matrx-table-row-actions": true,
15107
+ className: "inline-flex items-center justify-end gap-0",
15108
+ onClick: (event) => event.stopPropagation(),
15109
+ children: [
15110
+ showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15111
+ TableRowCopyControl,
15112
+ {
15113
+ size: "toolbar",
15114
+ copy: effectiveCopy,
15115
+ row: displayRow,
15116
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15117
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15118
+ }
15119
+ ) : null,
15120
+ renderRowActions(displayRow, controls)
15121
+ ]
14563
15122
  }
14564
- ),
14565
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(SelectContent, { children: [
14566
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14567
- groupableColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14568
- ] })
14569
- ]
14570
- }
14571
- ) : null,
14572
- columns.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14573
- TableToolbarAction,
14574
- {
14575
- HostAction: host.ToolbarAction,
14576
- ariaLabel: "Columns",
14577
- tooltip: "Show, hide, or reorder columns",
14578
- onClick: () => setColumnManagerOpen(true),
14579
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SlidersHorizontal, {})
14580
- }
14581
- ) : null,
14582
- toolbar?.refresh ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14583
- TableToolbarAction,
14584
- {
14585
- HostAction: host.ToolbarAction,
14586
- ariaLabel: toolbar.refresh.label ?? "Refresh",
14587
- tooltip: isFetching ? "Refreshing table data" : toolbar.refresh.label ?? "Refresh",
14588
- disabled: isFetching || toolbarActionPending.refresh,
14589
- busy: isFetching || toolbarActionPending.refresh,
14590
- onClick: () => {
14591
- if (toolbarActionPendingRef.current.refresh) return;
14592
- toolbarActionPendingRef.current.refresh = true;
14593
- setToolbarActionPending((current) => ({ ...current, refresh: true }));
14594
- setToolbarActionError(null);
14595
- void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
14596
- const message = cause instanceof Error ? cause.message : String(cause);
14597
- setToolbarActionError(`Refresh failed: ${message}`);
14598
- toast.error(`Refresh failed: ${message}`);
14599
- }).finally(() => {
14600
- toolbarActionPendingRef.current.refresh = false;
14601
- setToolbarActionPending((current) => ({ ...current, refresh: false }));
14602
- });
14603
- },
14604
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefreshControlIcon, { spinning: isFetching || toolbarActionPending.refresh })
14605
- }
14606
- ) : null,
14607
- toolbar?.add ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14608
- TableToolbarAction,
14609
- {
14610
- HostAction: host.ToolbarAction,
14611
- ariaLabel: "Add",
14612
- tooltip: toolbar.add.disabled ? toolbar.add.disabledReason ?? "Add unavailable" : "Add",
14613
- disabled: toolbar.add.disabled || toolbarActionPending.add,
14614
- busy: toolbarActionPending.add,
14615
- onClick: () => {
14616
- if (toolbarActionPendingRef.current.add) return;
14617
- toolbarActionPendingRef.current.add = true;
14618
- setToolbarActionPending((current) => ({ ...current, add: true }));
14619
- setToolbarActionError(null);
14620
- void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
14621
- const message = cause instanceof Error ? cause.message : String(cause);
14622
- setToolbarActionError(`Add failed: ${message}`);
14623
- toast.error(`Add failed: ${message}`);
14624
- }).finally(() => {
14625
- toolbarActionPendingRef.current.add = false;
14626
- setToolbarActionPending((current) => ({ ...current, add: false }));
14627
- });
14628
- },
14629
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Plus, {})
14630
- }
14631
- ) : null,
14632
- toolbar?.actions
14633
- ]
14634
- }
14635
- )
14636
- ]
14637
- }
14638
- )),
14639
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
14640
- coverageResult && coverageResult.notice && !toolbar?.singleRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14641
- "div",
14642
- {
14643
- "data-matrx-table-coverage": true,
14644
- "data-matrx-table-coverage-scope": coverageResult.scope,
14645
- role: "status",
14646
- className: "flex shrink-0 items-start gap-2 rounded-md border border-amber-300/70 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
14647
- children: [
14648
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 whitespace-nowrap font-semibold", children: coverageResult.badge }),
14649
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0", children: coverageResult.notice })
14650
- ]
14651
- }
14652
- ) : null,
14653
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
14654
- mobileCards ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14655
- "div",
14656
- {
14657
- ref: cardsScrollRef,
14658
- "data-matrx-table-cards-scroll": true,
14659
- "aria-busy": isLoading || isFetching,
14660
- ...appendQuery === null ? {} : { style: { containerType: "inline-size" } },
14661
- className: cn(
14662
- "relative flex h-full min-h-0 flex-col gap-2 overflow-y-auto rounded-md border border-border bg-card p-2",
14663
- mobileCardsClass
14664
- ),
14665
- children: [
14666
- isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14667
- "div",
14668
- {
14669
- role: "status",
14670
- className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14671
- children: [
14672
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
14673
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
14674
- ]
14675
- }
14676
- ) : null,
14677
- isLoading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14678
- Skeleton,
14679
- {
14680
- className: "h-52 w-full shrink-0 rounded-lg"
14681
- },
14682
- `mobile-card-sk-${index}`
14683
- )) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex min-h-48 flex-1 items-center justify-center px-4 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex max-w-sm flex-col items-center gap-2", children: [
14684
- renderedEmptyState?.icon,
14685
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
14686
- renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
14687
- renderedEmptyState?.action
14688
- ] }) }) : paginated.map((row, index) => {
14689
- const id = getRowId(row);
14690
- const displayRow = applyRowEdits(row, edits[id]);
14691
- const controls = buildRecordControls(displayRow);
14692
- const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
14693
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
14694
- renderCell: (columnKey) => {
14695
- const column = columns.find((candidate) => columnId(candidate) === columnKey);
14696
- return column ? renderRecordCell(row, column, index) : null;
14697
- },
14698
- selected: selectedIdSet.has(getSelectionId(row)),
14699
- selectable,
14700
- onSelectedChange: (nextSelected) => {
14701
- if (!selection || !selectable || nextSelected === selectedIdSet.has(getSelectionId(row))) {
14702
- return;
14703
- }
14704
- const next = new Set(selectedIdSet);
14705
- const selectionId = getSelectionId(row);
14706
- if (nextSelected) next.add(selectionId);
14707
- else next.delete(selectionId);
14708
- setSelectedIds(next);
14709
- },
14710
- actions: showInlineRowCopy || rowActions ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15123
+ ) : null,
15124
+ ...canExpandRow(row) ? {
15125
+ isExpanded: isRowExpanded(row),
15126
+ toggleExpanded: () => toggleExpanded(row)
15127
+ } : {},
15128
+ expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
15129
+ }) }, id);
15130
+ }),
15131
+ appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14711
15132
  "div",
14712
15133
  {
14713
- "data-matrx-table-row-actions": true,
14714
- className: "inline-flex items-center justify-end gap-0",
14715
- onClick: (event) => event.stopPropagation(),
15134
+ "data-matrx-table-append-loading-card": true,
15135
+ role: "status",
15136
+ "aria-live": "polite",
15137
+ className: "sticky bottom-0 z-20 flex h-16 w-full max-w-[100cqw] shrink-0 items-center justify-center gap-2 rounded-md border border-border bg-card/95 px-3 text-sm font-medium text-foreground shadow-sm backdrop-blur-sm",
14716
15138
  children: [
14717
- showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14718
- TableRowCopyControl,
15139
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15140
+ Loader2,
14719
15141
  {
14720
- size: "toolbar",
14721
- copy: effectiveCopy,
14722
- row: displayRow,
14723
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
14724
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15142
+ "aria-hidden": "true",
15143
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
14725
15144
  }
14726
- ) : null,
14727
- renderRowActions(displayRow, controls)
15145
+ ),
15146
+ "Loading more rows\u2026"
14728
15147
  ]
14729
15148
  }
15149
+ ) : null
15150
+ ]
15151
+ }
15152
+ ) : null,
15153
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15154
+ "div",
15155
+ {
15156
+ ref: scrollRef,
15157
+ "data-matrx-table-scroll": true,
15158
+ onScroll: updateScrollHint,
15159
+ ...spreadsheetEnabled ? {
15160
+ tabIndex: sheet.containerProps.tabIndex,
15161
+ onKeyDown: sheet.containerProps.onKeyDown,
15162
+ onCopy: sheet.containerProps.onCopy,
15163
+ onCut: sheet.containerProps.onCut,
15164
+ onPaste: sheet.containerProps.onPaste,
15165
+ "data-matrx-table-spreadsheet": "true"
15166
+ } : {},
15167
+ "aria-busy": isLoading || isFetching,
15168
+ style: { containerType: "inline-size" },
15169
+ className: cn(
15170
+ "relative h-full w-full overflow-auto rounded-md border border-border bg-card",
15171
+ mobileCards && tableWithCardsClass,
15172
+ tableClassName
15173
+ ),
15174
+ children: [
15175
+ hierarchy && draggedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15176
+ HierarchyRootDropTarget,
15177
+ {
15178
+ label: hierarchy.rootDropLabel ?? "Drop here to move to the top level",
15179
+ draggedLabel: hierarchy.itemLabel?.(draggedRow) ?? "Moving row"
15180
+ }
14730
15181
  ) : null,
14731
- ...canExpandRow(row) ? {
14732
- isExpanded: isRowExpanded(row),
14733
- toggleExpanded: () => toggleExpanded(row)
14734
- } : {},
14735
- expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
14736
- }) }, id);
14737
- }),
14738
- appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14739
- "div",
14740
- {
14741
- "data-matrx-table-append-loading-card": true,
14742
- role: "status",
14743
- "aria-live": "polite",
14744
- className: "sticky bottom-0 z-20 flex h-16 w-full max-w-[100cqw] shrink-0 items-center justify-center gap-2 rounded-md border border-border bg-card/95 px-3 text-sm font-medium text-foreground shadow-sm backdrop-blur-sm",
14745
- children: [
14746
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14747
- Loader2,
14748
- {
14749
- "aria-hidden": "true",
14750
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
14751
- }
14752
- ),
14753
- "Loading more rows\u2026"
14754
- ]
14755
- }
14756
- ) : null
14757
- ]
14758
- }
14759
- ) : null,
14760
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14761
- "div",
14762
- {
14763
- ref: scrollRef,
14764
- "data-matrx-table-scroll": true,
14765
- onScroll: updateScrollHint,
14766
- ...spreadsheetEnabled ? {
14767
- tabIndex: sheet.containerProps.tabIndex,
14768
- onKeyDown: sheet.containerProps.onKeyDown,
14769
- onCopy: sheet.containerProps.onCopy,
14770
- onCut: sheet.containerProps.onCut,
14771
- onPaste: sheet.containerProps.onPaste,
14772
- "data-matrx-table-spreadsheet": "true"
14773
- } : {},
14774
- "aria-busy": isLoading || isFetching,
14775
- style: { containerType: "inline-size" },
14776
- className: cn(
14777
- "relative h-full w-full overflow-auto rounded-md border border-border bg-card",
14778
- mobileCards && tableWithCardsClass,
14779
- tableClassName
14780
- ),
14781
- children: [
14782
- hierarchy && draggedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14783
- HierarchyRootDropTarget,
14784
- {
14785
- label: hierarchy.rootDropLabel ?? "Drop here to move to the top level",
14786
- draggedLabel: hierarchy.itemLabel?.(draggedRow) ?? "Moving row"
14787
- }
14788
- ) : null,
14789
- hierarchy && draggedRowId ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14790
- HierarchyDragIndicatorLayer,
14791
- {
14792
- scrollRef,
14793
- resolve: resolveHierarchyMove
14794
- }
14795
- ) : null,
14796
- isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14797
- "div",
14798
- {
14799
- role: "status",
14800
- className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14801
- children: [
14802
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
14803
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
14804
- ]
14805
- }
14806
- ) : null,
14807
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14808
- "table",
14809
- {
14810
- ref: tableRef,
14811
- style: !fixedLayoutActive ? void 0 : {
14812
- // Track the same preview variables as the cells. A stale
14813
- // pre-resize total redistributes spare width under fixed
14814
- // layout and defeats both exact shrinking and max bounds.
14815
- "--matrx-resized-table-width": `calc(${visibleColumns.map((column) => {
14816
- const id = columnId(column);
14817
- return resizableColumns && column.resizable !== false ? `var(${columnResizeCssVar(id)}, ${effectiveColumnWidths[id]}px)` : `${effectiveColumnWidths[id]}px`;
14818
- }).join(" + ")} + ${(selection ? fixedTableGeometry?.selectionWidth ?? 0 : 0) + (showActionsCol ? fixedTableGeometry?.actionsWidth ?? 0 : 0)}px)`
14819
- },
14820
- className: cn(
14821
- "table w-max min-w-full max-w-none caption-bottom overflow-visible text-sm sm:w-full sm:min-w-0 sm:max-w-full",
14822
- fixedLayoutActive && "sm:table-fixed sm:max-w-none sm:w-[var(--matrx-resized-table-width)]"
14823
- ),
14824
- children: [
14825
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14826
- "thead",
14827
- {
14828
- ref: tableHeaderRef,
14829
- "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
14830
- className: cn(
14831
- // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
14832
- // cell is `sm:z-20` so it can slide under the columns to
14833
- // its right; the header was `z-10`, so the frozen column's
14834
- // cells scrolled straight OVER the header they belong to
14835
- // and the top data row printed through the column names.
14836
- // Only a table long enough to scroll shows it, which is why
14837
- // it survived until a hundred-thousand-row screenshot.
14838
- "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
14839
- stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
14840
- ),
14841
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
14842
- selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14843
- "th",
14844
- {
14845
- "data-matrx-table-selection-column": true,
14846
- className: cn(
14847
- "h-9 w-9 px-2 text-left align-middle",
14848
- frozenColumns.selectionOffset !== null && "sm:sticky sm:left-0 sm:z-30 sm:bg-muted"
14849
- ),
14850
- style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
14851
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14852
- "div",
14853
- {
14854
- "data-matrx-table-selection-target": true,
14855
- onClick: (event) => {
14856
- if (event.target === event.currentTarget) {
14857
- event.currentTarget.querySelector('[role="checkbox"]')?.click();
14858
- }
14859
- },
14860
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14861
- Checkbox,
14862
- {
14863
- className: CHECKBOX_TAP_AREA,
14864
- checked: allOnPageSelected ? true : someOnPageSelected ? "indeterminate" : false,
14865
- disabled: selectableRows.length === 0,
14866
- onCheckedChange: toggleAllOnPage,
14867
- "aria-label": allOnPageSelected ? "Clear selection on this page" : "Select every row on this page"
14868
- }
14869
- )
14870
- }
14871
- )
14872
- }
14873
- ) : null,
14874
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14875
- "th",
14876
- {
14877
- "aria-hidden": "true",
14878
- "data-matrx-table-column-spacer": "start",
14879
- className: "border-0 p-0",
14880
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14881
- },
14882
- "matrx-column-spacer-start"
14883
- ) : null,
14884
- renderedColumns.map((col) => {
14885
- const id = columnId(col);
14886
- const meta = filterMeta.get(id);
14887
- const isSorted = sort?.id === id;
14888
- const headerLabel = col.label ?? (typeof col.header === "string" ? col.header : id);
14889
- const defaultSortDirection = col.defaultSortDirection ?? "asc";
14890
- const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
14891
- const columnResizable = resizableColumns && col.resizable !== false;
14892
- const marker = resolveColumnMarker(col);
14893
- const compact = col.compact || marker !== void 0;
14894
- const width = marker && col.width === void 0 ? 40 : col.width;
14895
- const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
14896
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14897
- "th",
14898
- {
14899
- "data-matrx-table-column-id": id,
14900
- "data-matrx-header-column-id": id,
14901
- "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
14902
- "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
14903
- className: cn(
14904
- "relative h-9 text-left align-middle max-sm:whitespace-nowrap",
14905
- // ICON COLUMN (MatrxColumnDef.compact): a 40px star
14906
- // column cannot honor `width: 40` while paying 16px of
14907
- // padding for a 14px glyph.
14908
- compact ? "px-1" : "px-2",
14909
- // An intentional mobile column set (see
14910
- // MatrxColumnDef.mobileHidden). CSS, not a JS
14911
- // breakpoint — no hydration mismatch, and the column
14912
- // stays sortable/filterable from the toolbar.
14913
- col.mobileHidden && "max-sm:hidden",
14914
- // Consumer widths are desktop tuning: applied from `sm`
14915
- // up via a CSS var. The minimum is load-bearing: without
14916
- // it, a crowded table crushes a declared 190px column to a
14917
- // few characters wide instead of using the canonical
14918
- // horizontal-scroll surface.
14919
- (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
14920
- col.headerClassName,
14921
- isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
14922
- alignForFormat(col) === "center" && "text-center",
14923
- alignForFormat(col) === "right" && "text-right",
14924
- frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
14925
- frozenEdgeId === id && FROZEN_EDGE_CLASS
14926
- ),
14927
- style: {
14928
- ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
14929
- ...frozenStyle(id)
14930
- },
14931
- children: [
14932
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14933
- ColumnHeaderCell,
14934
- {
14935
- label: col.header,
14936
- labelText: headerLabel,
14937
- ...facetsEnabled ? {
14938
- facets: facetState[id]?.data ?? null,
14939
- facetsLoading: facetState[id]?.loading ?? false,
14940
- ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
14941
- onFilterOpen: () => requestFacets(id)
14942
- } : {},
14943
- sortable: col.sortable !== false,
14944
- isSorted: Boolean(isSorted),
14945
- sortDirection: sort?.direction ?? defaultSortDirection,
14946
- onSortAsc: () => setSort({ id, direction: "asc" }),
14947
- onSortDesc: () => setSort({ id, direction: "desc" }),
14948
- onClearSort: () => setSort(null),
14949
- onHeaderSortClick: () => {
14950
- if (!isSorted) {
14951
- setSort({ id, direction: defaultSortDirection });
14952
- return;
14953
- }
14954
- if (sort?.direction === "asc") {
14955
- setSort({ id, direction: "desc" });
14956
- return;
14957
- }
14958
- setSort({ id, direction: "asc" });
14959
- },
14960
- filterKind: meta?.kind ?? null,
14961
- filterValue: columnFilters[id],
14962
- onFilterChange: (next) => setColumnFilter(id, next),
14963
- selectOptions: meta?.options,
14964
- selectSingle: col.filterSingle,
14965
- selectAllowCustom: col.filterAllowCustom,
14966
- filterApplyLabel: filterUi?.applyLabel,
14967
- filterShowCancel: filterUi?.showCancel,
14968
- filterFooterHint: filterUi?.footerHint,
14969
- allowNegation: filterUi?.allowNegation,
14970
- allowNullModes: filterUi?.allowNullModes,
14971
- align: col.align,
14972
- compact,
14973
- marker,
14974
- reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0
14975
- }
14976
- ) }),
14977
- columnResizable ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14978
- ColumnResizeHandle,
14979
- {
14980
- columnId: id,
14981
- label: headerLabel,
14982
- tableRef,
14983
- minWidth,
14984
- maxWidth: col.maxWidth,
14985
- onResizeStart: captureResizeGestureSnapshot,
14986
- onResizeCancel: clearResizeGestureSnapshot,
14987
- onResizeCommit: (columnId2, width2) => commitColumnWidths(
14988
- { ...effectiveColumnWidths, [columnId2]: width2 },
14989
- true
14990
- )
14991
- }
14992
- ) : null
14993
- ]
14994
- },
14995
- id
14996
- );
14997
- }),
14998
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14999
- "th",
15000
- {
15001
- "aria-hidden": "true",
15002
- "data-matrx-table-column-spacer": "end",
15003
- className: "border-0 p-0",
15004
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15005
- },
15006
- "matrx-column-spacer-end"
15007
- ) : null,
15008
- showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15009
- "th",
15010
- {
15011
- "data-matrx-table-actions-column": true,
15012
- className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
15013
- style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15014
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
15015
- }
15016
- )
15017
- ] })
15018
- }
15019
- ),
15020
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tbody", { children: [
15021
- isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { className: "border-b border-border/60", children: [
15022
- selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
15023
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15024
- "td",
15025
- {
15026
- "aria-hidden": "true",
15027
- "data-matrx-table-column-spacer": "start",
15028
- className: "border-0 p-0",
15029
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15030
- },
15031
- "matrx-column-spacer-start"
15032
- ) : null,
15033
- renderedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15034
- "td",
15035
- {
15036
- className: cn(
15037
- "py-2",
15038
- col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15039
- col.mobileHidden && "max-sm:hidden"
15040
- ),
15041
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-5 w-full" })
15042
- },
15043
- columnId(col)
15044
- )),
15045
- showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "ml-auto h-5 w-10" }) }),
15046
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15047
- "td",
15048
- {
15049
- "aria-hidden": "true",
15050
- "data-matrx-table-column-spacer": "end",
15051
- className: "border-0 p-0",
15052
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15053
- },
15054
- "matrx-column-spacer-end"
15055
- ) : null
15056
- ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15057
- "td",
15058
- {
15059
- colSpan: renderedColSpan,
15060
- className: "px-0 py-12 text-center",
15061
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-empty-state": true, className: "sticky left-0 flex flex-col items-center gap-2 whitespace-normal px-4", style: { width: "100cqw", maxWidth: "100%" }, children: [
15062
- renderedEmptyState?.icon,
15063
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15064
- renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15065
- renderedEmptyState?.action
15066
- ] })
15067
- }
15068
- ) }) : bodyEntries.map((entry) => {
15069
- if (entry.kind === "spacer") {
15070
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15071
- "tr",
15072
- {
15073
- "aria-hidden": "true",
15074
- "data-matrx-table-virtual-spacer": entry.place,
15075
- style: { height: `${entry.height}px` },
15076
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15077
- "td",
15078
- {
15079
- className: "border-0 p-0",
15080
- colSpan: renderedColSpan
15081
- }
15082
- )
15083
- },
15084
- `matrx-virtual-spacer-${entry.place}`
15085
- );
15086
- }
15087
- if (entry.kind === "group") {
15088
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15089
- TableGroupHeaderRow,
15090
- {
15091
- group: entry.group,
15092
- collapsed: entry.collapsed,
15093
- columns: renderedColumns,
15094
- spacers: columnSpacers,
15095
- leadingCols,
15096
- showActionsCol,
15097
- rowNoun: grouping?.rowNoun ?? "row",
15098
- ...grouping?.renderLabel ? { renderLabel: grouping.renderLabel } : {},
15099
- onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
15100
- },
15101
- `matrx-group-${entry.group.key}`
15102
- );
15103
- }
15104
- const row = entry.row;
15105
- const index = entry.index;
15106
- const id = getRowId(row);
15107
- const isSelected = selectedId === id;
15108
- const rowEdits = edits[id];
15109
- const displayRow = applyRowEdits(row, rowEdits);
15110
- const controls = buildRecordControls(displayRow);
15111
- const isChecked = selectedIdSet.has(getSelectionId(row));
15112
- const background = rowBackground({
15113
- tint: rowTintColor(row, id),
15114
- highlighted: highlightedIndex === index,
15115
- selected: isSelected,
15116
- checked: isChecked,
15117
- zebraOdd: zebra && index % 2 === 1,
15118
- hoverable: detailEnabled || Boolean(onRowOpen) || canExpandRow(row)
15119
- });
15120
- const canHoverFrozenCell = background.frozenHover;
15121
- const frozenRowBackgroundClass = background.frozen;
15122
- const rowNode = /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15123
- "tr",
15182
+ hierarchy && draggedRowId ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15183
+ HierarchyDragIndicatorLayer,
15184
+ {
15185
+ scrollRef,
15186
+ resolve: resolveHierarchyMove
15187
+ }
15188
+ ) : null,
15189
+ isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15190
+ "div",
15191
+ {
15192
+ role: "status",
15193
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
15194
+ children: [
15195
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
15196
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
15197
+ ]
15198
+ }
15199
+ ) : null,
15200
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15201
+ "table",
15202
+ {
15203
+ ref: tableRef,
15204
+ style: !fixedLayoutActive ? void 0 : {
15205
+ // Track the same preview variables as the cells. A stale
15206
+ // pre-resize total redistributes spare width under fixed
15207
+ // layout and defeats both exact shrinking and max bounds.
15208
+ "--matrx-resized-table-width": `calc(${visibleColumns.map((column) => {
15209
+ const id = columnId(column);
15210
+ return resizableColumns && column.resizable !== false ? `var(${columnResizeCssVar(id)}, ${effectiveColumnWidths[id]}px)` : `${effectiveColumnWidths[id]}px`;
15211
+ }).join(" + ")} + ${(selection ? fixedTableGeometry?.selectionWidth ?? 0 : 0) + (showActionsCol ? fixedTableGeometry?.actionsWidth ?? 0 : 0)}px)`
15212
+ },
15213
+ className: cn(
15214
+ "table w-max min-w-full max-w-none caption-bottom overflow-visible text-sm sm:w-full sm:min-w-0 sm:max-w-full",
15215
+ fixedLayoutActive && "sm:table-fixed sm:max-w-none sm:w-[var(--matrx-resized-table-width)]"
15216
+ ),
15217
+ children: [
15218
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15219
+ "thead",
15124
15220
  {
15125
- "data-row-id": id,
15126
- style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
15127
- "data-state": isSelected ? "selected" : void 0,
15128
- onClick: (e) => {
15129
- if (rowEventTargetsInteractiveDescendant(e.target)) return;
15130
- if (opensRowHref(e)) {
15131
- const href = getRowHref?.(row);
15132
- if (href) window.open(href, "_blank", "noopener");
15133
- return;
15134
- }
15135
- openRow(row);
15136
- },
15137
- onAuxClick: (e) => {
15138
- if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
15139
- const href = getRowHref?.(row);
15140
- if (href) {
15141
- e.preventDefault();
15142
- window.open(href, "_blank", "noopener");
15143
- }
15144
- },
15221
+ ref: tableHeaderRef,
15222
+ "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
15145
15223
  className: cn(
15146
- // Keep the mobile row opaque while the complete row scrolls
15147
- // horizontally. The translucent zebra/hover tints remain
15148
- // desktop-only so densely layered mobile content stays crisp.
15149
- "group/matrx-row border-b border-border/60 bg-card transition-colors",
15150
- (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer",
15151
- rowClassName?.(row, index),
15152
- draggedRowId === id && "opacity-40",
15153
- // LAST, and decided once: tint, then highlight / open / ticked, then
15154
- // the zebra stripe (./row-background.ts). Under a tint no stripe is
15155
- // emitted at all, so no `sm:` class can win the cascade over it.
15156
- background.row
15224
+ // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
15225
+ // cell is `sm:z-20` so it can slide under the columns to
15226
+ // its right; the header was `z-10`, so the frozen column's
15227
+ // cells scrolled straight OVER the header they belong to
15228
+ // and the top data row printed through the column names.
15229
+ // Only a table long enough to scroll shows it, which is why
15230
+ // it survived until a hundred-thousand-row screenshot.
15231
+ "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
15232
+ stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
15157
15233
  ),
15158
- children: [
15234
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15159
15235
  selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15160
- "td",
15236
+ "th",
15161
15237
  {
15238
+ "data-matrx-table-selection-column": true,
15162
15239
  className: cn(
15163
- "px-2 py-1.5 align-middle lg:py-0.5",
15164
- // When a data column is frozen the checkbox
15165
- // column has to freeze with it, or the first
15166
- // frozen column slides over the checkboxes.
15167
- frozenColumns.selectionOffset !== null && cn(
15168
- "sm:sticky sm:left-0 sm:z-20",
15169
- frozenRowBackgroundClass,
15170
- canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
15171
- )
15240
+ "h-9 w-9 px-2 text-left align-middle",
15241
+ frozenColumns.selectionOffset !== null && "sm:sticky sm:left-0 sm:z-30 sm:bg-muted"
15172
15242
  ),
15173
15243
  style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15174
- onClick: (e) => e.stopPropagation(),
15175
- children: selection.isRowSelectable?.(row) ?? true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15244
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15176
15245
  "div",
15177
15246
  {
15178
15247
  "data-matrx-table-selection-target": true,
@@ -15185,17 +15254,18 @@ function MatrxDataTableCore({
15185
15254
  Checkbox,
15186
15255
  {
15187
15256
  className: CHECKBOX_TAP_AREA,
15188
- checked: isChecked,
15189
- "aria-label": `Select this ${selectionNoun}`,
15190
- onClick: (e) => toggleRowSelected(index, e.shiftKey)
15257
+ checked: allOnPageSelected ? true : someOnPageSelected ? "indeterminate" : false,
15258
+ disabled: selectableRows.length === 0,
15259
+ onCheckedChange: toggleAllOnPage,
15260
+ "aria-label": allOnPageSelected ? "Clear selection on this page" : "Select every row on this page"
15191
15261
  }
15192
15262
  )
15193
15263
  }
15194
- ) : null
15264
+ )
15195
15265
  }
15196
15266
  ) : null,
15197
15267
  columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15198
- "td",
15268
+ "th",
15199
15269
  {
15200
15270
  "aria-hidden": "true",
15201
15271
  "data-matrx-table-column-spacer": "start",
@@ -15204,145 +15274,124 @@ function MatrxDataTableCore({
15204
15274
  },
15205
15275
  "matrx-column-spacer-start"
15206
15276
  ) : null,
15207
- renderedColumns.map((col, colIdx) => {
15208
- const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
15209
- const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
15210
- const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
15211
- const frozen = frozenColumns.offsets[columnId(col)] !== void 0;
15212
- const frozenCellTint = cellTintColor(
15213
- row,
15214
- id,
15215
- columnId(col)
15216
- );
15277
+ renderedColumns.map((col) => {
15278
+ const id = columnId(col);
15279
+ const meta = filterMeta.get(id);
15280
+ const isSorted = sort?.id === id;
15281
+ const headerLabel = col.label ?? (typeof col.header === "string" ? col.header : id);
15282
+ const defaultSortDirection = col.defaultSortDirection ?? "asc";
15283
+ const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
15284
+ const columnResizable = resizableColumns && col.resizable !== false;
15285
+ const marker = resolveColumnMarker(col);
15286
+ const compact = col.compact || marker !== void 0;
15287
+ const width = marker && col.width === void 0 ? 40 : col.width;
15288
+ const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
15217
15289
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15218
- "td",
15290
+ "th",
15219
15291
  {
15220
- "data-matrx-table-column-id": columnId(col),
15292
+ "data-matrx-table-column-id": id,
15293
+ "data-matrx-header-column-id": id,
15221
15294
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15222
- ...sheetCell ? {
15223
- "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
15224
- "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
15225
- ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
15226
- ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
15227
- ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
15228
- onMouseDown: sheetCell.onMouseDown,
15229
- onMouseEnter: sheetCell.onMouseEnter
15230
- } : {},
15295
+ "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
15231
15296
  className: cn(
15232
- "py-1.5 align-middle lg:py-0.5",
15233
- sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
15234
- sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
15235
- sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
15236
- carriesFillHandle && "relative",
15237
- col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15238
- // nowrap (NOT truncate — truncate clips the cell and
15239
- // defeats w-max, killing the horizontal scroll).
15240
- "max-sm:whitespace-nowrap",
15297
+ "relative h-9 text-left align-middle max-sm:whitespace-nowrap",
15298
+ // ICON COLUMN (MatrxColumnDef.compact): a 40px star
15299
+ // column cannot honor `width: 40` while paying 16px of
15300
+ // padding for a 14px glyph.
15301
+ compact ? "px-1" : "px-2",
15302
+ // An intentional mobile column set (see
15303
+ // MatrxColumnDef.mobileHidden). CSS, not a JS
15304
+ // breakpoint — no hydration mismatch, and the column
15305
+ // stays sortable/filterable from the toolbar.
15241
15306
  col.mobileHidden && "max-sm:hidden",
15242
- ((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)]",
15243
- col.className,
15307
+ // Consumer widths are desktop tuning: applied from `sm`
15308
+ // up via a CSS var. The minimum is load-bearing: without
15309
+ // it, a crowded table crushes a declared 190px column to a
15310
+ // few characters wide instead of using the canonical
15311
+ // horizontal-scroll surface.
15312
+ (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
15313
+ col.headerClassName,
15244
15314
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15245
15315
  alignForFormat(col) === "center" && "text-center",
15246
15316
  alignForFormat(col) === "right" && "text-right",
15247
- frozen && FROZEN_CELL_CLASS,
15248
- frozen && frozenRowBackgroundClass,
15249
- frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
15250
- frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
15251
- frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
15252
- // A cell tint paints OVER the row tint beneath
15253
- // it, and the consumer's own hook comes last so
15254
- // it can always win.
15255
- cellTintClass(row, id, columnId(col)),
15256
- frozen && frozenCellTint && FROZEN_CELL_TINT_CLASS[frozenCellTint],
15257
- cellClassName?.(row, columnId(col), index)
15317
+ frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
15318
+ frozenEdgeId === id && FROZEN_EDGE_CLASS
15258
15319
  ),
15259
15320
  style: {
15260
- ...columnWidthVar(
15261
- resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
15262
- windowedWidthFor(columnId(col), col.width),
15263
- columnId(col),
15264
- resizableColumns && col.resizable !== false
15265
- ),
15266
- ...frozenStyle(columnId(col))
15321
+ ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
15322
+ ...frozenStyle(id)
15267
15323
  },
15268
15324
  children: [
15269
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15270
- "div",
15325
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15326
+ ColumnHeaderCell,
15271
15327
  {
15272
- className: cn(
15273
- colIdx === 0 && (hierarchy || canExpandRow(row)) && "flex min-w-0 items-center"
15274
- ),
15275
- children: [
15276
- colIdx === 0 && hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15277
- HierarchyDragHandle,
15278
- {
15279
- id,
15280
- label: hierarchy.itemLabel?.(row) ?? "row",
15281
- disabled: !(hierarchy.canReparent?.(row) ?? true)
15282
- }
15283
- ) : null,
15284
- colIdx === 0 && canExpandRow(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15285
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15286
- Button,
15287
- {
15288
- type: "button",
15289
- variant: "ghost",
15290
- size: "icon",
15291
- "data-matrx-table-expand-toggle": true,
15292
- className: "mr-1 h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground [&_svg]:size-4",
15293
- "aria-label": isRowExpanded(row) ? "Collapse row details" : "Expand row details",
15294
- "aria-expanded": isRowExpanded(row),
15295
- onClick: (event) => {
15296
- event.stopPropagation();
15297
- toggleExpanded(row);
15298
- },
15299
- onKeyDown: (event) => {
15300
- if (event.key !== "Enter" && event.key !== " ") return;
15301
- event.preventDefault();
15302
- event.stopPropagation();
15303
- toggleExpanded(row);
15304
- },
15305
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15306
- ChevronRight,
15307
- {
15308
- className: cn(
15309
- "transition-transform",
15310
- isRowExpanded(row) && "rotate-90"
15311
- )
15312
- }
15313
- )
15314
- }
15315
- ) }),
15316
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: isRowExpanded(row) ? "Collapse details" : "Expand details" })
15317
- ] }) : null,
15318
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15319
- "div",
15320
- {
15321
- "data-matrx-cell-content": true,
15322
- className: "min-w-0 max-w-full flex-1 whitespace-normal break-words [overflow-wrap:anywhere] sm:[&_*]:min-w-0 sm:[&_*]:whitespace-normal sm:[&_*]:[overflow-wrap:anywhere] sm:[&_.truncate]:whitespace-nowrap sm:[&_[data-slot=badge]]:whitespace-nowrap sm:[&_[data-slot=badge]]:[overflow-wrap:normal] sm:[&_[data-matrx-cell-control]]:w-max sm:[&_[data-matrx-cell-control]]:min-w-max sm:[&_[data-matrx-cell-control]]:max-w-none sm:[&_[data-matrx-cell-control]]:whitespace-nowrap sm:[&_[data-matrx-cell-control]]:[overflow-wrap:normal] sm:[&_[data-matrx-cell-control]_*]:whitespace-nowrap sm:[&_[data-matrx-cell-control]_*]:[overflow-wrap:normal] [&_[data-matrx-cell-fill]]:!w-full [&_[data-matrx-cell-fill]]:!min-w-0 [&_[data-matrx-cell-fill]]:!max-w-full sm:[&_[data-matrx-cell-fill]]:!w-full sm:[&_[data-matrx-cell-fill]]:!min-w-0 sm:[&_[data-matrx-cell-fill]]:!max-w-full",
15323
- children: renderRecordCell(row, col, index)
15324
- }
15325
- )
15326
- ]
15328
+ label: col.header,
15329
+ labelText: headerLabel,
15330
+ ...facetsEnabled ? {
15331
+ facets: facetState[id]?.data ?? null,
15332
+ facetsLoading: facetState[id]?.loading ?? false,
15333
+ ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
15334
+ onFilterOpen: () => requestFacets(id)
15335
+ } : {},
15336
+ sortable: col.sortable !== false,
15337
+ isSorted: Boolean(isSorted),
15338
+ sortDirection: sort?.direction ?? defaultSortDirection,
15339
+ onSortAsc: () => setSort({ id, direction: "asc" }),
15340
+ onSortDesc: () => setSort({ id, direction: "desc" }),
15341
+ onClearSort: () => setSort(null),
15342
+ onHeaderSortClick: () => {
15343
+ if (!isSorted) {
15344
+ setSort({ id, direction: defaultSortDirection });
15345
+ return;
15346
+ }
15347
+ if (sort?.direction === "asc") {
15348
+ setSort({ id, direction: "desc" });
15349
+ return;
15350
+ }
15351
+ setSort({ id, direction: "asc" });
15352
+ },
15353
+ filterKind: meta?.kind ?? null,
15354
+ filterValue: columnFilters[id],
15355
+ onFilterChange: (next) => setColumnFilter(id, next),
15356
+ selectOptions: meta?.options,
15357
+ selectSingle: col.filterSingle,
15358
+ selectAllowCustom: col.filterAllowCustom,
15359
+ filterApplyLabel: filterUi?.applyLabel,
15360
+ filterShowCancel: filterUi?.showCancel,
15361
+ filterFooterHint: filterUi?.footerHint,
15362
+ allowNegation: filterUi?.allowNegation,
15363
+ allowNullModes: filterUi?.allowNullModes,
15364
+ align: col.align,
15365
+ compact,
15366
+ marker,
15367
+ reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0,
15368
+ menuItems: col.headerMenu,
15369
+ MenuIcon: host.MenuIcon
15327
15370
  }
15328
- ),
15329
- carriesFillHandle ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15330
- "button",
15371
+ ) }),
15372
+ columnResizable ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15373
+ ColumnResizeHandle,
15331
15374
  {
15332
- type: "button",
15333
- "data-matrx-table-fill-handle": true,
15334
- "aria-label": "Drag to fill the cells below or beside this selection",
15335
- onMouseDown: sheet.startFillDrag,
15336
- className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
15375
+ columnId: id,
15376
+ label: headerLabel,
15377
+ tableRef,
15378
+ minWidth,
15379
+ maxWidth: col.maxWidth,
15380
+ onResizeStart: captureResizeGestureSnapshot,
15381
+ onResizeCancel: clearResizeGestureSnapshot,
15382
+ onResizeCommit: (columnId2, width2) => commitColumnWidths(
15383
+ { ...effectiveColumnWidths, [columnId2]: width2 },
15384
+ true
15385
+ )
15337
15386
  }
15338
15387
  ) : null
15339
15388
  ]
15340
15389
  },
15341
- columnId(col)
15390
+ id
15342
15391
  );
15343
15392
  }),
15344
15393
  columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15345
- "td",
15394
+ "th",
15346
15395
  {
15347
15396
  "aria-hidden": "true",
15348
15397
  "data-matrx-table-column-spacer": "end",
@@ -15352,629 +15401,977 @@ function MatrxDataTableCore({
15352
15401
  "matrx-column-spacer-end"
15353
15402
  ) : null,
15354
15403
  showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15355
- "td",
15404
+ "th",
15356
15405
  {
15357
- "data-matrx-table-row-action-cell": true,
15358
- className: "text-right align-middle",
15406
+ "data-matrx-table-actions-column": true,
15407
+ className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
15359
15408
  style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15360
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15361
- "div",
15362
- {
15363
- "data-matrx-table-row-actions": true,
15364
- className: "inline-flex items-center justify-end gap-0",
15365
- onClick: (e) => e.stopPropagation(),
15366
- children: [
15367
- showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15368
- TableRowCopyControl,
15369
- {
15370
- size: "toolbar",
15371
- copy: effectiveCopy,
15372
- row: displayRow,
15373
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15374
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15375
- }
15376
- ) : null,
15377
- renderRowActions(displayRow, controls),
15378
- windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15379
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15380
- Button,
15381
- {
15382
- type: "button",
15383
- variant: "ghost",
15384
- size: "icon",
15385
- "data-matrx-table-icon-action": true,
15386
- className: "h-11 w-11 text-muted-foreground hover:text-foreground [&_svg]:size-5",
15387
- "aria-label": windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window",
15388
- onClick: (e) => {
15389
- e.stopPropagation();
15390
- if (windowConfig?.openOnRowClick === true) openDetail(row);
15391
- else openWindow(row);
15392
- },
15393
- children: opensWindowOnRowClick ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
15394
- }
15395
- ) }),
15396
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window" })
15397
- ] }) : null
15398
- ]
15399
- }
15400
- )
15409
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
15401
15410
  }
15402
15411
  )
15403
- ]
15404
- },
15405
- id
15406
- );
15407
- const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
15408
- const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15409
- "tr",
15410
- {
15411
- "data-matrx-table-expanded-detail": id,
15412
- className: cn("bg-muted/20", expandedDetail.className),
15413
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15412
+ ] })
15413
+ }
15414
+ ),
15415
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tbody", { children: [
15416
+ isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { className: "border-b border-border/60", children: [
15417
+ selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
15418
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15414
15419
  "td",
15415
15420
  {
15416
- colSpan: renderedColSpan,
15417
- className: "p-4",
15418
- children: expandedDetail.render(displayRow, controls)
15419
- }
15420
- )
15421
- },
15422
- `${id}:expanded-detail`
15423
- ) : null;
15424
- return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15425
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15426
- HierarchyDroppableRow,
15421
+ "aria-hidden": "true",
15422
+ "data-matrx-table-column-spacer": "start",
15423
+ className: "border-0 p-0",
15424
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15425
+ },
15426
+ "matrx-column-spacer-start"
15427
+ ) : null,
15428
+ renderedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15429
+ "td",
15430
+ {
15431
+ className: cn(
15432
+ "py-2",
15433
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15434
+ col.mobileHidden && "max-sm:hidden"
15435
+ ),
15436
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-5 w-full" })
15437
+ },
15438
+ columnId(col)
15439
+ )),
15440
+ showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "ml-auto h-5 w-10" }) }),
15441
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15442
+ "td",
15443
+ {
15444
+ "aria-hidden": "true",
15445
+ "data-matrx-table-column-spacer": "end",
15446
+ className: "border-0 p-0",
15447
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15448
+ },
15449
+ "matrx-column-spacer-end"
15450
+ ) : null
15451
+ ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15452
+ "td",
15427
15453
  {
15428
- id,
15429
- disabled: !canDropOn(row),
15430
- children: wrappedRow
15454
+ colSpan: renderedColSpan,
15455
+ className: "px-0 py-12 text-center",
15456
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-empty-state": true, className: "sticky left-0 flex flex-col items-center gap-2 whitespace-normal px-4", style: { width: "100cqw", maxWidth: "100%" }, children: [
15457
+ renderedEmptyState?.icon,
15458
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15459
+ renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15460
+ renderedEmptyState?.action
15461
+ ] })
15431
15462
  }
15432
- ),
15433
- inlineDetail
15434
- ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15435
- wrappedRow,
15436
- inlineDetail
15437
- ] }, id);
15438
- }),
15439
- appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15440
- "td",
15441
- {
15442
- colSpan: renderedColSpan,
15443
- className: "sticky bottom-0 z-20 h-16 border-y border-border bg-card/95 px-0 py-0 text-left shadow-sm backdrop-blur-sm",
15444
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15445
- "div",
15446
- {
15447
- role: "status",
15448
- "aria-live": "polite",
15449
- className: "sticky left-0 flex h-16 w-fit max-w-[100cqw] items-center justify-center gap-2 px-3 text-sm font-medium text-foreground",
15450
- children: [
15451
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15452
- Loader2,
15453
- {
15454
- "aria-hidden": "true",
15455
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15463
+ ) }) : bodyEntries.map((entry) => {
15464
+ if (entry.kind === "spacer") {
15465
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15466
+ "tr",
15467
+ {
15468
+ "aria-hidden": "true",
15469
+ "data-matrx-table-virtual-spacer": entry.place,
15470
+ style: { height: `${entry.height}px` },
15471
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15472
+ "td",
15473
+ {
15474
+ className: "border-0 p-0",
15475
+ colSpan: renderedColSpan
15476
+ }
15477
+ )
15478
+ },
15479
+ `matrx-virtual-spacer-${entry.place}`
15480
+ );
15481
+ }
15482
+ if (entry.kind === "group") {
15483
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15484
+ TableGroupHeaderRow,
15485
+ {
15486
+ group: entry.group,
15487
+ collapsed: entry.collapsed,
15488
+ columns: renderedColumns,
15489
+ spacers: columnSpacers,
15490
+ leadingCols,
15491
+ showActionsCol,
15492
+ rowNoun: grouping?.rowNoun ?? "row",
15493
+ ...grouping?.renderLabel ? { renderLabel: grouping.renderLabel } : {},
15494
+ onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
15495
+ },
15496
+ `matrx-group-${entry.group.key}`
15497
+ );
15498
+ }
15499
+ const row = entry.row;
15500
+ const index = entry.index;
15501
+ const id = getRowId(row);
15502
+ const isSelected = selectedId === id;
15503
+ const rowEdits = edits[id];
15504
+ const displayRow = applyRowEdits(row, rowEdits);
15505
+ const controls = buildRecordControls(displayRow);
15506
+ const isChecked = selectedIdSet.has(getSelectionId(row));
15507
+ const background = rowBackground({
15508
+ tint: rowTintColor(row, id),
15509
+ highlighted: highlightedIndex === index,
15510
+ selected: isSelected,
15511
+ checked: isChecked,
15512
+ zebraOdd: zebra && index % 2 === 1,
15513
+ hoverable: detailEnabled || Boolean(onRowOpen) || canExpandRow(row)
15514
+ });
15515
+ const canHoverFrozenCell = background.frozenHover;
15516
+ const frozenRowBackgroundClass = background.frozen;
15517
+ const rowNode = /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15518
+ "tr",
15519
+ {
15520
+ "data-row-id": id,
15521
+ style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
15522
+ "data-state": isSelected ? "selected" : void 0,
15523
+ onClick: (e) => {
15524
+ if (rowEventTargetsInteractiveDescendant(e.target)) return;
15525
+ if (opensRowHref(e)) {
15526
+ const href = getRowHref?.(row);
15527
+ if (href) window.open(href, "_blank", "noopener");
15528
+ return;
15456
15529
  }
15530
+ openRow(row);
15531
+ },
15532
+ onAuxClick: (e) => {
15533
+ if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
15534
+ const href = getRowHref?.(row);
15535
+ if (href) {
15536
+ e.preventDefault();
15537
+ window.open(href, "_blank", "noopener");
15538
+ }
15539
+ },
15540
+ className: cn(
15541
+ // Keep the mobile row opaque while the complete row scrolls
15542
+ // horizontally. The translucent zebra/hover tints remain
15543
+ // desktop-only so densely layered mobile content stays crisp.
15544
+ "group/matrx-row border-b border-border/60 bg-card transition-colors",
15545
+ (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer",
15546
+ rowClassName?.(row, index),
15547
+ draggedRowId === id && "opacity-40",
15548
+ // LAST, and decided once: tint, then highlight / open / ticked, then
15549
+ // the zebra stripe (./row-background.ts). Under a tint no stripe is
15550
+ // emitted at all, so no `sm:` class can win the cascade over it.
15551
+ background.row
15457
15552
  ),
15458
- "Loading more rows\u2026"
15459
- ]
15460
- }
15461
- )
15462
- }
15463
- ) }) : null
15464
- ] }),
15465
- footerRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15466
- selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2" }) : null,
15467
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15468
- "td",
15469
- {
15470
- "aria-hidden": "true",
15471
- "data-matrx-table-column-spacer": "start",
15472
- className: "border-0 p-0",
15473
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15474
- },
15475
- "matrx-column-spacer-start"
15476
- ) : null,
15477
- renderedColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15478
- "td",
15479
- {
15480
- className: cn(
15481
- "px-2 py-2 align-middle",
15482
- (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
15483
- column.className,
15484
- column.align === "center" && "text-center",
15485
- column.align === "right" && "text-right"
15486
- ),
15487
- style: columnWidthVar(
15488
- resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
15489
- windowedWidthFor(columnId(column), column.width),
15490
- columnId(column),
15491
- resizableColumns && column.resizable !== false
15492
- ),
15493
- children: renderCell(row, column, index)
15494
- },
15495
- columnId(column)
15496
- )),
15497
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15498
- "td",
15499
- {
15500
- "aria-hidden": "true",
15501
- "data-matrx-table-column-spacer": "end",
15502
- className: "border-0 p-0",
15503
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15504
- },
15505
- "matrx-column-spacer-end"
15506
- ) : null,
15507
- showActionsCol ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", {}) : null
15508
- ] }, `footer-${index}`)) }) : null
15509
- ]
15510
- }
15511
- )
15512
- ]
15513
- }
15514
- ),
15515
- scrollHintLeft ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15516
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15517
- Button,
15518
- {
15519
- type: "button",
15520
- variant: "ghost",
15521
- size: "icon",
15522
- "data-matrx-table-icon-action": true,
15523
- "aria-label": "Scroll table left",
15524
- "data-matrx-table-scroll-control": "left",
15525
- className: "absolute left-1.5 top-1/2 z-30 hidden h-11 w-11 -translate-y-1/2 rounded-full border border-glass-edge bg-glass p-0 text-foreground shadow-glass backdrop-blur-glass backdrop-saturate-glass hover:bg-glass-hover active:bg-glass-active sm:inline-flex",
15526
- onClick: () => scrollTableHorizontally("left"),
15527
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronLeft, { className: "h-5 w-5" })
15528
- }
15529
- ) }),
15530
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the left" })
15531
- ] }) : null,
15532
- scrollHintRight ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15533
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15534
- Button,
15535
- {
15536
- type: "button",
15537
- variant: "ghost",
15538
- size: "icon",
15539
- "data-matrx-table-icon-action": true,
15540
- "aria-label": "Scroll table right",
15541
- "data-matrx-table-scroll-control": "right",
15542
- className: "absolute right-1.5 top-1/2 z-30 hidden h-11 w-11 -translate-y-1/2 rounded-full border border-glass-edge bg-glass p-0 text-foreground shadow-glass backdrop-blur-glass backdrop-saturate-glass hover:bg-glass-hover active:bg-glass-active sm:inline-flex",
15543
- onClick: () => scrollTableHorizontally("right"),
15544
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronRight, { className: "h-5 w-5" })
15545
- }
15546
- ) }),
15547
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the right" })
15548
- ] }) : null
15549
- ] }),
15550
- appendQuery ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15551
- "div",
15552
- {
15553
- "data-matrx-table-footer": true,
15554
- className: "flex min-h-8 shrink-0 flex-wrap items-center gap-2 border-t border-border",
15555
- "aria-busy": isLoading || isFetching,
15556
- children: [
15557
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15558
- "div",
15559
- {
15560
- ...isFetching && !isLoading ? {} : { role: "status", "aria-live": "polite" },
15561
- className: "flex w-full min-w-0 items-center gap-2 text-xs text-muted-foreground sm:w-auto sm:flex-1",
15562
- children: [
15563
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15564
- Loader2,
15565
- {
15566
- "data-matrx-table-append-loader": true,
15567
- "aria-hidden": "true",
15568
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15553
+ children: [
15554
+ selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15555
+ "td",
15556
+ {
15557
+ className: cn(
15558
+ "px-2 py-1.5 align-middle lg:py-0.5",
15559
+ // When a data column is frozen the checkbox
15560
+ // column has to freeze with it, or the first
15561
+ // frozen column slides over the checkboxes.
15562
+ frozenColumns.selectionOffset !== null && cn(
15563
+ "sm:sticky sm:left-0 sm:z-20",
15564
+ frozenRowBackgroundClass,
15565
+ canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
15566
+ )
15567
+ ),
15568
+ style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15569
+ onClick: (e) => e.stopPropagation(),
15570
+ children: selection.isRowSelectable?.(row) ?? true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15571
+ "div",
15572
+ {
15573
+ "data-matrx-table-selection-target": true,
15574
+ onClick: (event) => {
15575
+ if (event.target === event.currentTarget) {
15576
+ event.currentTarget.querySelector('[role="checkbox"]')?.click();
15577
+ }
15578
+ },
15579
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15580
+ Checkbox,
15581
+ {
15582
+ className: CHECKBOX_TAP_AREA,
15583
+ checked: isChecked,
15584
+ "aria-label": `Select this ${selectionNoun}`,
15585
+ onClick: (e) => toggleRowSelected(index, e.shiftKey)
15586
+ }
15587
+ )
15588
+ }
15589
+ ) : null
15590
+ }
15591
+ ) : null,
15592
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15593
+ "td",
15594
+ {
15595
+ "aria-hidden": "true",
15596
+ "data-matrx-table-column-spacer": "start",
15597
+ className: "border-0 p-0",
15598
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15599
+ },
15600
+ "matrx-column-spacer-start"
15601
+ ) : null,
15602
+ renderedColumns.map((col, colIdx) => {
15603
+ const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
15604
+ const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
15605
+ const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
15606
+ const frozen = frozenColumns.offsets[columnId(col)] !== void 0;
15607
+ const frozenCellTint = cellTintColor(
15608
+ row,
15609
+ id,
15610
+ columnId(col)
15611
+ );
15612
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15613
+ "td",
15614
+ {
15615
+ "data-matrx-table-column-id": columnId(col),
15616
+ "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15617
+ ...sheetCell ? {
15618
+ "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
15619
+ "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
15620
+ ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
15621
+ ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
15622
+ ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
15623
+ onMouseDown: sheetCell.onMouseDown,
15624
+ onMouseEnter: sheetCell.onMouseEnter
15625
+ } : {},
15626
+ className: cn(
15627
+ "py-1.5 align-middle lg:py-0.5",
15628
+ sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
15629
+ sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
15630
+ sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
15631
+ carriesFillHandle && "relative",
15632
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15633
+ // nowrap (NOT truncate — truncate clips the cell and
15634
+ // defeats w-max, killing the horizontal scroll).
15635
+ "max-sm:whitespace-nowrap",
15636
+ col.mobileHidden && "max-sm:hidden",
15637
+ ((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)]",
15638
+ col.className,
15639
+ isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15640
+ alignForFormat(col) === "center" && "text-center",
15641
+ alignForFormat(col) === "right" && "text-right",
15642
+ frozen && FROZEN_CELL_CLASS,
15643
+ frozen && frozenRowBackgroundClass,
15644
+ frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
15645
+ frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
15646
+ frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
15647
+ // A cell tint paints OVER the row tint beneath
15648
+ // it, and the consumer's own hook comes last so
15649
+ // it can always win.
15650
+ cellTintClass(row, id, columnId(col)),
15651
+ frozen && frozenCellTint && FROZEN_CELL_TINT_CLASS[frozenCellTint],
15652
+ cellClassName?.(row, columnId(col), index)
15653
+ ),
15654
+ style: {
15655
+ ...columnWidthVar(
15656
+ resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
15657
+ windowedWidthFor(columnId(col), col.width),
15658
+ columnId(col),
15659
+ resizableColumns && col.resizable !== false
15660
+ ),
15661
+ ...frozenStyle(columnId(col))
15662
+ },
15663
+ children: [
15664
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15665
+ "div",
15666
+ {
15667
+ className: cn(
15668
+ colIdx === 0 && (hierarchy || canExpandRow(row)) && "flex min-w-0 items-center"
15669
+ ),
15670
+ children: [
15671
+ colIdx === 0 && hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15672
+ HierarchyDragHandle,
15673
+ {
15674
+ id,
15675
+ label: hierarchy.itemLabel?.(row) ?? "row",
15676
+ disabled: !(hierarchy.canReparent?.(row) ?? true)
15677
+ }
15678
+ ) : null,
15679
+ colIdx === 0 && canExpandRow(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15680
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15681
+ Button,
15682
+ {
15683
+ type: "button",
15684
+ variant: "ghost",
15685
+ size: "icon",
15686
+ "data-matrx-table-expand-toggle": true,
15687
+ className: "mr-1 h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground [&_svg]:size-4",
15688
+ "aria-label": isRowExpanded(row) ? "Collapse row details" : "Expand row details",
15689
+ "aria-expanded": isRowExpanded(row),
15690
+ onClick: (event) => {
15691
+ event.stopPropagation();
15692
+ toggleExpanded(row);
15693
+ },
15694
+ onKeyDown: (event) => {
15695
+ if (event.key !== "Enter" && event.key !== " ") return;
15696
+ event.preventDefault();
15697
+ event.stopPropagation();
15698
+ toggleExpanded(row);
15699
+ },
15700
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15701
+ ChevronRight,
15702
+ {
15703
+ className: cn(
15704
+ "transition-transform",
15705
+ isRowExpanded(row) && "rotate-90"
15706
+ )
15707
+ }
15708
+ )
15709
+ }
15710
+ ) }),
15711
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: isRowExpanded(row) ? "Collapse details" : "Expand details" })
15712
+ ] }) : null,
15713
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15714
+ "div",
15715
+ {
15716
+ "data-matrx-cell-content": true,
15717
+ className: "min-w-0 max-w-full flex-1 whitespace-normal break-words [overflow-wrap:anywhere] sm:[&_*]:min-w-0 sm:[&_*]:whitespace-normal sm:[&_*]:[overflow-wrap:anywhere] sm:[&_.truncate]:whitespace-nowrap sm:[&_[data-slot=badge]]:whitespace-nowrap sm:[&_[data-slot=badge]]:[overflow-wrap:normal] sm:[&_[data-matrx-cell-control]]:w-max sm:[&_[data-matrx-cell-control]]:min-w-max sm:[&_[data-matrx-cell-control]]:max-w-none sm:[&_[data-matrx-cell-control]]:whitespace-nowrap sm:[&_[data-matrx-cell-control]]:[overflow-wrap:normal] sm:[&_[data-matrx-cell-control]_*]:whitespace-nowrap sm:[&_[data-matrx-cell-control]_*]:[overflow-wrap:normal] [&_[data-matrx-cell-fill]]:!w-full [&_[data-matrx-cell-fill]]:!min-w-0 [&_[data-matrx-cell-fill]]:!max-w-full sm:[&_[data-matrx-cell-fill]]:!w-full sm:[&_[data-matrx-cell-fill]]:!min-w-0 sm:[&_[data-matrx-cell-fill]]:!max-w-full",
15718
+ children: renderRecordCell(row, col, index)
15719
+ }
15720
+ )
15721
+ ]
15722
+ }
15723
+ ),
15724
+ carriesFillHandle ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15725
+ "button",
15726
+ {
15727
+ type: "button",
15728
+ "data-matrx-table-fill-handle": true,
15729
+ "aria-label": "Drag to fill the cells below or beside this selection",
15730
+ onMouseDown: sheet.startFillDrag,
15731
+ className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
15732
+ }
15733
+ ) : null
15734
+ ]
15735
+ },
15736
+ columnId(col)
15737
+ );
15738
+ }),
15739
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15740
+ "td",
15741
+ {
15742
+ "aria-hidden": "true",
15743
+ "data-matrx-table-column-spacer": "end",
15744
+ className: "border-0 p-0",
15745
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15746
+ },
15747
+ "matrx-column-spacer-end"
15748
+ ) : null,
15749
+ showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15750
+ "td",
15751
+ {
15752
+ "data-matrx-table-row-action-cell": true,
15753
+ className: "text-right align-middle",
15754
+ style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15755
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15756
+ "div",
15757
+ {
15758
+ "data-matrx-table-row-actions": true,
15759
+ className: "inline-flex items-center justify-end gap-0",
15760
+ onClick: (e) => e.stopPropagation(),
15761
+ children: [
15762
+ showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15763
+ TableRowCopyControl,
15764
+ {
15765
+ size: "toolbar",
15766
+ copy: effectiveCopy,
15767
+ row: displayRow,
15768
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15769
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15770
+ }
15771
+ ) : null,
15772
+ renderRowActions(displayRow, controls),
15773
+ windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15774
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15775
+ Button,
15776
+ {
15777
+ type: "button",
15778
+ variant: "ghost",
15779
+ size: "icon",
15780
+ "data-matrx-table-icon-action": true,
15781
+ className: "h-11 w-11 text-muted-foreground hover:text-foreground [&_svg]:size-5",
15782
+ "aria-label": windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window",
15783
+ onClick: (e) => {
15784
+ e.stopPropagation();
15785
+ if (windowConfig?.openOnRowClick === true) openDetail(row);
15786
+ else openWindow(row);
15787
+ },
15788
+ children: opensWindowOnRowClick ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
15789
+ }
15790
+ ) }),
15791
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window" })
15792
+ ] }) : null
15793
+ ]
15794
+ }
15795
+ )
15796
+ }
15797
+ )
15798
+ ]
15799
+ },
15800
+ id
15801
+ );
15802
+ const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
15803
+ const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15804
+ "tr",
15805
+ {
15806
+ "data-matrx-table-expanded-detail": id,
15807
+ className: cn("bg-muted/20", expandedDetail.className),
15808
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15809
+ "td",
15810
+ {
15811
+ colSpan: renderedColSpan,
15812
+ className: "p-4",
15813
+ children: expandedDetail.render(displayRow, controls)
15814
+ }
15815
+ )
15816
+ },
15817
+ `${id}:expanded-detail`
15818
+ ) : null;
15819
+ return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15820
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15821
+ HierarchyDroppableRow,
15822
+ {
15823
+ id,
15824
+ disabled: !canDropOn(row),
15825
+ children: wrappedRow
15826
+ }
15827
+ ),
15828
+ inlineDetail
15829
+ ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15830
+ wrappedRow,
15831
+ inlineDetail
15832
+ ] }, id);
15833
+ }),
15834
+ appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15835
+ "td",
15836
+ {
15837
+ colSpan: renderedColSpan,
15838
+ className: "sticky bottom-0 z-20 h-16 border-y border-border bg-card/95 px-0 py-0 text-left shadow-sm backdrop-blur-sm",
15839
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15840
+ "div",
15841
+ {
15842
+ role: "status",
15843
+ "aria-live": "polite",
15844
+ className: "sticky left-0 flex h-16 w-fit max-w-[100cqw] items-center justify-center gap-2 px-3 text-sm font-medium text-foreground",
15845
+ children: [
15846
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15847
+ Loader2,
15848
+ {
15849
+ "aria-hidden": "true",
15850
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15851
+ }
15852
+ ),
15853
+ "Loading more rows\u2026"
15854
+ ]
15855
+ }
15856
+ )
15857
+ }
15858
+ ) }) : null
15859
+ ] }),
15860
+ footerRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15861
+ selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2" }) : null,
15862
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15863
+ "td",
15864
+ {
15865
+ "aria-hidden": "true",
15866
+ "data-matrx-table-column-spacer": "start",
15867
+ className: "border-0 p-0",
15868
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15869
+ },
15870
+ "matrx-column-spacer-start"
15871
+ ) : null,
15872
+ renderedColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15873
+ "td",
15874
+ {
15875
+ className: cn(
15876
+ "px-2 py-2 align-middle",
15877
+ (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
15878
+ column.className,
15879
+ column.align === "center" && "text-center",
15880
+ column.align === "right" && "text-right"
15881
+ ),
15882
+ style: columnWidthVar(
15883
+ resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
15884
+ windowedWidthFor(columnId(column), column.width),
15885
+ columnId(column),
15886
+ resizableColumns && column.resizable !== false
15887
+ ),
15888
+ children: renderCell(row, column, index)
15889
+ },
15890
+ columnId(column)
15891
+ )),
15892
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15893
+ "td",
15894
+ {
15895
+ "aria-hidden": "true",
15896
+ "data-matrx-table-column-spacer": "end",
15897
+ className: "border-0 p-0",
15898
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15899
+ },
15900
+ "matrx-column-spacer-end"
15901
+ ) : null,
15902
+ showActionsCol ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", {}) : null
15903
+ ] }, `footer-${index}`)) }) : null
15904
+ ]
15569
15905
  }
15570
- ) : null,
15571
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2", children: [
15572
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "min-w-0 break-words font-medium text-foreground", children: isLoading ? "Loading rows\u2026" : appendQuery.pagination.sourceRecords ? `${(0, import_format5.formatCount)(processed.length)} ${appendQuery.pagination.sourceRecords.rowLabel}${appendQuery.pagination.sourceRecords.matchedRecords === void 0 ? "" : ` (${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.matchedRecords)} matching ${appendQuery.pagination.sourceRecords.label})`} \xB7 ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.loaded)}${appendQuery.pagination.sourceRecords.total === void 0 ? "" : ` / ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.total)}`} ${appendQuery.pagination.sourceRecords.label} loaded` : `${(0, import_format5.formatCount)(data.length)} loaded${appendQuery.pagination.totalItems === void 0 ? "" : ` of ${(0, import_format5.formatCount)(appendQuery.pagination.totalItems)}`}` }),
15573
- appendQuery.pagination.sourceUntil || appendUsesLocalProcessing ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15574
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", "aria-label": "Source coverage", className: "shrink-0 text-xs text-muted-foreground underline decoration-dotted underline-offset-4", children: "Source info" }) }),
15575
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(TooltipContent, { children: [
15576
- appendQuery.pagination.sourceUntil ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { children: [
15577
- "Source fixed through ",
15578
- new Date(appendQuery.pagination.sourceUntil).toLocaleString(),
15579
- "."
15580
- ] }) : null,
15581
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: !isLoading && !isFetching && !appendQuery.pagination.loadAll?.isLoading && !scrollPagination.error && !appendQuery.pagination.hasNextPage && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? "All source rows are loaded." : appendUsesLocalProcessing ? sourceOwnsColumnFilters ? "Local processing covers loaded rows; source-owned column filters are applied by the source." : "Local search and filters cover loaded rows." : "Source paging is active." })
15582
- ] })
15583
- ] }) : null,
15584
- scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "text-destructive", children: scrollPagination.error.message }) : null,
15585
- appendScrollPolicy?.mode === "suspended" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { role: "status", children: [
15586
- appendScrollPolicy.reason,
15587
- " Load more remains available."
15588
- ] }) : null
15589
- ] })
15906
+ )
15590
15907
  ]
15591
15908
  }
15592
15909
  ),
15593
- appendQuery.pagination.sourcePageSize ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15594
- Select,
15595
- {
15596
- value: String(appendQuery.pagination.sourcePageSize.value),
15597
- onValueChange: (value) => appendQuery.pagination.sourcePageSize?.onChange(Number(value)),
15598
- children: [
15599
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15600
- SelectTrigger,
15601
- {
15602
- "aria-label": "Rows per load",
15603
- className: "h-8 min-w-20 shrink-0 rounded-md border border-border bg-card px-3 text-sm [&_svg]:size-5",
15604
- disabled: isLoading || isFetching || appendQuery.pagination.loadAll?.isLoading,
15605
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, {})
15606
- }
15607
- ),
15608
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectContent, { children: appendQuery.pagination.sourcePageSize.options.map((size) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: String(size), children: size }, size)) })
15609
- ]
15610
- }
15611
- ) : null,
15612
- appendQuery.pagination.loadAll?.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15613
- Button,
15614
- {
15615
- type: "button",
15616
- variant: "outline",
15617
- className: "h-8 shrink-0 px-3 text-sm",
15618
- onClick: appendQuery.pagination.loadAll.stop,
15619
- children: "Stop loading"
15620
- }
15621
- ) : scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15622
- Button,
15623
- {
15624
- type: "button",
15625
- variant: "outline",
15626
- className: "h-8 shrink-0 px-3 text-sm",
15627
- onClick: appendQuery.pagination.retrySource ?? (appendQuery.pagination.hasNextPage ? appendQuery.pagination.loadNextPage : appendQuery.pagination.refresh),
15628
- children: appendQuery.pagination.retrySource ? "Retry source" : appendQuery.pagination.hasNextPage ? "Retry" : "Reload rows"
15629
- }
15630
- ) : appendQuery.pagination.hasNextPage || scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 max-w-full flex-wrap gap-0", children: [
15631
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15910
+ scrollHintLeft ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15911
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15632
15912
  Button,
15633
15913
  {
15634
15914
  type: "button",
15635
- variant: "outline",
15636
- className: "h-8 px-3 text-sm",
15637
- disabled: isLoading || isFetching,
15638
- onClick: () => void scrollPagination.requestNextPage(),
15639
- children: "Load more"
15915
+ variant: "ghost",
15916
+ size: "icon",
15917
+ "data-matrx-table-icon-action": true,
15918
+ "aria-label": "Scroll table left",
15919
+ "data-matrx-table-scroll-control": "left",
15920
+ className: "absolute left-1.5 top-1/2 z-30 hidden h-11 w-11 -translate-y-1/2 rounded-full border border-glass-edge bg-glass p-0 text-foreground shadow-glass backdrop-blur-glass backdrop-saturate-glass hover:bg-glass-hover active:bg-glass-active sm:inline-flex",
15921
+ onClick: () => scrollTableHorizontally("left"),
15922
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronLeft, { className: "h-5 w-5" })
15640
15923
  }
15641
- ),
15642
- appendQuery.pagination.loadAll ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15924
+ ) }),
15925
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the left" })
15926
+ ] }) : null,
15927
+ scrollHintRight ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15928
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15643
15929
  Button,
15644
15930
  {
15645
15931
  type: "button",
15646
15932
  variant: "ghost",
15647
- className: "h-8 px-3 text-sm",
15648
- disabled: isLoading || isFetching,
15649
- onClick: appendQuery.pagination.loadAll.request,
15650
- children: "Load all"
15651
- }
15652
- ) : null
15653
- ] }) : !isLoading && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 max-w-full break-words whitespace-normal px-3 text-xs text-muted-foreground sm:text-sm", children: "All rows loaded" }) : null
15654
- ]
15655
- }
15656
- ) : !hidePagination && (totalItems > 0 || sourceTotal !== void 0) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15657
- GenericTablePagination,
15658
- {
15659
- totalItems,
15660
- ...paginationLabelFormat ? { labelFormat: paginationLabelFormat } : sourceTotal === void 0 ? {} : { labelFormat: (start, end, total) => `${(0, import_format5.formatCount)(start)}\u2013${(0, import_format5.formatCount)(end)} of ${(0, import_format5.formatCount)(total)} matching \xB7 ${(0, import_format5.formatCount)(data.length)} / ${(0, import_format5.formatCount)(sourceTotal)} loaded` },
15661
- ...localProgressiveEnabled ? { progressive: {
15662
- shownCount: progressiveRows.shownCount,
15663
- loadedCount: data.length,
15664
- hasMore: progressiveRows.hasMore,
15665
- onShowMore: () => void progressiveRows.showMore()
15666
- } } : {},
15667
- itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
15668
- currentPage: safePage,
15669
- onPageChange: setPage,
15670
- onItemsPerPageChange: (n2) => {
15671
- setPageSize(n2);
15672
- if (!controlledQuery) setPage(1);
15673
- },
15674
- pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
15675
- showAllOption: allRowsAreLoaded,
15676
- allValue: 0,
15677
- allOptionLabel: "All loaded",
15678
- compact: true,
15679
- hideEntriesInfo: false
15680
- }
15681
- ) }) : null,
15682
- selection && selectionCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15683
- "div",
15684
- {
15685
- "data-matrx-table-bulk-bar": true,
15686
- className: "mt-2 flex shrink-0 flex-wrap items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-2 py-1.5",
15687
- children: [
15688
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-xs font-medium text-foreground", children: [
15689
- (0, import_format5.formatCount)(selectionCount),
15690
- " ",
15691
- selectionNoun,
15692
- selectionCount === 1 ? "" : "s",
15693
- " selected"
15694
- ] }),
15695
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15696
- Button,
15697
- {
15698
- type: "button",
15699
- variant: "ghost",
15700
- size: "sm",
15701
- className: "h-7 gap-1 px-2 text-xs",
15702
- onClick: () => {
15703
- lastToggledIndex.current = null;
15704
- selection.onSelectedIdsChange([]);
15705
- },
15706
- children: [
15707
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-3.5 w-3.5" }),
15708
- "Clear"
15709
- ]
15710
- }
15711
- ),
15712
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
15713
- effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15714
- CopyButtons,
15715
- {
15716
15933
  size: "icon",
15717
15934
  "data-matrx-table-icon-action": true,
15718
- sourceId: `${tableCopySourceNamespace}:selection`,
15719
- ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
15720
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
15721
- label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
15722
- ...copy === void 0 ? {} : {
15723
- human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
15724
- json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
15725
- },
15726
- agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
15727
- aiVariants: copySubsetVariantFor(
15728
- () => selectedRows,
15729
- "selected"
15730
- ),
15731
- export: {
15732
- items: [],
15733
- sheetRows: () => selectedRows.map(
15734
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
15735
- ),
15736
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
15737
- id: columnId(column),
15738
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
15739
- }))
15740
- }
15935
+ "aria-label": "Scroll table right",
15936
+ "data-matrx-table-scroll-control": "right",
15937
+ className: "absolute right-1.5 top-1/2 z-30 hidden h-11 w-11 -translate-y-1/2 rounded-full border border-glass-edge bg-glass p-0 text-foreground shadow-glass backdrop-blur-glass backdrop-saturate-glass hover:bg-glass-hover active:bg-glass-active sm:inline-flex",
15938
+ onClick: () => scrollTableHorizontally("right"),
15939
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronRight, { className: "h-5 w-5" })
15741
15940
  }
15742
- ) : null,
15743
- selection.actions?.(selectedRows, [...selectedIdSet])
15744
- ] })
15745
- ]
15746
- }
15747
- ) : null
15748
- ] }),
15749
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15750
- ColumnManager,
15751
- {
15752
- open: columnManagerOpen,
15753
- onOpenChange: setColumnManagerOpen,
15754
- columns,
15755
- order: effectiveColumnOrder,
15756
- hidden: effectiveHiddenColumnIds,
15757
- onApply: commitColumnState
15758
- }
15759
- ),
15760
- detailEnabled && selectedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15761
- SidePanelSurface,
15762
- {
15763
- title: resolveStringTitle(
15764
- detail?.title?.(selectedRow),
15765
- defaultRowTitle(selectedRow, columns)
15766
- ),
15767
- titleNode: detail?.title?.(selectedRow),
15768
- description: detail?.description?.(selectedRow),
15769
- onClose: () => setSelectedId(null),
15770
- defaultWidth: detail?.defaultWidth ?? 480,
15771
- headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
15772
- effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15773
- TableRowCopyControl,
15941
+ ) }),
15942
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the right" })
15943
+ ] }) : null
15944
+ ] }),
15945
+ appendQuery ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15946
+ "div",
15947
+ {
15948
+ "data-matrx-table-footer": true,
15949
+ className: "flex min-h-8 shrink-0 flex-wrap items-center gap-2 border-t border-border",
15950
+ "aria-busy": isLoading || isFetching,
15951
+ children: [
15952
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15953
+ "div",
15954
+ {
15955
+ ...isFetching && !isLoading ? {} : { role: "status", "aria-live": "polite" },
15956
+ className: "flex w-full min-w-0 items-center gap-2 text-xs text-muted-foreground sm:w-auto sm:flex-1",
15957
+ children: [
15958
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15959
+ Loader2,
15960
+ {
15961
+ "data-matrx-table-append-loader": true,
15962
+ "aria-hidden": "true",
15963
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15964
+ }
15965
+ ) : null,
15966
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2", children: [
15967
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "min-w-0 break-words font-medium text-foreground", children: isLoading ? "Loading rows\u2026" : appendQuery.pagination.sourceRecords ? `${(0, import_format5.formatCount)(processed.length)} ${appendQuery.pagination.sourceRecords.rowLabel}${appendQuery.pagination.sourceRecords.matchedRecords === void 0 ? "" : ` (${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.matchedRecords)} matching ${appendQuery.pagination.sourceRecords.label})`} \xB7 ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.loaded)}${appendQuery.pagination.sourceRecords.total === void 0 ? "" : ` / ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.total)}`} ${appendQuery.pagination.sourceRecords.label} loaded` : `${(0, import_format5.formatCount)(data.length)} loaded${appendQuery.pagination.totalItems === void 0 ? "" : ` of ${(0, import_format5.formatCount)(appendQuery.pagination.totalItems)}`}` }),
15968
+ appendQuery.pagination.sourceUntil || appendUsesLocalProcessing ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15969
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", "aria-label": "Source coverage", className: "shrink-0 text-xs text-muted-foreground underline decoration-dotted underline-offset-4", children: "Source info" }) }),
15970
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(TooltipContent, { children: [
15971
+ appendQuery.pagination.sourceUntil ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { children: [
15972
+ "Source fixed through ",
15973
+ new Date(appendQuery.pagination.sourceUntil).toLocaleString(),
15974
+ "."
15975
+ ] }) : null,
15976
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: !isLoading && !isFetching && !appendQuery.pagination.loadAll?.isLoading && !scrollPagination.error && !appendQuery.pagination.hasNextPage && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? "All source rows are loaded." : appendUsesLocalProcessing ? sourceOwnsColumnFilters ? "Local processing covers loaded rows; source-owned column filters are applied by the source." : "Local search and filters cover loaded rows." : "Source paging is active." })
15977
+ ] })
15978
+ ] }) : null,
15979
+ scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "text-destructive", children: scrollPagination.error.message }) : null,
15980
+ appendScrollPolicy?.mode === "suspended" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { role: "status", children: [
15981
+ appendScrollPolicy.reason,
15982
+ " Load more remains available."
15983
+ ] }) : null
15984
+ ] })
15985
+ ]
15986
+ }
15987
+ ),
15988
+ appendQuery.pagination.sourcePageSize ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15989
+ Select,
15990
+ {
15991
+ value: String(appendQuery.pagination.sourcePageSize.value),
15992
+ onValueChange: (value) => appendQuery.pagination.sourcePageSize?.onChange(Number(value)),
15993
+ children: [
15994
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15995
+ SelectTrigger,
15996
+ {
15997
+ "aria-label": "Rows per load",
15998
+ className: "h-8 min-w-20 shrink-0 rounded-md border border-border bg-card px-3 text-sm [&_svg]:size-5",
15999
+ disabled: isLoading || isFetching || appendQuery.pagination.loadAll?.isLoading,
16000
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, {})
16001
+ }
16002
+ ),
16003
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectContent, { children: appendQuery.pagination.sourcePageSize.options.map((size) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: String(size), children: size }, size)) })
16004
+ ]
16005
+ }
16006
+ ) : null,
16007
+ appendQuery.pagination.loadAll?.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16008
+ Button,
16009
+ {
16010
+ type: "button",
16011
+ variant: "outline",
16012
+ className: "h-8 shrink-0 px-3 text-sm",
16013
+ onClick: appendQuery.pagination.loadAll.stop,
16014
+ children: "Stop loading"
16015
+ }
16016
+ ) : scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16017
+ Button,
16018
+ {
16019
+ type: "button",
16020
+ variant: "outline",
16021
+ className: "h-8 shrink-0 px-3 text-sm",
16022
+ onClick: appendQuery.pagination.retrySource ?? (appendQuery.pagination.hasNextPage ? appendQuery.pagination.loadNextPage : appendQuery.pagination.refresh),
16023
+ children: appendQuery.pagination.retrySource ? "Retry source" : appendQuery.pagination.hasNextPage ? "Retry" : "Reload rows"
16024
+ }
16025
+ ) : appendQuery.pagination.hasNextPage || scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 max-w-full flex-wrap gap-0", children: [
16026
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16027
+ Button,
16028
+ {
16029
+ type: "button",
16030
+ variant: "outline",
16031
+ className: "h-8 px-3 text-sm",
16032
+ disabled: isLoading || isFetching,
16033
+ onClick: () => void scrollPagination.requestNextPage(),
16034
+ children: "Load more"
16035
+ }
16036
+ ),
16037
+ appendQuery.pagination.loadAll ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16038
+ Button,
16039
+ {
16040
+ type: "button",
16041
+ variant: "ghost",
16042
+ className: "h-8 px-3 text-sm",
16043
+ disabled: isLoading || isFetching,
16044
+ onClick: appendQuery.pagination.loadAll.request,
16045
+ children: "Load all"
16046
+ }
16047
+ ) : null
16048
+ ] }) : !isLoading && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 max-w-full break-words whitespace-normal px-3 text-xs text-muted-foreground sm:text-sm", children: "All rows loaded" }) : null
16049
+ ]
16050
+ }
16051
+ ) : !hidePagination && (totalItems > 0 || sourceTotal !== void 0) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16052
+ GenericTablePagination,
15774
16053
  {
15775
- size: "icon",
15776
- "data-matrx-table-icon-action": true,
15777
- copy: effectiveCopy,
15778
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(selectedRow)}`,
15779
- row: selectedRow,
15780
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16054
+ totalItems,
16055
+ ...paginationLabelFormat ? { labelFormat: paginationLabelFormat } : sourceTotal === void 0 ? {} : { labelFormat: (start, end, total) => `${(0, import_format5.formatCount)(start)}\u2013${(0, import_format5.formatCount)(end)} of ${(0, import_format5.formatCount)(total)} matching \xB7 ${(0, import_format5.formatCount)(data.length)} / ${(0, import_format5.formatCount)(sourceTotal)} loaded` },
16056
+ ...localProgressiveEnabled ? { progressive: {
16057
+ shownCount: progressiveRows.shownCount,
16058
+ loadedCount: data.length,
16059
+ hasMore: progressiveRows.hasMore,
16060
+ onShowMore: () => void progressiveRows.showMore()
16061
+ } } : {},
16062
+ itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
16063
+ currentPage: safePage,
16064
+ onPageChange: setPage,
16065
+ onItemsPerPageChange: (n2) => {
16066
+ setPageSize(n2);
16067
+ if (!controlledQuery) setPage(1);
16068
+ },
16069
+ pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
16070
+ showAllOption: allRowsAreLoaded,
16071
+ allValue: 0,
16072
+ allOptionLabel: "All loaded",
16073
+ compact: true,
16074
+ hideEntriesInfo: false
15781
16075
  }
15782
- ) : null,
15783
- detail?.headerActions?.(selectedRow),
15784
- windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15785
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15786
- Button,
15787
- {
15788
- type: "button",
15789
- variant: "ghost",
15790
- size: "icon",
15791
- "data-matrx-table-icon-action": true,
15792
- className: "h-7 w-7",
15793
- "aria-label": "Open in window",
15794
- onClick: () => openWindow(selectedRow),
15795
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
15796
- }
15797
- ) }),
15798
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in window" })
15799
- ] }) : null
15800
- ] }),
15801
- children: detail?.render ? (
15802
- // The panel hands children a `min-h-0 flex-1 overflow-hidden` cell
15803
- // and expects the child to own scrolling. `DataRowInspector` does;
15804
- // custom `detail.render` bodies mostly did not, so their content
15805
- // silently cut off at the fold with no scrollbar. Owning it here
15806
- // fixes the whole class at once — a custom body that already
15807
- // scrolls (`h-full` + `overflow-y-auto` root) still resolves to
15808
- // this container's height, so no second scrollbar appears.
15809
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full min-h-0 overflow-y-auto", children: detail.render(selectedRow, {
15810
- closeDetail: () => setSelectedId(null),
15811
- openDetail: () => openDetail(selectedRow),
15812
- openWindow: () => openWindow(selectedRow),
15813
- closeWindow,
15814
- hasPendingEdits: Boolean(edits[getRowId(selectedRow)]),
15815
- discardPendingEdits: () => discardRowEdits(getRowId(selectedRow))
15816
- }) })
15817
- ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15818
- DataRowInspector,
15819
- {
15820
- row: selectedRow,
15821
- ...effectiveCopy?.rowKind === void 0 ? {} : { recordKind: effectiveCopy?.rowKind },
15822
- ...effectiveCopy?.label === void 0 ? {} : { recordLabel: effectiveCopy?.label },
15823
- ...effectiveCopy?.location === void 0 ? {} : { location: effectiveCopy?.location },
15824
- ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
15825
- }
15826
- )
15827
- }
15828
- ) : null,
15829
- windowRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15830
- DataRowWindow,
15831
- {
15832
- isOpen: true,
15833
- onClose: closeWindow,
15834
- title: windowConfig?.title?.(windowRow) ?? defaultRowTitle(windowRow, columns),
15835
- row: windowRow,
15836
- width: windowConfig?.width,
15837
- height: windowConfig?.height,
15838
- windowId: `matrx-data-row-${urlState?.id ?? "local"}-${getRowId(windowRow)}`,
15839
- defaultTab: windowConfig?.defaultTab,
15840
- headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
15841
- windowConfig?.openOnRowClick === true && detail?.enabled === true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15842
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15843
- Button,
15844
- {
15845
- type: "button",
15846
- variant: "ghost",
15847
- size: "icon",
15848
- "data-matrx-table-icon-action": true,
15849
- className: "h-7 w-7",
15850
- "aria-label": "Open in side panel",
15851
- onClick: () => {
15852
- closeWindow();
15853
- openDetail(windowRow);
15854
- },
15855
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" })
15856
- }
15857
- ) }),
15858
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in side panel" })
15859
- ] }) : null,
15860
- effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15861
- TableRowCopyControl,
16076
+ ) }) : null,
16077
+ selection && selectionCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16078
+ "div",
15862
16079
  {
15863
- size: "icon",
15864
- "data-matrx-table-icon-action": true,
15865
- copy: effectiveCopy,
15866
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(windowRow)}`,
15867
- row: windowRow,
15868
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16080
+ "data-matrx-table-bulk-bar": true,
16081
+ className: "mt-2 flex shrink-0 flex-wrap items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-2 py-1.5",
16082
+ children: [
16083
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-xs font-medium text-foreground", children: [
16084
+ (0, import_format5.formatCount)(selectionCount),
16085
+ " ",
16086
+ selectionNoun,
16087
+ selectionCount === 1 ? "" : "s",
16088
+ " selected"
16089
+ ] }),
16090
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16091
+ Button,
16092
+ {
16093
+ type: "button",
16094
+ variant: "ghost",
16095
+ size: "sm",
16096
+ className: "h-7 gap-1 px-2 text-xs",
16097
+ onClick: () => {
16098
+ lastToggledIndex.current = null;
16099
+ selection.onSelectedIdsChange([]);
16100
+ },
16101
+ children: [
16102
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-3.5 w-3.5" }),
16103
+ "Clear"
16104
+ ]
16105
+ }
16106
+ ),
16107
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
16108
+ effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16109
+ CopyButtons,
16110
+ {
16111
+ size: "icon",
16112
+ "data-matrx-table-icon-action": true,
16113
+ sourceId: `${tableCopySourceNamespace}:selection`,
16114
+ ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
16115
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
16116
+ label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
16117
+ ...copy === void 0 ? {} : {
16118
+ human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
16119
+ json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
16120
+ },
16121
+ agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
16122
+ aiVariants: copySubsetVariantFor(
16123
+ () => selectedRows,
16124
+ "selected"
16125
+ ),
16126
+ export: {
16127
+ items: [],
16128
+ sheetRows: () => selectedRows.map(
16129
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
16130
+ ),
16131
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
16132
+ id: columnId(column),
16133
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
16134
+ }))
16135
+ }
16136
+ }
16137
+ ) : null,
16138
+ selection.actions?.(selectedRows, [...selectedIdSet])
16139
+ ] })
16140
+ ]
15869
16141
  }
15870
16142
  ) : null
15871
16143
  ] }),
15872
- viewContent: windowConfig?.renderView?.(windowRow, {
15873
- closeDetail: () => setSelectedId(null),
15874
- openDetail: () => openDetail(windowRow),
15875
- openWindow: () => openWindow(windowRow),
15876
- closeWindow,
15877
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15878
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15879
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15880
- DataRowInspector,
16144
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16145
+ ColumnManager,
15881
16146
  {
15882
- row: windowRow,
15883
- recordKind: effectiveCopy?.rowKind ?? "record",
15884
- recordLabel: effectiveCopy?.label ?? "Record",
15885
- location: effectiveCopy?.location ?? "Table",
15886
- fieldCopy: copy !== false,
15887
- ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16147
+ open: columnManagerOpen,
16148
+ onOpenChange: setColumnManagerOpen,
16149
+ columns,
16150
+ order: effectiveColumnOrder,
16151
+ hidden: effectiveHiddenColumnIds,
16152
+ onApply: commitColumnState
15888
16153
  }
15889
16154
  ),
15890
- editContent: windowConfig?.renderEdit === false ? void 0 : windowConfig?.renderEdit ? windowConfig.renderEdit(windowRow, {
15891
- closeDetail: () => setSelectedId(null),
15892
- openDetail: () => openDetail(windowRow),
15893
- openWindow: () => openWindow(windowRow),
15894
- closeWindow,
15895
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15896
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15897
- }) : detail?.render?.(windowRow, {
15898
- closeDetail: () => setSelectedId(null),
15899
- openDetail: () => openDetail(windowRow),
15900
- openWindow: () => openWindow(windowRow),
15901
- closeWindow,
15902
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15903
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15904
- }),
15905
- children: windowConfig?.render?.(windowRow, {
15906
- closeDetail: () => setSelectedId(null),
15907
- openDetail: () => openDetail(windowRow),
15908
- openWindow: () => openWindow(windowRow),
15909
- closeWindow,
15910
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15911
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15912
- })
15913
- }
15914
- ) : null,
15915
- editEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
15916
- foreignDrafts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15917
- "div",
15918
- {
15919
- "data-matrx-foreign-drafts": true,
15920
- className: "fixed inset-x-3 bottom-20 z-50 flex flex-wrap items-center justify-center gap-2 rounded-lg border border-amber-500/40 bg-card px-3 py-2 text-xs shadow-lg sm:inset-x-auto sm:left-1/2 sm:-translate-x-1/2 sm:flex-nowrap",
15921
- children: [
15922
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Unsaved edits from previous results" }),
15923
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15924
- CopyButtons,
16155
+ detailEnabled && selectedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16156
+ SidePanelSurface,
16157
+ {
16158
+ title: resolveStringTitle(
16159
+ detail?.title?.(selectedRow),
16160
+ defaultRowTitle(selectedRow, columns)
16161
+ ),
16162
+ titleNode: detail?.title?.(selectedRow),
16163
+ description: detail?.description?.(selectedRow),
16164
+ onClose: () => setSelectedId(null),
16165
+ defaultWidth: detail?.defaultWidth ?? 480,
16166
+ headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
16167
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16168
+ TableRowCopyControl,
16169
+ {
16170
+ size: "icon",
16171
+ "data-matrx-table-icon-action": true,
16172
+ copy: effectiveCopy,
16173
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(selectedRow)}`,
16174
+ row: selectedRow,
16175
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16176
+ }
16177
+ ) : null,
16178
+ detail?.headerActions?.(selectedRow),
16179
+ windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
16180
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16181
+ Button,
16182
+ {
16183
+ type: "button",
16184
+ variant: "ghost",
16185
+ size: "icon",
16186
+ "data-matrx-table-icon-action": true,
16187
+ className: "h-7 w-7",
16188
+ "aria-label": "Open in window",
16189
+ onClick: () => openWindow(selectedRow),
16190
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
16191
+ }
16192
+ ) }),
16193
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in window" })
16194
+ ] }) : null
16195
+ ] }),
16196
+ children: detail?.render ? (
16197
+ // The panel hands children a `min-h-0 flex-1 overflow-hidden` cell
16198
+ // and expects the child to own scrolling. `DataRowInspector` does;
16199
+ // custom `detail.render` bodies mostly did not, so their content
16200
+ // silently cut off at the fold with no scrollbar. Owning it here
16201
+ // fixes the whole class at once — a custom body that already
16202
+ // scrolls (`h-full` + `overflow-y-auto` root) still resolves to
16203
+ // this container's height, so no second scrollbar appears.
16204
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full min-h-0 overflow-y-auto", children: detail.render(selectedRow, {
16205
+ closeDetail: () => setSelectedId(null),
16206
+ openDetail: () => openDetail(selectedRow),
16207
+ openWindow: () => openWindow(selectedRow),
16208
+ closeWindow,
16209
+ hasPendingEdits: Boolean(edits[getRowId(selectedRow)]),
16210
+ discardPendingEdits: () => discardRowEdits(getRowId(selectedRow))
16211
+ }) })
16212
+ ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16213
+ DataRowInspector,
16214
+ {
16215
+ row: selectedRow,
16216
+ ...effectiveCopy?.rowKind === void 0 ? {} : { recordKind: effectiveCopy?.rowKind },
16217
+ ...effectiveCopy?.label === void 0 ? {} : { recordLabel: effectiveCopy?.label },
16218
+ ...effectiveCopy?.location === void 0 ? {} : { location: effectiveCopy?.location },
16219
+ ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16220
+ }
16221
+ )
16222
+ }
16223
+ ) : null,
16224
+ windowRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16225
+ DataRowWindow,
16226
+ {
16227
+ isOpen: true,
16228
+ onClose: closeWindow,
16229
+ title: windowConfig?.title?.(windowRow) ?? defaultRowTitle(windowRow, columns),
16230
+ row: windowRow,
16231
+ width: windowConfig?.width,
16232
+ height: windowConfig?.height,
16233
+ windowId: `matrx-data-row-${urlState?.id ?? "local"}-${getRowId(windowRow)}`,
16234
+ defaultTab: windowConfig?.defaultTab,
16235
+ headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
16236
+ windowConfig?.openOnRowClick === true && detail?.enabled === true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
16237
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16238
+ Button,
16239
+ {
16240
+ type: "button",
16241
+ variant: "ghost",
16242
+ size: "icon",
16243
+ "data-matrx-table-icon-action": true,
16244
+ className: "h-7 w-7",
16245
+ "aria-label": "Open in side panel",
16246
+ onClick: () => {
16247
+ closeWindow();
16248
+ openDetail(windowRow);
16249
+ },
16250
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" })
16251
+ }
16252
+ ) }),
16253
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in side panel" })
16254
+ ] }) : null,
16255
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16256
+ TableRowCopyControl,
16257
+ {
16258
+ size: "icon",
16259
+ "data-matrx-table-icon-action": true,
16260
+ copy: effectiveCopy,
16261
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(windowRow)}`,
16262
+ row: windowRow,
16263
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16264
+ }
16265
+ ) : null
16266
+ ] }),
16267
+ viewContent: windowConfig?.renderView?.(windowRow, {
16268
+ closeDetail: () => setSelectedId(null),
16269
+ openDetail: () => openDetail(windowRow),
16270
+ openWindow: () => openWindow(windowRow),
16271
+ closeWindow,
16272
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16273
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16274
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16275
+ DataRowInspector,
15925
16276
  {
15926
- size: "xs",
15927
- label: "retained drafts",
15928
- human: () => JSON.stringify(foreignDraftPayload, null, 2),
15929
- json: () => foreignDraftPayload
16277
+ row: windowRow,
16278
+ recordKind: effectiveCopy?.rowKind ?? "record",
16279
+ recordLabel: effectiveCopy?.label ?? "Record",
16280
+ location: effectiveCopy?.location ?? "Table",
16281
+ fieldCopy: copy !== false,
16282
+ ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
15930
16283
  }
15931
16284
  ),
15932
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Button, { type: "button", size: "sm", variant: "ghost", className: "h-7 text-xs text-destructive", onClick: () => setConfirmForeignDraftDiscard(true), children: "Discard drafts" })
15933
- ]
15934
- }
15935
- ) : null,
15936
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15937
- ConfirmDialog,
15938
- {
15939
- open: confirmForeignDraftDiscard,
15940
- onOpenChange: setConfirmForeignDraftDiscard,
15941
- title: "Discard unsaved edits from previous results?",
15942
- description: "This permanently removes edits retained for other result sets. Copy drafts first if you may need them.",
15943
- confirmLabel: "Discard drafts",
15944
- cancelLabel: "Keep drafts",
15945
- variant: "destructive",
15946
- onConfirm: () => {
15947
- setEditsByScope(
15948
- (current) => Object.fromEntries(
15949
- Object.entries(current).filter(([scope]) => scope === editScopeKey)
15950
- )
15951
- );
15952
- setConfirmForeignDraftDiscard(false);
16285
+ editContent: windowConfig?.renderEdit === false ? void 0 : windowConfig?.renderEdit ? windowConfig.renderEdit(windowRow, {
16286
+ closeDetail: () => setSelectedId(null),
16287
+ openDetail: () => openDetail(windowRow),
16288
+ openWindow: () => openWindow(windowRow),
16289
+ closeWindow,
16290
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16291
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16292
+ }) : detail?.render?.(windowRow, {
16293
+ closeDetail: () => setSelectedId(null),
16294
+ openDetail: () => openDetail(windowRow),
16295
+ openWindow: () => openWindow(windowRow),
16296
+ closeWindow,
16297
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16298
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16299
+ }),
16300
+ children: windowConfig?.render?.(windowRow, {
16301
+ closeDetail: () => setSelectedId(null),
16302
+ openDetail: () => openDetail(windowRow),
16303
+ openWindow: () => openWindow(windowRow),
16304
+ closeWindow,
16305
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16306
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16307
+ })
15953
16308
  }
15954
- }
15955
- ),
15956
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15957
- DirtySavePill,
15958
- {
15959
- changeCount,
15960
- saving,
15961
- onSave: () => void handleSaveEdits(),
15962
- onCancel: handleCancelEdits
15963
- }
15964
- )
15965
- ] }) : null
15966
- ]
15967
- }
15968
- ),
15969
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_core3.DragOverlay, { children: draggedRow ? (
15970
- // w-max: the overlay inherits the ACTIVE node's size, and the active
15971
- // node is the 16px grip handle — without it the label wraps to one
15972
- // character per line.
15973
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "w-max max-w-xs truncate rounded border border-border bg-card px-2.5 py-1.5 text-sm font-medium text-foreground shadow-md", children: hierarchy?.itemLabel?.(draggedRow) ?? "Moving row" })
15974
- ) : null })
15975
- ]
16309
+ ) : null,
16310
+ editEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
16311
+ foreignDrafts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16312
+ "div",
16313
+ {
16314
+ "data-matrx-foreign-drafts": true,
16315
+ className: "fixed inset-x-3 bottom-20 z-50 flex flex-wrap items-center justify-center gap-2 rounded-lg border border-amber-500/40 bg-card px-3 py-2 text-xs shadow-lg sm:inset-x-auto sm:left-1/2 sm:-translate-x-1/2 sm:flex-nowrap",
16316
+ children: [
16317
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Unsaved edits from previous results" }),
16318
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16319
+ CopyButtons,
16320
+ {
16321
+ size: "xs",
16322
+ label: "retained drafts",
16323
+ human: () => JSON.stringify(foreignDraftPayload, null, 2),
16324
+ json: () => foreignDraftPayload
16325
+ }
16326
+ ),
16327
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Button, { type: "button", size: "sm", variant: "ghost", className: "h-7 text-xs text-destructive", onClick: () => setConfirmForeignDraftDiscard(true), children: "Discard drafts" })
16328
+ ]
16329
+ }
16330
+ ) : null,
16331
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16332
+ ConfirmDialog,
16333
+ {
16334
+ open: confirmForeignDraftDiscard,
16335
+ onOpenChange: setConfirmForeignDraftDiscard,
16336
+ title: "Discard unsaved edits from previous results?",
16337
+ description: "This permanently removes edits retained for other result sets. Copy drafts first if you may need them.",
16338
+ confirmLabel: "Discard drafts",
16339
+ cancelLabel: "Keep drafts",
16340
+ variant: "destructive",
16341
+ onConfirm: () => {
16342
+ setEditsByScope(
16343
+ (current) => Object.fromEntries(
16344
+ Object.entries(current).filter(([scope]) => scope === editScopeKey)
16345
+ )
16346
+ );
16347
+ setConfirmForeignDraftDiscard(false);
16348
+ }
16349
+ }
16350
+ ),
16351
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16352
+ DirtySavePill,
16353
+ {
16354
+ changeCount,
16355
+ saving,
16356
+ onSave: () => void handleSaveEdits(),
16357
+ onCancel: handleCancelEdits
16358
+ }
16359
+ )
16360
+ ] }) : null
16361
+ ]
16362
+ }
16363
+ ),
16364
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_core3.DragOverlay, { children: draggedRow ? (
16365
+ // w-max: the overlay inherits the ACTIVE node's size, and the active
16366
+ // node is the 16px grip handle — without it the label wraps to one
16367
+ // character per line.
16368
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "w-max max-w-xs truncate rounded border border-border bg-card px-2.5 py-1.5 text-sm font-medium text-foreground shadow-md", children: hierarchy?.itemLabel?.(draggedRow) ?? "Moving row" })
16369
+ ) : null })
16370
+ ]
16371
+ }
16372
+ )
15976
16373
  }
15977
- ) }) });
16374
+ ) });
15978
16375
  }
15979
16376
  function HierarchyDragHandle({
15980
16377
  id,
@@ -16565,6 +16962,34 @@ function SavedViewsControl({
16565
16962
  ] });
16566
16963
  }
16567
16964
 
16965
+ // src/data-table/menu-targets.ts
16966
+ var MATRX_TABLE_MENU_ICON_NAMES = [
16967
+ "copy",
16968
+ "copy-plus",
16969
+ "scissors",
16970
+ "clipboard-paste",
16971
+ "eraser",
16972
+ "pencil",
16973
+ "plus",
16974
+ "trash",
16975
+ "eye-off",
16976
+ "arrow-up-a-z",
16977
+ "arrow-down-a-z",
16978
+ "arrow-up-down",
16979
+ "arrow-down-to-line",
16980
+ "palette",
16981
+ "paintbrush",
16982
+ "panel-left",
16983
+ "panel-right",
16984
+ "settings",
16985
+ "sigma",
16986
+ "zap",
16987
+ "history",
16988
+ "link",
16989
+ "tag",
16990
+ "filter"
16991
+ ];
16992
+
16568
16993
  // src/data-table/tableXlsx.ts
16569
16994
  var XLSX = __toESM(require("xlsx"), 1);
16570
16995
  function downloadTableAsXlsx({