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