@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
@@ -6187,6 +6187,52 @@ function markerCopy(marker) {
6187
6187
 
6188
6188
  // src/data-table/ColumnHeaderCell.tsx
6189
6189
  import { Fragment as Fragment4, jsx as jsx31, jsxs as jsxs22 } from "react/jsx-runtime";
6190
+ function HeaderMenuItems({
6191
+ items,
6192
+ MenuIcon,
6193
+ onDone,
6194
+ depth = 0
6195
+ }) {
6196
+ const [openSubmenu, setOpenSubmenu] = useState14(null);
6197
+ return /* @__PURE__ */ jsx31("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) => {
6198
+ const disabled = Boolean(item.disabledReason);
6199
+ const hasSubmenu = Boolean(item.submenu && item.submenu.length > 0);
6200
+ return /* @__PURE__ */ jsxs22("div", { className: "flex flex-col", children: [
6201
+ /* @__PURE__ */ jsxs22(
6202
+ Button,
6203
+ {
6204
+ type: "button",
6205
+ variant: "ghost",
6206
+ size: "sm",
6207
+ disabled,
6208
+ "aria-disabled": disabled || void 0,
6209
+ ...hasSubmenu ? { "aria-expanded": openSubmenu === item.id } : {},
6210
+ "data-matrx-header-menu-item": item.id,
6211
+ className: cn(
6212
+ "h-8 justify-start gap-2 px-2 text-xs font-normal",
6213
+ item.destructive && "text-destructive hover:text-destructive"
6214
+ ),
6215
+ onClick: () => {
6216
+ if (disabled) return;
6217
+ if (hasSubmenu) {
6218
+ setOpenSubmenu((current) => current === item.id ? null : item.id);
6219
+ return;
6220
+ }
6221
+ onDone();
6222
+ item.onSelect?.();
6223
+ },
6224
+ children: [
6225
+ item.icon && MenuIcon ? /* @__PURE__ */ jsx31(MenuIcon, { name: item.icon, className: "h-3.5 w-3.5" }) : null,
6226
+ /* @__PURE__ */ jsx31("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label }),
6227
+ item.shortcut ? /* @__PURE__ */ jsx31("span", { className: "ml-3 text-[10px] text-muted-foreground", children: item.shortcut }) : null
6228
+ ]
6229
+ }
6230
+ ),
6231
+ disabled ? /* @__PURE__ */ jsx31("span", { className: "px-2 pb-1 text-[11px] leading-tight text-muted-foreground", children: item.disabledReason }) : null,
6232
+ hasSubmenu && openSubmenu === item.id ? /* @__PURE__ */ jsx31(HeaderMenuItems, { items: item.submenu, MenuIcon, onDone, depth: depth + 1 }) : null
6233
+ ] }, item.id);
6234
+ }) });
6235
+ }
6190
6236
  var HeaderButton = forwardRef12(({ dragDescription, "aria-describedby": tooltipDescription, ...props }, ref) => {
6191
6237
  const descriptions = [tooltipDescription, dragDescription].filter((value) => Boolean(value)).join(" ");
6192
6238
  return /* @__PURE__ */ jsx31(
@@ -6228,8 +6274,11 @@ function ColumnHeaderCell({
6228
6274
  facetsLoading = false,
6229
6275
  facetsError,
6230
6276
  onFilterOpen,
6231
- reorderColumn
6277
+ reorderColumn,
6278
+ menuItems,
6279
+ MenuIcon
6232
6280
  }) {
6281
+ const hasMenuItems = Boolean(menuItems && menuItems.length > 0);
6233
6282
  const reorderEnabled = reorderColumn !== void 0;
6234
6283
  const { attributes: reorderAttributes, listeners: reorderListeners, setNodeRef } = useDraggable(
6235
6284
  reorderColumn ? {
@@ -6286,7 +6335,7 @@ function ColumnHeaderCell({
6286
6335
  };
6287
6336
  const markerLabels = marker ? markerCopy(marker) : void 0;
6288
6337
  const accessibleLabel = markerLabels?.label ?? labelText;
6289
- const menuLabel = filterKind ? accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column" : accessibleLabel ? `Sort ${accessibleLabel}` : "Sort column";
6338
+ const menuLabel = filterKind ? accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column" : sortable ? accessibleLabel ? `Sort ${accessibleLabel}` : "Sort column" : accessibleLabel ? `${accessibleLabel} column options` : "Column options";
6290
6339
  const menuContent = /* @__PURE__ */ jsxs22(
6291
6340
  PopoverContent,
6292
6341
  {
@@ -6412,12 +6461,20 @@ function ColumnHeaderCell({
6412
6461
  filterShowCancel ? /* @__PURE__ */ jsx31(Button, { type: "button", variant: "ghost", size: "sm", onClick: cancelFilter, children: "Cancel" }) : null,
6413
6462
  /* @__PURE__ */ jsx31(Button, { type: "button", size: "sm", onClick: applyFilter, children: filterApplyLabel ?? "Apply" })
6414
6463
  ] })
6415
- ] }) : null
6464
+ ] }) : null,
6465
+ hasMenuItems ? /* @__PURE__ */ jsx31(
6466
+ "div",
6467
+ {
6468
+ className: cn((sortable || filterKind) && "mt-2 border-t border-border pt-2"),
6469
+ "data-matrx-header-menu": "consumer",
6470
+ children: /* @__PURE__ */ jsx31(HeaderMenuItems, { items: menuItems, MenuIcon, onDone: () => setFilterOpen(false) })
6471
+ }
6472
+ ) : null
6416
6473
  ]
6417
6474
  }
6418
6475
  );
6419
6476
  if (compact) {
6420
- if (!sortable && !filterKind) {
6477
+ if (!sortable && !filterKind && !hasMenuItems) {
6421
6478
  if (!reorderEnabled) {
6422
6479
  return /* @__PURE__ */ jsx31("div", { className: cn("flex items-center justify-center", className), children: label });
6423
6480
  }
@@ -6538,7 +6595,7 @@ function ColumnHeaderCell({
6538
6595
  "."
6539
6596
  ] })
6540
6597
  ] }) : /* @__PURE__ */ jsx31("span", { className: "truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
6541
- filterKind || sortable ? /* @__PURE__ */ jsxs22("div", { className: "inline-flex items-center", children: [
6598
+ filterKind || sortable || hasMenuItems ? /* @__PURE__ */ jsxs22("div", { className: "inline-flex items-center", children: [
6542
6599
  /* @__PURE__ */ jsxs22(Popover, { open: filterOpen, onOpenChange: setFilterOpen, children: [
6543
6600
  /* @__PURE__ */ jsxs22(Tooltip, { children: [
6544
6601
  /* @__PURE__ */ jsx31(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx31(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx31(
@@ -6553,7 +6610,7 @@ function ColumnHeaderCell({
6553
6610
  "rounded p-0.5 transition-colors",
6554
6611
  filterActive ? "text-primary hover:text-primary/80" : "text-muted-foreground/40 hover:text-muted-foreground"
6555
6612
  ),
6556
- children: filterKind ? /* @__PURE__ */ jsx31(ListFilter, { className: cn("h-3 w-3", filterActive && "fill-primary/20") }) : /* @__PURE__ */ jsx31(ArrowUpDown, { className: "h-3 w-3" })
6613
+ children: filterKind ? /* @__PURE__ */ jsx31(ListFilter, { className: cn("h-3 w-3", filterActive && "fill-primary/20") }) : sortable ? /* @__PURE__ */ jsx31(ArrowUpDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx31(ChevronDown, { className: "h-3 w-3" })
6557
6614
  }
6558
6615
  ) }) }),
6559
6616
  /* @__PURE__ */ jsx31(TooltipContent, { children: menuLabel })
@@ -8449,6 +8506,7 @@ function camePastAPortalIntoTheContainer(target) {
8449
8506
  if (!element) return false;
8450
8507
  return element.closest('[data-matrx-table-spreadsheet="true"]') === null;
8451
8508
  }
8509
+ var READ_ONLY_REASON = "This table is view-only \u2014 you can select and copy, but not change cells.";
8452
8510
  function useSpreadsheetGrid(options) {
8453
8511
  const [range, setRange] = useState16(null);
8454
8512
  const [fillTarget, setFillTarget] = useState16(null);
@@ -8515,10 +8573,18 @@ function useSpreadsheetGrid(options) {
8515
8573
  }));
8516
8574
  setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));
8517
8575
  }
8576
+ if (!current.onPatch) return;
8518
8577
  await current.onPatch(patches, intent);
8519
8578
  },
8520
8579
  []
8521
8580
  );
8581
+ const refuseWrite = useCallback7(() => {
8582
+ const current = optionsRef.current;
8583
+ if (!current) return true;
8584
+ if (!current.readOnly && current.onPatch) return false;
8585
+ current.notify?.(current.readOnlyReason ?? READ_ONLY_REASON);
8586
+ return true;
8587
+ }, []);
8522
8588
  const readValueAt = useCallback7((rowIndex, columnIndex) => {
8523
8589
  const current = optionsRef.current;
8524
8590
  const rowId = current?.rowIds[rowIndex];
@@ -8552,6 +8618,7 @@ function useSpreadsheetGrid(options) {
8552
8618
  []
8553
8619
  );
8554
8620
  const doFillDown = useCallback7(async () => {
8621
+ if (refuseWrite()) return;
8555
8622
  const base = fillRequestBase();
8556
8623
  if (!base || !bounds) return;
8557
8624
  const result = fillDown({
@@ -8561,8 +8628,9 @@ function useSpreadsheetGrid(options) {
8561
8628
  const screened = screenPatches(result.patches);
8562
8629
  await applyPatches(screened.accepted, "fill-down", "Filled down");
8563
8630
  announceSkips([...result.skipped, ...screened.refusals], result.summary);
8564
- }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches]);
8631
+ }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);
8565
8632
  const doClear = useCallback7(async () => {
8633
+ if (refuseWrite()) return;
8566
8634
  const current = optionsRef.current;
8567
8635
  if (!current || !bounds) return;
8568
8636
  const patches = [];
@@ -8589,20 +8657,22 @@ function useSpreadsheetGrid(options) {
8589
8657
  [...skipped, ...screened.refusals],
8590
8658
  `Cleared ${screened.accepted.length} cells.`
8591
8659
  );
8592
- }, [bounds, applyPatches, announceSkips, screenPatches]);
8660
+ }, [bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);
8593
8661
  const doUndo = useCallback7(async () => {
8662
+ if (refuseWrite()) return;
8594
8663
  const taken = popUndo(stack);
8595
8664
  if (!taken.entry) return;
8596
8665
  setStack(taken.stack);
8597
8666
  await applyPatches(taken.entry.undo, "undo", taken.entry.label);
8598
8667
  optionsRef.current?.notify?.(taken.entry.label);
8599
- }, [stack, applyPatches]);
8668
+ }, [stack, applyPatches, refuseWrite]);
8600
8669
  const doRedo = useCallback7(async () => {
8670
+ if (refuseWrite()) return;
8601
8671
  const taken = popRedo(stack);
8602
8672
  if (!taken.entry) return;
8603
8673
  setStack(taken.stack);
8604
8674
  await applyPatches(taken.entry.redo, "redo", taken.entry.label);
8605
- }, [stack, applyPatches]);
8675
+ }, [stack, applyPatches, refuseWrite]);
8606
8676
  const move = useCallback7(
8607
8677
  (direction, extend) => {
8608
8678
  setRange((previous) => {
@@ -8678,7 +8748,7 @@ function useSpreadsheetGrid(options) {
8678
8748
  case "edit":
8679
8749
  case "editSeeded": {
8680
8750
  const focused = range?.focus;
8681
- if (!focused || !current.onEditRequest) return;
8751
+ if (!focused || !current.onEditRequest || current.readOnly) return;
8682
8752
  const seed = action.kind === "editSeeded" ? action.seed : null;
8683
8753
  if (current.onEditRequest(focused, seed)) event.preventDefault();
8684
8754
  return;
@@ -8735,6 +8805,7 @@ function useSpreadsheetGrid(options) {
8735
8805
  const text = event.clipboardData?.getData("text/plain") ?? "";
8736
8806
  if (!text) return;
8737
8807
  event.preventDefault();
8808
+ if (refuseWrite()) return;
8738
8809
  const block = parseClipboardGrid(text);
8739
8810
  const start = range.focus;
8740
8811
  const startRow = rowIds.indexOf(start.rowId);
@@ -8797,7 +8868,7 @@ function useSpreadsheetGrid(options) {
8797
8868
  const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : "";
8798
8869
  announceSkips(refusals, `Pasted ${screened.accepted.length} cells.${tail}`);
8799
8870
  },
8800
- [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches]
8871
+ [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches, refuseWrite]
8801
8872
  );
8802
8873
  const cellProps = useCallback7(
8803
8874
  (rowId, columnId2) => {
@@ -8848,7 +8919,7 @@ function useSpreadsheetGrid(options) {
8848
8919
  const isFillHandleCell = useCallback7(
8849
8920
  (rowId, columnId2) => {
8850
8921
  const current = optionsRef.current;
8851
- if (!current || current.fillHandle === false || !bounds) return false;
8922
+ if (!current || current.fillHandle === false || current.readOnly || !bounds) return false;
8852
8923
  return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId2;
8853
8924
  },
8854
8925
  [bounds, rowIds, columnIds]
@@ -8875,6 +8946,7 @@ function useSpreadsheetGrid(options) {
8875
8946
  const source = boundsRef.current;
8876
8947
  setFillTarget(null);
8877
8948
  if (!target || !source) return;
8949
+ if (refuseWrite()) return;
8878
8950
  const base = fillRequestBase();
8879
8951
  if (!base) return;
8880
8952
  const result = fillRange({
@@ -8888,7 +8960,7 @@ function useSpreadsheetGrid(options) {
8888
8960
  };
8889
8961
  window.addEventListener("mouseup", commit);
8890
8962
  return () => window.removeEventListener("mouseup", commit);
8891
- }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches]);
8963
+ }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches, refuseWrite]);
8892
8964
  return {
8893
8965
  range,
8894
8966
  bounds,
@@ -10187,6 +10259,35 @@ function toText(value) {
10187
10259
  return String(value);
10188
10260
  return null;
10189
10261
  }
10262
+ function parseTimeOfDay(value) {
10263
+ if (value instanceof Date) {
10264
+ return { h: value.getHours(), m: value.getMinutes(), s: value.getSeconds() };
10265
+ }
10266
+ if (typeof value !== "string") return null;
10267
+ const text = value.trim();
10268
+ if (!text) return null;
10269
+ const m = /^(\d{1,2})(?::(\d{2}))?(?::(\d{2}))?\s*([aApP]\.?[mM]\.?)?$/.exec(text);
10270
+ if (m) {
10271
+ let h = Number(m[1]);
10272
+ const min = m[2] ? Number(m[2]) : 0;
10273
+ const sec = m[3] ? Number(m[3]) : 0;
10274
+ const ampm = m[4]?.toLowerCase().replace(/\./g, "");
10275
+ if (ampm) {
10276
+ if (h < 1 || h > 12) return null;
10277
+ if (ampm === "pm" && h !== 12) h += 12;
10278
+ if (ampm === "am" && h === 12) h = 0;
10279
+ }
10280
+ if (h > 23 || min > 59 || sec > 59) return null;
10281
+ return { h, m: min, s: sec };
10282
+ }
10283
+ if (/\d{4}-\d{2}-\d{2}T/.test(text)) {
10284
+ const d = new Date(text);
10285
+ if (!Number.isNaN(d.getTime())) {
10286
+ return { h: d.getHours(), m: d.getMinutes(), s: d.getSeconds() };
10287
+ }
10288
+ }
10289
+ return null;
10290
+ }
10190
10291
  var BARE_CALENDAR_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]00:00(?::00(?:\.0+)?)?)?$/;
10191
10292
  function toDate(value) {
10192
10293
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
@@ -10307,6 +10408,23 @@ var DEFS = [
10307
10408
  },
10308
10409
  parse: (raw) => raw === "" || raw == null ? null : String(raw).trim()
10309
10410
  },
10411
+ {
10412
+ // A postal address. Free text by design (addresses have no one shape
10413
+ // worldwide); the cell opens it in maps, which is the thing people do
10414
+ // with an address in a table.
10415
+ id: "address",
10416
+ label: "Address",
10417
+ description: "Street address \u2014 click to open in maps",
10418
+ group: "Text",
10419
+ base: "string",
10420
+ editor: "text",
10421
+ rich: true,
10422
+ format: (v) => {
10423
+ const t = toText(v)?.trim();
10424
+ return t ? t : null;
10425
+ },
10426
+ parse: (raw) => raw === "" || raw == null ? null : String(raw)
10427
+ },
10310
10428
  {
10311
10429
  id: "url",
10312
10430
  label: "Link",
@@ -10440,6 +10558,26 @@ var DEFS = [
10440
10558
  },
10441
10559
  parse: (raw) => toNumber(raw)
10442
10560
  },
10561
+ {
10562
+ // A percentage DRAWN as a bar — task completion, quota used, a score out of
10563
+ // 100. Same storage and scale rule as `percent`; only the rendering differs.
10564
+ id: "progress",
10565
+ label: "Progress bar",
10566
+ description: "A percentage shown as a bar",
10567
+ group: "Numbers",
10568
+ base: "number",
10569
+ alsoAccepts: ["integer"],
10570
+ editor: "number",
10571
+ rich: true,
10572
+ optionKeys: ["percentScale"],
10573
+ format: (v, o) => {
10574
+ const n2 = toNumber(v);
10575
+ if (n2 === null) return null;
10576
+ const shown = o.percentScale === "fraction" ? n2 * 100 : n2;
10577
+ return `${groupedNumber(shown, { precision: 0 })}%`;
10578
+ },
10579
+ parse: (raw) => toNumber(raw)
10580
+ },
10443
10581
  {
10444
10582
  id: "duration",
10445
10583
  label: "Duration",
@@ -10508,13 +10646,16 @@ var DEFS = [
10508
10646
  alsoAccepts: ["number"],
10509
10647
  editor: "number",
10510
10648
  numericAlign: true,
10649
+ // The name says BYTES on purpose: this cell holds a genuine byte length,
10650
+ // never a character or item count, and `formatFileSize` is only honest
10651
+ // about a quantity that is actually bytes.
10511
10652
  format: (v) => {
10512
- const n2 = toNumber(v);
10513
- return n2 === null ? null : formatFileSize(n2);
10653
+ const byteLength = toNumber(v);
10654
+ return byteLength === null ? null : formatFileSize(byteLength);
10514
10655
  },
10515
10656
  parse: (raw) => {
10516
- const n2 = toNumber(raw);
10517
- return n2 === null ? null : Math.trunc(n2);
10657
+ const byteLength = toNumber(raw);
10658
+ return byteLength === null ? null : Math.trunc(byteLength);
10518
10659
  }
10519
10660
  },
10520
10661
  // ── Choice ────────────────────────────────────────────────────────────────
@@ -10575,6 +10716,87 @@ var DEFS = [
10575
10716
  },
10576
10717
  parse: (raw) => raw === "" || raw == null ? null : String(raw)
10577
10718
  },
10719
+ {
10720
+ // A TIME OF DAY with no date: opening hours, a shift start, a meeting slot.
10721
+ // Stored as the string the <input type="time"> gives ("HH:MM" or
10722
+ // "HH:MM:SS"), shown in the viewer's locale ("2:30 PM" / "14:30"). The
10723
+ // storage type stays `string`; sorting is lexical on the 24-hour form,
10724
+ // which is chronological — the one reason the stored form is 24-hour.
10725
+ id: "time",
10726
+ label: "Time",
10727
+ description: "Time of day, without a date",
10728
+ group: "Dates",
10729
+ base: "string",
10730
+ editor: "time",
10731
+ optionKeys: ["timeSeconds"],
10732
+ format: (v, o) => {
10733
+ const parts = parseTimeOfDay(v);
10734
+ if (!parts) return null;
10735
+ const d = new Date(2e3, 0, 1, parts.h, parts.m, parts.s);
10736
+ return d.toLocaleTimeString(void 0, {
10737
+ hour: "numeric",
10738
+ minute: "2-digit",
10739
+ ...o.timeSeconds ? { second: "2-digit" } : {}
10740
+ });
10741
+ },
10742
+ // Normalise to the 24-hour "HH:MM[:SS]" form; a value that is not a time
10743
+ // is handed back unchanged so the validation layer can refuse it by name.
10744
+ parse: (raw) => {
10745
+ if (raw == null || raw === "") return null;
10746
+ const parts = parseTimeOfDay(raw);
10747
+ if (!parts) return raw;
10748
+ const two = (n2) => String(n2).padStart(2, "0");
10749
+ const fields = parts.s > 0 ? [parts.h, parts.m, parts.s] : [parts.h, parts.m];
10750
+ return fields.map(two).join(":");
10751
+ }
10752
+ },
10753
+ {
10754
+ // Assigned BY THE DATABASE when the row is inserted (trigger
10755
+ // `_udt_autonumber`), so it is the same number whichever door made the row
10756
+ // and never shifts when another row is deleted. Stored in the cell as an
10757
+ // integer; every editor refuses it. `prefix` turns 42 into "INV-42".
10758
+ id: "autonumber",
10759
+ label: "Autonumber",
10760
+ description: "A number the table assigns to each new row \u2014 never typed",
10761
+ group: "Numbers",
10762
+ base: "integer",
10763
+ alsoAccepts: ["number"],
10764
+ editor: "computed",
10765
+ numericAlign: true,
10766
+ optionKeys: ["prefix"],
10767
+ format: (v, o) => {
10768
+ const n2 = toNumber(v);
10769
+ if (n2 === null) return null;
10770
+ return `${o.prefix ?? ""}${Math.trunc(n2)}`;
10771
+ },
10772
+ parse: () => null
10773
+ },
10774
+ // ── System columns ────────────────────────────────────────────────────────
10775
+ // Filled from the ROW'S OWN RECORD on read (`withComputedColumns` in
10776
+ // `@ai-matrx/design-system/formulas`), never typed and never stored in the
10777
+ // cell. `editor: "computed"` makes every editor refuse them.
10778
+ {
10779
+ id: "created_time",
10780
+ label: "Created time",
10781
+ description: "When the row was added \u2014 filled in automatically",
10782
+ group: "Dates",
10783
+ base: "string",
10784
+ editor: "computed",
10785
+ optionKeys: ["dateStyle"],
10786
+ format: (v, o) => FIELD_FORMATS.datetime.format(v, o),
10787
+ parse: () => null
10788
+ },
10789
+ {
10790
+ id: "modified_time",
10791
+ label: "Last modified time",
10792
+ description: "When the row last changed \u2014 filled in automatically",
10793
+ group: "Dates",
10794
+ base: "string",
10795
+ editor: "computed",
10796
+ optionKeys: ["dateStyle"],
10797
+ format: (v, o) => FIELD_FORMATS.datetime.format(v, o),
10798
+ parse: () => null
10799
+ },
10578
10800
  {
10579
10801
  id: "relative_time",
10580
10802
  label: "Relative time",
@@ -10631,6 +10853,44 @@ var DEFS = [
10631
10853
  return toList(raw) ?? raw;
10632
10854
  }
10633
10855
  },
10856
+ {
10857
+ // FILES ATTACHED TO THE ROW. The cell stores the files feature's file ids
10858
+ // (`files.files.id`), never a URL or a name: a rename or a move in Files
10859
+ // changes nothing here, and access is the file's own (RLS) — a reader who
10860
+ // may see the row but not the file gets the chip's honest "no access", not
10861
+ // a broken link. Drawn by `AttachmentChips`, edited by `AttachmentInput`
10862
+ // through the ONE file picker.
10863
+ id: "attachment",
10864
+ label: "Attachments",
10865
+ description: "Files from your Files, shown as chips",
10866
+ group: "Structured",
10867
+ base: "array",
10868
+ // Not `string`: on a text column it must appear under "Other kinds" so
10869
+ // picking it retypes the column to a list, rather than storing a JSON
10870
+ // string of ids in a text cell.
10871
+ alsoAccepts: ["json"],
10872
+ editor: "attachment",
10873
+ rich: true,
10874
+ format: (v) => {
10875
+ const list = toList(v);
10876
+ if (list === null) return null;
10877
+ const n2 = list.filter((i) => String(i).trim()).length;
10878
+ return n2 === 0 ? "" : n2 === 1 ? "1 file" : `${n2} files`;
10879
+ },
10880
+ parse: (raw) => {
10881
+ if (raw == null || raw === "") return null;
10882
+ if (Array.isArray(raw)) return raw.map((i) => String(i).trim()).filter(Boolean);
10883
+ const text = String(raw).trim();
10884
+ if (text.startsWith("[")) {
10885
+ try {
10886
+ const parsed = JSON.parse(text);
10887
+ if (Array.isArray(parsed)) return parsed.map((i) => String(i).trim()).filter(Boolean);
10888
+ } catch {
10889
+ }
10890
+ }
10891
+ return text.split(",").map((s) => s.trim()).filter(Boolean);
10892
+ }
10893
+ },
10634
10894
  {
10635
10895
  id: "tags",
10636
10896
  label: "Tags",
@@ -10694,6 +10954,82 @@ var DEFS = [
10694
10954
  return trimmed === "" ? null : trimmed;
10695
10955
  }
10696
10956
  },
10957
+ {
10958
+ // A PERSON: one member of the organization the table belongs to, stored as
10959
+ // the user id. It is a choice column whose options are the members —
10960
+ // supplied at runtime by whoever renders it (features/data-tables/
10961
+ // person-choices.tsx), the same door a structured-list-bound column uses —
10962
+ // so the cell picker, the chip, filters, color-by and the agent scope are
10963
+ // all the choice system's, with no second implementation.
10964
+ id: "person",
10965
+ label: "Person",
10966
+ description: "Someone in your organization \u2014 pick from its members",
10967
+ group: "Choice",
10968
+ base: "string",
10969
+ editor: "select",
10970
+ rich: true,
10971
+ format: (v, o) => FIELD_FORMATS.choice.format(v, o),
10972
+ parse: (raw) => FIELD_FORMATS.choice.parse(raw, {})
10973
+ },
10974
+ {
10975
+ // A RECORD OF ANOTHER TABLE. The cell stores that record's ID; the screen
10976
+ // shows its WORDS. `person` is the precedent and the template — it has
10977
+ // stored a user id and displayed a name through `choiceMap` since
10978
+ // 2026-09-21 — so `relation` is a member of CHOICE_FORMAT_IDS and every
10979
+ // one of the ten readers that print a cell resolves it through the path
10980
+ // they already use for `person`. There is no eleventh place to remember.
10981
+ //
10982
+ // THREE STATES, THREE RENDERINGS, NO FOURTH (OLD-TABLES-CUTOVER §3.3):
10983
+ // resolved the target's words
10984
+ // unresolvable the amber fallback WITH the id, as an identifier — never
10985
+ // a blank, and never the bare uuid as if it were text, so a
10986
+ // person can act on it and support can trace it
10987
+ // withheld platform.relation_withheld_label(), with NO id — a cell
10988
+ // the reader may not see never leaks the identifier through
10989
+ // the gap between the two stores' refusals
10990
+ //
10991
+ // `format` therefore returns null (the fallback law's "cannot interpret")
10992
+ // for anything the resolver did not turn into words, and the renderer is
10993
+ // what distinguishes unresolvable from withheld. It never prints a bare id.
10994
+ id: "relation",
10995
+ label: "Relation",
10996
+ description: "A record of another table \u2014 pick it, and its name is shown",
10997
+ group: "Choice",
10998
+ base: "string",
10999
+ alsoAccepts: ["array", "json"],
11000
+ editor: "select",
11001
+ rich: true,
11002
+ optionKeys: ["relation_target", "relation_max", "on_delete", "display"],
11003
+ // IT DOES NOT DELEGATE TO `choice`, AND THAT IS THE WHOLE POINT.
11004
+ // `choice.format` deliberately returns the RAW VALUE when no options have
11005
+ // resolved yet — a structured list still loading should not make every cell
11006
+ // look off-list. For a label column that is right: the raw value IS the
11007
+ // label. For an identifier column it is the defect §3.3 names, and it is
11008
+ // how the first cut of this wave put four raw uuids on a real grid: the
11009
+ // fallback arm never ran, because returning the uuid counted as SUCCESS.
11010
+ //
11011
+ // Here, a value that did not resolve to words is a MISMATCH — `null` — so
11012
+ // the renderer's three-state arm owns it and shows the amber fallback with
11013
+ // the id marked as an identifier, or the withheld sentence. The id never
11014
+ // reaches the eye as if it were the cell's contents.
11015
+ format: (v, options) => {
11016
+ const list = toList(v);
11017
+ const values = list !== null ? list : v == null ? [] : [v];
11018
+ if (values.length === 0) return "";
11019
+ const words2 = [];
11020
+ for (const entry of values) {
11021
+ const text = toText(entry);
11022
+ if (text === null) return null;
11023
+ const trimmed = text.trim();
11024
+ if (trimmed === "") continue;
11025
+ const match = findChoice(options.choices, trimmed);
11026
+ if (!match) return null;
11027
+ words2.push(match.label ?? match.value);
11028
+ }
11029
+ return words2.length === 0 ? "" : words2.join(", ");
11030
+ },
11031
+ parse: (raw) => FIELD_FORMATS.choice.parse(raw, {})
11032
+ },
10697
11033
  {
10698
11034
  id: "multi_choice",
10699
11035
  label: "Multi-choice",
@@ -10738,7 +11074,7 @@ var DEFS = [
10738
11074
  // A formula column stores NOTHING. `metadata.format.options.formula` holds
10739
11075
  // the expression; the stored cell is always null; the value is computed on
10740
11076
  // read from the other columns of the same row
10741
- // (`features/data-tables/formulas.ts`). Grouped under Structured on purpose
11077
+ // (`@ai-matrx/design-system/formulas`). Grouped under Structured on purpose
10742
11078
  // — the picker's groups are a fixed vocabulary, and a formula is structure
10743
11079
  // over a plain string column, not a sixth family.
10744
11080
  //
@@ -13314,12 +13650,15 @@ function MatrxDataTableCore({
13314
13650
  return getCellValue(applyRowEdits(row, edits[rowId]), col);
13315
13651
  },
13316
13652
  isColumnWritable: (colId) => {
13653
+ if (spreadsheet.readOnly) return false;
13317
13654
  if (spreadsheet.writableColumnIds) return spreadsheet.writableColumnIds.includes(colId);
13318
13655
  const col = spreadsheetColumnById.get(colId);
13319
13656
  return Boolean(col && col.editable);
13320
13657
  },
13321
13658
  ...spreadsheet.columnRefusal ? { columnRefusal: spreadsheet.columnRefusal } : {},
13322
- onPatch: spreadsheet.onPatch,
13659
+ ...spreadsheet.onPatch ? { onPatch: spreadsheet.onPatch } : {},
13660
+ ...spreadsheet.readOnly ? { readOnly: true } : {},
13661
+ ...spreadsheet.readOnlyReason ? { readOnlyReason: spreadsheet.readOnlyReason } : {},
13323
13662
  ...spreadsheet.undoDepth === void 0 ? {} : { undoDepth: spreadsheet.undoDepth },
13324
13663
  ...spreadsheet.fillHandle === void 0 ? {} : { fillHandle: spreadsheet.fillHandle },
13325
13664
  ...spreadsheet.paste === void 0 ? {} : { paste: spreadsheet.paste },
@@ -13879,7 +14218,7 @@ function MatrxDataTableCore({
13879
14218
  setEdits({});
13880
14219
  edit?.onCancel?.();
13881
14220
  };
13882
- const resolveMenuLevel = (target) => {
14221
+ const menuPayloadFor = (target) => {
13883
14222
  const rowFor = (rowId) => {
13884
14223
  const row = data.find((candidate) => getRowId(candidate) === rowId);
13885
14224
  if (!row) return null;
@@ -13887,14 +14226,13 @@ function MatrxDataTableCore({
13887
14226
  };
13888
14227
  const columnFor = (id) => visibleColumns.find((col) => columnId(col) === id);
13889
14228
  const labelFor = (col, id) => col?.label ?? (typeof col?.header === "string" ? col.header : id);
13890
- const fromHost = (payload) => host.createDefaultMenuContext?.(payload) ?? null;
13891
14229
  switch (target.level) {
13892
14230
  case "row": {
13893
14231
  if (!target.rowId) return null;
13894
14232
  const displayRow = rowFor(target.rowId);
13895
14233
  if (!displayRow) return null;
13896
14234
  const controls = buildRecordControls(displayRow);
13897
- return contextMenu?.resolveRowContext?.(displayRow, controls) ?? fromHost({ level: "row", row: displayRow, rowId: target.rowId, controls });
14235
+ return { payload: { level: "row", row: displayRow, rowId: target.rowId, controls }, controls };
13898
14236
  }
13899
14237
  case "cell": {
13900
14238
  if (!target.rowId || !target.columnId) return null;
@@ -13903,45 +14241,94 @@ function MatrxDataTableCore({
13903
14241
  if (!displayRow || !col) return null;
13904
14242
  const controls = buildRecordControls(displayRow);
13905
14243
  const value = spreadsheet?.readCell ? spreadsheet.readCell(displayRow, target.columnId) : getCellValue(displayRow, col);
13906
- const args = {
13907
- row: displayRow,
13908
- rowId: target.rowId,
13909
- columnId: target.columnId,
13910
- columnLabel: labelFor(col, target.columnId),
13911
- value,
13912
- text: stringifyCellValue(value),
14244
+ return {
14245
+ payload: {
14246
+ level: "cell",
14247
+ row: displayRow,
14248
+ rowId: target.rowId,
14249
+ columnId: target.columnId,
14250
+ columnLabel: labelFor(col, target.columnId),
14251
+ value,
14252
+ text: stringifyCellValue(value),
14253
+ controls
14254
+ },
13913
14255
  controls
13914
14256
  };
13915
- return contextMenu?.resolveCellContext?.(args) ?? fromHost({ level: "cell", ...args });
13916
14257
  }
13917
14258
  case "column": {
13918
14259
  if (!target.columnId) return null;
13919
14260
  const col = columnFor(target.columnId);
13920
14261
  if (!col) return null;
13921
- const args = {
13922
- columnId: target.columnId,
13923
- columnLabel: labelFor(col, target.columnId),
13924
- values: paginated.map(
13925
- (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
13926
- )
14262
+ return {
14263
+ payload: {
14264
+ level: "column",
14265
+ columnId: target.columnId,
14266
+ columnLabel: labelFor(col, target.columnId),
14267
+ values: paginated.map(
14268
+ (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
14269
+ )
14270
+ }
13927
14271
  };
13928
- return contextMenu?.resolveColumnContext?.(args) ?? fromHost({ level: "column", ...args });
13929
14272
  }
13930
14273
  case "selection": {
13931
14274
  const addresses = sheetRef.current.selectionAddresses();
13932
14275
  const cellCount = addresses.rowIds.length * addresses.columnIds.length;
13933
- if (cellCount <= 1) {
13934
- return resolveMenuLevel({ ...target, level: "cell" });
13935
- }
13936
- const args = { ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount };
13937
- return contextMenu?.resolveSelectionContext?.(args) ?? fromHost({ level: "selection", ...args });
14276
+ if (cellCount <= 1) return menuPayloadFor({ ...target, level: "cell" });
14277
+ return {
14278
+ payload: { level: "selection", ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount }
14279
+ };
14280
+ }
14281
+ case "table":
14282
+ return {
14283
+ payload: { level: "table", rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) }
14284
+ };
14285
+ }
14286
+ };
14287
+ const resolveMenuLevel = (target) => {
14288
+ const built = menuPayloadFor(target);
14289
+ if (!built) return null;
14290
+ const { payload } = built;
14291
+ const fromHost = () => host.createDefaultMenuContext?.(payload) ?? null;
14292
+ switch (payload.level) {
14293
+ case "row":
14294
+ return contextMenu?.resolveRowContext?.(payload.row, built.controls) ?? fromHost();
14295
+ case "cell": {
14296
+ const { level: _level, ...args } = payload;
14297
+ return contextMenu?.resolveCellContext?.({ ...args, controls: built.controls }) ?? fromHost();
14298
+ }
14299
+ case "column": {
14300
+ const { level: _level, ...args } = payload;
14301
+ return contextMenu?.resolveColumnContext?.(args) ?? fromHost();
14302
+ }
14303
+ case "selection": {
14304
+ const { level: _level, ...args } = payload;
14305
+ return contextMenu?.resolveSelectionContext?.(args) ?? fromHost();
13938
14306
  }
13939
14307
  case "table": {
13940
- const args = { rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) };
13941
- return contextMenu?.resolveTableContext?.(args) ?? fromHost({ level: "table", ...args });
14308
+ const { level: _level, ...args } = payload;
14309
+ return contextMenu?.resolveTableContext?.(args) ?? fromHost();
13942
14310
  }
13943
14311
  }
13944
14312
  };
14313
+ const menuSectionsFor = (target) => {
14314
+ const resolve2 = contextMenu?.sections;
14315
+ if (!resolve2 || target.tableId !== tableContextMenuInstanceId) return [];
14316
+ const built = menuPayloadFor(target);
14317
+ if (!built) return [];
14318
+ try {
14319
+ return resolve2(built.payload);
14320
+ } catch (error) {
14321
+ console.error("[MatrxDataTable] contextMenu.sections threw; the menu opens without them.", error);
14322
+ return [];
14323
+ }
14324
+ };
14325
+ const menuSectionsForRef = useRef16(menuSectionsFor);
14326
+ menuSectionsForRef.current = menuSectionsFor;
14327
+ const hasMenuSections = Boolean(contextMenu?.sections);
14328
+ const boundarySections = useCallback8(
14329
+ (target) => menuSectionsForRef.current(target),
14330
+ []
14331
+ );
13945
14332
  const resolveMenuLevelRef = useRef16(resolveMenuLevel);
13946
14333
  resolveMenuLevelRef.current = resolveMenuLevel;
13947
14334
  useEffect16(() => {
@@ -14154,959 +14541,640 @@ function MatrxDataTableCore({
14154
14541
  },
14155
14542
  [columns, commitColumnState, commitColumnWidths, controlledQuery, intelligentSearchEnabled, intelligentSearchScopes, toolbar]
14156
14543
  );
14157
- return /* @__PURE__ */ jsx42(TooltipProvider, { children: /* @__PURE__ */ jsx42(ContextMenuBoundary, { label: tableId ?? "Data table", children: /* @__PURE__ */ jsxs32(
14158
- DndContext,
14544
+ return /* @__PURE__ */ jsx42(TooltipProvider, { children: /* @__PURE__ */ jsx42(
14545
+ ContextMenuBoundary,
14159
14546
  {
14160
- id: tableDndContextId,
14161
- sensors: hierarchySensors,
14162
- collisionDetection: pointerWithin,
14163
- accessibility: {
14164
- screenReaderInstructions: {
14165
- draggable: "Drag with a pointer. To reorder columns with a keyboard, open Columns and use Move earlier or Move later."
14166
- }
14167
- },
14168
- measuring: { droppable: { strategy: MeasuringStrategy.Always } },
14169
- onDragStart: (event) => {
14170
- const kind = event.active.data.current?.matrxDragKind;
14171
- if (kind === "column-header") {
14172
- const draggedId = String(event.active.data.current?.columnId);
14173
- draggedColumnId.current = draggedId;
14174
- columnDragActive.current = true;
14175
- setColumnDragPreview({ draggedId, targetId: null, after: false });
14176
- return;
14177
- }
14178
- if (kind === "column-manager") return;
14179
- setDraggedRowId(String(event.active.id));
14180
- },
14181
- onDragMove: (event) => {
14182
- if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
14183
- const overData = event.over?.data.current;
14184
- const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
14185
- const activeRect = event.active.rect.current.translated;
14186
- const overRect = event.over?.rect;
14187
- if (!overId || !activeRect || !overRect) {
14188
- setColumnDragPreview(
14189
- (current) => current?.targetId === null ? current : current ? { ...current, targetId: null } : current
14190
- );
14191
- columnDragScrollDirection.current = 0;
14192
- stopColumnDragScroll();
14193
- return;
14194
- }
14195
- const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14196
- setColumnDragPreview((current) => {
14197
- if (!current) return current;
14198
- return current.targetId === overId && current.after === after ? current : { ...current, targetId: String(overId), after };
14199
- });
14200
- const scroll = scrollRef.current;
14201
- if (!scroll) return;
14202
- const bounds = scroll.getBoundingClientRect();
14203
- const center = activeRect.left + activeRect.width / 2;
14204
- columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
14205
- if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
14206
- else startColumnDragScroll();
14207
- },
14208
- onDragCancel: () => {
14209
- columnDragActive.current = false;
14210
- draggedColumnId.current = null;
14211
- setColumnDragPreview(null);
14212
- stopColumnDragScroll();
14213
- setDraggedRowId(null);
14214
- },
14215
- onDragEnd: (event) => {
14216
- const kind = event.active.data.current?.matrxDragKind;
14217
- if (kind === "column-manager") return;
14218
- if (kind !== "column-header") {
14219
- endHierarchyDrag(event);
14220
- return;
14221
- }
14222
- const from = String(event.active.data.current?.columnId);
14223
- const targetData = event.over?.data.current;
14224
- const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
14225
- const activeRect = event.active.rect.current.translated;
14226
- const overRect = event.over?.rect;
14227
- columnDragActive.current = false;
14228
- draggedColumnId.current = null;
14229
- setColumnDragPreview(null);
14230
- stopColumnDragScroll();
14231
- if (columnDragPreview?.draggedId === from && columnDragPreview.targetId !== null && columnDragPreview.targetId !== from) {
14232
- commitColumnState(
14233
- projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
14234
- effectiveHiddenColumnIds
14235
- );
14236
- return;
14237
- }
14238
- if (!target || from === target || !activeRect || !overRect) return;
14239
- const order = [...effectiveColumnOrder];
14240
- const fromIndex = order.indexOf(from);
14241
- const targetIndex = order.indexOf(String(target));
14242
- if (fromIndex < 0 || targetIndex < 0) return;
14243
- order.splice(fromIndex, 1);
14244
- const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14245
- const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
14246
- order.splice(insertion, 0, from);
14247
- commitColumnState(order, effectiveHiddenColumnIds);
14248
- },
14249
- children: [
14250
- /* @__PURE__ */ jsxs32(
14251
- "div",
14252
- {
14253
- ref: rootRef,
14254
- "data-matrx-table": true,
14255
- "data-matrx-table-id": tableContextMenuInstanceId,
14256
- "data-matrx-table-implementation": "@ai-matrx/design-system",
14257
- "data-matrx-table-density": resolvedDensity,
14258
- "data-url-state-table": urlState?.id,
14259
- className: cn(
14260
- "flex h-full min-h-0 flex-col gap-2 px-0 py-2",
14261
- className
14262
- ),
14263
- children: [
14264
- notice,
14265
- savedViewError ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: savedViewError }) : null,
14266
- toolbarActionError ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: toolbarActionError }) : null,
14267
- 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__ */ jsxs32(
14268
- TableTitleRow,
14269
- {
14270
- ...toolbar?.singleRow ? { singleRow: true } : {},
14271
- tabs: viewTabs ? /* @__PURE__ */ jsx42(TableViewWorkspace, { snapshot: currentViewSnapshot, onApply: applySavedView, related: relatedTableActions }, tableId ?? "table") : void 0,
14272
- ...toolbar?.title === void 0 ? {} : { title: toolbar.title },
14273
- ...toolbar?.titleCount === void 0 ? {} : { count: toolbar.titleCount },
14274
- context: tableId && SavedViews ? /* @__PURE__ */ jsx42("span", { "data-matrx-table-view-control": true, children: /* @__PURE__ */ jsx42(
14275
- SavedViews,
14276
- {
14277
- tableId,
14278
- snapshot: currentViewSnapshot,
14279
- defaultSnapshot: declaredDefaultViewSnapshot,
14280
- onApply: applySavedView
14281
- }
14282
- ) }) : void 0,
14283
- search: toolbar?.customSearch ?? (showSearch ? intelligentSearchEnabled ? /* @__PURE__ */ jsx42(
14284
- TableSearch,
14285
- {
14286
- value: searchValue,
14287
- onChange: setSearchValue,
14288
- scope: searchScope,
14289
- onScopeChange: setSearchScope,
14290
- scopes: intelligentSearchScopes,
14291
- ...toolbar?.searchPlaceholder === void 0 ? {} : { placeholder: toolbar.searchPlaceholder }
14292
- }
14293
- ) : /* @__PURE__ */ jsxs32(
14294
- "div",
14547
+ label: tableId ?? "Data table",
14548
+ ...hasMenuSections ? { sections: boundarySections } : {},
14549
+ children: /* @__PURE__ */ jsxs32(
14550
+ DndContext,
14551
+ {
14552
+ id: tableDndContextId,
14553
+ sensors: hierarchySensors,
14554
+ collisionDetection: pointerWithin,
14555
+ accessibility: {
14556
+ screenReaderInstructions: {
14557
+ draggable: "Drag with a pointer. To reorder columns with a keyboard, open Columns and use Move earlier or Move later."
14558
+ }
14559
+ },
14560
+ measuring: { droppable: { strategy: MeasuringStrategy.Always } },
14561
+ onDragStart: (event) => {
14562
+ const kind = event.active.data.current?.matrxDragKind;
14563
+ if (kind === "column-header") {
14564
+ const draggedId = String(event.active.data.current?.columnId);
14565
+ draggedColumnId.current = draggedId;
14566
+ columnDragActive.current = true;
14567
+ setColumnDragPreview({ draggedId, targetId: null, after: false });
14568
+ return;
14569
+ }
14570
+ if (kind === "column-manager") return;
14571
+ setDraggedRowId(String(event.active.id));
14572
+ },
14573
+ onDragMove: (event) => {
14574
+ if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
14575
+ const overData = event.over?.data.current;
14576
+ const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
14577
+ const activeRect = event.active.rect.current.translated;
14578
+ const overRect = event.over?.rect;
14579
+ if (!overId || !activeRect || !overRect) {
14580
+ setColumnDragPreview(
14581
+ (current) => current?.targetId === null ? current : current ? { ...current, targetId: null } : current
14582
+ );
14583
+ columnDragScrollDirection.current = 0;
14584
+ stopColumnDragScroll();
14585
+ return;
14586
+ }
14587
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14588
+ setColumnDragPreview((current) => {
14589
+ if (!current) return current;
14590
+ return current.targetId === overId && current.after === after ? current : { ...current, targetId: String(overId), after };
14591
+ });
14592
+ const scroll = scrollRef.current;
14593
+ if (!scroll) return;
14594
+ const bounds = scroll.getBoundingClientRect();
14595
+ const center = activeRect.left + activeRect.width / 2;
14596
+ columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
14597
+ if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
14598
+ else startColumnDragScroll();
14599
+ },
14600
+ onDragCancel: () => {
14601
+ columnDragActive.current = false;
14602
+ draggedColumnId.current = null;
14603
+ setColumnDragPreview(null);
14604
+ stopColumnDragScroll();
14605
+ setDraggedRowId(null);
14606
+ },
14607
+ onDragEnd: (event) => {
14608
+ const kind = event.active.data.current?.matrxDragKind;
14609
+ if (kind === "column-manager") return;
14610
+ if (kind !== "column-header") {
14611
+ endHierarchyDrag(event);
14612
+ return;
14613
+ }
14614
+ const from = String(event.active.data.current?.columnId);
14615
+ const targetData = event.over?.data.current;
14616
+ const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
14617
+ const activeRect = event.active.rect.current.translated;
14618
+ const overRect = event.over?.rect;
14619
+ columnDragActive.current = false;
14620
+ draggedColumnId.current = null;
14621
+ setColumnDragPreview(null);
14622
+ stopColumnDragScroll();
14623
+ if (columnDragPreview?.draggedId === from && columnDragPreview.targetId !== null && columnDragPreview.targetId !== from) {
14624
+ commitColumnState(
14625
+ projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
14626
+ effectiveHiddenColumnIds
14627
+ );
14628
+ return;
14629
+ }
14630
+ if (!target || from === target || !activeRect || !overRect) return;
14631
+ const order = [...effectiveColumnOrder];
14632
+ const fromIndex = order.indexOf(from);
14633
+ const targetIndex = order.indexOf(String(target));
14634
+ if (fromIndex < 0 || targetIndex < 0) return;
14635
+ order.splice(fromIndex, 1);
14636
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14637
+ const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
14638
+ order.splice(insertion, 0, from);
14639
+ commitColumnState(order, effectiveHiddenColumnIds);
14640
+ },
14641
+ children: [
14642
+ /* @__PURE__ */ jsxs32(
14643
+ "div",
14644
+ {
14645
+ ref: rootRef,
14646
+ "data-matrx-table": true,
14647
+ "data-matrx-table-id": tableContextMenuInstanceId,
14648
+ "data-matrx-table-implementation": "@ai-matrx/design-system",
14649
+ "data-matrx-table-density": resolvedDensity,
14650
+ "data-url-state-table": urlState?.id,
14651
+ className: cn(
14652
+ "flex h-full min-h-0 flex-col gap-2 px-0 py-2",
14653
+ className
14654
+ ),
14655
+ children: [
14656
+ notice,
14657
+ savedViewError ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: savedViewError }) : null,
14658
+ toolbarActionError ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: toolbarActionError }) : null,
14659
+ 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__ */ jsxs32(
14660
+ TableTitleRow,
14295
14661
  {
14296
- "data-matrx-table-search": true,
14297
- className: cn(
14298
- "flex min-w-0 flex-1 sm:w-full sm:flex-none",
14299
- toolbar?.searchMatch ? "sm:max-w-md" : "sm:max-w-xs"
14300
- ),
14662
+ ...toolbar?.singleRow ? { singleRow: true } : {},
14663
+ tabs: viewTabs ? /* @__PURE__ */ jsx42(TableViewWorkspace, { snapshot: currentViewSnapshot, onApply: applySavedView, related: relatedTableActions }, tableId ?? "table") : void 0,
14664
+ ...toolbar?.title === void 0 ? {} : { title: toolbar.title },
14665
+ ...toolbar?.titleCount === void 0 ? {} : { count: toolbar.titleCount },
14666
+ context: tableId && SavedViews ? /* @__PURE__ */ jsx42("span", { "data-matrx-table-view-control": true, children: /* @__PURE__ */ jsx42(
14667
+ SavedViews,
14668
+ {
14669
+ tableId,
14670
+ snapshot: currentViewSnapshot,
14671
+ defaultSnapshot: declaredDefaultViewSnapshot,
14672
+ onApply: applySavedView
14673
+ }
14674
+ ) }) : void 0,
14675
+ search: toolbar?.customSearch ?? (showSearch ? intelligentSearchEnabled ? /* @__PURE__ */ jsx42(
14676
+ TableSearch,
14677
+ {
14678
+ value: searchValue,
14679
+ onChange: setSearchValue,
14680
+ scope: searchScope,
14681
+ onScopeChange: setSearchScope,
14682
+ scopes: intelligentSearchScopes,
14683
+ ...toolbar?.searchPlaceholder === void 0 ? {} : { placeholder: toolbar.searchPlaceholder }
14684
+ }
14685
+ ) : /* @__PURE__ */ jsxs32(
14686
+ "div",
14687
+ {
14688
+ "data-matrx-table-search": true,
14689
+ className: cn(
14690
+ "flex min-w-0 flex-1 sm:w-full sm:flex-none",
14691
+ toolbar?.searchMatch ? "sm:max-w-md" : "sm:max-w-xs"
14692
+ ),
14693
+ children: [
14694
+ /* @__PURE__ */ jsxs32("div", { className: "relative min-w-0 flex-1", children: [
14695
+ /* @__PURE__ */ jsx42(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14696
+ /* @__PURE__ */ jsx42(
14697
+ Input,
14698
+ {
14699
+ value: searchValue,
14700
+ onChange: (e) => setSearchValue(e.target.value),
14701
+ placeholder: toolbar?.searchPlaceholder ?? "Search\u2026",
14702
+ className: cn(
14703
+ "h-8 pl-7 pr-7 text-sm",
14704
+ toolbar?.searchMatch && "rounded-r-none"
14705
+ ),
14706
+ style: { fontSize: "16px" }
14707
+ }
14708
+ ),
14709
+ searchValue ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14710
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
14711
+ "button",
14712
+ {
14713
+ type: "button",
14714
+ "aria-label": "Clear search",
14715
+ className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14716
+ onClick: () => setSearchValue(""),
14717
+ children: /* @__PURE__ */ jsx42(X, { className: "h-5 w-5" })
14718
+ }
14719
+ ) }),
14720
+ /* @__PURE__ */ jsx42(TooltipContent, { children: "Clear search" })
14721
+ ] }) : null
14722
+ ] }),
14723
+ toolbar?.searchMatch ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14724
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs32(
14725
+ Button,
14726
+ {
14727
+ type: "button",
14728
+ variant: searchMatchMode === "whole_words" ? "secondary" : "outline",
14729
+ size: "sm",
14730
+ className: "h-8 shrink-0 gap-1 rounded-l-none border-l-0 px-2 text-xs",
14731
+ "aria-pressed": searchMatchMode === "whole_words",
14732
+ "aria-label": `Search match: ${searchMatchMode === "whole_words" ? "whole words" : "contains text"}`,
14733
+ onClick: () => setSearchMatchMode(
14734
+ searchMatchMode === "whole_words" ? "contains" : "whole_words"
14735
+ ),
14736
+ children: [
14737
+ /* @__PURE__ */ jsx42(WholeWord, { className: "h-5 w-5" }),
14738
+ searchMatchMode === "whole_words" ? "Whole words" : "Contains"
14739
+ ]
14740
+ }
14741
+ ) }),
14742
+ /* @__PURE__ */ jsx42(TooltipContent, { children: searchMatchMode === "whole_words" ? "Whole words \u2014 click to match text anywhere" : "Contains text \u2014 click to require whole words" })
14743
+ ] }) : null
14744
+ ]
14745
+ }
14746
+ ) : null),
14301
14747
  children: [
14302
- /* @__PURE__ */ jsxs32("div", { className: "relative min-w-0 flex-1", children: [
14748
+ toolbar?.layeredFilters ? /* @__PURE__ */ jsx42(
14749
+ LayeredFilterBuilder,
14750
+ {
14751
+ fields: toolbar.layeredFilters.fields,
14752
+ rules: layeredFilters,
14753
+ onChange: setLayeredFilters,
14754
+ maxRules: toolbar.layeredFilters.maxRules,
14755
+ label: toolbar.layeredFilters.label
14756
+ }
14757
+ ) : null,
14758
+ toolbar?.anyOf ? /* @__PURE__ */ jsxs32("div", { className: "relative w-full max-w-xs", children: [
14303
14759
  /* @__PURE__ */ jsx42(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14304
14760
  /* @__PURE__ */ jsx42(
14305
14761
  Input,
14306
14762
  {
14307
- value: searchValue,
14308
- onChange: (e) => setSearchValue(e.target.value),
14309
- placeholder: toolbar?.searchPlaceholder ?? "Search\u2026",
14310
- className: cn(
14311
- "h-8 pl-7 pr-7 text-sm",
14312
- toolbar?.searchMatch && "rounded-r-none"
14313
- ),
14763
+ value: anyOfValue,
14764
+ onChange: (e) => setAnyOfValue(e.target.value),
14765
+ placeholder: toolbar.anyOf.placeholder ?? "Match any of\u2026",
14766
+ className: "h-8 pl-7 pr-7 text-sm",
14314
14767
  style: { fontSize: "16px" }
14315
14768
  }
14316
14769
  ),
14317
- searchValue ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14770
+ anyOfValue ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14318
14771
  /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
14319
14772
  "button",
14320
14773
  {
14321
14774
  type: "button",
14322
- "aria-label": "Clear search",
14775
+ "aria-label": "Clear match-any search",
14323
14776
  className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14324
- onClick: () => setSearchValue(""),
14777
+ onClick: () => setAnyOfValue(""),
14325
14778
  children: /* @__PURE__ */ jsx42(X, { className: "h-5 w-5" })
14326
14779
  }
14327
14780
  ) }),
14328
- /* @__PURE__ */ jsx42(TooltipContent, { children: "Clear search" })
14781
+ /* @__PURE__ */ jsx42(TooltipContent, { children: "Clear match-any search" })
14329
14782
  ] }) : null
14330
- ] }),
14331
- toolbar?.searchMatch ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14332
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs32(
14333
- Button,
14334
- {
14335
- type: "button",
14336
- variant: searchMatchMode === "whole_words" ? "secondary" : "outline",
14337
- size: "sm",
14338
- className: "h-8 shrink-0 gap-1 rounded-l-none border-l-0 px-2 text-xs",
14339
- "aria-pressed": searchMatchMode === "whole_words",
14340
- "aria-label": `Search match: ${searchMatchMode === "whole_words" ? "whole words" : "contains text"}`,
14341
- onClick: () => setSearchMatchMode(
14342
- searchMatchMode === "whole_words" ? "contains" : "whole_words"
14343
- ),
14344
- children: [
14345
- /* @__PURE__ */ jsx42(WholeWord, { className: "h-5 w-5" }),
14346
- searchMatchMode === "whole_words" ? "Whole words" : "Contains"
14347
- ]
14348
- }
14349
- ) }),
14350
- /* @__PURE__ */ jsx42(TooltipContent, { children: searchMatchMode === "whole_words" ? "Whole words \u2014 click to match text anywhere" : "Contains text \u2014 click to require whole words" })
14351
- ] }) : null
14352
- ]
14353
- }
14354
- ) : null),
14355
- children: [
14356
- toolbar?.layeredFilters ? /* @__PURE__ */ jsx42(
14357
- LayeredFilterBuilder,
14358
- {
14359
- fields: toolbar.layeredFilters.fields,
14360
- rules: layeredFilters,
14361
- onChange: setLayeredFilters,
14362
- maxRules: toolbar.layeredFilters.maxRules,
14363
- label: toolbar.layeredFilters.label
14364
- }
14365
- ) : null,
14366
- toolbar?.anyOf ? /* @__PURE__ */ jsxs32("div", { className: "relative w-full max-w-xs", children: [
14367
- /* @__PURE__ */ jsx42(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14368
- /* @__PURE__ */ jsx42(
14369
- Input,
14370
- {
14371
- value: anyOfValue,
14372
- onChange: (e) => setAnyOfValue(e.target.value),
14373
- placeholder: toolbar.anyOf.placeholder ?? "Match any of\u2026",
14374
- className: "h-8 pl-7 pr-7 text-sm",
14375
- style: { fontSize: "16px" }
14376
- }
14377
- ),
14378
- anyOfValue ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14379
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
14380
- "button",
14783
+ ] }) : null,
14784
+ renderedFacets ? /* @__PURE__ */ jsx42(ToolbarFacets, { facets: renderedFacets }) : null,
14785
+ toolbar?.leading ? /* @__PURE__ */ jsx42(
14786
+ "div",
14381
14787
  {
14382
- type: "button",
14383
- "aria-label": "Clear match-any search",
14384
- className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14385
- onClick: () => setAnyOfValue(""),
14386
- children: /* @__PURE__ */ jsx42(X, { className: "h-5 w-5" })
14788
+ "data-matrx-table-toolbar-extras": true,
14789
+ className: "order-last flex w-full min-w-0 flex-wrap items-center gap-2",
14790
+ children: toolbar.leading
14387
14791
  }
14388
- ) }),
14389
- /* @__PURE__ */ jsx42(TooltipContent, { children: "Clear match-any search" })
14390
- ] }) : null
14391
- ] }) : null,
14392
- renderedFacets ? /* @__PURE__ */ jsx42(ToolbarFacets, { facets: renderedFacets }) : null,
14393
- toolbar?.leading ? /* @__PURE__ */ jsx42(
14792
+ ) : null,
14793
+ /* @__PURE__ */ jsxs32(
14794
+ "div",
14795
+ {
14796
+ "data-matrx-table-actions": true,
14797
+ className: cn(
14798
+ "ml-auto flex min-w-0 items-center gap-0",
14799
+ toolbar?.singleRow ? "shrink-0 flex-nowrap" : "flex-wrap"
14800
+ ),
14801
+ children: [
14802
+ toolbar?.singleRow && coverageResult && coverageResult.notice ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14803
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs32(
14804
+ "span",
14805
+ {
14806
+ "data-matrx-table-coverage": true,
14807
+ "data-matrx-table-coverage-scope": coverageResult.scope,
14808
+ role: "status",
14809
+ tabIndex: 0,
14810
+ 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",
14811
+ children: [
14812
+ coverageResult.badge,
14813
+ /* @__PURE__ */ jsxs32("span", { className: "sr-only", children: [
14814
+ ": ",
14815
+ coverageResult.notice
14816
+ ] })
14817
+ ]
14818
+ }
14819
+ ) }),
14820
+ /* @__PURE__ */ jsx42(TooltipContent, { className: "max-w-xs", children: coverageResult.notice })
14821
+ ] }) : null,
14822
+ /* @__PURE__ */ jsx42(
14823
+ TableToolbarAction,
14824
+ {
14825
+ HostAction: host.ToolbarAction,
14826
+ ariaLabel: "Clear all filters",
14827
+ tooltip: hasActiveFilters ? "Clear search and filters; keep columns and sort" : "No search or filters to clear",
14828
+ disabled: !hasActiveFilters,
14829
+ onClick: clearAllFilters,
14830
+ children: /* @__PURE__ */ jsx42(Eraser, {})
14831
+ }
14832
+ ),
14833
+ showToolbarCopy && effectiveCopy ? /* @__PURE__ */ jsx42(
14834
+ CopyButtons,
14835
+ {
14836
+ size: "toolbar",
14837
+ sourceId: `${tableCopySourceNamespace}:view`,
14838
+ label: effectiveCopy.listLabel ?? `${effectiveCopy.label} view`,
14839
+ ...effectiveCopy.references ? { references: effectiveCopy.references(processed, data) } : {},
14840
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14841
+ ...copy === void 0 ? {} : {
14842
+ human: () => effectiveCopy.listHuman?.(processed, data) ?? buildViewHuman(effectiveCopy, processed, effectiveCopyColumns),
14843
+ json: () => effectiveCopy.listJson?.(processed, data) ?? processed.map((r) => effectiveCopy.agentRow ? effectiveCopy.agentRow(r) : r)
14844
+ },
14845
+ agent: () => copy !== void 0 && effectiveCopy.listAgent ? effectiveCopy.listAgent(processed, data) : copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, processed, data, declaredVisibleCopyColumns, {
14846
+ search: searchValue,
14847
+ searchMatchMode,
14848
+ anyOf: anyOfValue,
14849
+ filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14850
+ sort: sort ? `${sort.id}:${sort.direction}` : null
14851
+ }) : buildViewAgentInput(effectiveCopy, processed, data, {
14852
+ search: searchValue,
14853
+ searchMatchMode,
14854
+ anyOf: anyOfValue,
14855
+ filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14856
+ sort: sort ? `${sort.id}:${sort.direction}` : null
14857
+ }),
14858
+ aiVariants: [
14859
+ ...effectiveCopy.aiVariants?.(processed, data) ?? [],
14860
+ ...copySubsetVariantFor(() => processed, "view")
14861
+ ],
14862
+ ...effectiveCopy.aiCustom ? { aiCustom: effectiveCopy.aiCustom(processed, data) } : {},
14863
+ export: {
14864
+ items: [],
14865
+ sheetRows: () => processed.map(
14866
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14867
+ ),
14868
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14869
+ id: columnId(column),
14870
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14871
+ })),
14872
+ ...effectiveCopy.export?.(processed, data)
14873
+ }
14874
+ }
14875
+ ) : null,
14876
+ grouping?.onColumnIdChange ? /* @__PURE__ */ jsxs32(
14877
+ Select,
14878
+ {
14879
+ value: groupingColumnId ?? NO_GROUPING_VALUE,
14880
+ onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14881
+ children: [
14882
+ /* @__PURE__ */ jsx42(
14883
+ SelectTrigger,
14884
+ {
14885
+ "data-matrx-table-group-by": true,
14886
+ "aria-label": "Group rows by a column",
14887
+ className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14888
+ children: /* @__PURE__ */ jsx42(SelectValue, { placeholder: "No groups" })
14889
+ }
14890
+ ),
14891
+ /* @__PURE__ */ jsxs32(SelectContent, { children: [
14892
+ /* @__PURE__ */ jsx42(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14893
+ groupableColumns.map((col) => /* @__PURE__ */ jsx42(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14894
+ ] })
14895
+ ]
14896
+ }
14897
+ ) : null,
14898
+ columns.length > 0 ? /* @__PURE__ */ jsx42(
14899
+ TableToolbarAction,
14900
+ {
14901
+ HostAction: host.ToolbarAction,
14902
+ ariaLabel: "Columns",
14903
+ tooltip: "Show, hide, or reorder columns",
14904
+ onClick: () => setColumnManagerOpen(true),
14905
+ children: /* @__PURE__ */ jsx42(SlidersHorizontal, {})
14906
+ }
14907
+ ) : null,
14908
+ toolbar?.refresh ? /* @__PURE__ */ jsx42(
14909
+ TableToolbarAction,
14910
+ {
14911
+ HostAction: host.ToolbarAction,
14912
+ ariaLabel: toolbar.refresh.label ?? "Refresh",
14913
+ tooltip: isFetching ? "Refreshing table data" : toolbar.refresh.label ?? "Refresh",
14914
+ disabled: isFetching || toolbarActionPending.refresh,
14915
+ busy: isFetching || toolbarActionPending.refresh,
14916
+ onClick: () => {
14917
+ if (toolbarActionPendingRef.current.refresh) return;
14918
+ toolbarActionPendingRef.current.refresh = true;
14919
+ setToolbarActionPending((current) => ({ ...current, refresh: true }));
14920
+ setToolbarActionError(null);
14921
+ void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
14922
+ const message = cause instanceof Error ? cause.message : String(cause);
14923
+ setToolbarActionError(`Refresh failed: ${message}`);
14924
+ toast.error(`Refresh failed: ${message}`);
14925
+ }).finally(() => {
14926
+ toolbarActionPendingRef.current.refresh = false;
14927
+ setToolbarActionPending((current) => ({ ...current, refresh: false }));
14928
+ });
14929
+ },
14930
+ children: /* @__PURE__ */ jsx42(RefreshControlIcon, { spinning: isFetching || toolbarActionPending.refresh })
14931
+ }
14932
+ ) : null,
14933
+ toolbar?.add ? /* @__PURE__ */ jsx42(
14934
+ TableToolbarAction,
14935
+ {
14936
+ HostAction: host.ToolbarAction,
14937
+ ariaLabel: "Add",
14938
+ tooltip: toolbar.add.disabled ? toolbar.add.disabledReason ?? "Add unavailable" : "Add",
14939
+ disabled: toolbar.add.disabled || toolbarActionPending.add,
14940
+ busy: toolbarActionPending.add,
14941
+ onClick: () => {
14942
+ if (toolbarActionPendingRef.current.add) return;
14943
+ toolbarActionPendingRef.current.add = true;
14944
+ setToolbarActionPending((current) => ({ ...current, add: true }));
14945
+ setToolbarActionError(null);
14946
+ void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
14947
+ const message = cause instanceof Error ? cause.message : String(cause);
14948
+ setToolbarActionError(`Add failed: ${message}`);
14949
+ toast.error(`Add failed: ${message}`);
14950
+ }).finally(() => {
14951
+ toolbarActionPendingRef.current.add = false;
14952
+ setToolbarActionPending((current) => ({ ...current, add: false }));
14953
+ });
14954
+ },
14955
+ children: /* @__PURE__ */ jsx42(Plus, {})
14956
+ }
14957
+ ) : null,
14958
+ toolbar?.actions
14959
+ ]
14960
+ }
14961
+ )
14962
+ ]
14963
+ }
14964
+ )),
14965
+ /* @__PURE__ */ jsxs32("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
14966
+ coverageResult && coverageResult.notice && !toolbar?.singleRow ? /* @__PURE__ */ jsxs32(
14394
14967
  "div",
14395
14968
  {
14396
- "data-matrx-table-toolbar-extras": true,
14397
- className: "order-last flex w-full min-w-0 flex-wrap items-center gap-2",
14398
- children: toolbar.leading
14969
+ "data-matrx-table-coverage": true,
14970
+ "data-matrx-table-coverage-scope": coverageResult.scope,
14971
+ role: "status",
14972
+ 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",
14973
+ children: [
14974
+ /* @__PURE__ */ jsx42("span", { className: "shrink-0 whitespace-nowrap font-semibold", children: coverageResult.badge }),
14975
+ /* @__PURE__ */ jsx42("span", { className: "min-w-0", children: coverageResult.notice })
14976
+ ]
14399
14977
  }
14400
14978
  ) : null,
14401
- /* @__PURE__ */ jsxs32(
14402
- "div",
14403
- {
14404
- "data-matrx-table-actions": true,
14405
- className: cn(
14406
- "ml-auto flex min-w-0 items-center gap-0",
14407
- toolbar?.singleRow ? "shrink-0 flex-nowrap" : "flex-wrap"
14408
- ),
14409
- children: [
14410
- toolbar?.singleRow && coverageResult && coverageResult.notice ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
14411
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs32(
14412
- "span",
14979
+ /* @__PURE__ */ jsxs32("div", { className: "relative min-h-0 flex-1", children: [
14980
+ mobileCards ? /* @__PURE__ */ jsxs32(
14981
+ "div",
14982
+ {
14983
+ ref: cardsScrollRef,
14984
+ "data-matrx-table-cards-scroll": true,
14985
+ "aria-busy": isLoading || isFetching,
14986
+ ...appendQuery === null ? {} : { style: { containerType: "inline-size" } },
14987
+ className: cn(
14988
+ "relative flex h-full min-h-0 flex-col gap-2 overflow-y-auto rounded-md border border-border bg-card p-2",
14989
+ mobileCardsClass
14990
+ ),
14991
+ children: [
14992
+ isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ jsxs32(
14993
+ "div",
14413
14994
  {
14414
- "data-matrx-table-coverage": true,
14415
- "data-matrx-table-coverage-scope": coverageResult.scope,
14416
14995
  role: "status",
14417
- tabIndex: 0,
14418
- 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",
14996
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14419
14997
  children: [
14420
- coverageResult.badge,
14421
- /* @__PURE__ */ jsxs32("span", { className: "sr-only", children: [
14422
- ": ",
14423
- coverageResult.notice
14424
- ] })
14998
+ /* @__PURE__ */ jsx42("div", { className: "h-full w-full animate-pulse bg-primary" }),
14999
+ /* @__PURE__ */ jsx42("span", { className: "sr-only", children: "Refreshing table data" })
14425
15000
  ]
14426
15001
  }
14427
- ) }),
14428
- /* @__PURE__ */ jsx42(TooltipContent, { className: "max-w-xs", children: coverageResult.notice })
14429
- ] }) : null,
14430
- /* @__PURE__ */ jsx42(
14431
- TableToolbarAction,
14432
- {
14433
- HostAction: host.ToolbarAction,
14434
- ariaLabel: "Clear all filters",
14435
- tooltip: hasActiveFilters ? "Clear search and filters; keep columns and sort" : "No search or filters to clear",
14436
- disabled: !hasActiveFilters,
14437
- onClick: clearAllFilters,
14438
- children: /* @__PURE__ */ jsx42(Eraser, {})
14439
- }
14440
- ),
14441
- showToolbarCopy && effectiveCopy ? /* @__PURE__ */ jsx42(
14442
- CopyButtons,
14443
- {
14444
- size: "toolbar",
14445
- sourceId: `${tableCopySourceNamespace}:view`,
14446
- label: effectiveCopy.listLabel ?? `${effectiveCopy.label} view`,
14447
- ...effectiveCopy.references ? { references: effectiveCopy.references(processed, data) } : {},
14448
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14449
- ...copy === void 0 ? {} : {
14450
- human: () => effectiveCopy.listHuman?.(processed, data) ?? buildViewHuman(effectiveCopy, processed, effectiveCopyColumns),
14451
- json: () => effectiveCopy.listJson?.(processed, data) ?? processed.map((r) => effectiveCopy.agentRow ? effectiveCopy.agentRow(r) : r)
15002
+ ) : null,
15003
+ isLoading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ jsx42(
15004
+ Skeleton,
15005
+ {
15006
+ className: "h-52 w-full shrink-0 rounded-lg"
14452
15007
  },
14453
- agent: () => copy !== void 0 && effectiveCopy.listAgent ? effectiveCopy.listAgent(processed, data) : copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, processed, data, declaredVisibleCopyColumns, {
14454
- search: searchValue,
14455
- searchMatchMode,
14456
- anyOf: anyOfValue,
14457
- filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14458
- sort: sort ? `${sort.id}:${sort.direction}` : null
14459
- }) : buildViewAgentInput(effectiveCopy, processed, data, {
14460
- search: searchValue,
14461
- searchMatchMode,
14462
- anyOf: anyOfValue,
14463
- filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14464
- sort: sort ? `${sort.id}:${sort.direction}` : null
14465
- }),
14466
- aiVariants: [
14467
- ...effectiveCopy.aiVariants?.(processed, data) ?? [],
14468
- ...copySubsetVariantFor(() => processed, "view")
14469
- ],
14470
- ...effectiveCopy.aiCustom ? { aiCustom: effectiveCopy.aiCustom(processed, data) } : {},
14471
- export: {
14472
- items: [],
14473
- sheetRows: () => processed.map(
14474
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14475
- ),
14476
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14477
- id: columnId(column),
14478
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14479
- })),
14480
- ...effectiveCopy.export?.(processed, data)
14481
- }
14482
- }
14483
- ) : null,
14484
- grouping?.onColumnIdChange ? /* @__PURE__ */ jsxs32(
14485
- Select,
14486
- {
14487
- value: groupingColumnId ?? NO_GROUPING_VALUE,
14488
- onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14489
- children: [
14490
- /* @__PURE__ */ jsx42(
14491
- SelectTrigger,
15008
+ `mobile-card-sk-${index}`
15009
+ )) : paginated.length === 0 ? /* @__PURE__ */ jsx42("div", { className: "flex min-h-48 flex-1 items-center justify-center px-4 py-12 text-center", children: /* @__PURE__ */ jsxs32("div", { className: "flex max-w-sm flex-col items-center gap-2", children: [
15010
+ renderedEmptyState?.icon,
15011
+ /* @__PURE__ */ jsx42("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15012
+ renderedEmptyState?.description ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15013
+ renderedEmptyState?.action
15014
+ ] }) }) : paginated.map((row, index) => {
15015
+ const id = getRowId(row);
15016
+ const displayRow = applyRowEdits(row, edits[id]);
15017
+ const controls = buildRecordControls(displayRow);
15018
+ const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
15019
+ return /* @__PURE__ */ jsx42("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
15020
+ renderCell: (columnKey) => {
15021
+ const column = columns.find((candidate) => columnId(candidate) === columnKey);
15022
+ return column ? renderRecordCell(row, column, index) : null;
15023
+ },
15024
+ selected: selectedIdSet.has(getSelectionId(row)),
15025
+ selectable,
15026
+ onSelectedChange: (nextSelected) => {
15027
+ if (!selection || !selectable || nextSelected === selectedIdSet.has(getSelectionId(row))) {
15028
+ return;
15029
+ }
15030
+ const next = new Set(selectedIdSet);
15031
+ const selectionId = getSelectionId(row);
15032
+ if (nextSelected) next.add(selectionId);
15033
+ else next.delete(selectionId);
15034
+ setSelectedIds(next);
15035
+ },
15036
+ actions: showInlineRowCopy || rowActions ? /* @__PURE__ */ jsxs32(
15037
+ "div",
14492
15038
  {
14493
- "data-matrx-table-group-by": true,
14494
- "aria-label": "Group rows by a column",
14495
- className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14496
- children: /* @__PURE__ */ jsx42(SelectValue, { placeholder: "No groups" })
15039
+ "data-matrx-table-row-actions": true,
15040
+ className: "inline-flex items-center justify-end gap-0",
15041
+ onClick: (event) => event.stopPropagation(),
15042
+ children: [
15043
+ showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ jsx42(
15044
+ TableRowCopyControl,
15045
+ {
15046
+ size: "toolbar",
15047
+ copy: effectiveCopy,
15048
+ row: displayRow,
15049
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15050
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15051
+ }
15052
+ ) : null,
15053
+ renderRowActions(displayRow, controls)
15054
+ ]
14497
15055
  }
14498
- ),
14499
- /* @__PURE__ */ jsxs32(SelectContent, { children: [
14500
- /* @__PURE__ */ jsx42(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14501
- groupableColumns.map((col) => /* @__PURE__ */ jsx42(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14502
- ] })
14503
- ]
14504
- }
14505
- ) : null,
14506
- columns.length > 0 ? /* @__PURE__ */ jsx42(
14507
- TableToolbarAction,
14508
- {
14509
- HostAction: host.ToolbarAction,
14510
- ariaLabel: "Columns",
14511
- tooltip: "Show, hide, or reorder columns",
14512
- onClick: () => setColumnManagerOpen(true),
14513
- children: /* @__PURE__ */ jsx42(SlidersHorizontal, {})
14514
- }
14515
- ) : null,
14516
- toolbar?.refresh ? /* @__PURE__ */ jsx42(
14517
- TableToolbarAction,
14518
- {
14519
- HostAction: host.ToolbarAction,
14520
- ariaLabel: toolbar.refresh.label ?? "Refresh",
14521
- tooltip: isFetching ? "Refreshing table data" : toolbar.refresh.label ?? "Refresh",
14522
- disabled: isFetching || toolbarActionPending.refresh,
14523
- busy: isFetching || toolbarActionPending.refresh,
14524
- onClick: () => {
14525
- if (toolbarActionPendingRef.current.refresh) return;
14526
- toolbarActionPendingRef.current.refresh = true;
14527
- setToolbarActionPending((current) => ({ ...current, refresh: true }));
14528
- setToolbarActionError(null);
14529
- void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
14530
- const message = cause instanceof Error ? cause.message : String(cause);
14531
- setToolbarActionError(`Refresh failed: ${message}`);
14532
- toast.error(`Refresh failed: ${message}`);
14533
- }).finally(() => {
14534
- toolbarActionPendingRef.current.refresh = false;
14535
- setToolbarActionPending((current) => ({ ...current, refresh: false }));
14536
- });
14537
- },
14538
- children: /* @__PURE__ */ jsx42(RefreshControlIcon, { spinning: isFetching || toolbarActionPending.refresh })
14539
- }
14540
- ) : null,
14541
- toolbar?.add ? /* @__PURE__ */ jsx42(
14542
- TableToolbarAction,
14543
- {
14544
- HostAction: host.ToolbarAction,
14545
- ariaLabel: "Add",
14546
- tooltip: toolbar.add.disabled ? toolbar.add.disabledReason ?? "Add unavailable" : "Add",
14547
- disabled: toolbar.add.disabled || toolbarActionPending.add,
14548
- busy: toolbarActionPending.add,
14549
- onClick: () => {
14550
- if (toolbarActionPendingRef.current.add) return;
14551
- toolbarActionPendingRef.current.add = true;
14552
- setToolbarActionPending((current) => ({ ...current, add: true }));
14553
- setToolbarActionError(null);
14554
- void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
14555
- const message = cause instanceof Error ? cause.message : String(cause);
14556
- setToolbarActionError(`Add failed: ${message}`);
14557
- toast.error(`Add failed: ${message}`);
14558
- }).finally(() => {
14559
- toolbarActionPendingRef.current.add = false;
14560
- setToolbarActionPending((current) => ({ ...current, add: false }));
14561
- });
14562
- },
14563
- children: /* @__PURE__ */ jsx42(Plus, {})
14564
- }
14565
- ) : null,
14566
- toolbar?.actions
14567
- ]
14568
- }
14569
- )
14570
- ]
14571
- }
14572
- )),
14573
- /* @__PURE__ */ jsxs32("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
14574
- coverageResult && coverageResult.notice && !toolbar?.singleRow ? /* @__PURE__ */ jsxs32(
14575
- "div",
14576
- {
14577
- "data-matrx-table-coverage": true,
14578
- "data-matrx-table-coverage-scope": coverageResult.scope,
14579
- role: "status",
14580
- 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",
14581
- children: [
14582
- /* @__PURE__ */ jsx42("span", { className: "shrink-0 whitespace-nowrap font-semibold", children: coverageResult.badge }),
14583
- /* @__PURE__ */ jsx42("span", { className: "min-w-0", children: coverageResult.notice })
14584
- ]
14585
- }
14586
- ) : null,
14587
- /* @__PURE__ */ jsxs32("div", { className: "relative min-h-0 flex-1", children: [
14588
- mobileCards ? /* @__PURE__ */ jsxs32(
14589
- "div",
14590
- {
14591
- ref: cardsScrollRef,
14592
- "data-matrx-table-cards-scroll": true,
14593
- "aria-busy": isLoading || isFetching,
14594
- ...appendQuery === null ? {} : { style: { containerType: "inline-size" } },
14595
- className: cn(
14596
- "relative flex h-full min-h-0 flex-col gap-2 overflow-y-auto rounded-md border border-border bg-card p-2",
14597
- mobileCardsClass
14598
- ),
14599
- children: [
14600
- isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ jsxs32(
14601
- "div",
14602
- {
14603
- role: "status",
14604
- className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14605
- children: [
14606
- /* @__PURE__ */ jsx42("div", { className: "h-full w-full animate-pulse bg-primary" }),
14607
- /* @__PURE__ */ jsx42("span", { className: "sr-only", children: "Refreshing table data" })
14608
- ]
14609
- }
14610
- ) : null,
14611
- isLoading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ jsx42(
14612
- Skeleton,
14613
- {
14614
- className: "h-52 w-full shrink-0 rounded-lg"
14615
- },
14616
- `mobile-card-sk-${index}`
14617
- )) : paginated.length === 0 ? /* @__PURE__ */ jsx42("div", { className: "flex min-h-48 flex-1 items-center justify-center px-4 py-12 text-center", children: /* @__PURE__ */ jsxs32("div", { className: "flex max-w-sm flex-col items-center gap-2", children: [
14618
- renderedEmptyState?.icon,
14619
- /* @__PURE__ */ jsx42("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
14620
- renderedEmptyState?.description ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
14621
- renderedEmptyState?.action
14622
- ] }) }) : paginated.map((row, index) => {
14623
- const id = getRowId(row);
14624
- const displayRow = applyRowEdits(row, edits[id]);
14625
- const controls = buildRecordControls(displayRow);
14626
- const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
14627
- return /* @__PURE__ */ jsx42("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
14628
- renderCell: (columnKey) => {
14629
- const column = columns.find((candidate) => columnId(candidate) === columnKey);
14630
- return column ? renderRecordCell(row, column, index) : null;
14631
- },
14632
- selected: selectedIdSet.has(getSelectionId(row)),
14633
- selectable,
14634
- onSelectedChange: (nextSelected) => {
14635
- if (!selection || !selectable || nextSelected === selectedIdSet.has(getSelectionId(row))) {
14636
- return;
14637
- }
14638
- const next = new Set(selectedIdSet);
14639
- const selectionId = getSelectionId(row);
14640
- if (nextSelected) next.add(selectionId);
14641
- else next.delete(selectionId);
14642
- setSelectedIds(next);
14643
- },
14644
- actions: showInlineRowCopy || rowActions ? /* @__PURE__ */ jsxs32(
15056
+ ) : null,
15057
+ ...canExpandRow(row) ? {
15058
+ isExpanded: isRowExpanded(row),
15059
+ toggleExpanded: () => toggleExpanded(row)
15060
+ } : {},
15061
+ expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
15062
+ }) }, id);
15063
+ }),
15064
+ appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsxs32(
14645
15065
  "div",
14646
15066
  {
14647
- "data-matrx-table-row-actions": true,
14648
- className: "inline-flex items-center justify-end gap-0",
14649
- onClick: (event) => event.stopPropagation(),
15067
+ "data-matrx-table-append-loading-card": true,
15068
+ role: "status",
15069
+ "aria-live": "polite",
15070
+ 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",
14650
15071
  children: [
14651
- showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ jsx42(
14652
- TableRowCopyControl,
15072
+ /* @__PURE__ */ jsx42(
15073
+ Loader2,
14653
15074
  {
14654
- size: "toolbar",
14655
- copy: effectiveCopy,
14656
- row: displayRow,
14657
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
14658
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15075
+ "aria-hidden": "true",
15076
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
14659
15077
  }
14660
- ) : null,
14661
- renderRowActions(displayRow, controls)
15078
+ ),
15079
+ "Loading more rows\u2026"
14662
15080
  ]
14663
15081
  }
15082
+ ) : null
15083
+ ]
15084
+ }
15085
+ ) : null,
15086
+ /* @__PURE__ */ jsxs32(
15087
+ "div",
15088
+ {
15089
+ ref: scrollRef,
15090
+ "data-matrx-table-scroll": true,
15091
+ onScroll: updateScrollHint,
15092
+ ...spreadsheetEnabled ? {
15093
+ tabIndex: sheet.containerProps.tabIndex,
15094
+ onKeyDown: sheet.containerProps.onKeyDown,
15095
+ onCopy: sheet.containerProps.onCopy,
15096
+ onCut: sheet.containerProps.onCut,
15097
+ onPaste: sheet.containerProps.onPaste,
15098
+ "data-matrx-table-spreadsheet": "true"
15099
+ } : {},
15100
+ "aria-busy": isLoading || isFetching,
15101
+ style: { containerType: "inline-size" },
15102
+ className: cn(
15103
+ "relative h-full w-full overflow-auto rounded-md border border-border bg-card",
15104
+ mobileCards && tableWithCardsClass,
15105
+ tableClassName
15106
+ ),
15107
+ children: [
15108
+ hierarchy && draggedRow ? /* @__PURE__ */ jsx42(
15109
+ HierarchyRootDropTarget,
15110
+ {
15111
+ label: hierarchy.rootDropLabel ?? "Drop here to move to the top level",
15112
+ draggedLabel: hierarchy.itemLabel?.(draggedRow) ?? "Moving row"
15113
+ }
14664
15114
  ) : null,
14665
- ...canExpandRow(row) ? {
14666
- isExpanded: isRowExpanded(row),
14667
- toggleExpanded: () => toggleExpanded(row)
14668
- } : {},
14669
- expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
14670
- }) }, id);
14671
- }),
14672
- appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsxs32(
14673
- "div",
14674
- {
14675
- "data-matrx-table-append-loading-card": true,
14676
- role: "status",
14677
- "aria-live": "polite",
14678
- 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",
14679
- children: [
14680
- /* @__PURE__ */ jsx42(
14681
- Loader2,
14682
- {
14683
- "aria-hidden": "true",
14684
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
14685
- }
14686
- ),
14687
- "Loading more rows\u2026"
14688
- ]
14689
- }
14690
- ) : null
14691
- ]
14692
- }
14693
- ) : null,
14694
- /* @__PURE__ */ jsxs32(
14695
- "div",
14696
- {
14697
- ref: scrollRef,
14698
- "data-matrx-table-scroll": true,
14699
- onScroll: updateScrollHint,
14700
- ...spreadsheetEnabled ? {
14701
- tabIndex: sheet.containerProps.tabIndex,
14702
- onKeyDown: sheet.containerProps.onKeyDown,
14703
- onCopy: sheet.containerProps.onCopy,
14704
- onCut: sheet.containerProps.onCut,
14705
- onPaste: sheet.containerProps.onPaste,
14706
- "data-matrx-table-spreadsheet": "true"
14707
- } : {},
14708
- "aria-busy": isLoading || isFetching,
14709
- style: { containerType: "inline-size" },
14710
- className: cn(
14711
- "relative h-full w-full overflow-auto rounded-md border border-border bg-card",
14712
- mobileCards && tableWithCardsClass,
14713
- tableClassName
14714
- ),
14715
- children: [
14716
- hierarchy && draggedRow ? /* @__PURE__ */ jsx42(
14717
- HierarchyRootDropTarget,
14718
- {
14719
- label: hierarchy.rootDropLabel ?? "Drop here to move to the top level",
14720
- draggedLabel: hierarchy.itemLabel?.(draggedRow) ?? "Moving row"
14721
- }
14722
- ) : null,
14723
- hierarchy && draggedRowId ? /* @__PURE__ */ jsx42(
14724
- HierarchyDragIndicatorLayer,
14725
- {
14726
- scrollRef,
14727
- resolve: resolveHierarchyMove
14728
- }
14729
- ) : null,
14730
- isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ jsxs32(
14731
- "div",
14732
- {
14733
- role: "status",
14734
- className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14735
- children: [
14736
- /* @__PURE__ */ jsx42("div", { className: "h-full w-full animate-pulse bg-primary" }),
14737
- /* @__PURE__ */ jsx42("span", { className: "sr-only", children: "Refreshing table data" })
14738
- ]
14739
- }
14740
- ) : null,
14741
- /* @__PURE__ */ jsxs32(
14742
- "table",
14743
- {
14744
- ref: tableRef,
14745
- style: !fixedLayoutActive ? void 0 : {
14746
- // Track the same preview variables as the cells. A stale
14747
- // pre-resize total redistributes spare width under fixed
14748
- // layout and defeats both exact shrinking and max bounds.
14749
- "--matrx-resized-table-width": `calc(${visibleColumns.map((column) => {
14750
- const id = columnId(column);
14751
- return resizableColumns && column.resizable !== false ? `var(${columnResizeCssVar(id)}, ${effectiveColumnWidths[id]}px)` : `${effectiveColumnWidths[id]}px`;
14752
- }).join(" + ")} + ${(selection ? fixedTableGeometry?.selectionWidth ?? 0 : 0) + (showActionsCol ? fixedTableGeometry?.actionsWidth ?? 0 : 0)}px)`
14753
- },
14754
- className: cn(
14755
- "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",
14756
- fixedLayoutActive && "sm:table-fixed sm:max-w-none sm:w-[var(--matrx-resized-table-width)]"
14757
- ),
14758
- children: [
14759
- /* @__PURE__ */ jsx42(
14760
- "thead",
14761
- {
14762
- ref: tableHeaderRef,
14763
- "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
14764
- className: cn(
14765
- // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
14766
- // cell is `sm:z-20` so it can slide under the columns to
14767
- // its right; the header was `z-10`, so the frozen column's
14768
- // cells scrolled straight OVER the header they belong to
14769
- // and the top data row printed through the column names.
14770
- // Only a table long enough to scroll shows it, which is why
14771
- // it survived until a hundred-thousand-row screenshot.
14772
- "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
14773
- stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
14774
- ),
14775
- children: /* @__PURE__ */ jsxs32("tr", { children: [
14776
- selection ? /* @__PURE__ */ jsx42(
14777
- "th",
14778
- {
14779
- "data-matrx-table-selection-column": true,
14780
- className: cn(
14781
- "h-9 w-9 px-2 text-left align-middle",
14782
- frozenColumns.selectionOffset !== null && "sm:sticky sm:left-0 sm:z-30 sm:bg-muted"
14783
- ),
14784
- style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
14785
- children: /* @__PURE__ */ jsx42(
14786
- "div",
14787
- {
14788
- "data-matrx-table-selection-target": true,
14789
- onClick: (event) => {
14790
- if (event.target === event.currentTarget) {
14791
- event.currentTarget.querySelector('[role="checkbox"]')?.click();
14792
- }
14793
- },
14794
- children: /* @__PURE__ */ jsx42(
14795
- Checkbox,
14796
- {
14797
- className: CHECKBOX_TAP_AREA,
14798
- checked: allOnPageSelected ? true : someOnPageSelected ? "indeterminate" : false,
14799
- disabled: selectableRows.length === 0,
14800
- onCheckedChange: toggleAllOnPage,
14801
- "aria-label": allOnPageSelected ? "Clear selection on this page" : "Select every row on this page"
14802
- }
14803
- )
14804
- }
14805
- )
14806
- }
14807
- ) : null,
14808
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14809
- "th",
14810
- {
14811
- "aria-hidden": "true",
14812
- "data-matrx-table-column-spacer": "start",
14813
- className: "border-0 p-0",
14814
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14815
- },
14816
- "matrx-column-spacer-start"
14817
- ) : null,
14818
- renderedColumns.map((col) => {
14819
- const id = columnId(col);
14820
- const meta = filterMeta.get(id);
14821
- const isSorted = sort?.id === id;
14822
- const headerLabel = col.label ?? (typeof col.header === "string" ? col.header : id);
14823
- const defaultSortDirection = col.defaultSortDirection ?? "asc";
14824
- const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
14825
- const columnResizable = resizableColumns && col.resizable !== false;
14826
- const marker = resolveColumnMarker(col);
14827
- const compact = col.compact || marker !== void 0;
14828
- const width = marker && col.width === void 0 ? 40 : col.width;
14829
- const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
14830
- return /* @__PURE__ */ jsxs32(
14831
- "th",
14832
- {
14833
- "data-matrx-table-column-id": id,
14834
- "data-matrx-header-column-id": id,
14835
- "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
14836
- "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
14837
- className: cn(
14838
- "relative h-9 text-left align-middle max-sm:whitespace-nowrap",
14839
- // ICON COLUMN (MatrxColumnDef.compact): a 40px star
14840
- // column cannot honor `width: 40` while paying 16px of
14841
- // padding for a 14px glyph.
14842
- compact ? "px-1" : "px-2",
14843
- // An intentional mobile column set (see
14844
- // MatrxColumnDef.mobileHidden). CSS, not a JS
14845
- // breakpoint — no hydration mismatch, and the column
14846
- // stays sortable/filterable from the toolbar.
14847
- col.mobileHidden && "max-sm:hidden",
14848
- // Consumer widths are desktop tuning: applied from `sm`
14849
- // up via a CSS var. The minimum is load-bearing: without
14850
- // it, a crowded table crushes a declared 190px column to a
14851
- // few characters wide instead of using the canonical
14852
- // horizontal-scroll surface.
14853
- (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
14854
- col.headerClassName,
14855
- isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
14856
- alignForFormat(col) === "center" && "text-center",
14857
- alignForFormat(col) === "right" && "text-right",
14858
- frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
14859
- frozenEdgeId === id && FROZEN_EDGE_CLASS
14860
- ),
14861
- style: {
14862
- ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
14863
- ...frozenStyle(id)
14864
- },
14865
- children: [
14866
- /* @__PURE__ */ jsx42(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ jsx42(
14867
- ColumnHeaderCell,
14868
- {
14869
- label: col.header,
14870
- labelText: headerLabel,
14871
- ...facetsEnabled ? {
14872
- facets: facetState[id]?.data ?? null,
14873
- facetsLoading: facetState[id]?.loading ?? false,
14874
- ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
14875
- onFilterOpen: () => requestFacets(id)
14876
- } : {},
14877
- sortable: col.sortable !== false,
14878
- isSorted: Boolean(isSorted),
14879
- sortDirection: sort?.direction ?? defaultSortDirection,
14880
- onSortAsc: () => setSort({ id, direction: "asc" }),
14881
- onSortDesc: () => setSort({ id, direction: "desc" }),
14882
- onClearSort: () => setSort(null),
14883
- onHeaderSortClick: () => {
14884
- if (!isSorted) {
14885
- setSort({ id, direction: defaultSortDirection });
14886
- return;
14887
- }
14888
- if (sort?.direction === "asc") {
14889
- setSort({ id, direction: "desc" });
14890
- return;
14891
- }
14892
- setSort({ id, direction: "asc" });
14893
- },
14894
- filterKind: meta?.kind ?? null,
14895
- filterValue: columnFilters[id],
14896
- onFilterChange: (next) => setColumnFilter(id, next),
14897
- selectOptions: meta?.options,
14898
- selectSingle: col.filterSingle,
14899
- selectAllowCustom: col.filterAllowCustom,
14900
- filterApplyLabel: filterUi?.applyLabel,
14901
- filterShowCancel: filterUi?.showCancel,
14902
- filterFooterHint: filterUi?.footerHint,
14903
- allowNegation: filterUi?.allowNegation,
14904
- allowNullModes: filterUi?.allowNullModes,
14905
- align: col.align,
14906
- compact,
14907
- marker,
14908
- reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0
14909
- }
14910
- ) }),
14911
- columnResizable ? /* @__PURE__ */ jsx42(
14912
- ColumnResizeHandle,
14913
- {
14914
- columnId: id,
14915
- label: headerLabel,
14916
- tableRef,
14917
- minWidth,
14918
- maxWidth: col.maxWidth,
14919
- onResizeStart: captureResizeGestureSnapshot,
14920
- onResizeCancel: clearResizeGestureSnapshot,
14921
- onResizeCommit: (columnId2, width2) => commitColumnWidths(
14922
- { ...effectiveColumnWidths, [columnId2]: width2 },
14923
- true
14924
- )
14925
- }
14926
- ) : null
14927
- ]
14928
- },
14929
- id
14930
- );
14931
- }),
14932
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14933
- "th",
14934
- {
14935
- "aria-hidden": "true",
14936
- "data-matrx-table-column-spacer": "end",
14937
- className: "border-0 p-0",
14938
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14939
- },
14940
- "matrx-column-spacer-end"
14941
- ) : null,
14942
- showActionsCol && /* @__PURE__ */ jsx42(
14943
- "th",
14944
- {
14945
- "data-matrx-table-actions-column": true,
14946
- className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
14947
- style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
14948
- children: /* @__PURE__ */ jsx42("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
14949
- }
14950
- )
14951
- ] })
14952
- }
14953
- ),
14954
- /* @__PURE__ */ jsxs32("tbody", { children: [
14955
- isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs32("tr", { className: "border-b border-border/60", children: [
14956
- selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
14957
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14958
- "td",
14959
- {
14960
- "aria-hidden": "true",
14961
- "data-matrx-table-column-spacer": "start",
14962
- className: "border-0 p-0",
14963
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14964
- },
14965
- "matrx-column-spacer-start"
14966
- ) : null,
14967
- renderedColumns.map((col) => /* @__PURE__ */ jsx42(
14968
- "td",
14969
- {
14970
- className: cn(
14971
- "py-2",
14972
- col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
14973
- col.mobileHidden && "max-sm:hidden"
14974
- ),
14975
- children: /* @__PURE__ */ jsx42(Skeleton, { className: "h-5 w-full" })
14976
- },
14977
- columnId(col)
14978
- )),
14979
- showActionsCol && /* @__PURE__ */ jsx42("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "ml-auto h-5 w-10" }) }),
14980
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14981
- "td",
14982
- {
14983
- "aria-hidden": "true",
14984
- "data-matrx-table-column-spacer": "end",
14985
- className: "border-0 p-0",
14986
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14987
- },
14988
- "matrx-column-spacer-end"
14989
- ) : null
14990
- ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ jsx42("tr", { children: /* @__PURE__ */ jsx42(
14991
- "td",
14992
- {
14993
- colSpan: renderedColSpan,
14994
- className: "px-0 py-12 text-center",
14995
- children: /* @__PURE__ */ jsxs32("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: [
14996
- renderedEmptyState?.icon,
14997
- /* @__PURE__ */ jsx42("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
14998
- renderedEmptyState?.description ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
14999
- renderedEmptyState?.action
15000
- ] })
15001
- }
15002
- ) }) : bodyEntries.map((entry) => {
15003
- if (entry.kind === "spacer") {
15004
- return /* @__PURE__ */ jsx42(
15005
- "tr",
15006
- {
15007
- "aria-hidden": "true",
15008
- "data-matrx-table-virtual-spacer": entry.place,
15009
- style: { height: `${entry.height}px` },
15010
- children: /* @__PURE__ */ jsx42(
15011
- "td",
15012
- {
15013
- className: "border-0 p-0",
15014
- colSpan: renderedColSpan
15015
- }
15016
- )
15017
- },
15018
- `matrx-virtual-spacer-${entry.place}`
15019
- );
15020
- }
15021
- if (entry.kind === "group") {
15022
- return /* @__PURE__ */ jsx42(
15023
- TableGroupHeaderRow,
15024
- {
15025
- group: entry.group,
15026
- collapsed: entry.collapsed,
15027
- columns: renderedColumns,
15028
- spacers: columnSpacers,
15029
- leadingCols,
15030
- showActionsCol,
15031
- rowNoun: grouping?.rowNoun ?? "row",
15032
- ...grouping?.renderLabel ? { renderLabel: grouping.renderLabel } : {},
15033
- onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
15034
- },
15035
- `matrx-group-${entry.group.key}`
15036
- );
15037
- }
15038
- const row = entry.row;
15039
- const index = entry.index;
15040
- const id = getRowId(row);
15041
- const isSelected = selectedId === id;
15042
- const rowEdits = edits[id];
15043
- const displayRow = applyRowEdits(row, rowEdits);
15044
- const controls = buildRecordControls(displayRow);
15045
- const isChecked = selectedIdSet.has(getSelectionId(row));
15046
- const background = rowBackground({
15047
- tint: rowTintColor(row, id),
15048
- highlighted: highlightedIndex === index,
15049
- selected: isSelected,
15050
- checked: isChecked,
15051
- zebraOdd: zebra && index % 2 === 1,
15052
- hoverable: detailEnabled || Boolean(onRowOpen) || canExpandRow(row)
15053
- });
15054
- const canHoverFrozenCell = background.frozenHover;
15055
- const frozenRowBackgroundClass = background.frozen;
15056
- const rowNode = /* @__PURE__ */ jsxs32(
15057
- "tr",
15115
+ hierarchy && draggedRowId ? /* @__PURE__ */ jsx42(
15116
+ HierarchyDragIndicatorLayer,
15117
+ {
15118
+ scrollRef,
15119
+ resolve: resolveHierarchyMove
15120
+ }
15121
+ ) : null,
15122
+ isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ jsxs32(
15123
+ "div",
15124
+ {
15125
+ role: "status",
15126
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
15127
+ children: [
15128
+ /* @__PURE__ */ jsx42("div", { className: "h-full w-full animate-pulse bg-primary" }),
15129
+ /* @__PURE__ */ jsx42("span", { className: "sr-only", children: "Refreshing table data" })
15130
+ ]
15131
+ }
15132
+ ) : null,
15133
+ /* @__PURE__ */ jsxs32(
15134
+ "table",
15135
+ {
15136
+ ref: tableRef,
15137
+ style: !fixedLayoutActive ? void 0 : {
15138
+ // Track the same preview variables as the cells. A stale
15139
+ // pre-resize total redistributes spare width under fixed
15140
+ // layout and defeats both exact shrinking and max bounds.
15141
+ "--matrx-resized-table-width": `calc(${visibleColumns.map((column) => {
15142
+ const id = columnId(column);
15143
+ return resizableColumns && column.resizable !== false ? `var(${columnResizeCssVar(id)}, ${effectiveColumnWidths[id]}px)` : `${effectiveColumnWidths[id]}px`;
15144
+ }).join(" + ")} + ${(selection ? fixedTableGeometry?.selectionWidth ?? 0 : 0) + (showActionsCol ? fixedTableGeometry?.actionsWidth ?? 0 : 0)}px)`
15145
+ },
15146
+ className: cn(
15147
+ "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",
15148
+ fixedLayoutActive && "sm:table-fixed sm:max-w-none sm:w-[var(--matrx-resized-table-width)]"
15149
+ ),
15150
+ children: [
15151
+ /* @__PURE__ */ jsx42(
15152
+ "thead",
15058
15153
  {
15059
- "data-row-id": id,
15060
- style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
15061
- "data-state": isSelected ? "selected" : void 0,
15062
- onClick: (e) => {
15063
- if (rowEventTargetsInteractiveDescendant(e.target)) return;
15064
- if (opensRowHref(e)) {
15065
- const href = getRowHref?.(row);
15066
- if (href) window.open(href, "_blank", "noopener");
15067
- return;
15068
- }
15069
- openRow(row);
15070
- },
15071
- onAuxClick: (e) => {
15072
- if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
15073
- const href = getRowHref?.(row);
15074
- if (href) {
15075
- e.preventDefault();
15076
- window.open(href, "_blank", "noopener");
15077
- }
15078
- },
15154
+ ref: tableHeaderRef,
15155
+ "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
15079
15156
  className: cn(
15080
- // Keep the mobile row opaque while the complete row scrolls
15081
- // horizontally. The translucent zebra/hover tints remain
15082
- // desktop-only so densely layered mobile content stays crisp.
15083
- "group/matrx-row border-b border-border/60 bg-card transition-colors",
15084
- (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer",
15085
- rowClassName?.(row, index),
15086
- draggedRowId === id && "opacity-40",
15087
- // LAST, and decided once: tint, then highlight / open / ticked, then
15088
- // the zebra stripe (./row-background.ts). Under a tint no stripe is
15089
- // emitted at all, so no `sm:` class can win the cascade over it.
15090
- background.row
15157
+ // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
15158
+ // cell is `sm:z-20` so it can slide under the columns to
15159
+ // its right; the header was `z-10`, so the frozen column's
15160
+ // cells scrolled straight OVER the header they belong to
15161
+ // and the top data row printed through the column names.
15162
+ // Only a table long enough to scroll shows it, which is why
15163
+ // it survived until a hundred-thousand-row screenshot.
15164
+ "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
15165
+ stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
15091
15166
  ),
15092
- children: [
15167
+ children: /* @__PURE__ */ jsxs32("tr", { children: [
15093
15168
  selection ? /* @__PURE__ */ jsx42(
15094
- "td",
15169
+ "th",
15095
15170
  {
15171
+ "data-matrx-table-selection-column": true,
15096
15172
  className: cn(
15097
- "px-2 py-1.5 align-middle lg:py-0.5",
15098
- // When a data column is frozen the checkbox
15099
- // column has to freeze with it, or the first
15100
- // frozen column slides over the checkboxes.
15101
- frozenColumns.selectionOffset !== null && cn(
15102
- "sm:sticky sm:left-0 sm:z-20",
15103
- frozenRowBackgroundClass,
15104
- canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
15105
- )
15173
+ "h-9 w-9 px-2 text-left align-middle",
15174
+ frozenColumns.selectionOffset !== null && "sm:sticky sm:left-0 sm:z-30 sm:bg-muted"
15106
15175
  ),
15107
15176
  style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15108
- onClick: (e) => e.stopPropagation(),
15109
- children: selection.isRowSelectable?.(row) ?? true ? /* @__PURE__ */ jsx42(
15177
+ children: /* @__PURE__ */ jsx42(
15110
15178
  "div",
15111
15179
  {
15112
15180
  "data-matrx-table-selection-target": true,
@@ -15119,17 +15187,18 @@ function MatrxDataTableCore({
15119
15187
  Checkbox,
15120
15188
  {
15121
15189
  className: CHECKBOX_TAP_AREA,
15122
- checked: isChecked,
15123
- "aria-label": `Select this ${selectionNoun}`,
15124
- onClick: (e) => toggleRowSelected(index, e.shiftKey)
15190
+ checked: allOnPageSelected ? true : someOnPageSelected ? "indeterminate" : false,
15191
+ disabled: selectableRows.length === 0,
15192
+ onCheckedChange: toggleAllOnPage,
15193
+ "aria-label": allOnPageSelected ? "Clear selection on this page" : "Select every row on this page"
15125
15194
  }
15126
15195
  )
15127
15196
  }
15128
- ) : null
15197
+ )
15129
15198
  }
15130
15199
  ) : null,
15131
15200
  columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
15132
- "td",
15201
+ "th",
15133
15202
  {
15134
15203
  "aria-hidden": "true",
15135
15204
  "data-matrx-table-column-spacer": "start",
@@ -15138,145 +15207,124 @@ function MatrxDataTableCore({
15138
15207
  },
15139
15208
  "matrx-column-spacer-start"
15140
15209
  ) : null,
15141
- renderedColumns.map((col, colIdx) => {
15142
- const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
15143
- const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
15144
- const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
15145
- const frozen = frozenColumns.offsets[columnId(col)] !== void 0;
15146
- const frozenCellTint = cellTintColor(
15147
- row,
15148
- id,
15149
- columnId(col)
15150
- );
15210
+ renderedColumns.map((col) => {
15211
+ const id = columnId(col);
15212
+ const meta = filterMeta.get(id);
15213
+ const isSorted = sort?.id === id;
15214
+ const headerLabel = col.label ?? (typeof col.header === "string" ? col.header : id);
15215
+ const defaultSortDirection = col.defaultSortDirection ?? "asc";
15216
+ const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
15217
+ const columnResizable = resizableColumns && col.resizable !== false;
15218
+ const marker = resolveColumnMarker(col);
15219
+ const compact = col.compact || marker !== void 0;
15220
+ const width = marker && col.width === void 0 ? 40 : col.width;
15221
+ const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
15151
15222
  return /* @__PURE__ */ jsxs32(
15152
- "td",
15223
+ "th",
15153
15224
  {
15154
- "data-matrx-table-column-id": columnId(col),
15225
+ "data-matrx-table-column-id": id,
15226
+ "data-matrx-header-column-id": id,
15155
15227
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15156
- ...sheetCell ? {
15157
- "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
15158
- "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
15159
- ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
15160
- ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
15161
- ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
15162
- onMouseDown: sheetCell.onMouseDown,
15163
- onMouseEnter: sheetCell.onMouseEnter
15164
- } : {},
15228
+ "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
15165
15229
  className: cn(
15166
- "py-1.5 align-middle lg:py-0.5",
15167
- sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
15168
- sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
15169
- sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
15170
- carriesFillHandle && "relative",
15171
- col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15172
- // nowrap (NOT truncate — truncate clips the cell and
15173
- // defeats w-max, killing the horizontal scroll).
15174
- "max-sm:whitespace-nowrap",
15230
+ "relative h-9 text-left align-middle max-sm:whitespace-nowrap",
15231
+ // ICON COLUMN (MatrxColumnDef.compact): a 40px star
15232
+ // column cannot honor `width: 40` while paying 16px of
15233
+ // padding for a 14px glyph.
15234
+ compact ? "px-1" : "px-2",
15235
+ // An intentional mobile column set (see
15236
+ // MatrxColumnDef.mobileHidden). CSS, not a JS
15237
+ // breakpoint — no hydration mismatch, and the column
15238
+ // stays sortable/filterable from the toolbar.
15175
15239
  col.mobileHidden && "max-sm:hidden",
15176
- ((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)]",
15177
- col.className,
15240
+ // Consumer widths are desktop tuning: applied from `sm`
15241
+ // up via a CSS var. The minimum is load-bearing: without
15242
+ // it, a crowded table crushes a declared 190px column to a
15243
+ // few characters wide instead of using the canonical
15244
+ // horizontal-scroll surface.
15245
+ (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
15246
+ col.headerClassName,
15178
15247
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15179
15248
  alignForFormat(col) === "center" && "text-center",
15180
15249
  alignForFormat(col) === "right" && "text-right",
15181
- frozen && FROZEN_CELL_CLASS,
15182
- frozen && frozenRowBackgroundClass,
15183
- frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
15184
- frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
15185
- frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
15186
- // A cell tint paints OVER the row tint beneath
15187
- // it, and the consumer's own hook comes last so
15188
- // it can always win.
15189
- cellTintClass(row, id, columnId(col)),
15190
- frozen && frozenCellTint && FROZEN_CELL_TINT_CLASS[frozenCellTint],
15191
- cellClassName?.(row, columnId(col), index)
15250
+ frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
15251
+ frozenEdgeId === id && FROZEN_EDGE_CLASS
15192
15252
  ),
15193
15253
  style: {
15194
- ...columnWidthVar(
15195
- resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
15196
- windowedWidthFor(columnId(col), col.width),
15197
- columnId(col),
15198
- resizableColumns && col.resizable !== false
15199
- ),
15200
- ...frozenStyle(columnId(col))
15254
+ ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
15255
+ ...frozenStyle(id)
15201
15256
  },
15202
15257
  children: [
15203
- /* @__PURE__ */ jsxs32(
15204
- "div",
15258
+ /* @__PURE__ */ jsx42(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ jsx42(
15259
+ ColumnHeaderCell,
15205
15260
  {
15206
- className: cn(
15207
- colIdx === 0 && (hierarchy || canExpandRow(row)) && "flex min-w-0 items-center"
15208
- ),
15209
- children: [
15210
- colIdx === 0 && hierarchy ? /* @__PURE__ */ jsx42(
15211
- HierarchyDragHandle,
15212
- {
15213
- id,
15214
- label: hierarchy.itemLabel?.(row) ?? "row",
15215
- disabled: !(hierarchy.canReparent?.(row) ?? true)
15216
- }
15217
- ) : null,
15218
- colIdx === 0 && canExpandRow(row) ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15219
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15220
- Button,
15221
- {
15222
- type: "button",
15223
- variant: "ghost",
15224
- size: "icon",
15225
- "data-matrx-table-expand-toggle": true,
15226
- className: "mr-1 h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground [&_svg]:size-4",
15227
- "aria-label": isRowExpanded(row) ? "Collapse row details" : "Expand row details",
15228
- "aria-expanded": isRowExpanded(row),
15229
- onClick: (event) => {
15230
- event.stopPropagation();
15231
- toggleExpanded(row);
15232
- },
15233
- onKeyDown: (event) => {
15234
- if (event.key !== "Enter" && event.key !== " ") return;
15235
- event.preventDefault();
15236
- event.stopPropagation();
15237
- toggleExpanded(row);
15238
- },
15239
- children: /* @__PURE__ */ jsx42(
15240
- ChevronRight,
15241
- {
15242
- className: cn(
15243
- "transition-transform",
15244
- isRowExpanded(row) && "rotate-90"
15245
- )
15246
- }
15247
- )
15248
- }
15249
- ) }),
15250
- /* @__PURE__ */ jsx42(TooltipContent, { children: isRowExpanded(row) ? "Collapse details" : "Expand details" })
15251
- ] }) : null,
15252
- /* @__PURE__ */ jsx42(
15253
- "div",
15254
- {
15255
- "data-matrx-cell-content": true,
15256
- 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",
15257
- children: renderRecordCell(row, col, index)
15258
- }
15259
- )
15260
- ]
15261
+ label: col.header,
15262
+ labelText: headerLabel,
15263
+ ...facetsEnabled ? {
15264
+ facets: facetState[id]?.data ?? null,
15265
+ facetsLoading: facetState[id]?.loading ?? false,
15266
+ ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
15267
+ onFilterOpen: () => requestFacets(id)
15268
+ } : {},
15269
+ sortable: col.sortable !== false,
15270
+ isSorted: Boolean(isSorted),
15271
+ sortDirection: sort?.direction ?? defaultSortDirection,
15272
+ onSortAsc: () => setSort({ id, direction: "asc" }),
15273
+ onSortDesc: () => setSort({ id, direction: "desc" }),
15274
+ onClearSort: () => setSort(null),
15275
+ onHeaderSortClick: () => {
15276
+ if (!isSorted) {
15277
+ setSort({ id, direction: defaultSortDirection });
15278
+ return;
15279
+ }
15280
+ if (sort?.direction === "asc") {
15281
+ setSort({ id, direction: "desc" });
15282
+ return;
15283
+ }
15284
+ setSort({ id, direction: "asc" });
15285
+ },
15286
+ filterKind: meta?.kind ?? null,
15287
+ filterValue: columnFilters[id],
15288
+ onFilterChange: (next) => setColumnFilter(id, next),
15289
+ selectOptions: meta?.options,
15290
+ selectSingle: col.filterSingle,
15291
+ selectAllowCustom: col.filterAllowCustom,
15292
+ filterApplyLabel: filterUi?.applyLabel,
15293
+ filterShowCancel: filterUi?.showCancel,
15294
+ filterFooterHint: filterUi?.footerHint,
15295
+ allowNegation: filterUi?.allowNegation,
15296
+ allowNullModes: filterUi?.allowNullModes,
15297
+ align: col.align,
15298
+ compact,
15299
+ marker,
15300
+ reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0,
15301
+ menuItems: col.headerMenu,
15302
+ MenuIcon: host.MenuIcon
15261
15303
  }
15262
- ),
15263
- carriesFillHandle ? /* @__PURE__ */ jsx42(
15264
- "button",
15304
+ ) }),
15305
+ columnResizable ? /* @__PURE__ */ jsx42(
15306
+ ColumnResizeHandle,
15265
15307
  {
15266
- type: "button",
15267
- "data-matrx-table-fill-handle": true,
15268
- "aria-label": "Drag to fill the cells below or beside this selection",
15269
- onMouseDown: sheet.startFillDrag,
15270
- className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
15308
+ columnId: id,
15309
+ label: headerLabel,
15310
+ tableRef,
15311
+ minWidth,
15312
+ maxWidth: col.maxWidth,
15313
+ onResizeStart: captureResizeGestureSnapshot,
15314
+ onResizeCancel: clearResizeGestureSnapshot,
15315
+ onResizeCommit: (columnId2, width2) => commitColumnWidths(
15316
+ { ...effectiveColumnWidths, [columnId2]: width2 },
15317
+ true
15318
+ )
15271
15319
  }
15272
15320
  ) : null
15273
15321
  ]
15274
15322
  },
15275
- columnId(col)
15323
+ id
15276
15324
  );
15277
15325
  }),
15278
15326
  columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
15279
- "td",
15327
+ "th",
15280
15328
  {
15281
15329
  "aria-hidden": "true",
15282
15330
  "data-matrx-table-column-spacer": "end",
@@ -15286,629 +15334,977 @@ function MatrxDataTableCore({
15286
15334
  "matrx-column-spacer-end"
15287
15335
  ) : null,
15288
15336
  showActionsCol && /* @__PURE__ */ jsx42(
15289
- "td",
15337
+ "th",
15290
15338
  {
15291
- "data-matrx-table-row-action-cell": true,
15292
- className: "text-right align-middle",
15339
+ "data-matrx-table-actions-column": true,
15340
+ className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
15293
15341
  style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15294
- children: /* @__PURE__ */ jsxs32(
15295
- "div",
15296
- {
15297
- "data-matrx-table-row-actions": true,
15298
- className: "inline-flex items-center justify-end gap-0",
15299
- onClick: (e) => e.stopPropagation(),
15300
- children: [
15301
- showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ jsx42(
15302
- TableRowCopyControl,
15303
- {
15304
- size: "toolbar",
15305
- copy: effectiveCopy,
15306
- row: displayRow,
15307
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15308
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15309
- }
15310
- ) : null,
15311
- renderRowActions(displayRow, controls),
15312
- windowEnabled ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15313
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15314
- Button,
15315
- {
15316
- type: "button",
15317
- variant: "ghost",
15318
- size: "icon",
15319
- "data-matrx-table-icon-action": true,
15320
- className: "h-11 w-11 text-muted-foreground hover:text-foreground [&_svg]:size-5",
15321
- "aria-label": windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window",
15322
- onClick: (e) => {
15323
- e.stopPropagation();
15324
- if (windowConfig?.openOnRowClick === true) openDetail(row);
15325
- else openWindow(row);
15326
- },
15327
- children: opensWindowOnRowClick ? /* @__PURE__ */ jsx42(PanelRightOpen, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx42(PanelRight, { className: "h-5 w-5" })
15328
- }
15329
- ) }),
15330
- /* @__PURE__ */ jsx42(TooltipContent, { children: windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window" })
15331
- ] }) : null
15332
- ]
15333
- }
15334
- )
15342
+ children: /* @__PURE__ */ jsx42("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
15335
15343
  }
15336
15344
  )
15337
- ]
15338
- },
15339
- id
15340
- );
15341
- const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
15342
- const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ jsx42(
15343
- "tr",
15344
- {
15345
- "data-matrx-table-expanded-detail": id,
15346
- className: cn("bg-muted/20", expandedDetail.className),
15347
- children: /* @__PURE__ */ jsx42(
15345
+ ] })
15346
+ }
15347
+ ),
15348
+ /* @__PURE__ */ jsxs32("tbody", { children: [
15349
+ isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs32("tr", { className: "border-b border-border/60", children: [
15350
+ selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
15351
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
15348
15352
  "td",
15349
15353
  {
15350
- colSpan: renderedColSpan,
15351
- className: "p-4",
15352
- children: expandedDetail.render(displayRow, controls)
15353
- }
15354
- )
15355
- },
15356
- `${id}:expanded-detail`
15357
- ) : null;
15358
- return hierarchy ? /* @__PURE__ */ jsxs32(Fragment6, { children: [
15359
- /* @__PURE__ */ jsx42(
15360
- HierarchyDroppableRow,
15354
+ "aria-hidden": "true",
15355
+ "data-matrx-table-column-spacer": "start",
15356
+ className: "border-0 p-0",
15357
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15358
+ },
15359
+ "matrx-column-spacer-start"
15360
+ ) : null,
15361
+ renderedColumns.map((col) => /* @__PURE__ */ jsx42(
15362
+ "td",
15363
+ {
15364
+ className: cn(
15365
+ "py-2",
15366
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15367
+ col.mobileHidden && "max-sm:hidden"
15368
+ ),
15369
+ children: /* @__PURE__ */ jsx42(Skeleton, { className: "h-5 w-full" })
15370
+ },
15371
+ columnId(col)
15372
+ )),
15373
+ showActionsCol && /* @__PURE__ */ jsx42("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "ml-auto h-5 w-10" }) }),
15374
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
15375
+ "td",
15376
+ {
15377
+ "aria-hidden": "true",
15378
+ "data-matrx-table-column-spacer": "end",
15379
+ className: "border-0 p-0",
15380
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15381
+ },
15382
+ "matrx-column-spacer-end"
15383
+ ) : null
15384
+ ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ jsx42("tr", { children: /* @__PURE__ */ jsx42(
15385
+ "td",
15361
15386
  {
15362
- id,
15363
- disabled: !canDropOn(row),
15364
- children: wrappedRow
15387
+ colSpan: renderedColSpan,
15388
+ className: "px-0 py-12 text-center",
15389
+ children: /* @__PURE__ */ jsxs32("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: [
15390
+ renderedEmptyState?.icon,
15391
+ /* @__PURE__ */ jsx42("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15392
+ renderedEmptyState?.description ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15393
+ renderedEmptyState?.action
15394
+ ] })
15365
15395
  }
15366
- ),
15367
- inlineDetail
15368
- ] }, id) : /* @__PURE__ */ jsxs32(Fragment6, { children: [
15369
- wrappedRow,
15370
- inlineDetail
15371
- ] }, id);
15372
- }),
15373
- appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsx42("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ jsx42(
15374
- "td",
15375
- {
15376
- colSpan: renderedColSpan,
15377
- 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",
15378
- children: /* @__PURE__ */ jsxs32(
15379
- "div",
15380
- {
15381
- role: "status",
15382
- "aria-live": "polite",
15383
- 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",
15384
- children: [
15385
- /* @__PURE__ */ jsx42(
15386
- Loader2,
15387
- {
15388
- "aria-hidden": "true",
15389
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15396
+ ) }) : bodyEntries.map((entry) => {
15397
+ if (entry.kind === "spacer") {
15398
+ return /* @__PURE__ */ jsx42(
15399
+ "tr",
15400
+ {
15401
+ "aria-hidden": "true",
15402
+ "data-matrx-table-virtual-spacer": entry.place,
15403
+ style: { height: `${entry.height}px` },
15404
+ children: /* @__PURE__ */ jsx42(
15405
+ "td",
15406
+ {
15407
+ className: "border-0 p-0",
15408
+ colSpan: renderedColSpan
15409
+ }
15410
+ )
15411
+ },
15412
+ `matrx-virtual-spacer-${entry.place}`
15413
+ );
15414
+ }
15415
+ if (entry.kind === "group") {
15416
+ return /* @__PURE__ */ jsx42(
15417
+ TableGroupHeaderRow,
15418
+ {
15419
+ group: entry.group,
15420
+ collapsed: entry.collapsed,
15421
+ columns: renderedColumns,
15422
+ spacers: columnSpacers,
15423
+ leadingCols,
15424
+ showActionsCol,
15425
+ rowNoun: grouping?.rowNoun ?? "row",
15426
+ ...grouping?.renderLabel ? { renderLabel: grouping.renderLabel } : {},
15427
+ onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
15428
+ },
15429
+ `matrx-group-${entry.group.key}`
15430
+ );
15431
+ }
15432
+ const row = entry.row;
15433
+ const index = entry.index;
15434
+ const id = getRowId(row);
15435
+ const isSelected = selectedId === id;
15436
+ const rowEdits = edits[id];
15437
+ const displayRow = applyRowEdits(row, rowEdits);
15438
+ const controls = buildRecordControls(displayRow);
15439
+ const isChecked = selectedIdSet.has(getSelectionId(row));
15440
+ const background = rowBackground({
15441
+ tint: rowTintColor(row, id),
15442
+ highlighted: highlightedIndex === index,
15443
+ selected: isSelected,
15444
+ checked: isChecked,
15445
+ zebraOdd: zebra && index % 2 === 1,
15446
+ hoverable: detailEnabled || Boolean(onRowOpen) || canExpandRow(row)
15447
+ });
15448
+ const canHoverFrozenCell = background.frozenHover;
15449
+ const frozenRowBackgroundClass = background.frozen;
15450
+ const rowNode = /* @__PURE__ */ jsxs32(
15451
+ "tr",
15452
+ {
15453
+ "data-row-id": id,
15454
+ style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
15455
+ "data-state": isSelected ? "selected" : void 0,
15456
+ onClick: (e) => {
15457
+ if (rowEventTargetsInteractiveDescendant(e.target)) return;
15458
+ if (opensRowHref(e)) {
15459
+ const href = getRowHref?.(row);
15460
+ if (href) window.open(href, "_blank", "noopener");
15461
+ return;
15390
15462
  }
15463
+ openRow(row);
15464
+ },
15465
+ onAuxClick: (e) => {
15466
+ if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
15467
+ const href = getRowHref?.(row);
15468
+ if (href) {
15469
+ e.preventDefault();
15470
+ window.open(href, "_blank", "noopener");
15471
+ }
15472
+ },
15473
+ className: cn(
15474
+ // Keep the mobile row opaque while the complete row scrolls
15475
+ // horizontally. The translucent zebra/hover tints remain
15476
+ // desktop-only so densely layered mobile content stays crisp.
15477
+ "group/matrx-row border-b border-border/60 bg-card transition-colors",
15478
+ (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer",
15479
+ rowClassName?.(row, index),
15480
+ draggedRowId === id && "opacity-40",
15481
+ // LAST, and decided once: tint, then highlight / open / ticked, then
15482
+ // the zebra stripe (./row-background.ts). Under a tint no stripe is
15483
+ // emitted at all, so no `sm:` class can win the cascade over it.
15484
+ background.row
15391
15485
  ),
15392
- "Loading more rows\u2026"
15393
- ]
15394
- }
15395
- )
15396
- }
15397
- ) }) : null
15398
- ] }),
15399
- footerRows.length > 0 ? /* @__PURE__ */ jsx42("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ jsxs32("tr", { children: [
15400
- selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2" }) : null,
15401
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
15402
- "td",
15403
- {
15404
- "aria-hidden": "true",
15405
- "data-matrx-table-column-spacer": "start",
15406
- className: "border-0 p-0",
15407
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15408
- },
15409
- "matrx-column-spacer-start"
15410
- ) : null,
15411
- renderedColumns.map((column) => /* @__PURE__ */ jsx42(
15412
- "td",
15413
- {
15414
- className: cn(
15415
- "px-2 py-2 align-middle",
15416
- (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
15417
- column.className,
15418
- column.align === "center" && "text-center",
15419
- column.align === "right" && "text-right"
15420
- ),
15421
- style: columnWidthVar(
15422
- resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
15423
- windowedWidthFor(columnId(column), column.width),
15424
- columnId(column),
15425
- resizableColumns && column.resizable !== false
15426
- ),
15427
- children: renderCell(row, column, index)
15428
- },
15429
- columnId(column)
15430
- )),
15431
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
15432
- "td",
15433
- {
15434
- "aria-hidden": "true",
15435
- "data-matrx-table-column-spacer": "end",
15436
- className: "border-0 p-0",
15437
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15438
- },
15439
- "matrx-column-spacer-end"
15440
- ) : null,
15441
- showActionsCol ? /* @__PURE__ */ jsx42("td", {}) : null
15442
- ] }, `footer-${index}`)) }) : null
15443
- ]
15444
- }
15445
- )
15446
- ]
15447
- }
15448
- ),
15449
- scrollHintLeft ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15450
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15451
- Button,
15452
- {
15453
- type: "button",
15454
- variant: "ghost",
15455
- size: "icon",
15456
- "data-matrx-table-icon-action": true,
15457
- "aria-label": "Scroll table left",
15458
- "data-matrx-table-scroll-control": "left",
15459
- 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",
15460
- onClick: () => scrollTableHorizontally("left"),
15461
- children: /* @__PURE__ */ jsx42(ChevronLeft, { className: "h-5 w-5" })
15462
- }
15463
- ) }),
15464
- /* @__PURE__ */ jsx42(TooltipContent, { children: "More columns to the left" })
15465
- ] }) : null,
15466
- scrollHintRight ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15467
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15468
- Button,
15469
- {
15470
- type: "button",
15471
- variant: "ghost",
15472
- size: "icon",
15473
- "data-matrx-table-icon-action": true,
15474
- "aria-label": "Scroll table right",
15475
- "data-matrx-table-scroll-control": "right",
15476
- 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",
15477
- onClick: () => scrollTableHorizontally("right"),
15478
- children: /* @__PURE__ */ jsx42(ChevronRight, { className: "h-5 w-5" })
15479
- }
15480
- ) }),
15481
- /* @__PURE__ */ jsx42(TooltipContent, { children: "More columns to the right" })
15482
- ] }) : null
15483
- ] }),
15484
- appendQuery ? /* @__PURE__ */ jsxs32(
15485
- "div",
15486
- {
15487
- "data-matrx-table-footer": true,
15488
- className: "flex min-h-8 shrink-0 flex-wrap items-center gap-2 border-t border-border",
15489
- "aria-busy": isLoading || isFetching,
15490
- children: [
15491
- /* @__PURE__ */ jsxs32(
15492
- "div",
15493
- {
15494
- ...isFetching && !isLoading ? {} : { role: "status", "aria-live": "polite" },
15495
- className: "flex w-full min-w-0 items-center gap-2 text-xs text-muted-foreground sm:w-auto sm:flex-1",
15496
- children: [
15497
- isLoading ? /* @__PURE__ */ jsx42(
15498
- Loader2,
15499
- {
15500
- "data-matrx-table-append-loader": true,
15501
- "aria-hidden": "true",
15502
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15486
+ children: [
15487
+ selection ? /* @__PURE__ */ jsx42(
15488
+ "td",
15489
+ {
15490
+ className: cn(
15491
+ "px-2 py-1.5 align-middle lg:py-0.5",
15492
+ // When a data column is frozen the checkbox
15493
+ // column has to freeze with it, or the first
15494
+ // frozen column slides over the checkboxes.
15495
+ frozenColumns.selectionOffset !== null && cn(
15496
+ "sm:sticky sm:left-0 sm:z-20",
15497
+ frozenRowBackgroundClass,
15498
+ canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
15499
+ )
15500
+ ),
15501
+ style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15502
+ onClick: (e) => e.stopPropagation(),
15503
+ children: selection.isRowSelectable?.(row) ?? true ? /* @__PURE__ */ jsx42(
15504
+ "div",
15505
+ {
15506
+ "data-matrx-table-selection-target": true,
15507
+ onClick: (event) => {
15508
+ if (event.target === event.currentTarget) {
15509
+ event.currentTarget.querySelector('[role="checkbox"]')?.click();
15510
+ }
15511
+ },
15512
+ children: /* @__PURE__ */ jsx42(
15513
+ Checkbox,
15514
+ {
15515
+ className: CHECKBOX_TAP_AREA,
15516
+ checked: isChecked,
15517
+ "aria-label": `Select this ${selectionNoun}`,
15518
+ onClick: (e) => toggleRowSelected(index, e.shiftKey)
15519
+ }
15520
+ )
15521
+ }
15522
+ ) : null
15523
+ }
15524
+ ) : null,
15525
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
15526
+ "td",
15527
+ {
15528
+ "aria-hidden": "true",
15529
+ "data-matrx-table-column-spacer": "start",
15530
+ className: "border-0 p-0",
15531
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15532
+ },
15533
+ "matrx-column-spacer-start"
15534
+ ) : null,
15535
+ renderedColumns.map((col, colIdx) => {
15536
+ const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
15537
+ const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
15538
+ const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
15539
+ const frozen = frozenColumns.offsets[columnId(col)] !== void 0;
15540
+ const frozenCellTint = cellTintColor(
15541
+ row,
15542
+ id,
15543
+ columnId(col)
15544
+ );
15545
+ return /* @__PURE__ */ jsxs32(
15546
+ "td",
15547
+ {
15548
+ "data-matrx-table-column-id": columnId(col),
15549
+ "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15550
+ ...sheetCell ? {
15551
+ "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
15552
+ "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
15553
+ ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
15554
+ ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
15555
+ ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
15556
+ onMouseDown: sheetCell.onMouseDown,
15557
+ onMouseEnter: sheetCell.onMouseEnter
15558
+ } : {},
15559
+ className: cn(
15560
+ "py-1.5 align-middle lg:py-0.5",
15561
+ sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
15562
+ sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
15563
+ sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
15564
+ carriesFillHandle && "relative",
15565
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15566
+ // nowrap (NOT truncate — truncate clips the cell and
15567
+ // defeats w-max, killing the horizontal scroll).
15568
+ "max-sm:whitespace-nowrap",
15569
+ col.mobileHidden && "max-sm:hidden",
15570
+ ((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)]",
15571
+ col.className,
15572
+ isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15573
+ alignForFormat(col) === "center" && "text-center",
15574
+ alignForFormat(col) === "right" && "text-right",
15575
+ frozen && FROZEN_CELL_CLASS,
15576
+ frozen && frozenRowBackgroundClass,
15577
+ frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
15578
+ frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
15579
+ frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
15580
+ // A cell tint paints OVER the row tint beneath
15581
+ // it, and the consumer's own hook comes last so
15582
+ // it can always win.
15583
+ cellTintClass(row, id, columnId(col)),
15584
+ frozen && frozenCellTint && FROZEN_CELL_TINT_CLASS[frozenCellTint],
15585
+ cellClassName?.(row, columnId(col), index)
15586
+ ),
15587
+ style: {
15588
+ ...columnWidthVar(
15589
+ resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
15590
+ windowedWidthFor(columnId(col), col.width),
15591
+ columnId(col),
15592
+ resizableColumns && col.resizable !== false
15593
+ ),
15594
+ ...frozenStyle(columnId(col))
15595
+ },
15596
+ children: [
15597
+ /* @__PURE__ */ jsxs32(
15598
+ "div",
15599
+ {
15600
+ className: cn(
15601
+ colIdx === 0 && (hierarchy || canExpandRow(row)) && "flex min-w-0 items-center"
15602
+ ),
15603
+ children: [
15604
+ colIdx === 0 && hierarchy ? /* @__PURE__ */ jsx42(
15605
+ HierarchyDragHandle,
15606
+ {
15607
+ id,
15608
+ label: hierarchy.itemLabel?.(row) ?? "row",
15609
+ disabled: !(hierarchy.canReparent?.(row) ?? true)
15610
+ }
15611
+ ) : null,
15612
+ colIdx === 0 && canExpandRow(row) ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15613
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15614
+ Button,
15615
+ {
15616
+ type: "button",
15617
+ variant: "ghost",
15618
+ size: "icon",
15619
+ "data-matrx-table-expand-toggle": true,
15620
+ className: "mr-1 h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground [&_svg]:size-4",
15621
+ "aria-label": isRowExpanded(row) ? "Collapse row details" : "Expand row details",
15622
+ "aria-expanded": isRowExpanded(row),
15623
+ onClick: (event) => {
15624
+ event.stopPropagation();
15625
+ toggleExpanded(row);
15626
+ },
15627
+ onKeyDown: (event) => {
15628
+ if (event.key !== "Enter" && event.key !== " ") return;
15629
+ event.preventDefault();
15630
+ event.stopPropagation();
15631
+ toggleExpanded(row);
15632
+ },
15633
+ children: /* @__PURE__ */ jsx42(
15634
+ ChevronRight,
15635
+ {
15636
+ className: cn(
15637
+ "transition-transform",
15638
+ isRowExpanded(row) && "rotate-90"
15639
+ )
15640
+ }
15641
+ )
15642
+ }
15643
+ ) }),
15644
+ /* @__PURE__ */ jsx42(TooltipContent, { children: isRowExpanded(row) ? "Collapse details" : "Expand details" })
15645
+ ] }) : null,
15646
+ /* @__PURE__ */ jsx42(
15647
+ "div",
15648
+ {
15649
+ "data-matrx-cell-content": true,
15650
+ 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",
15651
+ children: renderRecordCell(row, col, index)
15652
+ }
15653
+ )
15654
+ ]
15655
+ }
15656
+ ),
15657
+ carriesFillHandle ? /* @__PURE__ */ jsx42(
15658
+ "button",
15659
+ {
15660
+ type: "button",
15661
+ "data-matrx-table-fill-handle": true,
15662
+ "aria-label": "Drag to fill the cells below or beside this selection",
15663
+ onMouseDown: sheet.startFillDrag,
15664
+ className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
15665
+ }
15666
+ ) : null
15667
+ ]
15668
+ },
15669
+ columnId(col)
15670
+ );
15671
+ }),
15672
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
15673
+ "td",
15674
+ {
15675
+ "aria-hidden": "true",
15676
+ "data-matrx-table-column-spacer": "end",
15677
+ className: "border-0 p-0",
15678
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15679
+ },
15680
+ "matrx-column-spacer-end"
15681
+ ) : null,
15682
+ showActionsCol && /* @__PURE__ */ jsx42(
15683
+ "td",
15684
+ {
15685
+ "data-matrx-table-row-action-cell": true,
15686
+ className: "text-right align-middle",
15687
+ style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15688
+ children: /* @__PURE__ */ jsxs32(
15689
+ "div",
15690
+ {
15691
+ "data-matrx-table-row-actions": true,
15692
+ className: "inline-flex items-center justify-end gap-0",
15693
+ onClick: (e) => e.stopPropagation(),
15694
+ children: [
15695
+ showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ jsx42(
15696
+ TableRowCopyControl,
15697
+ {
15698
+ size: "toolbar",
15699
+ copy: effectiveCopy,
15700
+ row: displayRow,
15701
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15702
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15703
+ }
15704
+ ) : null,
15705
+ renderRowActions(displayRow, controls),
15706
+ windowEnabled ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15707
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15708
+ Button,
15709
+ {
15710
+ type: "button",
15711
+ variant: "ghost",
15712
+ size: "icon",
15713
+ "data-matrx-table-icon-action": true,
15714
+ className: "h-11 w-11 text-muted-foreground hover:text-foreground [&_svg]:size-5",
15715
+ "aria-label": windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window",
15716
+ onClick: (e) => {
15717
+ e.stopPropagation();
15718
+ if (windowConfig?.openOnRowClick === true) openDetail(row);
15719
+ else openWindow(row);
15720
+ },
15721
+ children: opensWindowOnRowClick ? /* @__PURE__ */ jsx42(PanelRightOpen, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx42(PanelRight, { className: "h-5 w-5" })
15722
+ }
15723
+ ) }),
15724
+ /* @__PURE__ */ jsx42(TooltipContent, { children: windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window" })
15725
+ ] }) : null
15726
+ ]
15727
+ }
15728
+ )
15729
+ }
15730
+ )
15731
+ ]
15732
+ },
15733
+ id
15734
+ );
15735
+ const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
15736
+ const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ jsx42(
15737
+ "tr",
15738
+ {
15739
+ "data-matrx-table-expanded-detail": id,
15740
+ className: cn("bg-muted/20", expandedDetail.className),
15741
+ children: /* @__PURE__ */ jsx42(
15742
+ "td",
15743
+ {
15744
+ colSpan: renderedColSpan,
15745
+ className: "p-4",
15746
+ children: expandedDetail.render(displayRow, controls)
15747
+ }
15748
+ )
15749
+ },
15750
+ `${id}:expanded-detail`
15751
+ ) : null;
15752
+ return hierarchy ? /* @__PURE__ */ jsxs32(Fragment6, { children: [
15753
+ /* @__PURE__ */ jsx42(
15754
+ HierarchyDroppableRow,
15755
+ {
15756
+ id,
15757
+ disabled: !canDropOn(row),
15758
+ children: wrappedRow
15759
+ }
15760
+ ),
15761
+ inlineDetail
15762
+ ] }, id) : /* @__PURE__ */ jsxs32(Fragment6, { children: [
15763
+ wrappedRow,
15764
+ inlineDetail
15765
+ ] }, id);
15766
+ }),
15767
+ appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsx42("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ jsx42(
15768
+ "td",
15769
+ {
15770
+ colSpan: renderedColSpan,
15771
+ 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",
15772
+ children: /* @__PURE__ */ jsxs32(
15773
+ "div",
15774
+ {
15775
+ role: "status",
15776
+ "aria-live": "polite",
15777
+ 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",
15778
+ children: [
15779
+ /* @__PURE__ */ jsx42(
15780
+ Loader2,
15781
+ {
15782
+ "aria-hidden": "true",
15783
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15784
+ }
15785
+ ),
15786
+ "Loading more rows\u2026"
15787
+ ]
15788
+ }
15789
+ )
15790
+ }
15791
+ ) }) : null
15792
+ ] }),
15793
+ footerRows.length > 0 ? /* @__PURE__ */ jsx42("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ jsxs32("tr", { children: [
15794
+ selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2" }) : null,
15795
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
15796
+ "td",
15797
+ {
15798
+ "aria-hidden": "true",
15799
+ "data-matrx-table-column-spacer": "start",
15800
+ className: "border-0 p-0",
15801
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15802
+ },
15803
+ "matrx-column-spacer-start"
15804
+ ) : null,
15805
+ renderedColumns.map((column) => /* @__PURE__ */ jsx42(
15806
+ "td",
15807
+ {
15808
+ className: cn(
15809
+ "px-2 py-2 align-middle",
15810
+ (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
15811
+ column.className,
15812
+ column.align === "center" && "text-center",
15813
+ column.align === "right" && "text-right"
15814
+ ),
15815
+ style: columnWidthVar(
15816
+ resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
15817
+ windowedWidthFor(columnId(column), column.width),
15818
+ columnId(column),
15819
+ resizableColumns && column.resizable !== false
15820
+ ),
15821
+ children: renderCell(row, column, index)
15822
+ },
15823
+ columnId(column)
15824
+ )),
15825
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
15826
+ "td",
15827
+ {
15828
+ "aria-hidden": "true",
15829
+ "data-matrx-table-column-spacer": "end",
15830
+ className: "border-0 p-0",
15831
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15832
+ },
15833
+ "matrx-column-spacer-end"
15834
+ ) : null,
15835
+ showActionsCol ? /* @__PURE__ */ jsx42("td", {}) : null
15836
+ ] }, `footer-${index}`)) }) : null
15837
+ ]
15503
15838
  }
15504
- ) : null,
15505
- /* @__PURE__ */ jsxs32("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2", children: [
15506
- /* @__PURE__ */ jsx42("p", { className: "min-w-0 break-words font-medium text-foreground", children: isLoading ? "Loading rows\u2026" : appendQuery.pagination.sourceRecords ? `${formatCount3(processed.length)} ${appendQuery.pagination.sourceRecords.rowLabel}${appendQuery.pagination.sourceRecords.matchedRecords === void 0 ? "" : ` (${formatCount3(appendQuery.pagination.sourceRecords.matchedRecords)} matching ${appendQuery.pagination.sourceRecords.label})`} \xB7 ${formatCount3(appendQuery.pagination.sourceRecords.loaded)}${appendQuery.pagination.sourceRecords.total === void 0 ? "" : ` / ${formatCount3(appendQuery.pagination.sourceRecords.total)}`} ${appendQuery.pagination.sourceRecords.label} loaded` : `${formatCount3(data.length)} loaded${appendQuery.pagination.totalItems === void 0 ? "" : ` of ${formatCount3(appendQuery.pagination.totalItems)}`}` }),
15507
- appendQuery.pagination.sourceUntil || appendUsesLocalProcessing ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15508
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42("button", { type: "button", "aria-label": "Source coverage", className: "shrink-0 text-xs text-muted-foreground underline decoration-dotted underline-offset-4", children: "Source info" }) }),
15509
- /* @__PURE__ */ jsxs32(TooltipContent, { children: [
15510
- appendQuery.pagination.sourceUntil ? /* @__PURE__ */ jsxs32("p", { children: [
15511
- "Source fixed through ",
15512
- new Date(appendQuery.pagination.sourceUntil).toLocaleString(),
15513
- "."
15514
- ] }) : null,
15515
- /* @__PURE__ */ jsx42("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." })
15516
- ] })
15517
- ] }) : null,
15518
- scrollPagination.error ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "text-destructive", children: scrollPagination.error.message }) : null,
15519
- appendScrollPolicy?.mode === "suspended" ? /* @__PURE__ */ jsxs32("p", { role: "status", children: [
15520
- appendScrollPolicy.reason,
15521
- " Load more remains available."
15522
- ] }) : null
15523
- ] })
15839
+ )
15524
15840
  ]
15525
15841
  }
15526
15842
  ),
15527
- appendQuery.pagination.sourcePageSize ? /* @__PURE__ */ jsxs32(
15528
- Select,
15529
- {
15530
- value: String(appendQuery.pagination.sourcePageSize.value),
15531
- onValueChange: (value) => appendQuery.pagination.sourcePageSize?.onChange(Number(value)),
15532
- children: [
15533
- /* @__PURE__ */ jsx42(
15534
- SelectTrigger,
15535
- {
15536
- "aria-label": "Rows per load",
15537
- className: "h-8 min-w-20 shrink-0 rounded-md border border-border bg-card px-3 text-sm [&_svg]:size-5",
15538
- disabled: isLoading || isFetching || appendQuery.pagination.loadAll?.isLoading,
15539
- children: /* @__PURE__ */ jsx42(SelectValue, {})
15540
- }
15541
- ),
15542
- /* @__PURE__ */ jsx42(SelectContent, { children: appendQuery.pagination.sourcePageSize.options.map((size) => /* @__PURE__ */ jsx42(SelectItem, { value: String(size), children: size }, size)) })
15543
- ]
15544
- }
15545
- ) : null,
15546
- appendQuery.pagination.loadAll?.isLoading ? /* @__PURE__ */ jsx42(
15547
- Button,
15548
- {
15549
- type: "button",
15550
- variant: "outline",
15551
- className: "h-8 shrink-0 px-3 text-sm",
15552
- onClick: appendQuery.pagination.loadAll.stop,
15553
- children: "Stop loading"
15554
- }
15555
- ) : scrollPagination.error ? /* @__PURE__ */ jsx42(
15556
- Button,
15557
- {
15558
- type: "button",
15559
- variant: "outline",
15560
- className: "h-8 shrink-0 px-3 text-sm",
15561
- onClick: appendQuery.pagination.retrySource ?? (appendQuery.pagination.hasNextPage ? appendQuery.pagination.loadNextPage : appendQuery.pagination.refresh),
15562
- children: appendQuery.pagination.retrySource ? "Retry source" : appendQuery.pagination.hasNextPage ? "Retry" : "Reload rows"
15563
- }
15564
- ) : appendQuery.pagination.hasNextPage || scrollPagination.error ? /* @__PURE__ */ jsxs32("div", { className: "flex min-w-0 max-w-full flex-wrap gap-0", children: [
15565
- /* @__PURE__ */ jsx42(
15843
+ scrollHintLeft ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15844
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15566
15845
  Button,
15567
15846
  {
15568
15847
  type: "button",
15569
- variant: "outline",
15570
- className: "h-8 px-3 text-sm",
15571
- disabled: isLoading || isFetching,
15572
- onClick: () => void scrollPagination.requestNextPage(),
15573
- children: "Load more"
15848
+ variant: "ghost",
15849
+ size: "icon",
15850
+ "data-matrx-table-icon-action": true,
15851
+ "aria-label": "Scroll table left",
15852
+ "data-matrx-table-scroll-control": "left",
15853
+ 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",
15854
+ onClick: () => scrollTableHorizontally("left"),
15855
+ children: /* @__PURE__ */ jsx42(ChevronLeft, { className: "h-5 w-5" })
15574
15856
  }
15575
- ),
15576
- appendQuery.pagination.loadAll ? /* @__PURE__ */ jsx42(
15857
+ ) }),
15858
+ /* @__PURE__ */ jsx42(TooltipContent, { children: "More columns to the left" })
15859
+ ] }) : null,
15860
+ scrollHintRight ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15861
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15577
15862
  Button,
15578
15863
  {
15579
15864
  type: "button",
15580
15865
  variant: "ghost",
15581
- className: "h-8 px-3 text-sm",
15582
- disabled: isLoading || isFetching,
15583
- onClick: appendQuery.pagination.loadAll.request,
15584
- children: "Load all"
15585
- }
15586
- ) : null
15587
- ] }) : !isLoading && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? /* @__PURE__ */ jsx42("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
15588
- ]
15589
- }
15590
- ) : !hidePagination && (totalItems > 0 || sourceTotal !== void 0) ? /* @__PURE__ */ jsx42("div", { className: "shrink-0", children: /* @__PURE__ */ jsx42(
15591
- GenericTablePagination,
15592
- {
15593
- totalItems,
15594
- ...paginationLabelFormat ? { labelFormat: paginationLabelFormat } : sourceTotal === void 0 ? {} : { labelFormat: (start, end, total) => `${formatCount3(start)}\u2013${formatCount3(end)} of ${formatCount3(total)} matching \xB7 ${formatCount3(data.length)} / ${formatCount3(sourceTotal)} loaded` },
15595
- ...localProgressiveEnabled ? { progressive: {
15596
- shownCount: progressiveRows.shownCount,
15597
- loadedCount: data.length,
15598
- hasMore: progressiveRows.hasMore,
15599
- onShowMore: () => void progressiveRows.showMore()
15600
- } } : {},
15601
- itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
15602
- currentPage: safePage,
15603
- onPageChange: setPage,
15604
- onItemsPerPageChange: (n2) => {
15605
- setPageSize(n2);
15606
- if (!controlledQuery) setPage(1);
15607
- },
15608
- pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
15609
- showAllOption: allRowsAreLoaded,
15610
- allValue: 0,
15611
- allOptionLabel: "All loaded",
15612
- compact: true,
15613
- hideEntriesInfo: false
15614
- }
15615
- ) }) : null,
15616
- selection && selectionCount > 0 ? /* @__PURE__ */ jsxs32(
15617
- "div",
15618
- {
15619
- "data-matrx-table-bulk-bar": true,
15620
- 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",
15621
- children: [
15622
- /* @__PURE__ */ jsxs32("span", { className: "text-xs font-medium text-foreground", children: [
15623
- formatCount3(selectionCount),
15624
- " ",
15625
- selectionNoun,
15626
- selectionCount === 1 ? "" : "s",
15627
- " selected"
15628
- ] }),
15629
- /* @__PURE__ */ jsxs32(
15630
- Button,
15631
- {
15632
- type: "button",
15633
- variant: "ghost",
15634
- size: "sm",
15635
- className: "h-7 gap-1 px-2 text-xs",
15636
- onClick: () => {
15637
- lastToggledIndex.current = null;
15638
- selection.onSelectedIdsChange([]);
15639
- },
15640
- children: [
15641
- /* @__PURE__ */ jsx42(X, { className: "h-3.5 w-3.5" }),
15642
- "Clear"
15643
- ]
15644
- }
15645
- ),
15646
- /* @__PURE__ */ jsxs32("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
15647
- effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ jsx42(
15648
- CopyButtons,
15649
- {
15650
15866
  size: "icon",
15651
15867
  "data-matrx-table-icon-action": true,
15652
- sourceId: `${tableCopySourceNamespace}:selection`,
15653
- ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
15654
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
15655
- label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
15656
- ...copy === void 0 ? {} : {
15657
- human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
15658
- json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
15659
- },
15660
- agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
15661
- aiVariants: copySubsetVariantFor(
15662
- () => selectedRows,
15663
- "selected"
15664
- ),
15665
- export: {
15666
- items: [],
15667
- sheetRows: () => selectedRows.map(
15668
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
15669
- ),
15670
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
15671
- id: columnId(column),
15672
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
15673
- }))
15674
- }
15868
+ "aria-label": "Scroll table right",
15869
+ "data-matrx-table-scroll-control": "right",
15870
+ 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",
15871
+ onClick: () => scrollTableHorizontally("right"),
15872
+ children: /* @__PURE__ */ jsx42(ChevronRight, { className: "h-5 w-5" })
15675
15873
  }
15676
- ) : null,
15677
- selection.actions?.(selectedRows, [...selectedIdSet])
15678
- ] })
15679
- ]
15680
- }
15681
- ) : null
15682
- ] }),
15683
- /* @__PURE__ */ jsx42(
15684
- ColumnManager,
15685
- {
15686
- open: columnManagerOpen,
15687
- onOpenChange: setColumnManagerOpen,
15688
- columns,
15689
- order: effectiveColumnOrder,
15690
- hidden: effectiveHiddenColumnIds,
15691
- onApply: commitColumnState
15692
- }
15693
- ),
15694
- detailEnabled && selectedRow ? /* @__PURE__ */ jsx42(
15695
- SidePanelSurface,
15696
- {
15697
- title: resolveStringTitle(
15698
- detail?.title?.(selectedRow),
15699
- defaultRowTitle(selectedRow, columns)
15700
- ),
15701
- titleNode: detail?.title?.(selectedRow),
15702
- description: detail?.description?.(selectedRow),
15703
- onClose: () => setSelectedId(null),
15704
- defaultWidth: detail?.defaultWidth ?? 480,
15705
- headerActions: /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-1", children: [
15706
- effectiveCopy && showRowCopy ? /* @__PURE__ */ jsx42(
15707
- TableRowCopyControl,
15874
+ ) }),
15875
+ /* @__PURE__ */ jsx42(TooltipContent, { children: "More columns to the right" })
15876
+ ] }) : null
15877
+ ] }),
15878
+ appendQuery ? /* @__PURE__ */ jsxs32(
15879
+ "div",
15880
+ {
15881
+ "data-matrx-table-footer": true,
15882
+ className: "flex min-h-8 shrink-0 flex-wrap items-center gap-2 border-t border-border",
15883
+ "aria-busy": isLoading || isFetching,
15884
+ children: [
15885
+ /* @__PURE__ */ jsxs32(
15886
+ "div",
15887
+ {
15888
+ ...isFetching && !isLoading ? {} : { role: "status", "aria-live": "polite" },
15889
+ className: "flex w-full min-w-0 items-center gap-2 text-xs text-muted-foreground sm:w-auto sm:flex-1",
15890
+ children: [
15891
+ isLoading ? /* @__PURE__ */ jsx42(
15892
+ Loader2,
15893
+ {
15894
+ "data-matrx-table-append-loader": true,
15895
+ "aria-hidden": "true",
15896
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15897
+ }
15898
+ ) : null,
15899
+ /* @__PURE__ */ jsxs32("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2", children: [
15900
+ /* @__PURE__ */ jsx42("p", { className: "min-w-0 break-words font-medium text-foreground", children: isLoading ? "Loading rows\u2026" : appendQuery.pagination.sourceRecords ? `${formatCount3(processed.length)} ${appendQuery.pagination.sourceRecords.rowLabel}${appendQuery.pagination.sourceRecords.matchedRecords === void 0 ? "" : ` (${formatCount3(appendQuery.pagination.sourceRecords.matchedRecords)} matching ${appendQuery.pagination.sourceRecords.label})`} \xB7 ${formatCount3(appendQuery.pagination.sourceRecords.loaded)}${appendQuery.pagination.sourceRecords.total === void 0 ? "" : ` / ${formatCount3(appendQuery.pagination.sourceRecords.total)}`} ${appendQuery.pagination.sourceRecords.label} loaded` : `${formatCount3(data.length)} loaded${appendQuery.pagination.totalItems === void 0 ? "" : ` of ${formatCount3(appendQuery.pagination.totalItems)}`}` }),
15901
+ appendQuery.pagination.sourceUntil || appendUsesLocalProcessing ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15902
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42("button", { type: "button", "aria-label": "Source coverage", className: "shrink-0 text-xs text-muted-foreground underline decoration-dotted underline-offset-4", children: "Source info" }) }),
15903
+ /* @__PURE__ */ jsxs32(TooltipContent, { children: [
15904
+ appendQuery.pagination.sourceUntil ? /* @__PURE__ */ jsxs32("p", { children: [
15905
+ "Source fixed through ",
15906
+ new Date(appendQuery.pagination.sourceUntil).toLocaleString(),
15907
+ "."
15908
+ ] }) : null,
15909
+ /* @__PURE__ */ jsx42("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." })
15910
+ ] })
15911
+ ] }) : null,
15912
+ scrollPagination.error ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "text-destructive", children: scrollPagination.error.message }) : null,
15913
+ appendScrollPolicy?.mode === "suspended" ? /* @__PURE__ */ jsxs32("p", { role: "status", children: [
15914
+ appendScrollPolicy.reason,
15915
+ " Load more remains available."
15916
+ ] }) : null
15917
+ ] })
15918
+ ]
15919
+ }
15920
+ ),
15921
+ appendQuery.pagination.sourcePageSize ? /* @__PURE__ */ jsxs32(
15922
+ Select,
15923
+ {
15924
+ value: String(appendQuery.pagination.sourcePageSize.value),
15925
+ onValueChange: (value) => appendQuery.pagination.sourcePageSize?.onChange(Number(value)),
15926
+ children: [
15927
+ /* @__PURE__ */ jsx42(
15928
+ SelectTrigger,
15929
+ {
15930
+ "aria-label": "Rows per load",
15931
+ className: "h-8 min-w-20 shrink-0 rounded-md border border-border bg-card px-3 text-sm [&_svg]:size-5",
15932
+ disabled: isLoading || isFetching || appendQuery.pagination.loadAll?.isLoading,
15933
+ children: /* @__PURE__ */ jsx42(SelectValue, {})
15934
+ }
15935
+ ),
15936
+ /* @__PURE__ */ jsx42(SelectContent, { children: appendQuery.pagination.sourcePageSize.options.map((size) => /* @__PURE__ */ jsx42(SelectItem, { value: String(size), children: size }, size)) })
15937
+ ]
15938
+ }
15939
+ ) : null,
15940
+ appendQuery.pagination.loadAll?.isLoading ? /* @__PURE__ */ jsx42(
15941
+ Button,
15942
+ {
15943
+ type: "button",
15944
+ variant: "outline",
15945
+ className: "h-8 shrink-0 px-3 text-sm",
15946
+ onClick: appendQuery.pagination.loadAll.stop,
15947
+ children: "Stop loading"
15948
+ }
15949
+ ) : scrollPagination.error ? /* @__PURE__ */ jsx42(
15950
+ Button,
15951
+ {
15952
+ type: "button",
15953
+ variant: "outline",
15954
+ className: "h-8 shrink-0 px-3 text-sm",
15955
+ onClick: appendQuery.pagination.retrySource ?? (appendQuery.pagination.hasNextPage ? appendQuery.pagination.loadNextPage : appendQuery.pagination.refresh),
15956
+ children: appendQuery.pagination.retrySource ? "Retry source" : appendQuery.pagination.hasNextPage ? "Retry" : "Reload rows"
15957
+ }
15958
+ ) : appendQuery.pagination.hasNextPage || scrollPagination.error ? /* @__PURE__ */ jsxs32("div", { className: "flex min-w-0 max-w-full flex-wrap gap-0", children: [
15959
+ /* @__PURE__ */ jsx42(
15960
+ Button,
15961
+ {
15962
+ type: "button",
15963
+ variant: "outline",
15964
+ className: "h-8 px-3 text-sm",
15965
+ disabled: isLoading || isFetching,
15966
+ onClick: () => void scrollPagination.requestNextPage(),
15967
+ children: "Load more"
15968
+ }
15969
+ ),
15970
+ appendQuery.pagination.loadAll ? /* @__PURE__ */ jsx42(
15971
+ Button,
15972
+ {
15973
+ type: "button",
15974
+ variant: "ghost",
15975
+ className: "h-8 px-3 text-sm",
15976
+ disabled: isLoading || isFetching,
15977
+ onClick: appendQuery.pagination.loadAll.request,
15978
+ children: "Load all"
15979
+ }
15980
+ ) : null
15981
+ ] }) : !isLoading && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? /* @__PURE__ */ jsx42("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
15982
+ ]
15983
+ }
15984
+ ) : !hidePagination && (totalItems > 0 || sourceTotal !== void 0) ? /* @__PURE__ */ jsx42("div", { className: "shrink-0", children: /* @__PURE__ */ jsx42(
15985
+ GenericTablePagination,
15708
15986
  {
15709
- size: "icon",
15710
- "data-matrx-table-icon-action": true,
15711
- copy: effectiveCopy,
15712
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(selectedRow)}`,
15713
- row: selectedRow,
15714
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15987
+ totalItems,
15988
+ ...paginationLabelFormat ? { labelFormat: paginationLabelFormat } : sourceTotal === void 0 ? {} : { labelFormat: (start, end, total) => `${formatCount3(start)}\u2013${formatCount3(end)} of ${formatCount3(total)} matching \xB7 ${formatCount3(data.length)} / ${formatCount3(sourceTotal)} loaded` },
15989
+ ...localProgressiveEnabled ? { progressive: {
15990
+ shownCount: progressiveRows.shownCount,
15991
+ loadedCount: data.length,
15992
+ hasMore: progressiveRows.hasMore,
15993
+ onShowMore: () => void progressiveRows.showMore()
15994
+ } } : {},
15995
+ itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
15996
+ currentPage: safePage,
15997
+ onPageChange: setPage,
15998
+ onItemsPerPageChange: (n2) => {
15999
+ setPageSize(n2);
16000
+ if (!controlledQuery) setPage(1);
16001
+ },
16002
+ pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
16003
+ showAllOption: allRowsAreLoaded,
16004
+ allValue: 0,
16005
+ allOptionLabel: "All loaded",
16006
+ compact: true,
16007
+ hideEntriesInfo: false
15715
16008
  }
15716
- ) : null,
15717
- detail?.headerActions?.(selectedRow),
15718
- windowEnabled ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15719
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15720
- Button,
15721
- {
15722
- type: "button",
15723
- variant: "ghost",
15724
- size: "icon",
15725
- "data-matrx-table-icon-action": true,
15726
- className: "h-7 w-7",
15727
- "aria-label": "Open in window",
15728
- onClick: () => openWindow(selectedRow),
15729
- children: /* @__PURE__ */ jsx42(PanelRight, { className: "h-5 w-5" })
15730
- }
15731
- ) }),
15732
- /* @__PURE__ */ jsx42(TooltipContent, { children: "Open in window" })
15733
- ] }) : null
15734
- ] }),
15735
- children: detail?.render ? (
15736
- // The panel hands children a `min-h-0 flex-1 overflow-hidden` cell
15737
- // and expects the child to own scrolling. `DataRowInspector` does;
15738
- // custom `detail.render` bodies mostly did not, so their content
15739
- // silently cut off at the fold with no scrollbar. Owning it here
15740
- // fixes the whole class at once — a custom body that already
15741
- // scrolls (`h-full` + `overflow-y-auto` root) still resolves to
15742
- // this container's height, so no second scrollbar appears.
15743
- /* @__PURE__ */ jsx42("div", { className: "h-full min-h-0 overflow-y-auto", children: detail.render(selectedRow, {
15744
- closeDetail: () => setSelectedId(null),
15745
- openDetail: () => openDetail(selectedRow),
15746
- openWindow: () => openWindow(selectedRow),
15747
- closeWindow,
15748
- hasPendingEdits: Boolean(edits[getRowId(selectedRow)]),
15749
- discardPendingEdits: () => discardRowEdits(getRowId(selectedRow))
15750
- }) })
15751
- ) : /* @__PURE__ */ jsx42(
15752
- DataRowInspector,
15753
- {
15754
- row: selectedRow,
15755
- ...effectiveCopy?.rowKind === void 0 ? {} : { recordKind: effectiveCopy?.rowKind },
15756
- ...effectiveCopy?.label === void 0 ? {} : { recordLabel: effectiveCopy?.label },
15757
- ...effectiveCopy?.location === void 0 ? {} : { location: effectiveCopy?.location },
15758
- ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
15759
- }
15760
- )
15761
- }
15762
- ) : null,
15763
- windowRow ? /* @__PURE__ */ jsx42(
15764
- DataRowWindow,
15765
- {
15766
- isOpen: true,
15767
- onClose: closeWindow,
15768
- title: windowConfig?.title?.(windowRow) ?? defaultRowTitle(windowRow, columns),
15769
- row: windowRow,
15770
- width: windowConfig?.width,
15771
- height: windowConfig?.height,
15772
- windowId: `matrx-data-row-${urlState?.id ?? "local"}-${getRowId(windowRow)}`,
15773
- defaultTab: windowConfig?.defaultTab,
15774
- headerActions: /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-1", children: [
15775
- windowConfig?.openOnRowClick === true && detail?.enabled === true ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
15776
- /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
15777
- Button,
15778
- {
15779
- type: "button",
15780
- variant: "ghost",
15781
- size: "icon",
15782
- "data-matrx-table-icon-action": true,
15783
- className: "h-7 w-7",
15784
- "aria-label": "Open in side panel",
15785
- onClick: () => {
15786
- closeWindow();
15787
- openDetail(windowRow);
15788
- },
15789
- children: /* @__PURE__ */ jsx42(PanelRightOpen, { className: "h-5 w-5" })
15790
- }
15791
- ) }),
15792
- /* @__PURE__ */ jsx42(TooltipContent, { children: "Open in side panel" })
15793
- ] }) : null,
15794
- effectiveCopy && showRowCopy ? /* @__PURE__ */ jsx42(
15795
- TableRowCopyControl,
16009
+ ) }) : null,
16010
+ selection && selectionCount > 0 ? /* @__PURE__ */ jsxs32(
16011
+ "div",
15796
16012
  {
15797
- size: "icon",
15798
- "data-matrx-table-icon-action": true,
15799
- copy: effectiveCopy,
15800
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(windowRow)}`,
15801
- row: windowRow,
15802
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16013
+ "data-matrx-table-bulk-bar": true,
16014
+ 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",
16015
+ children: [
16016
+ /* @__PURE__ */ jsxs32("span", { className: "text-xs font-medium text-foreground", children: [
16017
+ formatCount3(selectionCount),
16018
+ " ",
16019
+ selectionNoun,
16020
+ selectionCount === 1 ? "" : "s",
16021
+ " selected"
16022
+ ] }),
16023
+ /* @__PURE__ */ jsxs32(
16024
+ Button,
16025
+ {
16026
+ type: "button",
16027
+ variant: "ghost",
16028
+ size: "sm",
16029
+ className: "h-7 gap-1 px-2 text-xs",
16030
+ onClick: () => {
16031
+ lastToggledIndex.current = null;
16032
+ selection.onSelectedIdsChange([]);
16033
+ },
16034
+ children: [
16035
+ /* @__PURE__ */ jsx42(X, { className: "h-3.5 w-3.5" }),
16036
+ "Clear"
16037
+ ]
16038
+ }
16039
+ ),
16040
+ /* @__PURE__ */ jsxs32("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
16041
+ effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ jsx42(
16042
+ CopyButtons,
16043
+ {
16044
+ size: "icon",
16045
+ "data-matrx-table-icon-action": true,
16046
+ sourceId: `${tableCopySourceNamespace}:selection`,
16047
+ ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
16048
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
16049
+ label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
16050
+ ...copy === void 0 ? {} : {
16051
+ human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
16052
+ json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
16053
+ },
16054
+ agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
16055
+ aiVariants: copySubsetVariantFor(
16056
+ () => selectedRows,
16057
+ "selected"
16058
+ ),
16059
+ export: {
16060
+ items: [],
16061
+ sheetRows: () => selectedRows.map(
16062
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
16063
+ ),
16064
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
16065
+ id: columnId(column),
16066
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
16067
+ }))
16068
+ }
16069
+ }
16070
+ ) : null,
16071
+ selection.actions?.(selectedRows, [...selectedIdSet])
16072
+ ] })
16073
+ ]
15803
16074
  }
15804
16075
  ) : null
15805
16076
  ] }),
15806
- viewContent: windowConfig?.renderView?.(windowRow, {
15807
- closeDetail: () => setSelectedId(null),
15808
- openDetail: () => openDetail(windowRow),
15809
- openWindow: () => openWindow(windowRow),
15810
- closeWindow,
15811
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15812
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15813
- }) ?? /* @__PURE__ */ jsx42(
15814
- DataRowInspector,
16077
+ /* @__PURE__ */ jsx42(
16078
+ ColumnManager,
15815
16079
  {
15816
- row: windowRow,
15817
- recordKind: effectiveCopy?.rowKind ?? "record",
15818
- recordLabel: effectiveCopy?.label ?? "Record",
15819
- location: effectiveCopy?.location ?? "Table",
15820
- fieldCopy: copy !== false,
15821
- ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16080
+ open: columnManagerOpen,
16081
+ onOpenChange: setColumnManagerOpen,
16082
+ columns,
16083
+ order: effectiveColumnOrder,
16084
+ hidden: effectiveHiddenColumnIds,
16085
+ onApply: commitColumnState
15822
16086
  }
15823
16087
  ),
15824
- editContent: windowConfig?.renderEdit === false ? void 0 : windowConfig?.renderEdit ? windowConfig.renderEdit(windowRow, {
15825
- closeDetail: () => setSelectedId(null),
15826
- openDetail: () => openDetail(windowRow),
15827
- openWindow: () => openWindow(windowRow),
15828
- closeWindow,
15829
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15830
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15831
- }) : detail?.render?.(windowRow, {
15832
- closeDetail: () => setSelectedId(null),
15833
- openDetail: () => openDetail(windowRow),
15834
- openWindow: () => openWindow(windowRow),
15835
- closeWindow,
15836
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15837
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15838
- }),
15839
- children: windowConfig?.render?.(windowRow, {
15840
- closeDetail: () => setSelectedId(null),
15841
- openDetail: () => openDetail(windowRow),
15842
- openWindow: () => openWindow(windowRow),
15843
- closeWindow,
15844
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
15845
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
15846
- })
15847
- }
15848
- ) : null,
15849
- editEnabled ? /* @__PURE__ */ jsxs32(Fragment7, { children: [
15850
- foreignDrafts.length > 0 ? /* @__PURE__ */ jsxs32(
15851
- "div",
15852
- {
15853
- "data-matrx-foreign-drafts": true,
15854
- 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",
15855
- children: [
15856
- /* @__PURE__ */ jsx42("span", { children: "Unsaved edits from previous results" }),
15857
- /* @__PURE__ */ jsx42(
15858
- CopyButtons,
16088
+ detailEnabled && selectedRow ? /* @__PURE__ */ jsx42(
16089
+ SidePanelSurface,
16090
+ {
16091
+ title: resolveStringTitle(
16092
+ detail?.title?.(selectedRow),
16093
+ defaultRowTitle(selectedRow, columns)
16094
+ ),
16095
+ titleNode: detail?.title?.(selectedRow),
16096
+ description: detail?.description?.(selectedRow),
16097
+ onClose: () => setSelectedId(null),
16098
+ defaultWidth: detail?.defaultWidth ?? 480,
16099
+ headerActions: /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-1", children: [
16100
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ jsx42(
16101
+ TableRowCopyControl,
16102
+ {
16103
+ size: "icon",
16104
+ "data-matrx-table-icon-action": true,
16105
+ copy: effectiveCopy,
16106
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(selectedRow)}`,
16107
+ row: selectedRow,
16108
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16109
+ }
16110
+ ) : null,
16111
+ detail?.headerActions?.(selectedRow),
16112
+ windowEnabled ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
16113
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
16114
+ Button,
16115
+ {
16116
+ type: "button",
16117
+ variant: "ghost",
16118
+ size: "icon",
16119
+ "data-matrx-table-icon-action": true,
16120
+ className: "h-7 w-7",
16121
+ "aria-label": "Open in window",
16122
+ onClick: () => openWindow(selectedRow),
16123
+ children: /* @__PURE__ */ jsx42(PanelRight, { className: "h-5 w-5" })
16124
+ }
16125
+ ) }),
16126
+ /* @__PURE__ */ jsx42(TooltipContent, { children: "Open in window" })
16127
+ ] }) : null
16128
+ ] }),
16129
+ children: detail?.render ? (
16130
+ // The panel hands children a `min-h-0 flex-1 overflow-hidden` cell
16131
+ // and expects the child to own scrolling. `DataRowInspector` does;
16132
+ // custom `detail.render` bodies mostly did not, so their content
16133
+ // silently cut off at the fold with no scrollbar. Owning it here
16134
+ // fixes the whole class at once — a custom body that already
16135
+ // scrolls (`h-full` + `overflow-y-auto` root) still resolves to
16136
+ // this container's height, so no second scrollbar appears.
16137
+ /* @__PURE__ */ jsx42("div", { className: "h-full min-h-0 overflow-y-auto", children: detail.render(selectedRow, {
16138
+ closeDetail: () => setSelectedId(null),
16139
+ openDetail: () => openDetail(selectedRow),
16140
+ openWindow: () => openWindow(selectedRow),
16141
+ closeWindow,
16142
+ hasPendingEdits: Boolean(edits[getRowId(selectedRow)]),
16143
+ discardPendingEdits: () => discardRowEdits(getRowId(selectedRow))
16144
+ }) })
16145
+ ) : /* @__PURE__ */ jsx42(
16146
+ DataRowInspector,
16147
+ {
16148
+ row: selectedRow,
16149
+ ...effectiveCopy?.rowKind === void 0 ? {} : { recordKind: effectiveCopy?.rowKind },
16150
+ ...effectiveCopy?.label === void 0 ? {} : { recordLabel: effectiveCopy?.label },
16151
+ ...effectiveCopy?.location === void 0 ? {} : { location: effectiveCopy?.location },
16152
+ ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16153
+ }
16154
+ )
16155
+ }
16156
+ ) : null,
16157
+ windowRow ? /* @__PURE__ */ jsx42(
16158
+ DataRowWindow,
16159
+ {
16160
+ isOpen: true,
16161
+ onClose: closeWindow,
16162
+ title: windowConfig?.title?.(windowRow) ?? defaultRowTitle(windowRow, columns),
16163
+ row: windowRow,
16164
+ width: windowConfig?.width,
16165
+ height: windowConfig?.height,
16166
+ windowId: `matrx-data-row-${urlState?.id ?? "local"}-${getRowId(windowRow)}`,
16167
+ defaultTab: windowConfig?.defaultTab,
16168
+ headerActions: /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-1", children: [
16169
+ windowConfig?.openOnRowClick === true && detail?.enabled === true ? /* @__PURE__ */ jsxs32(Tooltip, { children: [
16170
+ /* @__PURE__ */ jsx42(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx42(
16171
+ Button,
16172
+ {
16173
+ type: "button",
16174
+ variant: "ghost",
16175
+ size: "icon",
16176
+ "data-matrx-table-icon-action": true,
16177
+ className: "h-7 w-7",
16178
+ "aria-label": "Open in side panel",
16179
+ onClick: () => {
16180
+ closeWindow();
16181
+ openDetail(windowRow);
16182
+ },
16183
+ children: /* @__PURE__ */ jsx42(PanelRightOpen, { className: "h-5 w-5" })
16184
+ }
16185
+ ) }),
16186
+ /* @__PURE__ */ jsx42(TooltipContent, { children: "Open in side panel" })
16187
+ ] }) : null,
16188
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ jsx42(
16189
+ TableRowCopyControl,
16190
+ {
16191
+ size: "icon",
16192
+ "data-matrx-table-icon-action": true,
16193
+ copy: effectiveCopy,
16194
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(windowRow)}`,
16195
+ row: windowRow,
16196
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16197
+ }
16198
+ ) : null
16199
+ ] }),
16200
+ viewContent: windowConfig?.renderView?.(windowRow, {
16201
+ closeDetail: () => setSelectedId(null),
16202
+ openDetail: () => openDetail(windowRow),
16203
+ openWindow: () => openWindow(windowRow),
16204
+ closeWindow,
16205
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16206
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16207
+ }) ?? /* @__PURE__ */ jsx42(
16208
+ DataRowInspector,
15859
16209
  {
15860
- size: "xs",
15861
- label: "retained drafts",
15862
- human: () => JSON.stringify(foreignDraftPayload, null, 2),
15863
- json: () => foreignDraftPayload
16210
+ row: windowRow,
16211
+ recordKind: effectiveCopy?.rowKind ?? "record",
16212
+ recordLabel: effectiveCopy?.label ?? "Record",
16213
+ location: effectiveCopy?.location ?? "Table",
16214
+ fieldCopy: copy !== false,
16215
+ ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
15864
16216
  }
15865
16217
  ),
15866
- /* @__PURE__ */ jsx42(Button, { type: "button", size: "sm", variant: "ghost", className: "h-7 text-xs text-destructive", onClick: () => setConfirmForeignDraftDiscard(true), children: "Discard drafts" })
15867
- ]
15868
- }
15869
- ) : null,
15870
- /* @__PURE__ */ jsx42(
15871
- ConfirmDialog,
15872
- {
15873
- open: confirmForeignDraftDiscard,
15874
- onOpenChange: setConfirmForeignDraftDiscard,
15875
- title: "Discard unsaved edits from previous results?",
15876
- description: "This permanently removes edits retained for other result sets. Copy drafts first if you may need them.",
15877
- confirmLabel: "Discard drafts",
15878
- cancelLabel: "Keep drafts",
15879
- variant: "destructive",
15880
- onConfirm: () => {
15881
- setEditsByScope(
15882
- (current) => Object.fromEntries(
15883
- Object.entries(current).filter(([scope]) => scope === editScopeKey)
15884
- )
15885
- );
15886
- setConfirmForeignDraftDiscard(false);
16218
+ editContent: windowConfig?.renderEdit === false ? void 0 : windowConfig?.renderEdit ? windowConfig.renderEdit(windowRow, {
16219
+ closeDetail: () => setSelectedId(null),
16220
+ openDetail: () => openDetail(windowRow),
16221
+ openWindow: () => openWindow(windowRow),
16222
+ closeWindow,
16223
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16224
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16225
+ }) : detail?.render?.(windowRow, {
16226
+ closeDetail: () => setSelectedId(null),
16227
+ openDetail: () => openDetail(windowRow),
16228
+ openWindow: () => openWindow(windowRow),
16229
+ closeWindow,
16230
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16231
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16232
+ }),
16233
+ children: windowConfig?.render?.(windowRow, {
16234
+ closeDetail: () => setSelectedId(null),
16235
+ openDetail: () => openDetail(windowRow),
16236
+ openWindow: () => openWindow(windowRow),
16237
+ closeWindow,
16238
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16239
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16240
+ })
15887
16241
  }
15888
- }
15889
- ),
15890
- /* @__PURE__ */ jsx42(
15891
- DirtySavePill,
15892
- {
15893
- changeCount,
15894
- saving,
15895
- onSave: () => void handleSaveEdits(),
15896
- onCancel: handleCancelEdits
15897
- }
15898
- )
15899
- ] }) : null
15900
- ]
15901
- }
15902
- ),
15903
- /* @__PURE__ */ jsx42(DragOverlay, { children: draggedRow ? (
15904
- // w-max: the overlay inherits the ACTIVE node's size, and the active
15905
- // node is the 16px grip handle — without it the label wraps to one
15906
- // character per line.
15907
- /* @__PURE__ */ jsx42("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" })
15908
- ) : null })
15909
- ]
16242
+ ) : null,
16243
+ editEnabled ? /* @__PURE__ */ jsxs32(Fragment7, { children: [
16244
+ foreignDrafts.length > 0 ? /* @__PURE__ */ jsxs32(
16245
+ "div",
16246
+ {
16247
+ "data-matrx-foreign-drafts": true,
16248
+ 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",
16249
+ children: [
16250
+ /* @__PURE__ */ jsx42("span", { children: "Unsaved edits from previous results" }),
16251
+ /* @__PURE__ */ jsx42(
16252
+ CopyButtons,
16253
+ {
16254
+ size: "xs",
16255
+ label: "retained drafts",
16256
+ human: () => JSON.stringify(foreignDraftPayload, null, 2),
16257
+ json: () => foreignDraftPayload
16258
+ }
16259
+ ),
16260
+ /* @__PURE__ */ jsx42(Button, { type: "button", size: "sm", variant: "ghost", className: "h-7 text-xs text-destructive", onClick: () => setConfirmForeignDraftDiscard(true), children: "Discard drafts" })
16261
+ ]
16262
+ }
16263
+ ) : null,
16264
+ /* @__PURE__ */ jsx42(
16265
+ ConfirmDialog,
16266
+ {
16267
+ open: confirmForeignDraftDiscard,
16268
+ onOpenChange: setConfirmForeignDraftDiscard,
16269
+ title: "Discard unsaved edits from previous results?",
16270
+ description: "This permanently removes edits retained for other result sets. Copy drafts first if you may need them.",
16271
+ confirmLabel: "Discard drafts",
16272
+ cancelLabel: "Keep drafts",
16273
+ variant: "destructive",
16274
+ onConfirm: () => {
16275
+ setEditsByScope(
16276
+ (current) => Object.fromEntries(
16277
+ Object.entries(current).filter(([scope]) => scope === editScopeKey)
16278
+ )
16279
+ );
16280
+ setConfirmForeignDraftDiscard(false);
16281
+ }
16282
+ }
16283
+ ),
16284
+ /* @__PURE__ */ jsx42(
16285
+ DirtySavePill,
16286
+ {
16287
+ changeCount,
16288
+ saving,
16289
+ onSave: () => void handleSaveEdits(),
16290
+ onCancel: handleCancelEdits
16291
+ }
16292
+ )
16293
+ ] }) : null
16294
+ ]
16295
+ }
16296
+ ),
16297
+ /* @__PURE__ */ jsx42(DragOverlay, { children: draggedRow ? (
16298
+ // w-max: the overlay inherits the ACTIVE node's size, and the active
16299
+ // node is the 16px grip handle — without it the label wraps to one
16300
+ // character per line.
16301
+ /* @__PURE__ */ jsx42("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" })
16302
+ ) : null })
16303
+ ]
16304
+ }
16305
+ )
15910
16306
  }
15911
- ) }) });
16307
+ ) });
15912
16308
  }
15913
16309
  function HierarchyDragHandle({
15914
16310
  id,
@@ -16499,6 +16895,34 @@ function SavedViewsControl({
16499
16895
  ] });
16500
16896
  }
16501
16897
 
16898
+ // src/data-table/menu-targets.ts
16899
+ var MATRX_TABLE_MENU_ICON_NAMES = [
16900
+ "copy",
16901
+ "copy-plus",
16902
+ "scissors",
16903
+ "clipboard-paste",
16904
+ "eraser",
16905
+ "pencil",
16906
+ "plus",
16907
+ "trash",
16908
+ "eye-off",
16909
+ "arrow-up-a-z",
16910
+ "arrow-down-a-z",
16911
+ "arrow-up-down",
16912
+ "arrow-down-to-line",
16913
+ "palette",
16914
+ "paintbrush",
16915
+ "panel-left",
16916
+ "panel-right",
16917
+ "settings",
16918
+ "sigma",
16919
+ "zap",
16920
+ "history",
16921
+ "link",
16922
+ "tag",
16923
+ "filter"
16924
+ ];
16925
+
16502
16926
  // src/data-table/tableXlsx.ts
16503
16927
  import * as XLSX from "xlsx";
16504
16928
  function downloadTableAsXlsx({
@@ -16548,6 +16972,7 @@ export {
16548
16972
  FORMAT_MISMATCH_CLASS,
16549
16973
  FormattedCell,
16550
16974
  GenericTablePagination,
16975
+ MATRX_TABLE_MENU_ICON_NAMES,
16551
16976
  MatrxDataTable,
16552
16977
  MatrxDataTableProvider,
16553
16978
  MatrxTableRowAlchemyProvider,