@ai-matrx/design-system 0.41.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 (51) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/data-table/copy-helpers.d.cts +2 -1
  3. package/dist/data-table/copy-helpers.d.ts +2 -1
  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 +2 -1
  9. package/dist/data-table/filter-engine.d.ts +2 -1
  10. package/dist/data-table/host.cjs.map +1 -1
  11. package/dist/data-table/host.d.cts +18 -4
  12. package/dist/data-table/host.d.ts +18 -4
  13. package/dist/data-table/host.js.map +1 -1
  14. package/dist/data-table/index.cjs +1810 -1649
  15. package/dist/data-table/index.cjs.map +1 -1
  16. package/dist/data-table/index.d.cts +4 -4
  17. package/dist/data-table/index.d.ts +4 -4
  18. package/dist/data-table/index.js +1810 -1649
  19. package/dist/data-table/index.js.map +1 -1
  20. package/dist/data-table/infer-filter.d.cts +2 -1
  21. package/dist/data-table/infer-filter.d.ts +2 -1
  22. package/dist/data-table/layered-filters.d.cts +2 -1
  23. package/dist/data-table/layered-filters.d.ts +2 -1
  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 +2 -1
  33. package/dist/data-table/query-control.d.ts +2 -1
  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 +2 -1
  42. package/dist/data-table/types.d.ts +2 -1
  43. package/dist/data-table/url-state.d.cts +2 -1
  44. package/dist/data-table/url-state.d.ts +2 -1
  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/xlsx.d.cts +2 -1
  48. package/dist/data-table/xlsx.d.ts +2 -1
  49. package/dist/{types-Dx5hJgCi.d.ts → types-afUz93xo.d.ts} +33 -1
  50. package/dist/{types-DNkbHRVK.d.cts → types-q5ficDSo.d.cts} +33 -1
  51. package/package.json +1 -1
@@ -44,6 +44,7 @@ __export(data_table_exports, {
44
44
  FORMAT_MISMATCH_CLASS: () => FORMAT_MISMATCH_CLASS,
45
45
  FormattedCell: () => FormattedCell,
46
46
  GenericTablePagination: () => GenericTablePagination,
47
+ MATRX_TABLE_MENU_ICON_NAMES: () => MATRX_TABLE_MENU_ICON_NAMES,
47
48
  MatrxDataTable: () => MatrxDataTable,
48
49
  MatrxDataTableProvider: () => MatrxDataTableProvider,
49
50
  MatrxTableRowAlchemyProvider: () => MatrxTableRowAlchemyProvider,
@@ -6266,6 +6267,52 @@ function markerCopy(marker) {
6266
6267
 
6267
6268
  // src/data-table/ColumnHeaderCell.tsx
6268
6269
  var import_jsx_runtime31 = require("react/jsx-runtime");
6270
+ function HeaderMenuItems({
6271
+ items,
6272
+ MenuIcon,
6273
+ onDone,
6274
+ depth = 0
6275
+ }) {
6276
+ const [openSubmenu, setOpenSubmenu] = (0, import_react13.useState)(null);
6277
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("flex flex-col gap-0.5", depth > 0 && "ml-3 border-l border-border pl-1"), role: "group", children: items.map((item) => {
6278
+ const disabled = Boolean(item.disabledReason);
6279
+ const hasSubmenu = Boolean(item.submenu && item.submenu.length > 0);
6280
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-col", children: [
6281
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
6282
+ Button,
6283
+ {
6284
+ type: "button",
6285
+ variant: "ghost",
6286
+ size: "sm",
6287
+ disabled,
6288
+ "aria-disabled": disabled || void 0,
6289
+ ...hasSubmenu ? { "aria-expanded": openSubmenu === item.id } : {},
6290
+ "data-matrx-header-menu-item": item.id,
6291
+ className: cn(
6292
+ "h-8 justify-start gap-2 px-2 text-xs font-normal",
6293
+ item.destructive && "text-destructive hover:text-destructive"
6294
+ ),
6295
+ onClick: () => {
6296
+ if (disabled) return;
6297
+ if (hasSubmenu) {
6298
+ setOpenSubmenu((current) => current === item.id ? null : item.id);
6299
+ return;
6300
+ }
6301
+ onDone();
6302
+ item.onSelect?.();
6303
+ },
6304
+ children: [
6305
+ item.icon && MenuIcon ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MenuIcon, { name: item.icon, className: "h-3.5 w-3.5" }) : null,
6306
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label }),
6307
+ item.shortcut ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "ml-3 text-[10px] text-muted-foreground", children: item.shortcut }) : null
6308
+ ]
6309
+ }
6310
+ ),
6311
+ disabled ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "px-2 pb-1 text-[11px] leading-tight text-muted-foreground", children: item.disabledReason }) : null,
6312
+ hasSubmenu && openSubmenu === item.id ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(HeaderMenuItems, { items: item.submenu, MenuIcon, onDone, depth: depth + 1 }) : null
6313
+ ] }, item.id);
6314
+ }) });
6315
+ }
6269
6316
  var HeaderButton = (0, import_react13.forwardRef)(({ dragDescription, "aria-describedby": tooltipDescription, ...props }, ref) => {
6270
6317
  const descriptions = [tooltipDescription, dragDescription].filter((value) => Boolean(value)).join(" ");
6271
6318
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -6307,8 +6354,11 @@ function ColumnHeaderCell({
6307
6354
  facetsLoading = false,
6308
6355
  facetsError,
6309
6356
  onFilterOpen,
6310
- reorderColumn
6357
+ reorderColumn,
6358
+ menuItems,
6359
+ MenuIcon
6311
6360
  }) {
6361
+ const hasMenuItems = Boolean(menuItems && menuItems.length > 0);
6312
6362
  const reorderEnabled = reorderColumn !== void 0;
6313
6363
  const { attributes: reorderAttributes, listeners: reorderListeners, setNodeRef } = (0, import_core.useDraggable)(
6314
6364
  reorderColumn ? {
@@ -6365,7 +6415,7 @@ function ColumnHeaderCell({
6365
6415
  };
6366
6416
  const markerLabels = marker ? markerCopy(marker) : void 0;
6367
6417
  const accessibleLabel = markerLabels?.label ?? labelText;
6368
- const menuLabel = filterKind ? accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column" : accessibleLabel ? `Sort ${accessibleLabel}` : "Sort column";
6418
+ const menuLabel = filterKind ? accessibleLabel ? `Sort or filter ${accessibleLabel}` : "Sort or filter column" : sortable ? accessibleLabel ? `Sort ${accessibleLabel}` : "Sort column" : accessibleLabel ? `${accessibleLabel} column options` : "Column options";
6369
6419
  const menuContent = /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
6370
6420
  PopoverContent,
6371
6421
  {
@@ -6491,12 +6541,20 @@ function ColumnHeaderCell({
6491
6541
  filterShowCancel ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Button, { type: "button", variant: "ghost", size: "sm", onClick: cancelFilter, children: "Cancel" }) : null,
6492
6542
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Button, { type: "button", size: "sm", onClick: applyFilter, children: filterApplyLabel ?? "Apply" })
6493
6543
  ] })
6494
- ] }) : null
6544
+ ] }) : null,
6545
+ hasMenuItems ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6546
+ "div",
6547
+ {
6548
+ className: cn((sortable || filterKind) && "mt-2 border-t border-border pt-2"),
6549
+ "data-matrx-header-menu": "consumer",
6550
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(HeaderMenuItems, { items: menuItems, MenuIcon, onDone: () => setFilterOpen(false) })
6551
+ }
6552
+ ) : null
6495
6553
  ]
6496
6554
  }
6497
6555
  );
6498
6556
  if (compact) {
6499
- if (!sortable && !filterKind) {
6557
+ if (!sortable && !filterKind && !hasMenuItems) {
6500
6558
  if (!reorderEnabled) {
6501
6559
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cn("flex items-center justify-center", className), children: label });
6502
6560
  }
@@ -6617,7 +6675,7 @@ function ColumnHeaderCell({
6617
6675
  "."
6618
6676
  ] })
6619
6677
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
6620
- filterKind || sortable ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "inline-flex items-center", children: [
6678
+ filterKind || sortable || hasMenuItems ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "inline-flex items-center", children: [
6621
6679
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Popover, { open: filterOpen, onOpenChange: setFilterOpen, children: [
6622
6680
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Tooltip, { children: [
6623
6681
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -6632,7 +6690,7 @@ function ColumnHeaderCell({
6632
6690
  "rounded p-0.5 transition-colors",
6633
6691
  filterActive ? "text-primary hover:text-primary/80" : "text-muted-foreground/40 hover:text-muted-foreground"
6634
6692
  ),
6635
- children: filterKind ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ListFilter, { className: cn("h-3 w-3", filterActive && "fill-primary/20") }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArrowUpDown, { className: "h-3 w-3" })
6693
+ children: filterKind ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ListFilter, { className: cn("h-3 w-3", filterActive && "fill-primary/20") }) : sortable ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ArrowUpDown, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ChevronDown, { className: "h-3 w-3" })
6636
6694
  }
6637
6695
  ) }) }),
6638
6696
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TooltipContent, { children: menuLabel })
@@ -8523,6 +8581,7 @@ function camePastAPortalIntoTheContainer(target) {
8523
8581
  if (!element) return false;
8524
8582
  return element.closest('[data-matrx-table-spreadsheet="true"]') === null;
8525
8583
  }
8584
+ var READ_ONLY_REASON = "This table is view-only \u2014 you can select and copy, but not change cells.";
8526
8585
  function useSpreadsheetGrid(options) {
8527
8586
  const [range, setRange] = (0, import_react15.useState)(null);
8528
8587
  const [fillTarget, setFillTarget] = (0, import_react15.useState)(null);
@@ -8589,10 +8648,18 @@ function useSpreadsheetGrid(options) {
8589
8648
  }));
8590
8649
  setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));
8591
8650
  }
8651
+ if (!current.onPatch) return;
8592
8652
  await current.onPatch(patches, intent);
8593
8653
  },
8594
8654
  []
8595
8655
  );
8656
+ const refuseWrite = (0, import_react15.useCallback)(() => {
8657
+ const current = optionsRef.current;
8658
+ if (!current) return true;
8659
+ if (!current.readOnly && current.onPatch) return false;
8660
+ current.notify?.(current.readOnlyReason ?? READ_ONLY_REASON);
8661
+ return true;
8662
+ }, []);
8596
8663
  const readValueAt = (0, import_react15.useCallback)((rowIndex, columnIndex) => {
8597
8664
  const current = optionsRef.current;
8598
8665
  const rowId = current?.rowIds[rowIndex];
@@ -8626,6 +8693,7 @@ function useSpreadsheetGrid(options) {
8626
8693
  []
8627
8694
  );
8628
8695
  const doFillDown = (0, import_react15.useCallback)(async () => {
8696
+ if (refuseWrite()) return;
8629
8697
  const base = fillRequestBase();
8630
8698
  if (!base || !bounds) return;
8631
8699
  const result = fillDown({
@@ -8635,8 +8703,9 @@ function useSpreadsheetGrid(options) {
8635
8703
  const screened = screenPatches(result.patches);
8636
8704
  await applyPatches(screened.accepted, "fill-down", "Filled down");
8637
8705
  announceSkips([...result.skipped, ...screened.refusals], result.summary);
8638
- }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches]);
8706
+ }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);
8639
8707
  const doClear = (0, import_react15.useCallback)(async () => {
8708
+ if (refuseWrite()) return;
8640
8709
  const current = optionsRef.current;
8641
8710
  if (!current || !bounds) return;
8642
8711
  const patches = [];
@@ -8663,20 +8732,22 @@ function useSpreadsheetGrid(options) {
8663
8732
  [...skipped, ...screened.refusals],
8664
8733
  `Cleared ${screened.accepted.length} cells.`
8665
8734
  );
8666
- }, [bounds, applyPatches, announceSkips, screenPatches]);
8735
+ }, [bounds, applyPatches, announceSkips, screenPatches, refuseWrite]);
8667
8736
  const doUndo = (0, import_react15.useCallback)(async () => {
8737
+ if (refuseWrite()) return;
8668
8738
  const taken = popUndo(stack);
8669
8739
  if (!taken.entry) return;
8670
8740
  setStack(taken.stack);
8671
8741
  await applyPatches(taken.entry.undo, "undo", taken.entry.label);
8672
8742
  optionsRef.current?.notify?.(taken.entry.label);
8673
- }, [stack, applyPatches]);
8743
+ }, [stack, applyPatches, refuseWrite]);
8674
8744
  const doRedo = (0, import_react15.useCallback)(async () => {
8745
+ if (refuseWrite()) return;
8675
8746
  const taken = popRedo(stack);
8676
8747
  if (!taken.entry) return;
8677
8748
  setStack(taken.stack);
8678
8749
  await applyPatches(taken.entry.redo, "redo", taken.entry.label);
8679
- }, [stack, applyPatches]);
8750
+ }, [stack, applyPatches, refuseWrite]);
8680
8751
  const move = (0, import_react15.useCallback)(
8681
8752
  (direction, extend) => {
8682
8753
  setRange((previous) => {
@@ -8752,7 +8823,7 @@ function useSpreadsheetGrid(options) {
8752
8823
  case "edit":
8753
8824
  case "editSeeded": {
8754
8825
  const focused = range?.focus;
8755
- if (!focused || !current.onEditRequest) return;
8826
+ if (!focused || !current.onEditRequest || current.readOnly) return;
8756
8827
  const seed = action.kind === "editSeeded" ? action.seed : null;
8757
8828
  if (current.onEditRequest(focused, seed)) event.preventDefault();
8758
8829
  return;
@@ -8809,6 +8880,7 @@ function useSpreadsheetGrid(options) {
8809
8880
  const text = event.clipboardData?.getData("text/plain") ?? "";
8810
8881
  if (!text) return;
8811
8882
  event.preventDefault();
8883
+ if (refuseWrite()) return;
8812
8884
  const block = parseClipboardGrid(text);
8813
8885
  const start = range.focus;
8814
8886
  const startRow = rowIds.indexOf(start.rowId);
@@ -8871,7 +8943,7 @@ function useSpreadsheetGrid(options) {
8871
8943
  const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : "";
8872
8944
  announceSkips(refusals, `Pasted ${screened.accepted.length} cells.${tail}`);
8873
8945
  },
8874
- [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches]
8946
+ [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches, refuseWrite]
8875
8947
  );
8876
8948
  const cellProps = (0, import_react15.useCallback)(
8877
8949
  (rowId, columnId2) => {
@@ -8922,7 +8994,7 @@ function useSpreadsheetGrid(options) {
8922
8994
  const isFillHandleCell = (0, import_react15.useCallback)(
8923
8995
  (rowId, columnId2) => {
8924
8996
  const current = optionsRef.current;
8925
- if (!current || current.fillHandle === false || !bounds) return false;
8997
+ if (!current || current.fillHandle === false || current.readOnly || !bounds) return false;
8926
8998
  return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId2;
8927
8999
  },
8928
9000
  [bounds, rowIds, columnIds]
@@ -8949,6 +9021,7 @@ function useSpreadsheetGrid(options) {
8949
9021
  const source = boundsRef.current;
8950
9022
  setFillTarget(null);
8951
9023
  if (!target || !source) return;
9024
+ if (refuseWrite()) return;
8952
9025
  const base = fillRequestBase();
8953
9026
  if (!base) return;
8954
9027
  const result = fillRange({
@@ -8962,7 +9035,7 @@ function useSpreadsheetGrid(options) {
8962
9035
  };
8963
9036
  window.addEventListener("mouseup", commit);
8964
9037
  return () => window.removeEventListener("mouseup", commit);
8965
- }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches]);
9038
+ }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches, refuseWrite]);
8966
9039
  return {
8967
9040
  range,
8968
9041
  bounds,
@@ -13644,12 +13717,15 @@ function MatrxDataTableCore({
13644
13717
  return getCellValue(applyRowEdits(row, edits[rowId]), col);
13645
13718
  },
13646
13719
  isColumnWritable: (colId) => {
13720
+ if (spreadsheet.readOnly) return false;
13647
13721
  if (spreadsheet.writableColumnIds) return spreadsheet.writableColumnIds.includes(colId);
13648
13722
  const col = spreadsheetColumnById.get(colId);
13649
13723
  return Boolean(col && col.editable);
13650
13724
  },
13651
13725
  ...spreadsheet.columnRefusal ? { columnRefusal: spreadsheet.columnRefusal } : {},
13652
- onPatch: spreadsheet.onPatch,
13726
+ ...spreadsheet.onPatch ? { onPatch: spreadsheet.onPatch } : {},
13727
+ ...spreadsheet.readOnly ? { readOnly: true } : {},
13728
+ ...spreadsheet.readOnlyReason ? { readOnlyReason: spreadsheet.readOnlyReason } : {},
13653
13729
  ...spreadsheet.undoDepth === void 0 ? {} : { undoDepth: spreadsheet.undoDepth },
13654
13730
  ...spreadsheet.fillHandle === void 0 ? {} : { fillHandle: spreadsheet.fillHandle },
13655
13731
  ...spreadsheet.paste === void 0 ? {} : { paste: spreadsheet.paste },
@@ -14209,7 +14285,7 @@ function MatrxDataTableCore({
14209
14285
  setEdits({});
14210
14286
  edit?.onCancel?.();
14211
14287
  };
14212
- const resolveMenuLevel = (target) => {
14288
+ const menuPayloadFor = (target) => {
14213
14289
  const rowFor = (rowId) => {
14214
14290
  const row = data.find((candidate) => getRowId(candidate) === rowId);
14215
14291
  if (!row) return null;
@@ -14217,14 +14293,13 @@ function MatrxDataTableCore({
14217
14293
  };
14218
14294
  const columnFor = (id) => visibleColumns.find((col) => columnId(col) === id);
14219
14295
  const labelFor = (col, id) => col?.label ?? (typeof col?.header === "string" ? col.header : id);
14220
- const fromHost = (payload) => host.createDefaultMenuContext?.(payload) ?? null;
14221
14296
  switch (target.level) {
14222
14297
  case "row": {
14223
14298
  if (!target.rowId) return null;
14224
14299
  const displayRow = rowFor(target.rowId);
14225
14300
  if (!displayRow) return null;
14226
14301
  const controls = buildRecordControls(displayRow);
14227
- return contextMenu?.resolveRowContext?.(displayRow, controls) ?? fromHost({ level: "row", row: displayRow, rowId: target.rowId, controls });
14302
+ return { payload: { level: "row", row: displayRow, rowId: target.rowId, controls }, controls };
14228
14303
  }
14229
14304
  case "cell": {
14230
14305
  if (!target.rowId || !target.columnId) return null;
@@ -14233,45 +14308,94 @@ function MatrxDataTableCore({
14233
14308
  if (!displayRow || !col) return null;
14234
14309
  const controls = buildRecordControls(displayRow);
14235
14310
  const value = spreadsheet?.readCell ? spreadsheet.readCell(displayRow, target.columnId) : getCellValue(displayRow, col);
14236
- const args = {
14237
- row: displayRow,
14238
- rowId: target.rowId,
14239
- columnId: target.columnId,
14240
- columnLabel: labelFor(col, target.columnId),
14241
- value,
14242
- text: stringifyCellValue(value),
14311
+ return {
14312
+ payload: {
14313
+ level: "cell",
14314
+ row: displayRow,
14315
+ rowId: target.rowId,
14316
+ columnId: target.columnId,
14317
+ columnLabel: labelFor(col, target.columnId),
14318
+ value,
14319
+ text: stringifyCellValue(value),
14320
+ controls
14321
+ },
14243
14322
  controls
14244
14323
  };
14245
- return contextMenu?.resolveCellContext?.(args) ?? fromHost({ level: "cell", ...args });
14246
14324
  }
14247
14325
  case "column": {
14248
14326
  if (!target.columnId) return null;
14249
14327
  const col = columnFor(target.columnId);
14250
14328
  if (!col) return null;
14251
- const args = {
14252
- columnId: target.columnId,
14253
- columnLabel: labelFor(col, target.columnId),
14254
- values: paginated.map(
14255
- (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
14256
- )
14329
+ return {
14330
+ payload: {
14331
+ level: "column",
14332
+ columnId: target.columnId,
14333
+ columnLabel: labelFor(col, target.columnId),
14334
+ values: paginated.map(
14335
+ (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
14336
+ )
14337
+ }
14257
14338
  };
14258
- return contextMenu?.resolveColumnContext?.(args) ?? fromHost({ level: "column", ...args });
14259
14339
  }
14260
14340
  case "selection": {
14261
14341
  const addresses = sheetRef.current.selectionAddresses();
14262
14342
  const cellCount = addresses.rowIds.length * addresses.columnIds.length;
14263
- if (cellCount <= 1) {
14264
- return resolveMenuLevel({ ...target, level: "cell" });
14265
- }
14266
- const args = { ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount };
14267
- return contextMenu?.resolveSelectionContext?.(args) ?? fromHost({ level: "selection", ...args });
14343
+ if (cellCount <= 1) return menuPayloadFor({ ...target, level: "cell" });
14344
+ return {
14345
+ payload: { level: "selection", ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount }
14346
+ };
14347
+ }
14348
+ case "table":
14349
+ return {
14350
+ payload: { level: "table", rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) }
14351
+ };
14352
+ }
14353
+ };
14354
+ const resolveMenuLevel = (target) => {
14355
+ const built = menuPayloadFor(target);
14356
+ if (!built) return null;
14357
+ const { payload } = built;
14358
+ const fromHost = () => host.createDefaultMenuContext?.(payload) ?? null;
14359
+ switch (payload.level) {
14360
+ case "row":
14361
+ return contextMenu?.resolveRowContext?.(payload.row, built.controls) ?? fromHost();
14362
+ case "cell": {
14363
+ const { level: _level, ...args } = payload;
14364
+ return contextMenu?.resolveCellContext?.({ ...args, controls: built.controls }) ?? fromHost();
14365
+ }
14366
+ case "column": {
14367
+ const { level: _level, ...args } = payload;
14368
+ return contextMenu?.resolveColumnContext?.(args) ?? fromHost();
14369
+ }
14370
+ case "selection": {
14371
+ const { level: _level, ...args } = payload;
14372
+ return contextMenu?.resolveSelectionContext?.(args) ?? fromHost();
14268
14373
  }
14269
14374
  case "table": {
14270
- const args = { rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) };
14271
- return contextMenu?.resolveTableContext?.(args) ?? fromHost({ level: "table", ...args });
14375
+ const { level: _level, ...args } = payload;
14376
+ return contextMenu?.resolveTableContext?.(args) ?? fromHost();
14272
14377
  }
14273
14378
  }
14274
14379
  };
14380
+ const menuSectionsFor = (target) => {
14381
+ const resolve2 = contextMenu?.sections;
14382
+ if (!resolve2 || target.tableId !== tableContextMenuInstanceId) return [];
14383
+ const built = menuPayloadFor(target);
14384
+ if (!built) return [];
14385
+ try {
14386
+ return resolve2(built.payload);
14387
+ } catch (error) {
14388
+ console.error("[MatrxDataTable] contextMenu.sections threw; the menu opens without them.", error);
14389
+ return [];
14390
+ }
14391
+ };
14392
+ const menuSectionsForRef = (0, import_react21.useRef)(menuSectionsFor);
14393
+ menuSectionsForRef.current = menuSectionsFor;
14394
+ const hasMenuSections = Boolean(contextMenu?.sections);
14395
+ const boundarySections = (0, import_react21.useCallback)(
14396
+ (target) => menuSectionsForRef.current(target),
14397
+ []
14398
+ );
14275
14399
  const resolveMenuLevelRef = (0, import_react21.useRef)(resolveMenuLevel);
14276
14400
  resolveMenuLevelRef.current = resolveMenuLevel;
14277
14401
  (0, import_react21.useEffect)(() => {
@@ -14484,959 +14608,640 @@ function MatrxDataTableCore({
14484
14608
  },
14485
14609
  [columns, commitColumnState, commitColumnWidths, controlledQuery, intelligentSearchEnabled, intelligentSearchScopes, toolbar]
14486
14610
  );
14487
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ContextMenuBoundary, { label: tableId ?? "Data table", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14488
- import_core3.DndContext,
14611
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14612
+ ContextMenuBoundary,
14489
14613
  {
14490
- id: tableDndContextId,
14491
- sensors: hierarchySensors,
14492
- collisionDetection: import_core3.pointerWithin,
14493
- accessibility: {
14494
- screenReaderInstructions: {
14495
- draggable: "Drag with a pointer. To reorder columns with a keyboard, open Columns and use Move earlier or Move later."
14496
- }
14497
- },
14498
- measuring: { droppable: { strategy: import_core3.MeasuringStrategy.Always } },
14499
- onDragStart: (event) => {
14500
- const kind = event.active.data.current?.matrxDragKind;
14501
- if (kind === "column-header") {
14502
- const draggedId = String(event.active.data.current?.columnId);
14503
- draggedColumnId.current = draggedId;
14504
- columnDragActive.current = true;
14505
- setColumnDragPreview({ draggedId, targetId: null, after: false });
14506
- return;
14507
- }
14508
- if (kind === "column-manager") return;
14509
- setDraggedRowId(String(event.active.id));
14510
- },
14511
- onDragMove: (event) => {
14512
- if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
14513
- const overData = event.over?.data.current;
14514
- const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
14515
- const activeRect = event.active.rect.current.translated;
14516
- const overRect = event.over?.rect;
14517
- if (!overId || !activeRect || !overRect) {
14518
- setColumnDragPreview(
14519
- (current) => current?.targetId === null ? current : current ? { ...current, targetId: null } : current
14520
- );
14521
- columnDragScrollDirection.current = 0;
14522
- stopColumnDragScroll();
14523
- return;
14524
- }
14525
- const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14526
- setColumnDragPreview((current) => {
14527
- if (!current) return current;
14528
- return current.targetId === overId && current.after === after ? current : { ...current, targetId: String(overId), after };
14529
- });
14530
- const scroll = scrollRef.current;
14531
- if (!scroll) return;
14532
- const bounds = scroll.getBoundingClientRect();
14533
- const center = activeRect.left + activeRect.width / 2;
14534
- columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
14535
- if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
14536
- else startColumnDragScroll();
14537
- },
14538
- onDragCancel: () => {
14539
- columnDragActive.current = false;
14540
- draggedColumnId.current = null;
14541
- setColumnDragPreview(null);
14542
- stopColumnDragScroll();
14543
- setDraggedRowId(null);
14544
- },
14545
- onDragEnd: (event) => {
14546
- const kind = event.active.data.current?.matrxDragKind;
14547
- if (kind === "column-manager") return;
14548
- if (kind !== "column-header") {
14549
- endHierarchyDrag(event);
14550
- return;
14551
- }
14552
- const from = String(event.active.data.current?.columnId);
14553
- const targetData = event.over?.data.current;
14554
- const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
14555
- const activeRect = event.active.rect.current.translated;
14556
- const overRect = event.over?.rect;
14557
- columnDragActive.current = false;
14558
- draggedColumnId.current = null;
14559
- setColumnDragPreview(null);
14560
- stopColumnDragScroll();
14561
- if (columnDragPreview?.draggedId === from && columnDragPreview.targetId !== null && columnDragPreview.targetId !== from) {
14562
- commitColumnState(
14563
- projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
14564
- effectiveHiddenColumnIds
14565
- );
14566
- return;
14567
- }
14568
- if (!target || from === target || !activeRect || !overRect) return;
14569
- const order = [...effectiveColumnOrder];
14570
- const fromIndex = order.indexOf(from);
14571
- const targetIndex = order.indexOf(String(target));
14572
- if (fromIndex < 0 || targetIndex < 0) return;
14573
- order.splice(fromIndex, 1);
14574
- const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14575
- const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
14576
- order.splice(insertion, 0, from);
14577
- commitColumnState(order, effectiveHiddenColumnIds);
14578
- },
14579
- children: [
14580
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14581
- "div",
14582
- {
14583
- ref: rootRef,
14584
- "data-matrx-table": true,
14585
- "data-matrx-table-id": tableContextMenuInstanceId,
14586
- "data-matrx-table-implementation": "@ai-matrx/design-system",
14587
- "data-matrx-table-density": resolvedDensity,
14588
- "data-url-state-table": urlState?.id,
14589
- className: cn(
14590
- "flex h-full min-h-0 flex-col gap-2 px-0 py-2",
14591
- className
14592
- ),
14593
- children: [
14594
- notice,
14595
- savedViewError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: savedViewError }) : null,
14596
- toolbarActionError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: toolbarActionError }) : null,
14597
- placeToolbar(!hideToolbar && (viewTabs || toolbar?.title || toolbar?.titleCount || showSearch || toolbar?.customSearch || toolbar?.anyOf || toolbar?.layeredFilters || toolbar?.facets && toolbar.facets.length > 0 || toolbar?.leading || toolbar?.actions || toolbar?.refresh || toolbar?.add || tableId && SavedViews || hasActiveFilters || showToolbarCopy || columns.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14598
- TableTitleRow,
14599
- {
14600
- ...toolbar?.singleRow ? { singleRow: true } : {},
14601
- tabs: viewTabs ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TableViewWorkspace, { snapshot: currentViewSnapshot, onApply: applySavedView, related: relatedTableActions }, tableId ?? "table") : void 0,
14602
- ...toolbar?.title === void 0 ? {} : { title: toolbar.title },
14603
- ...toolbar?.titleCount === void 0 ? {} : { count: toolbar.titleCount },
14604
- context: tableId && SavedViews ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-view-control": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14605
- SavedViews,
14606
- {
14607
- tableId,
14608
- snapshot: currentViewSnapshot,
14609
- defaultSnapshot: declaredDefaultViewSnapshot,
14610
- onApply: applySavedView
14611
- }
14612
- ) }) : void 0,
14613
- search: toolbar?.customSearch ?? (showSearch ? intelligentSearchEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14614
- TableSearch,
14615
- {
14616
- value: searchValue,
14617
- onChange: setSearchValue,
14618
- scope: searchScope,
14619
- onScopeChange: setSearchScope,
14620
- scopes: intelligentSearchScopes,
14621
- ...toolbar?.searchPlaceholder === void 0 ? {} : { placeholder: toolbar.searchPlaceholder }
14622
- }
14623
- ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14624
- "div",
14614
+ label: tableId ?? "Data table",
14615
+ ...hasMenuSections ? { sections: boundarySections } : {},
14616
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14617
+ import_core3.DndContext,
14618
+ {
14619
+ id: tableDndContextId,
14620
+ sensors: hierarchySensors,
14621
+ collisionDetection: import_core3.pointerWithin,
14622
+ accessibility: {
14623
+ screenReaderInstructions: {
14624
+ draggable: "Drag with a pointer. To reorder columns with a keyboard, open Columns and use Move earlier or Move later."
14625
+ }
14626
+ },
14627
+ measuring: { droppable: { strategy: import_core3.MeasuringStrategy.Always } },
14628
+ onDragStart: (event) => {
14629
+ const kind = event.active.data.current?.matrxDragKind;
14630
+ if (kind === "column-header") {
14631
+ const draggedId = String(event.active.data.current?.columnId);
14632
+ draggedColumnId.current = draggedId;
14633
+ columnDragActive.current = true;
14634
+ setColumnDragPreview({ draggedId, targetId: null, after: false });
14635
+ return;
14636
+ }
14637
+ if (kind === "column-manager") return;
14638
+ setDraggedRowId(String(event.active.id));
14639
+ },
14640
+ onDragMove: (event) => {
14641
+ if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
14642
+ const overData = event.over?.data.current;
14643
+ const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
14644
+ const activeRect = event.active.rect.current.translated;
14645
+ const overRect = event.over?.rect;
14646
+ if (!overId || !activeRect || !overRect) {
14647
+ setColumnDragPreview(
14648
+ (current) => current?.targetId === null ? current : current ? { ...current, targetId: null } : current
14649
+ );
14650
+ columnDragScrollDirection.current = 0;
14651
+ stopColumnDragScroll();
14652
+ return;
14653
+ }
14654
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14655
+ setColumnDragPreview((current) => {
14656
+ if (!current) return current;
14657
+ return current.targetId === overId && current.after === after ? current : { ...current, targetId: String(overId), after };
14658
+ });
14659
+ const scroll = scrollRef.current;
14660
+ if (!scroll) return;
14661
+ const bounds = scroll.getBoundingClientRect();
14662
+ const center = activeRect.left + activeRect.width / 2;
14663
+ columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
14664
+ if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
14665
+ else startColumnDragScroll();
14666
+ },
14667
+ onDragCancel: () => {
14668
+ columnDragActive.current = false;
14669
+ draggedColumnId.current = null;
14670
+ setColumnDragPreview(null);
14671
+ stopColumnDragScroll();
14672
+ setDraggedRowId(null);
14673
+ },
14674
+ onDragEnd: (event) => {
14675
+ const kind = event.active.data.current?.matrxDragKind;
14676
+ if (kind === "column-manager") return;
14677
+ if (kind !== "column-header") {
14678
+ endHierarchyDrag(event);
14679
+ return;
14680
+ }
14681
+ const from = String(event.active.data.current?.columnId);
14682
+ const targetData = event.over?.data.current;
14683
+ const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
14684
+ const activeRect = event.active.rect.current.translated;
14685
+ const overRect = event.over?.rect;
14686
+ columnDragActive.current = false;
14687
+ draggedColumnId.current = null;
14688
+ setColumnDragPreview(null);
14689
+ stopColumnDragScroll();
14690
+ if (columnDragPreview?.draggedId === from && columnDragPreview.targetId !== null && columnDragPreview.targetId !== from) {
14691
+ commitColumnState(
14692
+ projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
14693
+ effectiveHiddenColumnIds
14694
+ );
14695
+ return;
14696
+ }
14697
+ if (!target || from === target || !activeRect || !overRect) return;
14698
+ const order = [...effectiveColumnOrder];
14699
+ const fromIndex = order.indexOf(from);
14700
+ const targetIndex = order.indexOf(String(target));
14701
+ if (fromIndex < 0 || targetIndex < 0) return;
14702
+ order.splice(fromIndex, 1);
14703
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
14704
+ const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
14705
+ order.splice(insertion, 0, from);
14706
+ commitColumnState(order, effectiveHiddenColumnIds);
14707
+ },
14708
+ children: [
14709
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14710
+ "div",
14711
+ {
14712
+ ref: rootRef,
14713
+ "data-matrx-table": true,
14714
+ "data-matrx-table-id": tableContextMenuInstanceId,
14715
+ "data-matrx-table-implementation": "@ai-matrx/design-system",
14716
+ "data-matrx-table-density": resolvedDensity,
14717
+ "data-url-state-table": urlState?.id,
14718
+ className: cn(
14719
+ "flex h-full min-h-0 flex-col gap-2 px-0 py-2",
14720
+ className
14721
+ ),
14722
+ children: [
14723
+ notice,
14724
+ savedViewError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: savedViewError }) : null,
14725
+ toolbarActionError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "shrink-0 text-xs text-destructive", children: toolbarActionError }) : null,
14726
+ placeToolbar(!hideToolbar && (viewTabs || toolbar?.title || toolbar?.titleCount || showSearch || toolbar?.customSearch || toolbar?.anyOf || toolbar?.layeredFilters || toolbar?.facets && toolbar.facets.length > 0 || toolbar?.leading || toolbar?.actions || toolbar?.refresh || toolbar?.add || tableId && SavedViews || hasActiveFilters || showToolbarCopy || columns.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14727
+ TableTitleRow,
14625
14728
  {
14626
- "data-matrx-table-search": true,
14627
- className: cn(
14628
- "flex min-w-0 flex-1 sm:w-full sm:flex-none",
14629
- toolbar?.searchMatch ? "sm:max-w-md" : "sm:max-w-xs"
14630
- ),
14729
+ ...toolbar?.singleRow ? { singleRow: true } : {},
14730
+ tabs: viewTabs ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TableViewWorkspace, { snapshot: currentViewSnapshot, onApply: applySavedView, related: relatedTableActions }, tableId ?? "table") : void 0,
14731
+ ...toolbar?.title === void 0 ? {} : { title: toolbar.title },
14732
+ ...toolbar?.titleCount === void 0 ? {} : { count: toolbar.titleCount },
14733
+ context: tableId && SavedViews ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-view-control": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14734
+ SavedViews,
14735
+ {
14736
+ tableId,
14737
+ snapshot: currentViewSnapshot,
14738
+ defaultSnapshot: declaredDefaultViewSnapshot,
14739
+ onApply: applySavedView
14740
+ }
14741
+ ) }) : void 0,
14742
+ search: toolbar?.customSearch ?? (showSearch ? intelligentSearchEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14743
+ TableSearch,
14744
+ {
14745
+ value: searchValue,
14746
+ onChange: setSearchValue,
14747
+ scope: searchScope,
14748
+ onScopeChange: setSearchScope,
14749
+ scopes: intelligentSearchScopes,
14750
+ ...toolbar?.searchPlaceholder === void 0 ? {} : { placeholder: toolbar.searchPlaceholder }
14751
+ }
14752
+ ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14753
+ "div",
14754
+ {
14755
+ "data-matrx-table-search": true,
14756
+ className: cn(
14757
+ "flex min-w-0 flex-1 sm:w-full sm:flex-none",
14758
+ toolbar?.searchMatch ? "sm:max-w-md" : "sm:max-w-xs"
14759
+ ),
14760
+ children: [
14761
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
14762
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14763
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14764
+ Input,
14765
+ {
14766
+ value: searchValue,
14767
+ onChange: (e) => setSearchValue(e.target.value),
14768
+ placeholder: toolbar?.searchPlaceholder ?? "Search\u2026",
14769
+ className: cn(
14770
+ "h-8 pl-7 pr-7 text-sm",
14771
+ toolbar?.searchMatch && "rounded-r-none"
14772
+ ),
14773
+ style: { fontSize: "16px" }
14774
+ }
14775
+ ),
14776
+ searchValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14777
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14778
+ "button",
14779
+ {
14780
+ type: "button",
14781
+ "aria-label": "Clear search",
14782
+ className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14783
+ onClick: () => setSearchValue(""),
14784
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-5 w-5" })
14785
+ }
14786
+ ) }),
14787
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear search" })
14788
+ ] }) : null
14789
+ ] }),
14790
+ toolbar?.searchMatch ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14791
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14792
+ Button,
14793
+ {
14794
+ type: "button",
14795
+ variant: searchMatchMode === "whole_words" ? "secondary" : "outline",
14796
+ size: "sm",
14797
+ className: "h-8 shrink-0 gap-1 rounded-l-none border-l-0 px-2 text-xs",
14798
+ "aria-pressed": searchMatchMode === "whole_words",
14799
+ "aria-label": `Search match: ${searchMatchMode === "whole_words" ? "whole words" : "contains text"}`,
14800
+ onClick: () => setSearchMatchMode(
14801
+ searchMatchMode === "whole_words" ? "contains" : "whole_words"
14802
+ ),
14803
+ children: [
14804
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(WholeWord, { className: "h-5 w-5" }),
14805
+ searchMatchMode === "whole_words" ? "Whole words" : "Contains"
14806
+ ]
14807
+ }
14808
+ ) }),
14809
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: searchMatchMode === "whole_words" ? "Whole words \u2014 click to match text anywhere" : "Contains text \u2014 click to require whole words" })
14810
+ ] }) : null
14811
+ ]
14812
+ }
14813
+ ) : null),
14631
14814
  children: [
14632
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
14815
+ toolbar?.layeredFilters ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14816
+ LayeredFilterBuilder,
14817
+ {
14818
+ fields: toolbar.layeredFilters.fields,
14819
+ rules: layeredFilters,
14820
+ onChange: setLayeredFilters,
14821
+ maxRules: toolbar.layeredFilters.maxRules,
14822
+ label: toolbar.layeredFilters.label
14823
+ }
14824
+ ) : null,
14825
+ toolbar?.anyOf ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full max-w-xs", children: [
14633
14826
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14634
14827
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14635
14828
  Input,
14636
14829
  {
14637
- value: searchValue,
14638
- onChange: (e) => setSearchValue(e.target.value),
14639
- placeholder: toolbar?.searchPlaceholder ?? "Search\u2026",
14640
- className: cn(
14641
- "h-8 pl-7 pr-7 text-sm",
14642
- toolbar?.searchMatch && "rounded-r-none"
14643
- ),
14830
+ value: anyOfValue,
14831
+ onChange: (e) => setAnyOfValue(e.target.value),
14832
+ placeholder: toolbar.anyOf.placeholder ?? "Match any of\u2026",
14833
+ className: "h-8 pl-7 pr-7 text-sm",
14644
14834
  style: { fontSize: "16px" }
14645
14835
  }
14646
14836
  ),
14647
- searchValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14837
+ anyOfValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14648
14838
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14649
14839
  "button",
14650
14840
  {
14651
14841
  type: "button",
14652
- "aria-label": "Clear search",
14842
+ "aria-label": "Clear match-any search",
14653
14843
  className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14654
- onClick: () => setSearchValue(""),
14844
+ onClick: () => setAnyOfValue(""),
14655
14845
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-5 w-5" })
14656
14846
  }
14657
14847
  ) }),
14658
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear search" })
14848
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear match-any search" })
14659
14849
  ] }) : null
14660
- ] }),
14661
- toolbar?.searchMatch ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14662
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14663
- Button,
14664
- {
14665
- type: "button",
14666
- variant: searchMatchMode === "whole_words" ? "secondary" : "outline",
14667
- size: "sm",
14668
- className: "h-8 shrink-0 gap-1 rounded-l-none border-l-0 px-2 text-xs",
14669
- "aria-pressed": searchMatchMode === "whole_words",
14670
- "aria-label": `Search match: ${searchMatchMode === "whole_words" ? "whole words" : "contains text"}`,
14671
- onClick: () => setSearchMatchMode(
14672
- searchMatchMode === "whole_words" ? "contains" : "whole_words"
14850
+ ] }) : null,
14851
+ renderedFacets ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ToolbarFacets, { facets: renderedFacets }) : null,
14852
+ toolbar?.leading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14853
+ "div",
14854
+ {
14855
+ "data-matrx-table-toolbar-extras": true,
14856
+ className: "order-last flex w-full min-w-0 flex-wrap items-center gap-2",
14857
+ children: toolbar.leading
14858
+ }
14859
+ ) : null,
14860
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14861
+ "div",
14862
+ {
14863
+ "data-matrx-table-actions": true,
14864
+ className: cn(
14865
+ "ml-auto flex min-w-0 items-center gap-0",
14866
+ toolbar?.singleRow ? "shrink-0 flex-nowrap" : "flex-wrap"
14867
+ ),
14868
+ children: [
14869
+ toolbar?.singleRow && coverageResult && coverageResult.notice ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14870
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14871
+ "span",
14872
+ {
14873
+ "data-matrx-table-coverage": true,
14874
+ "data-matrx-table-coverage-scope": coverageResult.scope,
14875
+ role: "status",
14876
+ tabIndex: 0,
14877
+ className: "mr-1 inline-flex shrink-0 items-center whitespace-nowrap rounded-md border border-amber-300/70 bg-amber-50 px-2 py-0.5 text-[11px] font-semibold text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
14878
+ children: [
14879
+ coverageResult.badge,
14880
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "sr-only", children: [
14881
+ ": ",
14882
+ coverageResult.notice
14883
+ ] })
14884
+ ]
14885
+ }
14886
+ ) }),
14887
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { className: "max-w-xs", children: coverageResult.notice })
14888
+ ] }) : null,
14889
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14890
+ TableToolbarAction,
14891
+ {
14892
+ HostAction: host.ToolbarAction,
14893
+ ariaLabel: "Clear all filters",
14894
+ tooltip: hasActiveFilters ? "Clear search and filters; keep columns and sort" : "No search or filters to clear",
14895
+ disabled: !hasActiveFilters,
14896
+ onClick: clearAllFilters,
14897
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Eraser, {})
14898
+ }
14673
14899
  ),
14674
- children: [
14675
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(WholeWord, { className: "h-5 w-5" }),
14676
- searchMatchMode === "whole_words" ? "Whole words" : "Contains"
14677
- ]
14678
- }
14679
- ) }),
14680
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: searchMatchMode === "whole_words" ? "Whole words \u2014 click to match text anywhere" : "Contains text \u2014 click to require whole words" })
14681
- ] }) : null
14900
+ showToolbarCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14901
+ CopyButtons,
14902
+ {
14903
+ size: "toolbar",
14904
+ sourceId: `${tableCopySourceNamespace}:view`,
14905
+ label: effectiveCopy.listLabel ?? `${effectiveCopy.label} view`,
14906
+ ...effectiveCopy.references ? { references: effectiveCopy.references(processed, data) } : {},
14907
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14908
+ ...copy === void 0 ? {} : {
14909
+ human: () => effectiveCopy.listHuman?.(processed, data) ?? buildViewHuman(effectiveCopy, processed, effectiveCopyColumns),
14910
+ json: () => effectiveCopy.listJson?.(processed, data) ?? processed.map((r) => effectiveCopy.agentRow ? effectiveCopy.agentRow(r) : r)
14911
+ },
14912
+ agent: () => copy !== void 0 && effectiveCopy.listAgent ? effectiveCopy.listAgent(processed, data) : copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, processed, data, declaredVisibleCopyColumns, {
14913
+ search: searchValue,
14914
+ searchMatchMode,
14915
+ anyOf: anyOfValue,
14916
+ filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14917
+ sort: sort ? `${sort.id}:${sort.direction}` : null
14918
+ }) : buildViewAgentInput(effectiveCopy, processed, data, {
14919
+ search: searchValue,
14920
+ searchMatchMode,
14921
+ anyOf: anyOfValue,
14922
+ filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14923
+ sort: sort ? `${sort.id}:${sort.direction}` : null
14924
+ }),
14925
+ aiVariants: [
14926
+ ...effectiveCopy.aiVariants?.(processed, data) ?? [],
14927
+ ...copySubsetVariantFor(() => processed, "view")
14928
+ ],
14929
+ ...effectiveCopy.aiCustom ? { aiCustom: effectiveCopy.aiCustom(processed, data) } : {},
14930
+ export: {
14931
+ items: [],
14932
+ sheetRows: () => processed.map(
14933
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14934
+ ),
14935
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14936
+ id: columnId(column),
14937
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14938
+ })),
14939
+ ...effectiveCopy.export?.(processed, data)
14940
+ }
14941
+ }
14942
+ ) : null,
14943
+ grouping?.onColumnIdChange ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14944
+ Select,
14945
+ {
14946
+ value: groupingColumnId ?? NO_GROUPING_VALUE,
14947
+ onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14948
+ children: [
14949
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14950
+ SelectTrigger,
14951
+ {
14952
+ "data-matrx-table-group-by": true,
14953
+ "aria-label": "Group rows by a column",
14954
+ className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14955
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, { placeholder: "No groups" })
14956
+ }
14957
+ ),
14958
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(SelectContent, { children: [
14959
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14960
+ groupableColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14961
+ ] })
14962
+ ]
14963
+ }
14964
+ ) : null,
14965
+ columns.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14966
+ TableToolbarAction,
14967
+ {
14968
+ HostAction: host.ToolbarAction,
14969
+ ariaLabel: "Columns",
14970
+ tooltip: "Show, hide, or reorder columns",
14971
+ onClick: () => setColumnManagerOpen(true),
14972
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SlidersHorizontal, {})
14973
+ }
14974
+ ) : null,
14975
+ toolbar?.refresh ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14976
+ TableToolbarAction,
14977
+ {
14978
+ HostAction: host.ToolbarAction,
14979
+ ariaLabel: toolbar.refresh.label ?? "Refresh",
14980
+ tooltip: isFetching ? "Refreshing table data" : toolbar.refresh.label ?? "Refresh",
14981
+ disabled: isFetching || toolbarActionPending.refresh,
14982
+ busy: isFetching || toolbarActionPending.refresh,
14983
+ onClick: () => {
14984
+ if (toolbarActionPendingRef.current.refresh) return;
14985
+ toolbarActionPendingRef.current.refresh = true;
14986
+ setToolbarActionPending((current) => ({ ...current, refresh: true }));
14987
+ setToolbarActionError(null);
14988
+ void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
14989
+ const message = cause instanceof Error ? cause.message : String(cause);
14990
+ setToolbarActionError(`Refresh failed: ${message}`);
14991
+ toast.error(`Refresh failed: ${message}`);
14992
+ }).finally(() => {
14993
+ toolbarActionPendingRef.current.refresh = false;
14994
+ setToolbarActionPending((current) => ({ ...current, refresh: false }));
14995
+ });
14996
+ },
14997
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefreshControlIcon, { spinning: isFetching || toolbarActionPending.refresh })
14998
+ }
14999
+ ) : null,
15000
+ toolbar?.add ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15001
+ TableToolbarAction,
15002
+ {
15003
+ HostAction: host.ToolbarAction,
15004
+ ariaLabel: "Add",
15005
+ tooltip: toolbar.add.disabled ? toolbar.add.disabledReason ?? "Add unavailable" : "Add",
15006
+ disabled: toolbar.add.disabled || toolbarActionPending.add,
15007
+ busy: toolbarActionPending.add,
15008
+ onClick: () => {
15009
+ if (toolbarActionPendingRef.current.add) return;
15010
+ toolbarActionPendingRef.current.add = true;
15011
+ setToolbarActionPending((current) => ({ ...current, add: true }));
15012
+ setToolbarActionError(null);
15013
+ void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
15014
+ const message = cause instanceof Error ? cause.message : String(cause);
15015
+ setToolbarActionError(`Add failed: ${message}`);
15016
+ toast.error(`Add failed: ${message}`);
15017
+ }).finally(() => {
15018
+ toolbarActionPendingRef.current.add = false;
15019
+ setToolbarActionPending((current) => ({ ...current, add: false }));
15020
+ });
15021
+ },
15022
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Plus, {})
15023
+ }
15024
+ ) : null,
15025
+ toolbar?.actions
15026
+ ]
15027
+ }
15028
+ )
14682
15029
  ]
14683
15030
  }
14684
- ) : null),
14685
- children: [
14686
- toolbar?.layeredFilters ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14687
- LayeredFilterBuilder,
14688
- {
14689
- fields: toolbar.layeredFilters.fields,
14690
- rules: layeredFilters,
14691
- onChange: setLayeredFilters,
14692
- maxRules: toolbar.layeredFilters.maxRules,
14693
- label: toolbar.layeredFilters.label
14694
- }
14695
- ) : null,
14696
- toolbar?.anyOf ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative w-full max-w-xs", children: [
14697
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Search, { className: "absolute left-2 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" }),
14698
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14699
- Input,
14700
- {
14701
- value: anyOfValue,
14702
- onChange: (e) => setAnyOfValue(e.target.value),
14703
- placeholder: toolbar.anyOf.placeholder ?? "Match any of\u2026",
14704
- className: "h-8 pl-7 pr-7 text-sm",
14705
- style: { fontSize: "16px" }
14706
- }
14707
- ),
14708
- anyOfValue ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14709
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14710
- "button",
14711
- {
14712
- type: "button",
14713
- "aria-label": "Clear match-any search",
14714
- className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground",
14715
- onClick: () => setAnyOfValue(""),
14716
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-5 w-5" })
14717
- }
14718
- ) }),
14719
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Clear match-any search" })
14720
- ] }) : null
14721
- ] }) : null,
14722
- renderedFacets ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ToolbarFacets, { facets: renderedFacets }) : null,
14723
- toolbar?.leading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15031
+ )),
15032
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
15033
+ coverageResult && coverageResult.notice && !toolbar?.singleRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14724
15034
  "div",
14725
15035
  {
14726
- "data-matrx-table-toolbar-extras": true,
14727
- className: "order-last flex w-full min-w-0 flex-wrap items-center gap-2",
14728
- children: toolbar.leading
15036
+ "data-matrx-table-coverage": true,
15037
+ "data-matrx-table-coverage-scope": coverageResult.scope,
15038
+ role: "status",
15039
+ className: "flex shrink-0 items-start gap-2 rounded-md border border-amber-300/70 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
15040
+ children: [
15041
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 whitespace-nowrap font-semibold", children: coverageResult.badge }),
15042
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0", children: coverageResult.notice })
15043
+ ]
14729
15044
  }
14730
15045
  ) : null,
14731
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14732
- "div",
14733
- {
14734
- "data-matrx-table-actions": true,
14735
- className: cn(
14736
- "ml-auto flex min-w-0 items-center gap-0",
14737
- toolbar?.singleRow ? "shrink-0 flex-nowrap" : "flex-wrap"
14738
- ),
14739
- children: [
14740
- toolbar?.singleRow && coverageResult && coverageResult.notice ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
14741
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14742
- "span",
15046
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
15047
+ mobileCards ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15048
+ "div",
15049
+ {
15050
+ ref: cardsScrollRef,
15051
+ "data-matrx-table-cards-scroll": true,
15052
+ "aria-busy": isLoading || isFetching,
15053
+ ...appendQuery === null ? {} : { style: { containerType: "inline-size" } },
15054
+ className: cn(
15055
+ "relative flex h-full min-h-0 flex-col gap-2 overflow-y-auto rounded-md border border-border bg-card p-2",
15056
+ mobileCardsClass
15057
+ ),
15058
+ children: [
15059
+ isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15060
+ "div",
14743
15061
  {
14744
- "data-matrx-table-coverage": true,
14745
- "data-matrx-table-coverage-scope": coverageResult.scope,
14746
15062
  role: "status",
14747
- tabIndex: 0,
14748
- className: "mr-1 inline-flex shrink-0 items-center whitespace-nowrap rounded-md border border-amber-300/70 bg-amber-50 px-2 py-0.5 text-[11px] font-semibold text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-200",
15063
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14749
15064
  children: [
14750
- coverageResult.badge,
14751
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "sr-only", children: [
14752
- ": ",
14753
- coverageResult.notice
14754
- ] })
15065
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
15066
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
14755
15067
  ]
14756
15068
  }
14757
- ) }),
14758
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { className: "max-w-xs", children: coverageResult.notice })
14759
- ] }) : null,
14760
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14761
- TableToolbarAction,
14762
- {
14763
- HostAction: host.ToolbarAction,
14764
- ariaLabel: "Clear all filters",
14765
- tooltip: hasActiveFilters ? "Clear search and filters; keep columns and sort" : "No search or filters to clear",
14766
- disabled: !hasActiveFilters,
14767
- onClick: clearAllFilters,
14768
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Eraser, {})
14769
- }
14770
- ),
14771
- showToolbarCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14772
- CopyButtons,
14773
- {
14774
- size: "toolbar",
14775
- sourceId: `${tableCopySourceNamespace}:view`,
14776
- label: effectiveCopy.listLabel ?? `${effectiveCopy.label} view`,
14777
- ...effectiveCopy.references ? { references: effectiveCopy.references(processed, data) } : {},
14778
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14779
- ...copy === void 0 ? {} : {
14780
- human: () => effectiveCopy.listHuman?.(processed, data) ?? buildViewHuman(effectiveCopy, processed, effectiveCopyColumns),
14781
- json: () => effectiveCopy.listJson?.(processed, data) ?? processed.map((r) => effectiveCopy.agentRow ? effectiveCopy.agentRow(r) : r)
15069
+ ) : null,
15070
+ isLoading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15071
+ Skeleton,
15072
+ {
15073
+ className: "h-52 w-full shrink-0 rounded-lg"
14782
15074
  },
14783
- agent: () => copy !== void 0 && effectiveCopy.listAgent ? effectiveCopy.listAgent(processed, data) : copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, processed, data, declaredVisibleCopyColumns, {
14784
- search: searchValue,
14785
- searchMatchMode,
14786
- anyOf: anyOfValue,
14787
- filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14788
- sort: sort ? `${sort.id}:${sort.direction}` : null
14789
- }) : buildViewAgentInput(effectiveCopy, processed, data, {
14790
- search: searchValue,
14791
- searchMatchMode,
14792
- anyOf: anyOfValue,
14793
- filterCount: activeFilterCount + completeLayeredFilterRules(layeredFilters).length,
14794
- sort: sort ? `${sort.id}:${sort.direction}` : null
14795
- }),
14796
- aiVariants: [
14797
- ...effectiveCopy.aiVariants?.(processed, data) ?? [],
14798
- ...copySubsetVariantFor(() => processed, "view")
14799
- ],
14800
- ...effectiveCopy.aiCustom ? { aiCustom: effectiveCopy.aiCustom(processed, data) } : {},
14801
- export: {
14802
- items: [],
14803
- sheetRows: () => processed.map(
14804
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14805
- ),
14806
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14807
- id: columnId(column),
14808
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14809
- })),
14810
- ...effectiveCopy.export?.(processed, data)
14811
- }
14812
- }
14813
- ) : null,
14814
- grouping?.onColumnIdChange ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14815
- Select,
14816
- {
14817
- value: groupingColumnId ?? NO_GROUPING_VALUE,
14818
- onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14819
- children: [
14820
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14821
- SelectTrigger,
15075
+ `mobile-card-sk-${index}`
15076
+ )) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex min-h-48 flex-1 items-center justify-center px-4 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex max-w-sm flex-col items-center gap-2", children: [
15077
+ renderedEmptyState?.icon,
15078
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15079
+ renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15080
+ renderedEmptyState?.action
15081
+ ] }) }) : paginated.map((row, index) => {
15082
+ const id = getRowId(row);
15083
+ const displayRow = applyRowEdits(row, edits[id]);
15084
+ const controls = buildRecordControls(displayRow);
15085
+ const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
15086
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
15087
+ renderCell: (columnKey) => {
15088
+ const column = columns.find((candidate) => columnId(candidate) === columnKey);
15089
+ return column ? renderRecordCell(row, column, index) : null;
15090
+ },
15091
+ selected: selectedIdSet.has(getSelectionId(row)),
15092
+ selectable,
15093
+ onSelectedChange: (nextSelected) => {
15094
+ if (!selection || !selectable || nextSelected === selectedIdSet.has(getSelectionId(row))) {
15095
+ return;
15096
+ }
15097
+ const next = new Set(selectedIdSet);
15098
+ const selectionId = getSelectionId(row);
15099
+ if (nextSelected) next.add(selectionId);
15100
+ else next.delete(selectionId);
15101
+ setSelectedIds(next);
15102
+ },
15103
+ actions: showInlineRowCopy || rowActions ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15104
+ "div",
14822
15105
  {
14823
- "data-matrx-table-group-by": true,
14824
- "aria-label": "Group rows by a column",
14825
- className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14826
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, { placeholder: "No groups" })
15106
+ "data-matrx-table-row-actions": true,
15107
+ className: "inline-flex items-center justify-end gap-0",
15108
+ onClick: (event) => event.stopPropagation(),
15109
+ children: [
15110
+ showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15111
+ TableRowCopyControl,
15112
+ {
15113
+ size: "toolbar",
15114
+ copy: effectiveCopy,
15115
+ row: displayRow,
15116
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15117
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15118
+ }
15119
+ ) : null,
15120
+ renderRowActions(displayRow, controls)
15121
+ ]
14827
15122
  }
14828
- ),
14829
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(SelectContent, { children: [
14830
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14831
- groupableColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14832
- ] })
14833
- ]
14834
- }
14835
- ) : null,
14836
- columns.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14837
- TableToolbarAction,
14838
- {
14839
- HostAction: host.ToolbarAction,
14840
- ariaLabel: "Columns",
14841
- tooltip: "Show, hide, or reorder columns",
14842
- onClick: () => setColumnManagerOpen(true),
14843
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SlidersHorizontal, {})
14844
- }
14845
- ) : null,
14846
- toolbar?.refresh ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14847
- TableToolbarAction,
14848
- {
14849
- HostAction: host.ToolbarAction,
14850
- ariaLabel: toolbar.refresh.label ?? "Refresh",
14851
- tooltip: isFetching ? "Refreshing table data" : toolbar.refresh.label ?? "Refresh",
14852
- disabled: isFetching || toolbarActionPending.refresh,
14853
- busy: isFetching || toolbarActionPending.refresh,
14854
- onClick: () => {
14855
- if (toolbarActionPendingRef.current.refresh) return;
14856
- toolbarActionPendingRef.current.refresh = true;
14857
- setToolbarActionPending((current) => ({ ...current, refresh: true }));
14858
- setToolbarActionError(null);
14859
- void Promise.resolve().then(() => toolbar.refresh?.onRefresh()).catch((cause) => {
14860
- const message = cause instanceof Error ? cause.message : String(cause);
14861
- setToolbarActionError(`Refresh failed: ${message}`);
14862
- toast.error(`Refresh failed: ${message}`);
14863
- }).finally(() => {
14864
- toolbarActionPendingRef.current.refresh = false;
14865
- setToolbarActionPending((current) => ({ ...current, refresh: false }));
14866
- });
14867
- },
14868
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefreshControlIcon, { spinning: isFetching || toolbarActionPending.refresh })
14869
- }
14870
- ) : null,
14871
- toolbar?.add ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14872
- TableToolbarAction,
14873
- {
14874
- HostAction: host.ToolbarAction,
14875
- ariaLabel: "Add",
14876
- tooltip: toolbar.add.disabled ? toolbar.add.disabledReason ?? "Add unavailable" : "Add",
14877
- disabled: toolbar.add.disabled || toolbarActionPending.add,
14878
- busy: toolbarActionPending.add,
14879
- onClick: () => {
14880
- if (toolbarActionPendingRef.current.add) return;
14881
- toolbarActionPendingRef.current.add = true;
14882
- setToolbarActionPending((current) => ({ ...current, add: true }));
14883
- setToolbarActionError(null);
14884
- void Promise.resolve().then(() => toolbar.add?.onAdd()).catch((cause) => {
14885
- const message = cause instanceof Error ? cause.message : String(cause);
14886
- setToolbarActionError(`Add failed: ${message}`);
14887
- toast.error(`Add failed: ${message}`);
14888
- }).finally(() => {
14889
- toolbarActionPendingRef.current.add = false;
14890
- setToolbarActionPending((current) => ({ ...current, add: false }));
14891
- });
14892
- },
14893
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Plus, {})
14894
- }
14895
- ) : null,
14896
- toolbar?.actions
14897
- ]
14898
- }
14899
- )
14900
- ]
14901
- }
14902
- )),
14903
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
14904
- coverageResult && coverageResult.notice && !toolbar?.singleRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14905
- "div",
14906
- {
14907
- "data-matrx-table-coverage": true,
14908
- "data-matrx-table-coverage-scope": coverageResult.scope,
14909
- role: "status",
14910
- 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",
14911
- children: [
14912
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0 whitespace-nowrap font-semibold", children: coverageResult.badge }),
14913
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0", children: coverageResult.notice })
14914
- ]
14915
- }
14916
- ) : null,
14917
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
14918
- mobileCards ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14919
- "div",
14920
- {
14921
- ref: cardsScrollRef,
14922
- "data-matrx-table-cards-scroll": true,
14923
- "aria-busy": isLoading || isFetching,
14924
- ...appendQuery === null ? {} : { style: { containerType: "inline-size" } },
14925
- className: cn(
14926
- "relative flex h-full min-h-0 flex-col gap-2 overflow-y-auto rounded-md border border-border bg-card p-2",
14927
- mobileCardsClass
14928
- ),
14929
- children: [
14930
- isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14931
- "div",
14932
- {
14933
- role: "status",
14934
- className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
14935
- children: [
14936
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
14937
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
14938
- ]
14939
- }
14940
- ) : null,
14941
- isLoading ? Array.from({ length: 4 }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14942
- Skeleton,
14943
- {
14944
- className: "h-52 w-full shrink-0 rounded-lg"
14945
- },
14946
- `mobile-card-sk-${index}`
14947
- )) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex min-h-48 flex-1 items-center justify-center px-4 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex max-w-sm flex-col items-center gap-2", children: [
14948
- renderedEmptyState?.icon,
14949
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
14950
- renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
14951
- renderedEmptyState?.action
14952
- ] }) }) : paginated.map((row, index) => {
14953
- const id = getRowId(row);
14954
- const displayRow = applyRowEdits(row, edits[id]);
14955
- const controls = buildRecordControls(displayRow);
14956
- const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
14957
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
14958
- renderCell: (columnKey) => {
14959
- const column = columns.find((candidate) => columnId(candidate) === columnKey);
14960
- return column ? renderRecordCell(row, column, index) : null;
14961
- },
14962
- selected: selectedIdSet.has(getSelectionId(row)),
14963
- selectable,
14964
- onSelectedChange: (nextSelected) => {
14965
- if (!selection || !selectable || nextSelected === selectedIdSet.has(getSelectionId(row))) {
14966
- return;
14967
- }
14968
- const next = new Set(selectedIdSet);
14969
- const selectionId = getSelectionId(row);
14970
- if (nextSelected) next.add(selectionId);
14971
- else next.delete(selectionId);
14972
- setSelectedIds(next);
14973
- },
14974
- actions: showInlineRowCopy || rowActions ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15123
+ ) : null,
15124
+ ...canExpandRow(row) ? {
15125
+ isExpanded: isRowExpanded(row),
15126
+ toggleExpanded: () => toggleExpanded(row)
15127
+ } : {},
15128
+ expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
15129
+ }) }, id);
15130
+ }),
15131
+ appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14975
15132
  "div",
14976
15133
  {
14977
- "data-matrx-table-row-actions": true,
14978
- className: "inline-flex items-center justify-end gap-0",
14979
- onClick: (event) => event.stopPropagation(),
15134
+ "data-matrx-table-append-loading-card": true,
15135
+ role: "status",
15136
+ "aria-live": "polite",
15137
+ className: "sticky bottom-0 z-20 flex h-16 w-full max-w-[100cqw] shrink-0 items-center justify-center gap-2 rounded-md border border-border bg-card/95 px-3 text-sm font-medium text-foreground shadow-sm backdrop-blur-sm",
14980
15138
  children: [
14981
- showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14982
- TableRowCopyControl,
15139
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15140
+ Loader2,
14983
15141
  {
14984
- size: "toolbar",
14985
- copy: effectiveCopy,
14986
- row: displayRow,
14987
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
14988
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15142
+ "aria-hidden": "true",
15143
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
14989
15144
  }
14990
- ) : null,
14991
- renderRowActions(displayRow, controls)
15145
+ ),
15146
+ "Loading more rows\u2026"
14992
15147
  ]
14993
15148
  }
15149
+ ) : null
15150
+ ]
15151
+ }
15152
+ ) : null,
15153
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15154
+ "div",
15155
+ {
15156
+ ref: scrollRef,
15157
+ "data-matrx-table-scroll": true,
15158
+ onScroll: updateScrollHint,
15159
+ ...spreadsheetEnabled ? {
15160
+ tabIndex: sheet.containerProps.tabIndex,
15161
+ onKeyDown: sheet.containerProps.onKeyDown,
15162
+ onCopy: sheet.containerProps.onCopy,
15163
+ onCut: sheet.containerProps.onCut,
15164
+ onPaste: sheet.containerProps.onPaste,
15165
+ "data-matrx-table-spreadsheet": "true"
15166
+ } : {},
15167
+ "aria-busy": isLoading || isFetching,
15168
+ style: { containerType: "inline-size" },
15169
+ className: cn(
15170
+ "relative h-full w-full overflow-auto rounded-md border border-border bg-card",
15171
+ mobileCards && tableWithCardsClass,
15172
+ tableClassName
15173
+ ),
15174
+ children: [
15175
+ hierarchy && draggedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15176
+ HierarchyRootDropTarget,
15177
+ {
15178
+ label: hierarchy.rootDropLabel ?? "Drop here to move to the top level",
15179
+ draggedLabel: hierarchy.itemLabel?.(draggedRow) ?? "Moving row"
15180
+ }
14994
15181
  ) : null,
14995
- ...canExpandRow(row) ? {
14996
- isExpanded: isRowExpanded(row),
14997
- toggleExpanded: () => toggleExpanded(row)
14998
- } : {},
14999
- expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
15000
- }) }, id);
15001
- }),
15002
- appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15003
- "div",
15004
- {
15005
- "data-matrx-table-append-loading-card": true,
15006
- role: "status",
15007
- "aria-live": "polite",
15008
- 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",
15009
- children: [
15010
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15011
- Loader2,
15012
- {
15013
- "aria-hidden": "true",
15014
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15015
- }
15016
- ),
15017
- "Loading more rows\u2026"
15018
- ]
15019
- }
15020
- ) : null
15021
- ]
15022
- }
15023
- ) : null,
15024
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15025
- "div",
15026
- {
15027
- ref: scrollRef,
15028
- "data-matrx-table-scroll": true,
15029
- onScroll: updateScrollHint,
15030
- ...spreadsheetEnabled ? {
15031
- tabIndex: sheet.containerProps.tabIndex,
15032
- onKeyDown: sheet.containerProps.onKeyDown,
15033
- onCopy: sheet.containerProps.onCopy,
15034
- onCut: sheet.containerProps.onCut,
15035
- onPaste: sheet.containerProps.onPaste,
15036
- "data-matrx-table-spreadsheet": "true"
15037
- } : {},
15038
- "aria-busy": isLoading || isFetching,
15039
- style: { containerType: "inline-size" },
15040
- className: cn(
15041
- "relative h-full w-full overflow-auto rounded-md border border-border bg-card",
15042
- mobileCards && tableWithCardsClass,
15043
- tableClassName
15044
- ),
15045
- children: [
15046
- hierarchy && draggedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15047
- HierarchyRootDropTarget,
15048
- {
15049
- label: hierarchy.rootDropLabel ?? "Drop here to move to the top level",
15050
- draggedLabel: hierarchy.itemLabel?.(draggedRow) ?? "Moving row"
15051
- }
15052
- ) : null,
15053
- hierarchy && draggedRowId ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15054
- HierarchyDragIndicatorLayer,
15055
- {
15056
- scrollRef,
15057
- resolve: resolveHierarchyMove
15058
- }
15059
- ) : null,
15060
- isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15061
- "div",
15062
- {
15063
- role: "status",
15064
- className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
15065
- children: [
15066
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
15067
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
15068
- ]
15069
- }
15070
- ) : null,
15071
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15072
- "table",
15073
- {
15074
- ref: tableRef,
15075
- style: !fixedLayoutActive ? void 0 : {
15076
- // Track the same preview variables as the cells. A stale
15077
- // pre-resize total redistributes spare width under fixed
15078
- // layout and defeats both exact shrinking and max bounds.
15079
- "--matrx-resized-table-width": `calc(${visibleColumns.map((column) => {
15080
- const id = columnId(column);
15081
- return resizableColumns && column.resizable !== false ? `var(${columnResizeCssVar(id)}, ${effectiveColumnWidths[id]}px)` : `${effectiveColumnWidths[id]}px`;
15082
- }).join(" + ")} + ${(selection ? fixedTableGeometry?.selectionWidth ?? 0 : 0) + (showActionsCol ? fixedTableGeometry?.actionsWidth ?? 0 : 0)}px)`
15083
- },
15084
- className: cn(
15085
- "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",
15086
- fixedLayoutActive && "sm:table-fixed sm:max-w-none sm:w-[var(--matrx-resized-table-width)]"
15087
- ),
15088
- children: [
15089
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15090
- "thead",
15091
- {
15092
- ref: tableHeaderRef,
15093
- "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
15094
- className: cn(
15095
- // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
15096
- // cell is `sm:z-20` so it can slide under the columns to
15097
- // its right; the header was `z-10`, so the frozen column's
15098
- // cells scrolled straight OVER the header they belong to
15099
- // and the top data row printed through the column names.
15100
- // Only a table long enough to scroll shows it, which is why
15101
- // it survived until a hundred-thousand-row screenshot.
15102
- "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
15103
- stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
15104
- ),
15105
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15106
- selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15107
- "th",
15108
- {
15109
- "data-matrx-table-selection-column": true,
15110
- className: cn(
15111
- "h-9 w-9 px-2 text-left align-middle",
15112
- frozenColumns.selectionOffset !== null && "sm:sticky sm:left-0 sm:z-30 sm:bg-muted"
15113
- ),
15114
- style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15115
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15116
- "div",
15117
- {
15118
- "data-matrx-table-selection-target": true,
15119
- onClick: (event) => {
15120
- if (event.target === event.currentTarget) {
15121
- event.currentTarget.querySelector('[role="checkbox"]')?.click();
15122
- }
15123
- },
15124
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15125
- Checkbox,
15126
- {
15127
- className: CHECKBOX_TAP_AREA,
15128
- checked: allOnPageSelected ? true : someOnPageSelected ? "indeterminate" : false,
15129
- disabled: selectableRows.length === 0,
15130
- onCheckedChange: toggleAllOnPage,
15131
- "aria-label": allOnPageSelected ? "Clear selection on this page" : "Select every row on this page"
15132
- }
15133
- )
15134
- }
15135
- )
15136
- }
15137
- ) : null,
15138
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15139
- "th",
15140
- {
15141
- "aria-hidden": "true",
15142
- "data-matrx-table-column-spacer": "start",
15143
- className: "border-0 p-0",
15144
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15145
- },
15146
- "matrx-column-spacer-start"
15147
- ) : null,
15148
- renderedColumns.map((col) => {
15149
- const id = columnId(col);
15150
- const meta = filterMeta.get(id);
15151
- const isSorted = sort?.id === id;
15152
- const headerLabel = col.label ?? (typeof col.header === "string" ? col.header : id);
15153
- const defaultSortDirection = col.defaultSortDirection ?? "asc";
15154
- const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
15155
- const columnResizable = resizableColumns && col.resizable !== false;
15156
- const marker = resolveColumnMarker(col);
15157
- const compact = col.compact || marker !== void 0;
15158
- const width = marker && col.width === void 0 ? 40 : col.width;
15159
- const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
15160
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15161
- "th",
15162
- {
15163
- "data-matrx-table-column-id": id,
15164
- "data-matrx-header-column-id": id,
15165
- "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15166
- "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
15167
- className: cn(
15168
- "relative h-9 text-left align-middle max-sm:whitespace-nowrap",
15169
- // ICON COLUMN (MatrxColumnDef.compact): a 40px star
15170
- // column cannot honor `width: 40` while paying 16px of
15171
- // padding for a 14px glyph.
15172
- compact ? "px-1" : "px-2",
15173
- // An intentional mobile column set (see
15174
- // MatrxColumnDef.mobileHidden). CSS, not a JS
15175
- // breakpoint — no hydration mismatch, and the column
15176
- // stays sortable/filterable from the toolbar.
15177
- col.mobileHidden && "max-sm:hidden",
15178
- // Consumer widths are desktop tuning: applied from `sm`
15179
- // up via a CSS var. The minimum is load-bearing: without
15180
- // it, a crowded table crushes a declared 190px column to a
15181
- // few characters wide instead of using the canonical
15182
- // horizontal-scroll surface.
15183
- (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
15184
- col.headerClassName,
15185
- isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15186
- alignForFormat(col) === "center" && "text-center",
15187
- alignForFormat(col) === "right" && "text-right",
15188
- frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
15189
- frozenEdgeId === id && FROZEN_EDGE_CLASS
15190
- ),
15191
- style: {
15192
- ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
15193
- ...frozenStyle(id)
15194
- },
15195
- children: [
15196
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15197
- ColumnHeaderCell,
15198
- {
15199
- label: col.header,
15200
- labelText: headerLabel,
15201
- ...facetsEnabled ? {
15202
- facets: facetState[id]?.data ?? null,
15203
- facetsLoading: facetState[id]?.loading ?? false,
15204
- ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
15205
- onFilterOpen: () => requestFacets(id)
15206
- } : {},
15207
- sortable: col.sortable !== false,
15208
- isSorted: Boolean(isSorted),
15209
- sortDirection: sort?.direction ?? defaultSortDirection,
15210
- onSortAsc: () => setSort({ id, direction: "asc" }),
15211
- onSortDesc: () => setSort({ id, direction: "desc" }),
15212
- onClearSort: () => setSort(null),
15213
- onHeaderSortClick: () => {
15214
- if (!isSorted) {
15215
- setSort({ id, direction: defaultSortDirection });
15216
- return;
15217
- }
15218
- if (sort?.direction === "asc") {
15219
- setSort({ id, direction: "desc" });
15220
- return;
15221
- }
15222
- setSort({ id, direction: "asc" });
15223
- },
15224
- filterKind: meta?.kind ?? null,
15225
- filterValue: columnFilters[id],
15226
- onFilterChange: (next) => setColumnFilter(id, next),
15227
- selectOptions: meta?.options,
15228
- selectSingle: col.filterSingle,
15229
- selectAllowCustom: col.filterAllowCustom,
15230
- filterApplyLabel: filterUi?.applyLabel,
15231
- filterShowCancel: filterUi?.showCancel,
15232
- filterFooterHint: filterUi?.footerHint,
15233
- allowNegation: filterUi?.allowNegation,
15234
- allowNullModes: filterUi?.allowNullModes,
15235
- align: col.align,
15236
- compact,
15237
- marker,
15238
- reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0
15239
- }
15240
- ) }),
15241
- columnResizable ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15242
- ColumnResizeHandle,
15243
- {
15244
- columnId: id,
15245
- label: headerLabel,
15246
- tableRef,
15247
- minWidth,
15248
- maxWidth: col.maxWidth,
15249
- onResizeStart: captureResizeGestureSnapshot,
15250
- onResizeCancel: clearResizeGestureSnapshot,
15251
- onResizeCommit: (columnId2, width2) => commitColumnWidths(
15252
- { ...effectiveColumnWidths, [columnId2]: width2 },
15253
- true
15254
- )
15255
- }
15256
- ) : null
15257
- ]
15258
- },
15259
- id
15260
- );
15261
- }),
15262
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15263
- "th",
15264
- {
15265
- "aria-hidden": "true",
15266
- "data-matrx-table-column-spacer": "end",
15267
- className: "border-0 p-0",
15268
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15269
- },
15270
- "matrx-column-spacer-end"
15271
- ) : null,
15272
- showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15273
- "th",
15274
- {
15275
- "data-matrx-table-actions-column": true,
15276
- className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
15277
- style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15278
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
15279
- }
15280
- )
15281
- ] })
15282
- }
15283
- ),
15284
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tbody", { children: [
15285
- isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { className: "border-b border-border/60", children: [
15286
- selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
15287
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15288
- "td",
15289
- {
15290
- "aria-hidden": "true",
15291
- "data-matrx-table-column-spacer": "start",
15292
- className: "border-0 p-0",
15293
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15294
- },
15295
- "matrx-column-spacer-start"
15296
- ) : null,
15297
- renderedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15298
- "td",
15299
- {
15300
- className: cn(
15301
- "py-2",
15302
- col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15303
- col.mobileHidden && "max-sm:hidden"
15304
- ),
15305
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-5 w-full" })
15306
- },
15307
- columnId(col)
15308
- )),
15309
- showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "ml-auto h-5 w-10" }) }),
15310
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15311
- "td",
15312
- {
15313
- "aria-hidden": "true",
15314
- "data-matrx-table-column-spacer": "end",
15315
- className: "border-0 p-0",
15316
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15317
- },
15318
- "matrx-column-spacer-end"
15319
- ) : null
15320
- ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15321
- "td",
15322
- {
15323
- colSpan: renderedColSpan,
15324
- className: "px-0 py-12 text-center",
15325
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-empty-state": true, className: "sticky left-0 flex flex-col items-center gap-2 whitespace-normal px-4", style: { width: "100cqw", maxWidth: "100%" }, children: [
15326
- renderedEmptyState?.icon,
15327
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15328
- renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15329
- renderedEmptyState?.action
15330
- ] })
15331
- }
15332
- ) }) : bodyEntries.map((entry) => {
15333
- if (entry.kind === "spacer") {
15334
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15335
- "tr",
15336
- {
15337
- "aria-hidden": "true",
15338
- "data-matrx-table-virtual-spacer": entry.place,
15339
- style: { height: `${entry.height}px` },
15340
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15341
- "td",
15342
- {
15343
- className: "border-0 p-0",
15344
- colSpan: renderedColSpan
15345
- }
15346
- )
15347
- },
15348
- `matrx-virtual-spacer-${entry.place}`
15349
- );
15350
- }
15351
- if (entry.kind === "group") {
15352
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15353
- TableGroupHeaderRow,
15354
- {
15355
- group: entry.group,
15356
- collapsed: entry.collapsed,
15357
- columns: renderedColumns,
15358
- spacers: columnSpacers,
15359
- leadingCols,
15360
- showActionsCol,
15361
- rowNoun: grouping?.rowNoun ?? "row",
15362
- ...grouping?.renderLabel ? { renderLabel: grouping.renderLabel } : {},
15363
- onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
15364
- },
15365
- `matrx-group-${entry.group.key}`
15366
- );
15367
- }
15368
- const row = entry.row;
15369
- const index = entry.index;
15370
- const id = getRowId(row);
15371
- const isSelected = selectedId === id;
15372
- const rowEdits = edits[id];
15373
- const displayRow = applyRowEdits(row, rowEdits);
15374
- const controls = buildRecordControls(displayRow);
15375
- const isChecked = selectedIdSet.has(getSelectionId(row));
15376
- const background = rowBackground({
15377
- tint: rowTintColor(row, id),
15378
- highlighted: highlightedIndex === index,
15379
- selected: isSelected,
15380
- checked: isChecked,
15381
- zebraOdd: zebra && index % 2 === 1,
15382
- hoverable: detailEnabled || Boolean(onRowOpen) || canExpandRow(row)
15383
- });
15384
- const canHoverFrozenCell = background.frozenHover;
15385
- const frozenRowBackgroundClass = background.frozen;
15386
- const rowNode = /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15387
- "tr",
15182
+ hierarchy && draggedRowId ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15183
+ HierarchyDragIndicatorLayer,
15184
+ {
15185
+ scrollRef,
15186
+ resolve: resolveHierarchyMove
15187
+ }
15188
+ ) : null,
15189
+ isFetching && !isLoading && appendQuery === null ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15190
+ "div",
15191
+ {
15192
+ role: "status",
15193
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary/15",
15194
+ children: [
15195
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full w-full animate-pulse bg-primary" }),
15196
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Refreshing table data" })
15197
+ ]
15198
+ }
15199
+ ) : null,
15200
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15201
+ "table",
15202
+ {
15203
+ ref: tableRef,
15204
+ style: !fixedLayoutActive ? void 0 : {
15205
+ // Track the same preview variables as the cells. A stale
15206
+ // pre-resize total redistributes spare width under fixed
15207
+ // layout and defeats both exact shrinking and max bounds.
15208
+ "--matrx-resized-table-width": `calc(${visibleColumns.map((column) => {
15209
+ const id = columnId(column);
15210
+ return resizableColumns && column.resizable !== false ? `var(${columnResizeCssVar(id)}, ${effectiveColumnWidths[id]}px)` : `${effectiveColumnWidths[id]}px`;
15211
+ }).join(" + ")} + ${(selection ? fixedTableGeometry?.selectionWidth ?? 0 : 0) + (showActionsCol ? fixedTableGeometry?.actionsWidth ?? 0 : 0)}px)`
15212
+ },
15213
+ className: cn(
15214
+ "table w-max min-w-full max-w-none caption-bottom overflow-visible text-sm sm:w-full sm:min-w-0 sm:max-w-full",
15215
+ fixedLayoutActive && "sm:table-fixed sm:max-w-none sm:w-[var(--matrx-resized-table-width)]"
15216
+ ),
15217
+ children: [
15218
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15219
+ "thead",
15388
15220
  {
15389
- "data-row-id": id,
15390
- style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
15391
- "data-state": isSelected ? "selected" : void 0,
15392
- onClick: (e) => {
15393
- if (rowEventTargetsInteractiveDescendant(e.target)) return;
15394
- if (opensRowHref(e)) {
15395
- const href = getRowHref?.(row);
15396
- if (href) window.open(href, "_blank", "noopener");
15397
- return;
15398
- }
15399
- openRow(row);
15400
- },
15401
- onAuxClick: (e) => {
15402
- if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
15403
- const href = getRowHref?.(row);
15404
- if (href) {
15405
- e.preventDefault();
15406
- window.open(href, "_blank", "noopener");
15407
- }
15408
- },
15221
+ ref: tableHeaderRef,
15222
+ "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
15409
15223
  className: cn(
15410
- // Keep the mobile row opaque while the complete row scrolls
15411
- // horizontally. The translucent zebra/hover tints remain
15412
- // desktop-only so densely layered mobile content stays crisp.
15413
- "group/matrx-row border-b border-border/60 bg-card transition-colors",
15414
- (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer",
15415
- rowClassName?.(row, index),
15416
- draggedRowId === id && "opacity-40",
15417
- // LAST, and decided once: tint, then highlight / open / ticked, then
15418
- // the zebra stripe (./row-background.ts). Under a tint no stripe is
15419
- // emitted at all, so no `sm:` class can win the cascade over it.
15420
- background.row
15224
+ // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
15225
+ // cell is `sm:z-20` so it can slide under the columns to
15226
+ // its right; the header was `z-10`, so the frozen column's
15227
+ // cells scrolled straight OVER the header they belong to
15228
+ // and the top data row printed through the column names.
15229
+ // Only a table long enough to scroll shows it, which is why
15230
+ // it survived until a hundred-thousand-row screenshot.
15231
+ "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
15232
+ stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
15421
15233
  ),
15422
- children: [
15234
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15423
15235
  selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15424
- "td",
15236
+ "th",
15425
15237
  {
15238
+ "data-matrx-table-selection-column": true,
15426
15239
  className: cn(
15427
- "px-2 py-1.5 align-middle lg:py-0.5",
15428
- // When a data column is frozen the checkbox
15429
- // column has to freeze with it, or the first
15430
- // frozen column slides over the checkboxes.
15431
- frozenColumns.selectionOffset !== null && cn(
15432
- "sm:sticky sm:left-0 sm:z-20",
15433
- frozenRowBackgroundClass,
15434
- canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
15435
- )
15240
+ "h-9 w-9 px-2 text-left align-middle",
15241
+ frozenColumns.selectionOffset !== null && "sm:sticky sm:left-0 sm:z-30 sm:bg-muted"
15436
15242
  ),
15437
15243
  style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15438
- onClick: (e) => e.stopPropagation(),
15439
- children: selection.isRowSelectable?.(row) ?? true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15244
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15440
15245
  "div",
15441
15246
  {
15442
15247
  "data-matrx-table-selection-target": true,
@@ -15449,17 +15254,18 @@ function MatrxDataTableCore({
15449
15254
  Checkbox,
15450
15255
  {
15451
15256
  className: CHECKBOX_TAP_AREA,
15452
- checked: isChecked,
15453
- "aria-label": `Select this ${selectionNoun}`,
15454
- onClick: (e) => toggleRowSelected(index, e.shiftKey)
15257
+ checked: allOnPageSelected ? true : someOnPageSelected ? "indeterminate" : false,
15258
+ disabled: selectableRows.length === 0,
15259
+ onCheckedChange: toggleAllOnPage,
15260
+ "aria-label": allOnPageSelected ? "Clear selection on this page" : "Select every row on this page"
15455
15261
  }
15456
15262
  )
15457
15263
  }
15458
- ) : null
15264
+ )
15459
15265
  }
15460
15266
  ) : null,
15461
15267
  columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15462
- "td",
15268
+ "th",
15463
15269
  {
15464
15270
  "aria-hidden": "true",
15465
15271
  "data-matrx-table-column-spacer": "start",
@@ -15468,145 +15274,124 @@ function MatrxDataTableCore({
15468
15274
  },
15469
15275
  "matrx-column-spacer-start"
15470
15276
  ) : null,
15471
- renderedColumns.map((col, colIdx) => {
15472
- const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
15473
- const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
15474
- const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
15475
- const frozen = frozenColumns.offsets[columnId(col)] !== void 0;
15476
- const frozenCellTint = cellTintColor(
15477
- row,
15478
- id,
15479
- columnId(col)
15480
- );
15277
+ renderedColumns.map((col) => {
15278
+ const id = columnId(col);
15279
+ const meta = filterMeta.get(id);
15280
+ const isSorted = sort?.id === id;
15281
+ const headerLabel = col.label ?? (typeof col.header === "string" ? col.header : id);
15282
+ const defaultSortDirection = col.defaultSortDirection ?? "asc";
15283
+ const isColumnDragShadow = columnDragPreview?.draggedId === id && columnDragPreview.targetId !== null;
15284
+ const columnResizable = resizableColumns && col.resizable !== false;
15285
+ const marker = resolveColumnMarker(col);
15286
+ const compact = col.compact || marker !== void 0;
15287
+ const width = marker && col.width === void 0 ? 40 : col.width;
15288
+ const minWidth = marker && col.minWidth === void 0 ? 40 : col.minWidth;
15481
15289
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15482
- "td",
15290
+ "th",
15483
15291
  {
15484
- "data-matrx-table-column-id": columnId(col),
15292
+ "data-matrx-table-column-id": id,
15293
+ "data-matrx-header-column-id": id,
15485
15294
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15486
- ...sheetCell ? {
15487
- "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
15488
- "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
15489
- ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
15490
- ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
15491
- ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
15492
- onMouseDown: sheetCell.onMouseDown,
15493
- onMouseEnter: sheetCell.onMouseEnter
15494
- } : {},
15295
+ "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
15495
15296
  className: cn(
15496
- "py-1.5 align-middle lg:py-0.5",
15497
- sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
15498
- sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
15499
- sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
15500
- carriesFillHandle && "relative",
15501
- col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15502
- // nowrap (NOT truncate — truncate clips the cell and
15503
- // defeats w-max, killing the horizontal scroll).
15504
- "max-sm:whitespace-nowrap",
15297
+ "relative h-9 text-left align-middle max-sm:whitespace-nowrap",
15298
+ // ICON COLUMN (MatrxColumnDef.compact): a 40px star
15299
+ // column cannot honor `width: 40` while paying 16px of
15300
+ // padding for a 14px glyph.
15301
+ compact ? "px-1" : "px-2",
15302
+ // An intentional mobile column set (see
15303
+ // MatrxColumnDef.mobileHidden). CSS, not a JS
15304
+ // breakpoint — no hydration mismatch, and the column
15305
+ // stays sortable/filterable from the toolbar.
15505
15306
  col.mobileHidden && "max-sm:hidden",
15506
- ((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)]",
15507
- col.className,
15307
+ // Consumer widths are desktop tuning: applied from `sm`
15308
+ // up via a CSS var. The minimum is load-bearing: without
15309
+ // it, a crowded table crushes a declared 190px column to a
15310
+ // few characters wide instead of using the canonical
15311
+ // horizontal-scroll surface.
15312
+ (width !== void 0 || columnResizable) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
15313
+ col.headerClassName,
15508
15314
  isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15509
15315
  alignForFormat(col) === "center" && "text-center",
15510
15316
  alignForFormat(col) === "right" && "text-right",
15511
- frozen && FROZEN_CELL_CLASS,
15512
- frozen && frozenRowBackgroundClass,
15513
- frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
15514
- frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
15515
- frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
15516
- // A cell tint paints OVER the row tint beneath
15517
- // it, and the consumer's own hook comes last so
15518
- // it can always win.
15519
- cellTintClass(row, id, columnId(col)),
15520
- frozen && frozenCellTint && FROZEN_CELL_TINT_CLASS[frozenCellTint],
15521
- cellClassName?.(row, columnId(col), index)
15317
+ frozenColumns.offsets[id] !== void 0 && FROZEN_HEADER_CLASS,
15318
+ frozenEdgeId === id && FROZEN_EDGE_CLASS
15522
15319
  ),
15523
15320
  style: {
15524
- ...columnWidthVar(
15525
- resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
15526
- windowedWidthFor(columnId(col), col.width),
15527
- columnId(col),
15528
- resizableColumns && col.resizable !== false
15529
- ),
15530
- ...frozenStyle(columnId(col))
15321
+ ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
15322
+ ...frozenStyle(id)
15531
15323
  },
15532
15324
  children: [
15533
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15534
- "div",
15325
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15326
+ ColumnHeaderCell,
15535
15327
  {
15536
- className: cn(
15537
- colIdx === 0 && (hierarchy || canExpandRow(row)) && "flex min-w-0 items-center"
15538
- ),
15539
- children: [
15540
- colIdx === 0 && hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15541
- HierarchyDragHandle,
15542
- {
15543
- id,
15544
- label: hierarchy.itemLabel?.(row) ?? "row",
15545
- disabled: !(hierarchy.canReparent?.(row) ?? true)
15546
- }
15547
- ) : null,
15548
- colIdx === 0 && canExpandRow(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15549
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15550
- Button,
15551
- {
15552
- type: "button",
15553
- variant: "ghost",
15554
- size: "icon",
15555
- "data-matrx-table-expand-toggle": true,
15556
- className: "mr-1 h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground [&_svg]:size-4",
15557
- "aria-label": isRowExpanded(row) ? "Collapse row details" : "Expand row details",
15558
- "aria-expanded": isRowExpanded(row),
15559
- onClick: (event) => {
15560
- event.stopPropagation();
15561
- toggleExpanded(row);
15562
- },
15563
- onKeyDown: (event) => {
15564
- if (event.key !== "Enter" && event.key !== " ") return;
15565
- event.preventDefault();
15566
- event.stopPropagation();
15567
- toggleExpanded(row);
15568
- },
15569
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15570
- ChevronRight,
15571
- {
15572
- className: cn(
15573
- "transition-transform",
15574
- isRowExpanded(row) && "rotate-90"
15575
- )
15576
- }
15577
- )
15578
- }
15579
- ) }),
15580
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: isRowExpanded(row) ? "Collapse details" : "Expand details" })
15581
- ] }) : null,
15582
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15583
- "div",
15584
- {
15585
- "data-matrx-cell-content": true,
15586
- 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",
15587
- children: renderRecordCell(row, col, index)
15588
- }
15589
- )
15590
- ]
15328
+ label: col.header,
15329
+ labelText: headerLabel,
15330
+ ...facetsEnabled ? {
15331
+ facets: facetState[id]?.data ?? null,
15332
+ facetsLoading: facetState[id]?.loading ?? false,
15333
+ ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
15334
+ onFilterOpen: () => requestFacets(id)
15335
+ } : {},
15336
+ sortable: col.sortable !== false,
15337
+ isSorted: Boolean(isSorted),
15338
+ sortDirection: sort?.direction ?? defaultSortDirection,
15339
+ onSortAsc: () => setSort({ id, direction: "asc" }),
15340
+ onSortDesc: () => setSort({ id, direction: "desc" }),
15341
+ onClearSort: () => setSort(null),
15342
+ onHeaderSortClick: () => {
15343
+ if (!isSorted) {
15344
+ setSort({ id, direction: defaultSortDirection });
15345
+ return;
15346
+ }
15347
+ if (sort?.direction === "asc") {
15348
+ setSort({ id, direction: "desc" });
15349
+ return;
15350
+ }
15351
+ setSort({ id, direction: "asc" });
15352
+ },
15353
+ filterKind: meta?.kind ?? null,
15354
+ filterValue: columnFilters[id],
15355
+ onFilterChange: (next) => setColumnFilter(id, next),
15356
+ selectOptions: meta?.options,
15357
+ selectSingle: col.filterSingle,
15358
+ selectAllowCustom: col.filterAllowCustom,
15359
+ filterApplyLabel: filterUi?.applyLabel,
15360
+ filterShowCancel: filterUi?.showCancel,
15361
+ filterFooterHint: filterUi?.footerHint,
15362
+ allowNegation: filterUi?.allowNegation,
15363
+ allowNullModes: filterUi?.allowNullModes,
15364
+ align: col.align,
15365
+ compact,
15366
+ marker,
15367
+ reorderColumn: reorderableColumns ? { id, label: headerLabel } : void 0,
15368
+ menuItems: col.headerMenu,
15369
+ MenuIcon: host.MenuIcon
15591
15370
  }
15592
- ),
15593
- carriesFillHandle ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15594
- "button",
15371
+ ) }),
15372
+ columnResizable ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15373
+ ColumnResizeHandle,
15595
15374
  {
15596
- type: "button",
15597
- "data-matrx-table-fill-handle": true,
15598
- "aria-label": "Drag to fill the cells below or beside this selection",
15599
- onMouseDown: sheet.startFillDrag,
15600
- className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
15375
+ columnId: id,
15376
+ label: headerLabel,
15377
+ tableRef,
15378
+ minWidth,
15379
+ maxWidth: col.maxWidth,
15380
+ onResizeStart: captureResizeGestureSnapshot,
15381
+ onResizeCancel: clearResizeGestureSnapshot,
15382
+ onResizeCommit: (columnId2, width2) => commitColumnWidths(
15383
+ { ...effectiveColumnWidths, [columnId2]: width2 },
15384
+ true
15385
+ )
15601
15386
  }
15602
15387
  ) : null
15603
15388
  ]
15604
15389
  },
15605
- columnId(col)
15390
+ id
15606
15391
  );
15607
15392
  }),
15608
15393
  columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15609
- "td",
15394
+ "th",
15610
15395
  {
15611
15396
  "aria-hidden": "true",
15612
15397
  "data-matrx-table-column-spacer": "end",
@@ -15616,629 +15401,977 @@ function MatrxDataTableCore({
15616
15401
  "matrx-column-spacer-end"
15617
15402
  ) : null,
15618
15403
  showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15619
- "td",
15404
+ "th",
15620
15405
  {
15621
- "data-matrx-table-row-action-cell": true,
15622
- className: "text-right align-middle",
15406
+ "data-matrx-table-actions-column": true,
15407
+ className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
15623
15408
  style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15624
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15625
- "div",
15626
- {
15627
- "data-matrx-table-row-actions": true,
15628
- className: "inline-flex items-center justify-end gap-0",
15629
- onClick: (e) => e.stopPropagation(),
15630
- children: [
15631
- showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15632
- TableRowCopyControl,
15633
- {
15634
- size: "toolbar",
15635
- copy: effectiveCopy,
15636
- row: displayRow,
15637
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15638
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15639
- }
15640
- ) : null,
15641
- renderRowActions(displayRow, controls),
15642
- windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15643
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15644
- Button,
15645
- {
15646
- type: "button",
15647
- variant: "ghost",
15648
- size: "icon",
15649
- "data-matrx-table-icon-action": true,
15650
- className: "h-11 w-11 text-muted-foreground hover:text-foreground [&_svg]:size-5",
15651
- "aria-label": windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window",
15652
- onClick: (e) => {
15653
- e.stopPropagation();
15654
- if (windowConfig?.openOnRowClick === true) openDetail(row);
15655
- else openWindow(row);
15656
- },
15657
- children: opensWindowOnRowClick ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
15658
- }
15659
- ) }),
15660
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window" })
15661
- ] }) : null
15662
- ]
15663
- }
15664
- )
15409
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
15665
15410
  }
15666
15411
  )
15667
- ]
15668
- },
15669
- id
15670
- );
15671
- const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
15672
- const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15673
- "tr",
15674
- {
15675
- "data-matrx-table-expanded-detail": id,
15676
- className: cn("bg-muted/20", expandedDetail.className),
15677
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15412
+ ] })
15413
+ }
15414
+ ),
15415
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tbody", { children: [
15416
+ isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { className: "border-b border-border/60", children: [
15417
+ selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
15418
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15678
15419
  "td",
15679
15420
  {
15680
- colSpan: renderedColSpan,
15681
- className: "p-4",
15682
- children: expandedDetail.render(displayRow, controls)
15683
- }
15684
- )
15685
- },
15686
- `${id}:expanded-detail`
15687
- ) : null;
15688
- return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15689
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15690
- HierarchyDroppableRow,
15421
+ "aria-hidden": "true",
15422
+ "data-matrx-table-column-spacer": "start",
15423
+ className: "border-0 p-0",
15424
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15425
+ },
15426
+ "matrx-column-spacer-start"
15427
+ ) : null,
15428
+ renderedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15429
+ "td",
15430
+ {
15431
+ className: cn(
15432
+ "py-2",
15433
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15434
+ col.mobileHidden && "max-sm:hidden"
15435
+ ),
15436
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-5 w-full" })
15437
+ },
15438
+ columnId(col)
15439
+ )),
15440
+ showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "ml-auto h-5 w-10" }) }),
15441
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15442
+ "td",
15443
+ {
15444
+ "aria-hidden": "true",
15445
+ "data-matrx-table-column-spacer": "end",
15446
+ className: "border-0 p-0",
15447
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15448
+ },
15449
+ "matrx-column-spacer-end"
15450
+ ) : null
15451
+ ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15452
+ "td",
15691
15453
  {
15692
- id,
15693
- disabled: !canDropOn(row),
15694
- children: wrappedRow
15454
+ colSpan: renderedColSpan,
15455
+ className: "px-0 py-12 text-center",
15456
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-empty-state": true, className: "sticky left-0 flex flex-col items-center gap-2 whitespace-normal px-4", style: { width: "100cqw", maxWidth: "100%" }, children: [
15457
+ renderedEmptyState?.icon,
15458
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm font-medium text-foreground", children: renderedEmptyState?.title ?? "No rows" }),
15459
+ renderedEmptyState?.description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-muted-foreground", children: renderedEmptyState.description }) : null,
15460
+ renderedEmptyState?.action
15461
+ ] })
15695
15462
  }
15696
- ),
15697
- inlineDetail
15698
- ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15699
- wrappedRow,
15700
- inlineDetail
15701
- ] }, id);
15702
- }),
15703
- appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15704
- "td",
15705
- {
15706
- colSpan: renderedColSpan,
15707
- 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",
15708
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15709
- "div",
15710
- {
15711
- role: "status",
15712
- "aria-live": "polite",
15713
- 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",
15714
- children: [
15715
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15716
- Loader2,
15717
- {
15718
- "aria-hidden": "true",
15719
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15463
+ ) }) : bodyEntries.map((entry) => {
15464
+ if (entry.kind === "spacer") {
15465
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15466
+ "tr",
15467
+ {
15468
+ "aria-hidden": "true",
15469
+ "data-matrx-table-virtual-spacer": entry.place,
15470
+ style: { height: `${entry.height}px` },
15471
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15472
+ "td",
15473
+ {
15474
+ className: "border-0 p-0",
15475
+ colSpan: renderedColSpan
15476
+ }
15477
+ )
15478
+ },
15479
+ `matrx-virtual-spacer-${entry.place}`
15480
+ );
15481
+ }
15482
+ if (entry.kind === "group") {
15483
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15484
+ TableGroupHeaderRow,
15485
+ {
15486
+ group: entry.group,
15487
+ collapsed: entry.collapsed,
15488
+ columns: renderedColumns,
15489
+ spacers: columnSpacers,
15490
+ leadingCols,
15491
+ showActionsCol,
15492
+ rowNoun: grouping?.rowNoun ?? "row",
15493
+ ...grouping?.renderLabel ? { renderLabel: grouping.renderLabel } : {},
15494
+ onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
15495
+ },
15496
+ `matrx-group-${entry.group.key}`
15497
+ );
15498
+ }
15499
+ const row = entry.row;
15500
+ const index = entry.index;
15501
+ const id = getRowId(row);
15502
+ const isSelected = selectedId === id;
15503
+ const rowEdits = edits[id];
15504
+ const displayRow = applyRowEdits(row, rowEdits);
15505
+ const controls = buildRecordControls(displayRow);
15506
+ const isChecked = selectedIdSet.has(getSelectionId(row));
15507
+ const background = rowBackground({
15508
+ tint: rowTintColor(row, id),
15509
+ highlighted: highlightedIndex === index,
15510
+ selected: isSelected,
15511
+ checked: isChecked,
15512
+ zebraOdd: zebra && index % 2 === 1,
15513
+ hoverable: detailEnabled || Boolean(onRowOpen) || canExpandRow(row)
15514
+ });
15515
+ const canHoverFrozenCell = background.frozenHover;
15516
+ const frozenRowBackgroundClass = background.frozen;
15517
+ const rowNode = /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15518
+ "tr",
15519
+ {
15520
+ "data-row-id": id,
15521
+ style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
15522
+ "data-state": isSelected ? "selected" : void 0,
15523
+ onClick: (e) => {
15524
+ if (rowEventTargetsInteractiveDescendant(e.target)) return;
15525
+ if (opensRowHref(e)) {
15526
+ const href = getRowHref?.(row);
15527
+ if (href) window.open(href, "_blank", "noopener");
15528
+ return;
15529
+ }
15530
+ openRow(row);
15531
+ },
15532
+ onAuxClick: (e) => {
15533
+ if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
15534
+ const href = getRowHref?.(row);
15535
+ if (href) {
15536
+ e.preventDefault();
15537
+ window.open(href, "_blank", "noopener");
15720
15538
  }
15539
+ },
15540
+ className: cn(
15541
+ // Keep the mobile row opaque while the complete row scrolls
15542
+ // horizontally. The translucent zebra/hover tints remain
15543
+ // desktop-only so densely layered mobile content stays crisp.
15544
+ "group/matrx-row border-b border-border/60 bg-card transition-colors",
15545
+ (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer",
15546
+ rowClassName?.(row, index),
15547
+ draggedRowId === id && "opacity-40",
15548
+ // LAST, and decided once: tint, then highlight / open / ticked, then
15549
+ // the zebra stripe (./row-background.ts). Under a tint no stripe is
15550
+ // emitted at all, so no `sm:` class can win the cascade over it.
15551
+ background.row
15721
15552
  ),
15722
- "Loading more rows\u2026"
15723
- ]
15724
- }
15725
- )
15726
- }
15727
- ) }) : null
15728
- ] }),
15729
- footerRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15730
- selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2" }) : null,
15731
- columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15732
- "td",
15733
- {
15734
- "aria-hidden": "true",
15735
- "data-matrx-table-column-spacer": "start",
15736
- className: "border-0 p-0",
15737
- style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15738
- },
15739
- "matrx-column-spacer-start"
15740
- ) : null,
15741
- renderedColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15742
- "td",
15743
- {
15744
- className: cn(
15745
- "px-2 py-2 align-middle",
15746
- (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
15747
- column.className,
15748
- column.align === "center" && "text-center",
15749
- column.align === "right" && "text-right"
15750
- ),
15751
- style: columnWidthVar(
15752
- resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
15753
- windowedWidthFor(columnId(column), column.width),
15754
- columnId(column),
15755
- resizableColumns && column.resizable !== false
15756
- ),
15757
- children: renderCell(row, column, index)
15758
- },
15759
- columnId(column)
15760
- )),
15761
- columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15762
- "td",
15763
- {
15764
- "aria-hidden": "true",
15765
- "data-matrx-table-column-spacer": "end",
15766
- className: "border-0 p-0",
15767
- style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15768
- },
15769
- "matrx-column-spacer-end"
15770
- ) : null,
15771
- showActionsCol ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", {}) : null
15772
- ] }, `footer-${index}`)) }) : null
15773
- ]
15774
- }
15775
- )
15776
- ]
15777
- }
15778
- ),
15779
- scrollHintLeft ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15780
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15781
- Button,
15782
- {
15783
- type: "button",
15784
- variant: "ghost",
15785
- size: "icon",
15786
- "data-matrx-table-icon-action": true,
15787
- "aria-label": "Scroll table left",
15788
- "data-matrx-table-scroll-control": "left",
15789
- 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",
15790
- onClick: () => scrollTableHorizontally("left"),
15791
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronLeft, { className: "h-5 w-5" })
15792
- }
15793
- ) }),
15794
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the left" })
15795
- ] }) : null,
15796
- scrollHintRight ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15797
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15798
- Button,
15799
- {
15800
- type: "button",
15801
- variant: "ghost",
15802
- size: "icon",
15803
- "data-matrx-table-icon-action": true,
15804
- "aria-label": "Scroll table right",
15805
- "data-matrx-table-scroll-control": "right",
15806
- 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",
15807
- onClick: () => scrollTableHorizontally("right"),
15808
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronRight, { className: "h-5 w-5" })
15809
- }
15810
- ) }),
15811
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the right" })
15812
- ] }) : null
15813
- ] }),
15814
- appendQuery ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15815
- "div",
15816
- {
15817
- "data-matrx-table-footer": true,
15818
- className: "flex min-h-8 shrink-0 flex-wrap items-center gap-2 border-t border-border",
15819
- "aria-busy": isLoading || isFetching,
15820
- children: [
15821
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15822
- "div",
15823
- {
15824
- ...isFetching && !isLoading ? {} : { role: "status", "aria-live": "polite" },
15825
- className: "flex w-full min-w-0 items-center gap-2 text-xs text-muted-foreground sm:w-auto sm:flex-1",
15826
- children: [
15827
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15828
- Loader2,
15829
- {
15830
- "data-matrx-table-append-loader": true,
15831
- "aria-hidden": "true",
15832
- className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15553
+ children: [
15554
+ selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15555
+ "td",
15556
+ {
15557
+ className: cn(
15558
+ "px-2 py-1.5 align-middle lg:py-0.5",
15559
+ // When a data column is frozen the checkbox
15560
+ // column has to freeze with it, or the first
15561
+ // frozen column slides over the checkboxes.
15562
+ frozenColumns.selectionOffset !== null && cn(
15563
+ "sm:sticky sm:left-0 sm:z-20",
15564
+ frozenRowBackgroundClass,
15565
+ canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]"
15566
+ )
15567
+ ),
15568
+ style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth),
15569
+ onClick: (e) => e.stopPropagation(),
15570
+ children: selection.isRowSelectable?.(row) ?? true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15571
+ "div",
15572
+ {
15573
+ "data-matrx-table-selection-target": true,
15574
+ onClick: (event) => {
15575
+ if (event.target === event.currentTarget) {
15576
+ event.currentTarget.querySelector('[role="checkbox"]')?.click();
15577
+ }
15578
+ },
15579
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15580
+ Checkbox,
15581
+ {
15582
+ className: CHECKBOX_TAP_AREA,
15583
+ checked: isChecked,
15584
+ "aria-label": `Select this ${selectionNoun}`,
15585
+ onClick: (e) => toggleRowSelected(index, e.shiftKey)
15586
+ }
15587
+ )
15588
+ }
15589
+ ) : null
15590
+ }
15591
+ ) : null,
15592
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15593
+ "td",
15594
+ {
15595
+ "aria-hidden": "true",
15596
+ "data-matrx-table-column-spacer": "start",
15597
+ className: "border-0 p-0",
15598
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15599
+ },
15600
+ "matrx-column-spacer-start"
15601
+ ) : null,
15602
+ renderedColumns.map((col, colIdx) => {
15603
+ const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
15604
+ const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
15605
+ const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
15606
+ const frozen = frozenColumns.offsets[columnId(col)] !== void 0;
15607
+ const frozenCellTint = cellTintColor(
15608
+ row,
15609
+ id,
15610
+ columnId(col)
15611
+ );
15612
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15613
+ "td",
15614
+ {
15615
+ "data-matrx-table-column-id": columnId(col),
15616
+ "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
15617
+ ...sheetCell ? {
15618
+ "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
15619
+ "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
15620
+ ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
15621
+ ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
15622
+ ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
15623
+ onMouseDown: sheetCell.onMouseDown,
15624
+ onMouseEnter: sheetCell.onMouseEnter
15625
+ } : {},
15626
+ className: cn(
15627
+ "py-1.5 align-middle lg:py-0.5",
15628
+ sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
15629
+ sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
15630
+ sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
15631
+ carriesFillHandle && "relative",
15632
+ col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
15633
+ // nowrap (NOT truncate — truncate clips the cell and
15634
+ // defeats w-max, killing the horizontal scroll).
15635
+ "max-sm:whitespace-nowrap",
15636
+ col.mobileHidden && "max-sm:hidden",
15637
+ ((resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width) !== void 0 || resizableColumns && col.resizable !== false) && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)] sm:max-w-[var(--matrx-col-w)]",
15638
+ col.className,
15639
+ isColumnDragShadow && "bg-primary/10 shadow-[inset_2px_0_0_var(--primary),inset_-2px_0_0_var(--primary)]",
15640
+ alignForFormat(col) === "center" && "text-center",
15641
+ alignForFormat(col) === "right" && "text-right",
15642
+ frozen && FROZEN_CELL_CLASS,
15643
+ frozen && frozenRowBackgroundClass,
15644
+ frozen && sheetCell?.["data-matrx-cell-selected"] && "sm:bg-[color-mix(in_oklab,var(--color-primary)_10%,var(--color-card))]",
15645
+ frozen && canHoverFrozenCell && "sm:group-hover/matrx-row:bg-[color-mix(in_oklab,var(--color-muted)_50%,var(--color-card))]",
15646
+ frozenEdgeId === columnId(col) && FROZEN_EDGE_CLASS,
15647
+ // A cell tint paints OVER the row tint beneath
15648
+ // it, and the consumer's own hook comes last so
15649
+ // it can always win.
15650
+ cellTintClass(row, id, columnId(col)),
15651
+ frozen && frozenCellTint && FROZEN_CELL_TINT_CLASS[frozenCellTint],
15652
+ cellClassName?.(row, columnId(col), index)
15653
+ ),
15654
+ style: {
15655
+ ...columnWidthVar(
15656
+ resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
15657
+ windowedWidthFor(columnId(col), col.width),
15658
+ columnId(col),
15659
+ resizableColumns && col.resizable !== false
15660
+ ),
15661
+ ...frozenStyle(columnId(col))
15662
+ },
15663
+ children: [
15664
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15665
+ "div",
15666
+ {
15667
+ className: cn(
15668
+ colIdx === 0 && (hierarchy || canExpandRow(row)) && "flex min-w-0 items-center"
15669
+ ),
15670
+ children: [
15671
+ colIdx === 0 && hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15672
+ HierarchyDragHandle,
15673
+ {
15674
+ id,
15675
+ label: hierarchy.itemLabel?.(row) ?? "row",
15676
+ disabled: !(hierarchy.canReparent?.(row) ?? true)
15677
+ }
15678
+ ) : null,
15679
+ colIdx === 0 && canExpandRow(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15680
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15681
+ Button,
15682
+ {
15683
+ type: "button",
15684
+ variant: "ghost",
15685
+ size: "icon",
15686
+ "data-matrx-table-expand-toggle": true,
15687
+ className: "mr-1 h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground [&_svg]:size-4",
15688
+ "aria-label": isRowExpanded(row) ? "Collapse row details" : "Expand row details",
15689
+ "aria-expanded": isRowExpanded(row),
15690
+ onClick: (event) => {
15691
+ event.stopPropagation();
15692
+ toggleExpanded(row);
15693
+ },
15694
+ onKeyDown: (event) => {
15695
+ if (event.key !== "Enter" && event.key !== " ") return;
15696
+ event.preventDefault();
15697
+ event.stopPropagation();
15698
+ toggleExpanded(row);
15699
+ },
15700
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15701
+ ChevronRight,
15702
+ {
15703
+ className: cn(
15704
+ "transition-transform",
15705
+ isRowExpanded(row) && "rotate-90"
15706
+ )
15707
+ }
15708
+ )
15709
+ }
15710
+ ) }),
15711
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: isRowExpanded(row) ? "Collapse details" : "Expand details" })
15712
+ ] }) : null,
15713
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15714
+ "div",
15715
+ {
15716
+ "data-matrx-cell-content": true,
15717
+ className: "min-w-0 max-w-full flex-1 whitespace-normal break-words [overflow-wrap:anywhere] sm:[&_*]:min-w-0 sm:[&_*]:whitespace-normal sm:[&_*]:[overflow-wrap:anywhere] sm:[&_.truncate]:whitespace-nowrap sm:[&_[data-slot=badge]]:whitespace-nowrap sm:[&_[data-slot=badge]]:[overflow-wrap:normal] sm:[&_[data-matrx-cell-control]]:w-max sm:[&_[data-matrx-cell-control]]:min-w-max sm:[&_[data-matrx-cell-control]]:max-w-none sm:[&_[data-matrx-cell-control]]:whitespace-nowrap sm:[&_[data-matrx-cell-control]]:[overflow-wrap:normal] sm:[&_[data-matrx-cell-control]_*]:whitespace-nowrap sm:[&_[data-matrx-cell-control]_*]:[overflow-wrap:normal] [&_[data-matrx-cell-fill]]:!w-full [&_[data-matrx-cell-fill]]:!min-w-0 [&_[data-matrx-cell-fill]]:!max-w-full sm:[&_[data-matrx-cell-fill]]:!w-full sm:[&_[data-matrx-cell-fill]]:!min-w-0 sm:[&_[data-matrx-cell-fill]]:!max-w-full",
15718
+ children: renderRecordCell(row, col, index)
15719
+ }
15720
+ )
15721
+ ]
15722
+ }
15723
+ ),
15724
+ carriesFillHandle ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15725
+ "button",
15726
+ {
15727
+ type: "button",
15728
+ "data-matrx-table-fill-handle": true,
15729
+ "aria-label": "Drag to fill the cells below or beside this selection",
15730
+ onMouseDown: sheet.startFillDrag,
15731
+ className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
15732
+ }
15733
+ ) : null
15734
+ ]
15735
+ },
15736
+ columnId(col)
15737
+ );
15738
+ }),
15739
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15740
+ "td",
15741
+ {
15742
+ "aria-hidden": "true",
15743
+ "data-matrx-table-column-spacer": "end",
15744
+ className: "border-0 p-0",
15745
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15746
+ },
15747
+ "matrx-column-spacer-end"
15748
+ ) : null,
15749
+ showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15750
+ "td",
15751
+ {
15752
+ "data-matrx-table-row-action-cell": true,
15753
+ className: "text-right align-middle",
15754
+ style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
15755
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15756
+ "div",
15757
+ {
15758
+ "data-matrx-table-row-actions": true,
15759
+ className: "inline-flex items-center justify-end gap-0",
15760
+ onClick: (e) => e.stopPropagation(),
15761
+ children: [
15762
+ showInlineRowCopy && effectiveCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15763
+ TableRowCopyControl,
15764
+ {
15765
+ size: "toolbar",
15766
+ copy: effectiveCopy,
15767
+ row: displayRow,
15768
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(displayRow)}`,
15769
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
15770
+ }
15771
+ ) : null,
15772
+ renderRowActions(displayRow, controls),
15773
+ windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15774
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15775
+ Button,
15776
+ {
15777
+ type: "button",
15778
+ variant: "ghost",
15779
+ size: "icon",
15780
+ "data-matrx-table-icon-action": true,
15781
+ className: "h-11 w-11 text-muted-foreground hover:text-foreground [&_svg]:size-5",
15782
+ "aria-label": windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window",
15783
+ onClick: (e) => {
15784
+ e.stopPropagation();
15785
+ if (windowConfig?.openOnRowClick === true) openDetail(row);
15786
+ else openWindow(row);
15787
+ },
15788
+ children: opensWindowOnRowClick ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
15789
+ }
15790
+ ) }),
15791
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: windowConfig?.openOnRowClick === true ? "Open in side panel" : "Open in window" })
15792
+ ] }) : null
15793
+ ]
15794
+ }
15795
+ )
15796
+ }
15797
+ )
15798
+ ]
15799
+ },
15800
+ id
15801
+ );
15802
+ const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
15803
+ const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15804
+ "tr",
15805
+ {
15806
+ "data-matrx-table-expanded-detail": id,
15807
+ className: cn("bg-muted/20", expandedDetail.className),
15808
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15809
+ "td",
15810
+ {
15811
+ colSpan: renderedColSpan,
15812
+ className: "p-4",
15813
+ children: expandedDetail.render(displayRow, controls)
15814
+ }
15815
+ )
15816
+ },
15817
+ `${id}:expanded-detail`
15818
+ ) : null;
15819
+ return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15820
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15821
+ HierarchyDroppableRow,
15822
+ {
15823
+ id,
15824
+ disabled: !canDropOn(row),
15825
+ children: wrappedRow
15826
+ }
15827
+ ),
15828
+ inlineDetail
15829
+ ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
15830
+ wrappedRow,
15831
+ inlineDetail
15832
+ ] }, id);
15833
+ }),
15834
+ appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15835
+ "td",
15836
+ {
15837
+ colSpan: renderedColSpan,
15838
+ className: "sticky bottom-0 z-20 h-16 border-y border-border bg-card/95 px-0 py-0 text-left shadow-sm backdrop-blur-sm",
15839
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15840
+ "div",
15841
+ {
15842
+ role: "status",
15843
+ "aria-live": "polite",
15844
+ className: "sticky left-0 flex h-16 w-fit max-w-[100cqw] items-center justify-center gap-2 px-3 text-sm font-medium text-foreground",
15845
+ children: [
15846
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15847
+ Loader2,
15848
+ {
15849
+ "aria-hidden": "true",
15850
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15851
+ }
15852
+ ),
15853
+ "Loading more rows\u2026"
15854
+ ]
15855
+ }
15856
+ )
15857
+ }
15858
+ ) }) : null
15859
+ ] }),
15860
+ footerRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
15861
+ selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2" }) : null,
15862
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15863
+ "td",
15864
+ {
15865
+ "aria-hidden": "true",
15866
+ "data-matrx-table-column-spacer": "start",
15867
+ className: "border-0 p-0",
15868
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
15869
+ },
15870
+ "matrx-column-spacer-start"
15871
+ ) : null,
15872
+ renderedColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15873
+ "td",
15874
+ {
15875
+ className: cn(
15876
+ "px-2 py-2 align-middle",
15877
+ (column.compact || resolveColumnMarker(column) !== void 0) && "px-1",
15878
+ column.className,
15879
+ column.align === "center" && "text-center",
15880
+ column.align === "right" && "text-right"
15881
+ ),
15882
+ style: columnWidthVar(
15883
+ resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
15884
+ windowedWidthFor(columnId(column), column.width),
15885
+ columnId(column),
15886
+ resizableColumns && column.resizable !== false
15887
+ ),
15888
+ children: renderCell(row, column, index)
15889
+ },
15890
+ columnId(column)
15891
+ )),
15892
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15893
+ "td",
15894
+ {
15895
+ "aria-hidden": "true",
15896
+ "data-matrx-table-column-spacer": "end",
15897
+ className: "border-0 p-0",
15898
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
15899
+ },
15900
+ "matrx-column-spacer-end"
15901
+ ) : null,
15902
+ showActionsCol ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", {}) : null
15903
+ ] }, `footer-${index}`)) }) : null
15904
+ ]
15833
15905
  }
15834
- ) : null,
15835
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2", children: [
15836
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "min-w-0 break-words font-medium text-foreground", children: isLoading ? "Loading rows\u2026" : appendQuery.pagination.sourceRecords ? `${(0, import_format5.formatCount)(processed.length)} ${appendQuery.pagination.sourceRecords.rowLabel}${appendQuery.pagination.sourceRecords.matchedRecords === void 0 ? "" : ` (${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.matchedRecords)} matching ${appendQuery.pagination.sourceRecords.label})`} \xB7 ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.loaded)}${appendQuery.pagination.sourceRecords.total === void 0 ? "" : ` / ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.total)}`} ${appendQuery.pagination.sourceRecords.label} loaded` : `${(0, import_format5.formatCount)(data.length)} loaded${appendQuery.pagination.totalItems === void 0 ? "" : ` of ${(0, import_format5.formatCount)(appendQuery.pagination.totalItems)}`}` }),
15837
- appendQuery.pagination.sourceUntil || appendUsesLocalProcessing ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15838
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", "aria-label": "Source coverage", className: "shrink-0 text-xs text-muted-foreground underline decoration-dotted underline-offset-4", children: "Source info" }) }),
15839
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(TooltipContent, { children: [
15840
- appendQuery.pagination.sourceUntil ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { children: [
15841
- "Source fixed through ",
15842
- new Date(appendQuery.pagination.sourceUntil).toLocaleString(),
15843
- "."
15844
- ] }) : null,
15845
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: !isLoading && !isFetching && !appendQuery.pagination.loadAll?.isLoading && !scrollPagination.error && !appendQuery.pagination.hasNextPage && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? "All source rows are loaded." : appendUsesLocalProcessing ? sourceOwnsColumnFilters ? "Local processing covers loaded rows; source-owned column filters are applied by the source." : "Local search and filters cover loaded rows." : "Source paging is active." })
15846
- ] })
15847
- ] }) : null,
15848
- scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "text-destructive", children: scrollPagination.error.message }) : null,
15849
- appendScrollPolicy?.mode === "suspended" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { role: "status", children: [
15850
- appendScrollPolicy.reason,
15851
- " Load more remains available."
15852
- ] }) : null
15853
- ] })
15906
+ )
15854
15907
  ]
15855
15908
  }
15856
15909
  ),
15857
- appendQuery.pagination.sourcePageSize ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15858
- Select,
15859
- {
15860
- value: String(appendQuery.pagination.sourcePageSize.value),
15861
- onValueChange: (value) => appendQuery.pagination.sourcePageSize?.onChange(Number(value)),
15862
- children: [
15863
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15864
- SelectTrigger,
15865
- {
15866
- "aria-label": "Rows per load",
15867
- className: "h-8 min-w-20 shrink-0 rounded-md border border-border bg-card px-3 text-sm [&_svg]:size-5",
15868
- disabled: isLoading || isFetching || appendQuery.pagination.loadAll?.isLoading,
15869
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, {})
15870
- }
15871
- ),
15872
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectContent, { children: appendQuery.pagination.sourcePageSize.options.map((size) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: String(size), children: size }, size)) })
15873
- ]
15874
- }
15875
- ) : null,
15876
- appendQuery.pagination.loadAll?.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15877
- Button,
15878
- {
15879
- type: "button",
15880
- variant: "outline",
15881
- className: "h-8 shrink-0 px-3 text-sm",
15882
- onClick: appendQuery.pagination.loadAll.stop,
15883
- children: "Stop loading"
15884
- }
15885
- ) : scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15886
- Button,
15887
- {
15888
- type: "button",
15889
- variant: "outline",
15890
- className: "h-8 shrink-0 px-3 text-sm",
15891
- onClick: appendQuery.pagination.retrySource ?? (appendQuery.pagination.hasNextPage ? appendQuery.pagination.loadNextPage : appendQuery.pagination.refresh),
15892
- children: appendQuery.pagination.retrySource ? "Retry source" : appendQuery.pagination.hasNextPage ? "Retry" : "Reload rows"
15893
- }
15894
- ) : appendQuery.pagination.hasNextPage || scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 max-w-full flex-wrap gap-0", children: [
15895
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15910
+ scrollHintLeft ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15911
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15896
15912
  Button,
15897
15913
  {
15898
15914
  type: "button",
15899
- variant: "outline",
15900
- className: "h-8 px-3 text-sm",
15901
- disabled: isLoading || isFetching,
15902
- onClick: () => void scrollPagination.requestNextPage(),
15903
- children: "Load more"
15915
+ variant: "ghost",
15916
+ size: "icon",
15917
+ "data-matrx-table-icon-action": true,
15918
+ "aria-label": "Scroll table left",
15919
+ "data-matrx-table-scroll-control": "left",
15920
+ className: "absolute left-1.5 top-1/2 z-30 hidden h-11 w-11 -translate-y-1/2 rounded-full border border-glass-edge bg-glass p-0 text-foreground shadow-glass backdrop-blur-glass backdrop-saturate-glass hover:bg-glass-hover active:bg-glass-active sm:inline-flex",
15921
+ onClick: () => scrollTableHorizontally("left"),
15922
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronLeft, { className: "h-5 w-5" })
15904
15923
  }
15905
- ),
15906
- appendQuery.pagination.loadAll ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15924
+ ) }),
15925
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the left" })
15926
+ ] }) : null,
15927
+ scrollHintRight ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15928
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15907
15929
  Button,
15908
15930
  {
15909
15931
  type: "button",
15910
15932
  variant: "ghost",
15911
- className: "h-8 px-3 text-sm",
15912
- disabled: isLoading || isFetching,
15913
- onClick: appendQuery.pagination.loadAll.request,
15914
- children: "Load all"
15915
- }
15916
- ) : null
15917
- ] }) : !isLoading && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 max-w-full break-words whitespace-normal px-3 text-xs text-muted-foreground sm:text-sm", children: "All rows loaded" }) : null
15918
- ]
15919
- }
15920
- ) : !hidePagination && (totalItems > 0 || sourceTotal !== void 0) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15921
- GenericTablePagination,
15922
- {
15923
- totalItems,
15924
- ...paginationLabelFormat ? { labelFormat: paginationLabelFormat } : sourceTotal === void 0 ? {} : { labelFormat: (start, end, total) => `${(0, import_format5.formatCount)(start)}\u2013${(0, import_format5.formatCount)(end)} of ${(0, import_format5.formatCount)(total)} matching \xB7 ${(0, import_format5.formatCount)(data.length)} / ${(0, import_format5.formatCount)(sourceTotal)} loaded` },
15925
- ...localProgressiveEnabled ? { progressive: {
15926
- shownCount: progressiveRows.shownCount,
15927
- loadedCount: data.length,
15928
- hasMore: progressiveRows.hasMore,
15929
- onShowMore: () => void progressiveRows.showMore()
15930
- } } : {},
15931
- itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
15932
- currentPage: safePage,
15933
- onPageChange: setPage,
15934
- onItemsPerPageChange: (n2) => {
15935
- setPageSize(n2);
15936
- if (!controlledQuery) setPage(1);
15937
- },
15938
- pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
15939
- showAllOption: allRowsAreLoaded,
15940
- allValue: 0,
15941
- allOptionLabel: "All loaded",
15942
- compact: true,
15943
- hideEntriesInfo: false
15944
- }
15945
- ) }) : null,
15946
- selection && selectionCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15947
- "div",
15948
- {
15949
- "data-matrx-table-bulk-bar": true,
15950
- 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",
15951
- children: [
15952
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-xs font-medium text-foreground", children: [
15953
- (0, import_format5.formatCount)(selectionCount),
15954
- " ",
15955
- selectionNoun,
15956
- selectionCount === 1 ? "" : "s",
15957
- " selected"
15958
- ] }),
15959
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15960
- Button,
15961
- {
15962
- type: "button",
15963
- variant: "ghost",
15964
- size: "sm",
15965
- className: "h-7 gap-1 px-2 text-xs",
15966
- onClick: () => {
15967
- lastToggledIndex.current = null;
15968
- selection.onSelectedIdsChange([]);
15969
- },
15970
- children: [
15971
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-3.5 w-3.5" }),
15972
- "Clear"
15973
- ]
15974
- }
15975
- ),
15976
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
15977
- effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15978
- CopyButtons,
15979
- {
15980
15933
  size: "icon",
15981
15934
  "data-matrx-table-icon-action": true,
15982
- sourceId: `${tableCopySourceNamespace}:selection`,
15983
- ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
15984
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
15985
- label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
15986
- ...copy === void 0 ? {} : {
15987
- human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
15988
- json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
15989
- },
15990
- agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
15991
- aiVariants: copySubsetVariantFor(
15992
- () => selectedRows,
15993
- "selected"
15994
- ),
15995
- export: {
15996
- items: [],
15997
- sheetRows: () => selectedRows.map(
15998
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
15999
- ),
16000
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
16001
- id: columnId(column),
16002
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
16003
- }))
16004
- }
15935
+ "aria-label": "Scroll table right",
15936
+ "data-matrx-table-scroll-control": "right",
15937
+ className: "absolute right-1.5 top-1/2 z-30 hidden h-11 w-11 -translate-y-1/2 rounded-full border border-glass-edge bg-glass p-0 text-foreground shadow-glass backdrop-blur-glass backdrop-saturate-glass hover:bg-glass-hover active:bg-glass-active sm:inline-flex",
15938
+ onClick: () => scrollTableHorizontally("right"),
15939
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronRight, { className: "h-5 w-5" })
16005
15940
  }
16006
- ) : null,
16007
- selection.actions?.(selectedRows, [...selectedIdSet])
16008
- ] })
16009
- ]
16010
- }
16011
- ) : null
16012
- ] }),
16013
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16014
- ColumnManager,
16015
- {
16016
- open: columnManagerOpen,
16017
- onOpenChange: setColumnManagerOpen,
16018
- columns,
16019
- order: effectiveColumnOrder,
16020
- hidden: effectiveHiddenColumnIds,
16021
- onApply: commitColumnState
16022
- }
16023
- ),
16024
- detailEnabled && selectedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16025
- SidePanelSurface,
16026
- {
16027
- title: resolveStringTitle(
16028
- detail?.title?.(selectedRow),
16029
- defaultRowTitle(selectedRow, columns)
16030
- ),
16031
- titleNode: detail?.title?.(selectedRow),
16032
- description: detail?.description?.(selectedRow),
16033
- onClose: () => setSelectedId(null),
16034
- defaultWidth: detail?.defaultWidth ?? 480,
16035
- headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
16036
- effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16037
- TableRowCopyControl,
15941
+ ) }),
15942
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "More columns to the right" })
15943
+ ] }) : null
15944
+ ] }),
15945
+ appendQuery ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15946
+ "div",
15947
+ {
15948
+ "data-matrx-table-footer": true,
15949
+ className: "flex min-h-8 shrink-0 flex-wrap items-center gap-2 border-t border-border",
15950
+ "aria-busy": isLoading || isFetching,
15951
+ children: [
15952
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15953
+ "div",
15954
+ {
15955
+ ...isFetching && !isLoading ? {} : { role: "status", "aria-live": "polite" },
15956
+ className: "flex w-full min-w-0 items-center gap-2 text-xs text-muted-foreground sm:w-auto sm:flex-1",
15957
+ children: [
15958
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15959
+ Loader2,
15960
+ {
15961
+ "data-matrx-table-append-loader": true,
15962
+ "aria-hidden": "true",
15963
+ className: "h-5 w-5 shrink-0 animate-spin text-primary motion-reduce:animate-none"
15964
+ }
15965
+ ) : null,
15966
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-2", children: [
15967
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "min-w-0 break-words font-medium text-foreground", children: isLoading ? "Loading rows\u2026" : appendQuery.pagination.sourceRecords ? `${(0, import_format5.formatCount)(processed.length)} ${appendQuery.pagination.sourceRecords.rowLabel}${appendQuery.pagination.sourceRecords.matchedRecords === void 0 ? "" : ` (${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.matchedRecords)} matching ${appendQuery.pagination.sourceRecords.label})`} \xB7 ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.loaded)}${appendQuery.pagination.sourceRecords.total === void 0 ? "" : ` / ${(0, import_format5.formatCount)(appendQuery.pagination.sourceRecords.total)}`} ${appendQuery.pagination.sourceRecords.label} loaded` : `${(0, import_format5.formatCount)(data.length)} loaded${appendQuery.pagination.totalItems === void 0 ? "" : ` of ${(0, import_format5.formatCount)(appendQuery.pagination.totalItems)}`}` }),
15968
+ appendQuery.pagination.sourceUntil || appendUsesLocalProcessing ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
15969
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("button", { type: "button", "aria-label": "Source coverage", className: "shrink-0 text-xs text-muted-foreground underline decoration-dotted underline-offset-4", children: "Source info" }) }),
15970
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(TooltipContent, { children: [
15971
+ appendQuery.pagination.sourceUntil ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { children: [
15972
+ "Source fixed through ",
15973
+ new Date(appendQuery.pagination.sourceUntil).toLocaleString(),
15974
+ "."
15975
+ ] }) : null,
15976
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { children: !isLoading && !isFetching && !appendQuery.pagination.loadAll?.isLoading && !scrollPagination.error && !appendQuery.pagination.hasNextPage && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? "All source rows are loaded." : appendUsesLocalProcessing ? sourceOwnsColumnFilters ? "Local processing covers loaded rows; source-owned column filters are applied by the source." : "Local search and filters cover loaded rows." : "Source paging is active." })
15977
+ ] })
15978
+ ] }) : null,
15979
+ scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "text-destructive", children: scrollPagination.error.message }) : null,
15980
+ appendScrollPolicy?.mode === "suspended" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("p", { role: "status", children: [
15981
+ appendScrollPolicy.reason,
15982
+ " Load more remains available."
15983
+ ] }) : null
15984
+ ] })
15985
+ ]
15986
+ }
15987
+ ),
15988
+ appendQuery.pagination.sourcePageSize ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15989
+ Select,
15990
+ {
15991
+ value: String(appendQuery.pagination.sourcePageSize.value),
15992
+ onValueChange: (value) => appendQuery.pagination.sourcePageSize?.onChange(Number(value)),
15993
+ children: [
15994
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15995
+ SelectTrigger,
15996
+ {
15997
+ "aria-label": "Rows per load",
15998
+ className: "h-8 min-w-20 shrink-0 rounded-md border border-border bg-card px-3 text-sm [&_svg]:size-5",
15999
+ disabled: isLoading || isFetching || appendQuery.pagination.loadAll?.isLoading,
16000
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, {})
16001
+ }
16002
+ ),
16003
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectContent, { children: appendQuery.pagination.sourcePageSize.options.map((size) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: String(size), children: size }, size)) })
16004
+ ]
16005
+ }
16006
+ ) : null,
16007
+ appendQuery.pagination.loadAll?.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16008
+ Button,
16009
+ {
16010
+ type: "button",
16011
+ variant: "outline",
16012
+ className: "h-8 shrink-0 px-3 text-sm",
16013
+ onClick: appendQuery.pagination.loadAll.stop,
16014
+ children: "Stop loading"
16015
+ }
16016
+ ) : scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16017
+ Button,
16018
+ {
16019
+ type: "button",
16020
+ variant: "outline",
16021
+ className: "h-8 shrink-0 px-3 text-sm",
16022
+ onClick: appendQuery.pagination.retrySource ?? (appendQuery.pagination.hasNextPage ? appendQuery.pagination.loadNextPage : appendQuery.pagination.refresh),
16023
+ children: appendQuery.pagination.retrySource ? "Retry source" : appendQuery.pagination.hasNextPage ? "Retry" : "Reload rows"
16024
+ }
16025
+ ) : appendQuery.pagination.hasNextPage || scrollPagination.error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 max-w-full flex-wrap gap-0", children: [
16026
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16027
+ Button,
16028
+ {
16029
+ type: "button",
16030
+ variant: "outline",
16031
+ className: "h-8 px-3 text-sm",
16032
+ disabled: isLoading || isFetching,
16033
+ onClick: () => void scrollPagination.requestNextPage(),
16034
+ children: "Load more"
16035
+ }
16036
+ ),
16037
+ appendQuery.pagination.loadAll ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16038
+ Button,
16039
+ {
16040
+ type: "button",
16041
+ variant: "ghost",
16042
+ className: "h-8 px-3 text-sm",
16043
+ disabled: isLoading || isFetching,
16044
+ onClick: appendQuery.pagination.loadAll.request,
16045
+ children: "Load all"
16046
+ }
16047
+ ) : null
16048
+ ] }) : !isLoading && appendQuery.pagination.totalItems !== void 0 && data.length === appendQuery.pagination.totalItems ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "min-w-0 max-w-full break-words whitespace-normal px-3 text-xs text-muted-foreground sm:text-sm", children: "All rows loaded" }) : null
16049
+ ]
16050
+ }
16051
+ ) : !hidePagination && (totalItems > 0 || sourceTotal !== void 0) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16052
+ GenericTablePagination,
16038
16053
  {
16039
- size: "icon",
16040
- "data-matrx-table-icon-action": true,
16041
- copy: effectiveCopy,
16042
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(selectedRow)}`,
16043
- row: selectedRow,
16044
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16054
+ totalItems,
16055
+ ...paginationLabelFormat ? { labelFormat: paginationLabelFormat } : sourceTotal === void 0 ? {} : { labelFormat: (start, end, total) => `${(0, import_format5.formatCount)(start)}\u2013${(0, import_format5.formatCount)(end)} of ${(0, import_format5.formatCount)(total)} matching \xB7 ${(0, import_format5.formatCount)(data.length)} / ${(0, import_format5.formatCount)(sourceTotal)} loaded` },
16056
+ ...localProgressiveEnabled ? { progressive: {
16057
+ shownCount: progressiveRows.shownCount,
16058
+ loadedCount: data.length,
16059
+ hasMore: progressiveRows.hasMore,
16060
+ onShowMore: () => void progressiveRows.showMore()
16061
+ } } : {},
16062
+ itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
16063
+ currentPage: safePage,
16064
+ onPageChange: setPage,
16065
+ onItemsPerPageChange: (n2) => {
16066
+ setPageSize(n2);
16067
+ if (!controlledQuery) setPage(1);
16068
+ },
16069
+ pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
16070
+ showAllOption: allRowsAreLoaded,
16071
+ allValue: 0,
16072
+ allOptionLabel: "All loaded",
16073
+ compact: true,
16074
+ hideEntriesInfo: false
16045
16075
  }
16046
- ) : null,
16047
- detail?.headerActions?.(selectedRow),
16048
- windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
16049
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16050
- Button,
16051
- {
16052
- type: "button",
16053
- variant: "ghost",
16054
- size: "icon",
16055
- "data-matrx-table-icon-action": true,
16056
- className: "h-7 w-7",
16057
- "aria-label": "Open in window",
16058
- onClick: () => openWindow(selectedRow),
16059
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
16060
- }
16061
- ) }),
16062
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in window" })
16063
- ] }) : null
16064
- ] }),
16065
- children: detail?.render ? (
16066
- // The panel hands children a `min-h-0 flex-1 overflow-hidden` cell
16067
- // and expects the child to own scrolling. `DataRowInspector` does;
16068
- // custom `detail.render` bodies mostly did not, so their content
16069
- // silently cut off at the fold with no scrollbar. Owning it here
16070
- // fixes the whole class at once — a custom body that already
16071
- // scrolls (`h-full` + `overflow-y-auto` root) still resolves to
16072
- // this container's height, so no second scrollbar appears.
16073
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full min-h-0 overflow-y-auto", children: detail.render(selectedRow, {
16074
- closeDetail: () => setSelectedId(null),
16075
- openDetail: () => openDetail(selectedRow),
16076
- openWindow: () => openWindow(selectedRow),
16077
- closeWindow,
16078
- hasPendingEdits: Boolean(edits[getRowId(selectedRow)]),
16079
- discardPendingEdits: () => discardRowEdits(getRowId(selectedRow))
16080
- }) })
16081
- ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16082
- DataRowInspector,
16083
- {
16084
- row: selectedRow,
16085
- ...effectiveCopy?.rowKind === void 0 ? {} : { recordKind: effectiveCopy?.rowKind },
16086
- ...effectiveCopy?.label === void 0 ? {} : { recordLabel: effectiveCopy?.label },
16087
- ...effectiveCopy?.location === void 0 ? {} : { location: effectiveCopy?.location },
16088
- ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16089
- }
16090
- )
16091
- }
16092
- ) : null,
16093
- windowRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16094
- DataRowWindow,
16095
- {
16096
- isOpen: true,
16097
- onClose: closeWindow,
16098
- title: windowConfig?.title?.(windowRow) ?? defaultRowTitle(windowRow, columns),
16099
- row: windowRow,
16100
- width: windowConfig?.width,
16101
- height: windowConfig?.height,
16102
- windowId: `matrx-data-row-${urlState?.id ?? "local"}-${getRowId(windowRow)}`,
16103
- defaultTab: windowConfig?.defaultTab,
16104
- headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
16105
- windowConfig?.openOnRowClick === true && detail?.enabled === true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
16106
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16107
- Button,
16108
- {
16109
- type: "button",
16110
- variant: "ghost",
16111
- size: "icon",
16112
- "data-matrx-table-icon-action": true,
16113
- className: "h-7 w-7",
16114
- "aria-label": "Open in side panel",
16115
- onClick: () => {
16116
- closeWindow();
16117
- openDetail(windowRow);
16118
- },
16119
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" })
16120
- }
16121
- ) }),
16122
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in side panel" })
16123
- ] }) : null,
16124
- effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16125
- TableRowCopyControl,
16076
+ ) }) : null,
16077
+ selection && selectionCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16078
+ "div",
16126
16079
  {
16127
- size: "icon",
16128
- "data-matrx-table-icon-action": true,
16129
- copy: effectiveCopy,
16130
- sourceId: `${tableCopySourceNamespace}:row:${getRowId(windowRow)}`,
16131
- row: windowRow,
16132
- ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16080
+ "data-matrx-table-bulk-bar": true,
16081
+ className: "mt-2 flex shrink-0 flex-wrap items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-2 py-1.5",
16082
+ children: [
16083
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-xs font-medium text-foreground", children: [
16084
+ (0, import_format5.formatCount)(selectionCount),
16085
+ " ",
16086
+ selectionNoun,
16087
+ selectionCount === 1 ? "" : "s",
16088
+ " selected"
16089
+ ] }),
16090
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16091
+ Button,
16092
+ {
16093
+ type: "button",
16094
+ variant: "ghost",
16095
+ size: "sm",
16096
+ className: "h-7 gap-1 px-2 text-xs",
16097
+ onClick: () => {
16098
+ lastToggledIndex.current = null;
16099
+ selection.onSelectedIdsChange([]);
16100
+ },
16101
+ children: [
16102
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-3.5 w-3.5" }),
16103
+ "Clear"
16104
+ ]
16105
+ }
16106
+ ),
16107
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
16108
+ effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16109
+ CopyButtons,
16110
+ {
16111
+ size: "icon",
16112
+ "data-matrx-table-icon-action": true,
16113
+ sourceId: `${tableCopySourceNamespace}:selection`,
16114
+ ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
16115
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
16116
+ label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
16117
+ ...copy === void 0 ? {} : {
16118
+ human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
16119
+ json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
16120
+ },
16121
+ agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
16122
+ aiVariants: copySubsetVariantFor(
16123
+ () => selectedRows,
16124
+ "selected"
16125
+ ),
16126
+ export: {
16127
+ items: [],
16128
+ sheetRows: () => selectedRows.map(
16129
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
16130
+ ),
16131
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
16132
+ id: columnId(column),
16133
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
16134
+ }))
16135
+ }
16136
+ }
16137
+ ) : null,
16138
+ selection.actions?.(selectedRows, [...selectedIdSet])
16139
+ ] })
16140
+ ]
16133
16141
  }
16134
16142
  ) : null
16135
16143
  ] }),
16136
- viewContent: windowConfig?.renderView?.(windowRow, {
16137
- closeDetail: () => setSelectedId(null),
16138
- openDetail: () => openDetail(windowRow),
16139
- openWindow: () => openWindow(windowRow),
16140
- closeWindow,
16141
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16142
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16143
- }) ?? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16144
- DataRowInspector,
16144
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16145
+ ColumnManager,
16145
16146
  {
16146
- row: windowRow,
16147
- recordKind: effectiveCopy?.rowKind ?? "record",
16148
- recordLabel: effectiveCopy?.label ?? "Record",
16149
- location: effectiveCopy?.location ?? "Table",
16150
- fieldCopy: copy !== false,
16151
- ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16147
+ open: columnManagerOpen,
16148
+ onOpenChange: setColumnManagerOpen,
16149
+ columns,
16150
+ order: effectiveColumnOrder,
16151
+ hidden: effectiveHiddenColumnIds,
16152
+ onApply: commitColumnState
16152
16153
  }
16153
16154
  ),
16154
- editContent: windowConfig?.renderEdit === false ? void 0 : windowConfig?.renderEdit ? windowConfig.renderEdit(windowRow, {
16155
- closeDetail: () => setSelectedId(null),
16156
- openDetail: () => openDetail(windowRow),
16157
- openWindow: () => openWindow(windowRow),
16158
- closeWindow,
16159
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16160
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16161
- }) : detail?.render?.(windowRow, {
16162
- closeDetail: () => setSelectedId(null),
16163
- openDetail: () => openDetail(windowRow),
16164
- openWindow: () => openWindow(windowRow),
16165
- closeWindow,
16166
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16167
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16168
- }),
16169
- children: windowConfig?.render?.(windowRow, {
16170
- closeDetail: () => setSelectedId(null),
16171
- openDetail: () => openDetail(windowRow),
16172
- openWindow: () => openWindow(windowRow),
16173
- closeWindow,
16174
- hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16175
- discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16176
- })
16177
- }
16178
- ) : null,
16179
- editEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
16180
- foreignDrafts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16181
- "div",
16182
- {
16183
- "data-matrx-foreign-drafts": true,
16184
- 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",
16185
- children: [
16186
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Unsaved edits from previous results" }),
16187
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16188
- CopyButtons,
16155
+ detailEnabled && selectedRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16156
+ SidePanelSurface,
16157
+ {
16158
+ title: resolveStringTitle(
16159
+ detail?.title?.(selectedRow),
16160
+ defaultRowTitle(selectedRow, columns)
16161
+ ),
16162
+ titleNode: detail?.title?.(selectedRow),
16163
+ description: detail?.description?.(selectedRow),
16164
+ onClose: () => setSelectedId(null),
16165
+ defaultWidth: detail?.defaultWidth ?? 480,
16166
+ headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
16167
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16168
+ TableRowCopyControl,
16169
+ {
16170
+ size: "icon",
16171
+ "data-matrx-table-icon-action": true,
16172
+ copy: effectiveCopy,
16173
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(selectedRow)}`,
16174
+ row: selectedRow,
16175
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16176
+ }
16177
+ ) : null,
16178
+ detail?.headerActions?.(selectedRow),
16179
+ windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
16180
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16181
+ Button,
16182
+ {
16183
+ type: "button",
16184
+ variant: "ghost",
16185
+ size: "icon",
16186
+ "data-matrx-table-icon-action": true,
16187
+ className: "h-7 w-7",
16188
+ "aria-label": "Open in window",
16189
+ onClick: () => openWindow(selectedRow),
16190
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRight, { className: "h-5 w-5" })
16191
+ }
16192
+ ) }),
16193
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in window" })
16194
+ ] }) : null
16195
+ ] }),
16196
+ children: detail?.render ? (
16197
+ // The panel hands children a `min-h-0 flex-1 overflow-hidden` cell
16198
+ // and expects the child to own scrolling. `DataRowInspector` does;
16199
+ // custom `detail.render` bodies mostly did not, so their content
16200
+ // silently cut off at the fold with no scrollbar. Owning it here
16201
+ // fixes the whole class at once — a custom body that already
16202
+ // scrolls (`h-full` + `overflow-y-auto` root) still resolves to
16203
+ // this container's height, so no second scrollbar appears.
16204
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "h-full min-h-0 overflow-y-auto", children: detail.render(selectedRow, {
16205
+ closeDetail: () => setSelectedId(null),
16206
+ openDetail: () => openDetail(selectedRow),
16207
+ openWindow: () => openWindow(selectedRow),
16208
+ closeWindow,
16209
+ hasPendingEdits: Boolean(edits[getRowId(selectedRow)]),
16210
+ discardPendingEdits: () => discardRowEdits(getRowId(selectedRow))
16211
+ }) })
16212
+ ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16213
+ DataRowInspector,
16214
+ {
16215
+ row: selectedRow,
16216
+ ...effectiveCopy?.rowKind === void 0 ? {} : { recordKind: effectiveCopy?.rowKind },
16217
+ ...effectiveCopy?.label === void 0 ? {} : { recordLabel: effectiveCopy?.label },
16218
+ ...effectiveCopy?.location === void 0 ? {} : { location: effectiveCopy?.location },
16219
+ ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16220
+ }
16221
+ )
16222
+ }
16223
+ ) : null,
16224
+ windowRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16225
+ DataRowWindow,
16226
+ {
16227
+ isOpen: true,
16228
+ onClose: closeWindow,
16229
+ title: windowConfig?.title?.(windowRow) ?? defaultRowTitle(windowRow, columns),
16230
+ row: windowRow,
16231
+ width: windowConfig?.width,
16232
+ height: windowConfig?.height,
16233
+ windowId: `matrx-data-row-${urlState?.id ?? "local"}-${getRowId(windowRow)}`,
16234
+ defaultTab: windowConfig?.defaultTab,
16235
+ headerActions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1", children: [
16236
+ windowConfig?.openOnRowClick === true && detail?.enabled === true ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(Tooltip, { children: [
16237
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16238
+ Button,
16239
+ {
16240
+ type: "button",
16241
+ variant: "ghost",
16242
+ size: "icon",
16243
+ "data-matrx-table-icon-action": true,
16244
+ className: "h-7 w-7",
16245
+ "aria-label": "Open in side panel",
16246
+ onClick: () => {
16247
+ closeWindow();
16248
+ openDetail(windowRow);
16249
+ },
16250
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(PanelRightOpen, { className: "h-5 w-5" })
16251
+ }
16252
+ ) }),
16253
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: "Open in side panel" })
16254
+ ] }) : null,
16255
+ effectiveCopy && showRowCopy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16256
+ TableRowCopyControl,
16257
+ {
16258
+ size: "icon",
16259
+ "data-matrx-table-icon-action": true,
16260
+ copy: effectiveCopy,
16261
+ sourceId: `${tableCopySourceNamespace}:row:${getRowId(windowRow)}`,
16262
+ row: windowRow,
16263
+ ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
16264
+ }
16265
+ ) : null
16266
+ ] }),
16267
+ viewContent: windowConfig?.renderView?.(windowRow, {
16268
+ closeDetail: () => setSelectedId(null),
16269
+ openDetail: () => openDetail(windowRow),
16270
+ openWindow: () => openWindow(windowRow),
16271
+ closeWindow,
16272
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16273
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16274
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16275
+ DataRowInspector,
16189
16276
  {
16190
- size: "xs",
16191
- label: "retained drafts",
16192
- human: () => JSON.stringify(foreignDraftPayload, null, 2),
16193
- json: () => foreignDraftPayload
16277
+ row: windowRow,
16278
+ recordKind: effectiveCopy?.rowKind ?? "record",
16279
+ recordLabel: effectiveCopy?.label ?? "Record",
16280
+ location: effectiveCopy?.location ?? "Table",
16281
+ fieldCopy: copy !== false,
16282
+ ...detail?.tokenForField === void 0 ? {} : { tokenForField: detail?.tokenForField }
16194
16283
  }
16195
16284
  ),
16196
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Button, { type: "button", size: "sm", variant: "ghost", className: "h-7 text-xs text-destructive", onClick: () => setConfirmForeignDraftDiscard(true), children: "Discard drafts" })
16197
- ]
16198
- }
16199
- ) : null,
16200
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16201
- ConfirmDialog,
16202
- {
16203
- open: confirmForeignDraftDiscard,
16204
- onOpenChange: setConfirmForeignDraftDiscard,
16205
- title: "Discard unsaved edits from previous results?",
16206
- description: "This permanently removes edits retained for other result sets. Copy drafts first if you may need them.",
16207
- confirmLabel: "Discard drafts",
16208
- cancelLabel: "Keep drafts",
16209
- variant: "destructive",
16210
- onConfirm: () => {
16211
- setEditsByScope(
16212
- (current) => Object.fromEntries(
16213
- Object.entries(current).filter(([scope]) => scope === editScopeKey)
16214
- )
16215
- );
16216
- setConfirmForeignDraftDiscard(false);
16285
+ editContent: windowConfig?.renderEdit === false ? void 0 : windowConfig?.renderEdit ? windowConfig.renderEdit(windowRow, {
16286
+ closeDetail: () => setSelectedId(null),
16287
+ openDetail: () => openDetail(windowRow),
16288
+ openWindow: () => openWindow(windowRow),
16289
+ closeWindow,
16290
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16291
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16292
+ }) : detail?.render?.(windowRow, {
16293
+ closeDetail: () => setSelectedId(null),
16294
+ openDetail: () => openDetail(windowRow),
16295
+ openWindow: () => openWindow(windowRow),
16296
+ closeWindow,
16297
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16298
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16299
+ }),
16300
+ children: windowConfig?.render?.(windowRow, {
16301
+ closeDetail: () => setSelectedId(null),
16302
+ openDetail: () => openDetail(windowRow),
16303
+ openWindow: () => openWindow(windowRow),
16304
+ closeWindow,
16305
+ hasPendingEdits: Boolean(edits[getRowId(windowRow)]),
16306
+ discardPendingEdits: () => discardRowEdits(getRowId(windowRow))
16307
+ })
16217
16308
  }
16218
- }
16219
- ),
16220
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16221
- DirtySavePill,
16222
- {
16223
- changeCount,
16224
- saving,
16225
- onSave: () => void handleSaveEdits(),
16226
- onCancel: handleCancelEdits
16227
- }
16228
- )
16229
- ] }) : null
16230
- ]
16231
- }
16232
- ),
16233
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_core3.DragOverlay, { children: draggedRow ? (
16234
- // w-max: the overlay inherits the ACTIVE node's size, and the active
16235
- // node is the 16px grip handle — without it the label wraps to one
16236
- // character per line.
16237
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "w-max max-w-xs truncate rounded border border-border bg-card px-2.5 py-1.5 text-sm font-medium text-foreground shadow-md", children: hierarchy?.itemLabel?.(draggedRow) ?? "Moving row" })
16238
- ) : null })
16239
- ]
16309
+ ) : null,
16310
+ editEnabled ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
16311
+ foreignDrafts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
16312
+ "div",
16313
+ {
16314
+ "data-matrx-foreign-drafts": true,
16315
+ className: "fixed inset-x-3 bottom-20 z-50 flex flex-wrap items-center justify-center gap-2 rounded-lg border border-amber-500/40 bg-card px-3 py-2 text-xs shadow-lg sm:inset-x-auto sm:left-1/2 sm:-translate-x-1/2 sm:flex-nowrap",
16316
+ children: [
16317
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: "Unsaved edits from previous results" }),
16318
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16319
+ CopyButtons,
16320
+ {
16321
+ size: "xs",
16322
+ label: "retained drafts",
16323
+ human: () => JSON.stringify(foreignDraftPayload, null, 2),
16324
+ json: () => foreignDraftPayload
16325
+ }
16326
+ ),
16327
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Button, { type: "button", size: "sm", variant: "ghost", className: "h-7 text-xs text-destructive", onClick: () => setConfirmForeignDraftDiscard(true), children: "Discard drafts" })
16328
+ ]
16329
+ }
16330
+ ) : null,
16331
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16332
+ ConfirmDialog,
16333
+ {
16334
+ open: confirmForeignDraftDiscard,
16335
+ onOpenChange: setConfirmForeignDraftDiscard,
16336
+ title: "Discard unsaved edits from previous results?",
16337
+ description: "This permanently removes edits retained for other result sets. Copy drafts first if you may need them.",
16338
+ confirmLabel: "Discard drafts",
16339
+ cancelLabel: "Keep drafts",
16340
+ variant: "destructive",
16341
+ onConfirm: () => {
16342
+ setEditsByScope(
16343
+ (current) => Object.fromEntries(
16344
+ Object.entries(current).filter(([scope]) => scope === editScopeKey)
16345
+ )
16346
+ );
16347
+ setConfirmForeignDraftDiscard(false);
16348
+ }
16349
+ }
16350
+ ),
16351
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
16352
+ DirtySavePill,
16353
+ {
16354
+ changeCount,
16355
+ saving,
16356
+ onSave: () => void handleSaveEdits(),
16357
+ onCancel: handleCancelEdits
16358
+ }
16359
+ )
16360
+ ] }) : null
16361
+ ]
16362
+ }
16363
+ ),
16364
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_core3.DragOverlay, { children: draggedRow ? (
16365
+ // w-max: the overlay inherits the ACTIVE node's size, and the active
16366
+ // node is the 16px grip handle — without it the label wraps to one
16367
+ // character per line.
16368
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "w-max max-w-xs truncate rounded border border-border bg-card px-2.5 py-1.5 text-sm font-medium text-foreground shadow-md", children: hierarchy?.itemLabel?.(draggedRow) ?? "Moving row" })
16369
+ ) : null })
16370
+ ]
16371
+ }
16372
+ )
16240
16373
  }
16241
- ) }) });
16374
+ ) });
16242
16375
  }
16243
16376
  function HierarchyDragHandle({
16244
16377
  id,
@@ -16829,6 +16962,34 @@ function SavedViewsControl({
16829
16962
  ] });
16830
16963
  }
16831
16964
 
16965
+ // src/data-table/menu-targets.ts
16966
+ var MATRX_TABLE_MENU_ICON_NAMES = [
16967
+ "copy",
16968
+ "copy-plus",
16969
+ "scissors",
16970
+ "clipboard-paste",
16971
+ "eraser",
16972
+ "pencil",
16973
+ "plus",
16974
+ "trash",
16975
+ "eye-off",
16976
+ "arrow-up-a-z",
16977
+ "arrow-down-a-z",
16978
+ "arrow-up-down",
16979
+ "arrow-down-to-line",
16980
+ "palette",
16981
+ "paintbrush",
16982
+ "panel-left",
16983
+ "panel-right",
16984
+ "settings",
16985
+ "sigma",
16986
+ "zap",
16987
+ "history",
16988
+ "link",
16989
+ "tag",
16990
+ "filter"
16991
+ ];
16992
+
16832
16993
  // src/data-table/tableXlsx.ts
16833
16994
  var XLSX = __toESM(require("xlsx"), 1);
16834
16995
  function downloadTableAsXlsx({