@ai-matrx/design-system 0.22.0 → 0.26.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 (115) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/dist/data-table/cell-undo.cjs +98 -0
  3. package/dist/data-table/cell-undo.cjs.map +1 -0
  4. package/dist/data-table/cell-undo.d.cts +85 -0
  5. package/dist/data-table/cell-undo.d.ts +85 -0
  6. package/dist/data-table/cell-undo.js +78 -0
  7. package/dist/data-table/cell-undo.js.map +1 -0
  8. package/dist/data-table/copy-helpers.d.cts +7 -2
  9. package/dist/data-table/copy-helpers.d.ts +7 -2
  10. package/dist/data-table/coverage.cjs +116 -0
  11. package/dist/data-table/coverage.cjs.map +1 -0
  12. package/dist/data-table/coverage.d.cts +65 -0
  13. package/dist/data-table/coverage.d.ts +65 -0
  14. package/dist/data-table/coverage.js +96 -0
  15. package/dist/data-table/coverage.js.map +1 -0
  16. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  17. package/dist/data-table/data-row-inspector.js.map +1 -1
  18. package/dist/data-table/data-row-window.cjs.map +1 -1
  19. package/dist/data-table/data-row-window.js.map +1 -1
  20. package/dist/data-table/facets.cjs +118 -0
  21. package/dist/data-table/facets.cjs.map +1 -0
  22. package/dist/data-table/facets.d.cts +100 -0
  23. package/dist/data-table/facets.d.ts +100 -0
  24. package/dist/data-table/facets.js +98 -0
  25. package/dist/data-table/facets.js.map +1 -0
  26. package/dist/data-table/fill.cjs +299 -0
  27. package/dist/data-table/fill.cjs.map +1 -0
  28. package/dist/data-table/fill.d.cts +129 -0
  29. package/dist/data-table/fill.d.ts +129 -0
  30. package/dist/data-table/fill.js +277 -0
  31. package/dist/data-table/fill.js.map +1 -0
  32. package/dist/data-table/filter-engine.d.cts +7 -2
  33. package/dist/data-table/filter-engine.d.ts +7 -2
  34. package/dist/data-table/grid-clipboard.cjs +165 -0
  35. package/dist/data-table/grid-clipboard.cjs.map +1 -0
  36. package/dist/data-table/grid-clipboard.d.cts +85 -0
  37. package/dist/data-table/grid-clipboard.d.ts +85 -0
  38. package/dist/data-table/grid-clipboard.js +145 -0
  39. package/dist/data-table/grid-clipboard.js.map +1 -0
  40. package/dist/data-table/grid-selection.cjs +258 -0
  41. package/dist/data-table/grid-selection.cjs.map +1 -0
  42. package/dist/data-table/grid-selection.d.cts +166 -0
  43. package/dist/data-table/grid-selection.d.ts +166 -0
  44. package/dist/data-table/grid-selection.js +238 -0
  45. package/dist/data-table/grid-selection.js.map +1 -0
  46. package/dist/data-table/grouping.cjs +302 -0
  47. package/dist/data-table/grouping.cjs.map +1 -0
  48. package/dist/data-table/grouping.d.cts +119 -0
  49. package/dist/data-table/grouping.d.ts +119 -0
  50. package/dist/data-table/grouping.js +282 -0
  51. package/dist/data-table/grouping.js.map +1 -0
  52. package/dist/data-table/host.cjs.map +1 -1
  53. package/dist/data-table/host.d.cts +18 -4
  54. package/dist/data-table/host.d.ts +18 -4
  55. package/dist/data-table/host.js.map +1 -1
  56. package/dist/data-table/index.cjs +3173 -451
  57. package/dist/data-table/index.cjs.map +1 -1
  58. package/dist/data-table/index.d.cts +13 -3
  59. package/dist/data-table/index.d.ts +13 -3
  60. package/dist/data-table/index.js +3174 -451
  61. package/dist/data-table/index.js.map +1 -1
  62. package/dist/data-table/infer-filter.d.cts +7 -2
  63. package/dist/data-table/infer-filter.d.ts +7 -2
  64. package/dist/data-table/layered-filters.d.cts +7 -2
  65. package/dist/data-table/layered-filters.d.ts +7 -2
  66. package/dist/data-table/menu-targets.cjs +77 -0
  67. package/dist/data-table/menu-targets.cjs.map +1 -0
  68. package/dist/data-table/menu-targets.d.cts +108 -0
  69. package/dist/data-table/menu-targets.d.ts +108 -0
  70. package/dist/data-table/menu-targets.js +57 -0
  71. package/dist/data-table/menu-targets.js.map +1 -0
  72. package/dist/data-table/pagination.cjs.map +1 -1
  73. package/dist/data-table/pagination.js.map +1 -1
  74. package/dist/data-table/query-control.d.cts +7 -2
  75. package/dist/data-table/query-control.d.ts +7 -2
  76. package/dist/data-table/spreadsheet.cjs +1062 -0
  77. package/dist/data-table/spreadsheet.cjs.map +1 -0
  78. package/dist/data-table/spreadsheet.d.cts +153 -0
  79. package/dist/data-table/spreadsheet.d.ts +153 -0
  80. package/dist/data-table/spreadsheet.js +1042 -0
  81. package/dist/data-table/spreadsheet.js.map +1 -0
  82. package/dist/data-table/types.cjs.map +1 -1
  83. package/dist/data-table/types.d.cts +7 -2
  84. package/dist/data-table/types.d.ts +7 -2
  85. package/dist/data-table/url-state.d.cts +7 -2
  86. package/dist/data-table/url-state.d.ts +7 -2
  87. package/dist/data-table/uuid-cell.cjs.map +1 -1
  88. package/dist/data-table/uuid-cell.js.map +1 -1
  89. package/dist/data-table/validation.cjs +229 -0
  90. package/dist/data-table/validation.cjs.map +1 -0
  91. package/dist/data-table/validation.d.cts +125 -0
  92. package/dist/data-table/validation.d.ts +125 -0
  93. package/dist/data-table/validation.js +209 -0
  94. package/dist/data-table/validation.js.map +1 -0
  95. package/dist/data-table/virtual-window.cjs +203 -0
  96. package/dist/data-table/virtual-window.cjs.map +1 -0
  97. package/dist/data-table/virtual-window.d.cts +84 -0
  98. package/dist/data-table/virtual-window.d.ts +84 -0
  99. package/dist/data-table/virtual-window.js +183 -0
  100. package/dist/data-table/virtual-window.js.map +1 -0
  101. package/dist/data-table/xlsx.d.cts +7 -2
  102. package/dist/data-table/xlsx.d.ts +7 -2
  103. package/dist/field-formats/index.d.cts +2 -2
  104. package/dist/field-formats/index.d.ts +2 -2
  105. package/dist/formulas/index.cjs +1041 -0
  106. package/dist/formulas/index.cjs.map +1 -0
  107. package/dist/formulas/index.d.cts +389 -0
  108. package/dist/formulas/index.d.ts +389 -0
  109. package/dist/formulas/index.js +1019 -0
  110. package/dist/formulas/index.js.map +1 -0
  111. package/dist/{types-BdbvlF9-.d.cts → types-CJWU1Gae.d.cts} +1 -1
  112. package/dist/{types-BdbvlF9-.d.ts → types-CJWU1Gae.d.ts} +1 -1
  113. package/dist/{types-D-flTpK0.d.ts → types-Cu5_eUG1.d.ts} +268 -4
  114. package/dist/{types-Sn249eoy.d.cts → types-DrJXAAoD.d.cts} +268 -4
  115. package/package.json +129 -2
@@ -35,8 +35,11 @@ __export(data_table_exports, {
35
35
  CELL_TINT_CLASS: () => CELL_TINT_CLASS,
36
36
  COLOR_RULE_OPS: () => COLOR_RULE_OPS,
37
37
  COLOR_RULE_OP_LABELS: () => COLOR_RULE_OP_LABELS,
38
+ DEFAULT_OVERSCAN: () => DEFAULT_OVERSCAN,
39
+ DEFAULT_UNDO_DEPTH: () => DEFAULT_UNDO_DEPTH,
38
40
  DataRowInspector: () => DataRowInspector,
39
41
  DataRowWindow: () => DataRowWindow,
42
+ EMPTY_GROUP_KEY: () => EMPTY_GROUP_KEY,
40
43
  EMPTY_TABLE_STYLE: () => EMPTY_TABLE_STYLE,
41
44
  FORMAT_MISMATCH_CLASS: () => FORMAT_MISMATCH_CLASS,
42
45
  FormattedCell: () => FormattedCell,
@@ -47,6 +50,7 @@ __export(data_table_exports, {
47
50
  MatrxUuidCell: () => MatrxUuidCell,
48
51
  NO_FROZEN_COLUMNS: () => NO_FROZEN_COLUMNS,
49
52
  OPS_WITH_VALUE: () => OPS_WITH_VALUE,
53
+ PARTIAL_BADGE: () => PARTIAL_BADGE,
50
54
  ROW_TINT_CLASS: () => ROW_TINT_CLASS,
51
55
  STYLE_COLORS: () => STYLE_COLORS,
52
56
  STYLE_COLOR_LABELS: () => STYLE_COLOR_LABELS,
@@ -56,22 +60,62 @@ __export(data_table_exports, {
56
60
  TABLE_STYLE_VERSION: () => TABLE_STYLE_VERSION,
57
61
  TableTitleRow: () => TableTitleRow,
58
62
  TableViewTabs: () => TableViewTabs,
63
+ UNKNOWN_TOTAL_BADGE: () => UNKNOWN_TOTAL_BADGE,
59
64
  alignForFormat: () => alignForFormat,
65
+ allRange: () => allRange,
60
66
  applyStylePath: () => applyStylePath,
67
+ boundsContain: () => boundsContain,
61
68
  buildRowAgentInput: () => buildRowAgentInput,
62
69
  buildViewAgentInput: () => buildViewAgentInput,
63
70
  buildViewHuman: () => buildViewHuman,
71
+ canRedo: () => canRedo,
72
+ canUndo: () => canUndo,
73
+ cellClipboardText: () => cellClipboardText,
74
+ cellDomKey: () => cellDomKey,
75
+ cellsInRange: () => cellsInRange,
76
+ classifyGridKey: () => classifyGridKey,
77
+ coerceGroupNumber: () => coerceGroupNumber,
64
78
  collectSelectOptions: () => collectSelectOptions,
65
79
  colorForChoice: () => colorForChoice,
80
+ columnRange: () => columnRange,
81
+ computeVariableWindow: () => computeVariableWindow,
82
+ computeWindow: () => computeWindow,
83
+ createUndoStack: () => createUndoStack,
84
+ describeCoverage: () => describeCoverage,
85
+ detectSeries: () => detectSeries,
86
+ directClickKinds: () => directClickKinds,
66
87
  downloadTableAsXlsx: () => downloadTableAsXlsx,
67
88
  evaluateRule: () => evaluateRule,
89
+ fillDown: () => fillDown,
90
+ fillRange: () => fillRange,
91
+ fillTargetFor: () => fillTargetFor,
68
92
  filterKindLabel: () => filterKindLabel,
93
+ flattenGroups: () => flattenGroups,
69
94
  formatColumnValue: () => formatColumnValue,
95
+ formatGroupNumber: () => formatGroupNumber,
96
+ gridToTsv: () => gridToTsv,
97
+ groupKeyOf: () => groupKeyOf,
98
+ groupRows: () => groupRows,
99
+ groupSummary: () => groupSummary,
70
100
  inferTableSearchRole: () => inferTableSearchRole,
101
+ isBlankGroupValue: () => isBlankGroupValue,
102
+ isDirectClickEditor: () => isDirectClickEditor,
103
+ isGroupCollapsed: () => isGroupCollapsed,
104
+ isSingleCell: () => isSingleCell,
105
+ isSingleCellRange: () => isSingleCellRange,
71
106
  isStyleColor: () => isStyleColor,
107
+ moveSelection: () => moveSelection,
108
+ parseClipboardGrid: () => parseClipboardGrid,
72
109
  parseTableStyle: () => parseTableStyle,
73
110
  parseTableViewSnapshot: () => parseTableViewSnapshot,
74
111
  pixelWidth: () => pixelWidth,
112
+ planPaste: () => planPaste,
113
+ popRedo: () => popRedo,
114
+ popUndo: () => popUndo,
115
+ pushUndo: () => pushUndo,
116
+ rangeBounds: () => rangeBounds,
117
+ rangeRows: () => rangeRows,
118
+ rangeSize: () => rangeSize,
75
119
  rankTableSearchRows: () => rankTableSearchRows,
76
120
  resolveCellColor: () => resolveCellColor,
77
121
  resolveColumnMarker: () => resolveColumnMarker,
@@ -79,23 +123,31 @@ __export(data_table_exports, {
79
123
  resolveFrozenColumns: () => resolveFrozenColumns,
80
124
  resolveRowColor: () => resolveRowColor,
81
125
  resolveTableSearchScopes: () => resolveTableSearchScopes,
126
+ rowRange: () => rowRange,
82
127
  rowsToCsvFromColumns: () => rowsToCsvFromColumns,
83
128
  rowsToMarkdownTable: () => rowsToMarkdownTable,
84
129
  rowsToRecordsFromColumns: () => rowsToRecordsFromColumns,
130
+ sameCell: () => sameCell,
131
+ scrollOffsetForIndex: () => scrollOffsetForIndex,
85
132
  shortUuid: () => shortUuid,
133
+ storedValuesEqual: () => storedValuesEqual,
86
134
  stylePath: () => stylePath,
87
135
  tableStyleFromMetadata: () => tableStyleFromMetadata,
88
136
  tableStyleIsEmpty: () => tableStyleIsEmpty,
89
137
  tableUrlParamPrefix: () => tableUrlParamPrefix,
138
+ tileIndex: () => tileIndex,
139
+ toggleGroupCollapsed: () => toggleGroupCollapsed,
140
+ undoEntryFor: () => undoEntryFor,
90
141
  useMatrxDataTableHost: () => useMatrxDataTableHost,
91
142
  useMatrxTableRowAlchemy: () => useMatrxTableRowAlchemy,
92
143
  useScrollPagination: () => useScrollPagination,
144
+ useSpreadsheetGrid: () => useSpreadsheetGrid,
93
145
  useTableUrlState: () => useTableUrlState
94
146
  });
95
147
  module.exports = __toCommonJS(data_table_exports);
96
148
 
97
149
  // src/data-table/MatrxDataTable.tsx
98
- var import_react20 = require("react");
150
+ var import_react21 = require("react");
99
151
  var import_core3 = require("@dnd-kit/core");
100
152
 
101
153
  // src/data-table/icons.tsx
@@ -5175,10 +5227,10 @@ function passesColumnFilter(value, filter) {
5175
5227
  if (raw === "") return false;
5176
5228
  const normalized = raw.replace(/[^0-9.-]/g, "");
5177
5229
  if (normalized === "") return false;
5178
- const n = typeof value === "number" ? value : Number(normalized);
5179
- if (!Number.isFinite(n)) return false;
5180
- if (filter.min !== void 0 && n < filter.min) return false;
5181
- if (filter.max !== void 0 && n > filter.max) return false;
5230
+ const n2 = typeof value === "number" ? value : Number(normalized);
5231
+ if (!Number.isFinite(n2)) return false;
5232
+ if (filter.min !== void 0 && n2 < filter.min) return false;
5233
+ if (filter.max !== void 0 && n2 > filter.max) return false;
5182
5234
  return true;
5183
5235
  }
5184
5236
  }
@@ -5241,26 +5293,26 @@ function filterAndSortRows(rows, columns, columnFilters, sort, globalSearch, any
5241
5293
  return result;
5242
5294
  }
5243
5295
  function countActiveColumnFilters(filters) {
5244
- let n = 0;
5296
+ let n2 = 0;
5245
5297
  for (const f of Object.values(filters)) {
5246
5298
  if (!f) continue;
5247
5299
  switch (f.kind) {
5248
5300
  case "text":
5249
- if (f.value.trim() || f.mode && f.mode !== "contains") n += 1;
5301
+ if (f.value.trim() || f.mode && f.mode !== "contains") n2 += 1;
5250
5302
  break;
5251
5303
  case "select":
5252
5304
  if (f.values !== void 0 || f.value && f.value !== "__all__")
5253
- n += 1;
5305
+ n2 += 1;
5254
5306
  break;
5255
5307
  case "boolean":
5256
- n += 1;
5308
+ n2 += 1;
5257
5309
  break;
5258
5310
  case "number":
5259
- if (f.min !== void 0 || f.max !== void 0) n += 1;
5311
+ if (f.min !== void 0 || f.max !== void 0) n2 += 1;
5260
5312
  break;
5261
5313
  }
5262
5314
  }
5263
- return n;
5315
+ return n2;
5264
5316
  }
5265
5317
  function isColumnFilterActive(filter) {
5266
5318
  if (!filter) return false;
@@ -5512,11 +5564,11 @@ var import_react11 = require("react");
5512
5564
  var import_url_state = require("@ai-matrx/kit/url-state");
5513
5565
  function parseSort(raw) {
5514
5566
  if (!raw) return null;
5515
- const at = raw.lastIndexOf(".");
5516
- if (at <= 0) return null;
5517
- const direction = raw.slice(at + 1);
5567
+ const at2 = raw.lastIndexOf(".");
5568
+ if (at2 <= 0) return null;
5569
+ const direction = raw.slice(at2 + 1);
5518
5570
  if (direction !== "asc" && direction !== "desc") return null;
5519
- return { id: raw.slice(0, at), direction };
5571
+ return { id: raw.slice(0, at2), direction };
5520
5572
  }
5521
5573
  function parseObject(raw, fallback) {
5522
5574
  if (!raw) return fallback;
@@ -5539,8 +5591,8 @@ function parseLayeredFilters(raw) {
5539
5591
  }
5540
5592
  }
5541
5593
  function positiveInt(raw, fallback) {
5542
- const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
5543
- return Number.isFinite(n) && n > 0 ? n : fallback;
5594
+ const n2 = raw ? Number.parseInt(raw, 10) : Number.NaN;
5595
+ return Number.isFinite(n2) && n2 > 0 ? n2 : fallback;
5544
5596
  }
5545
5597
  function tableUrlParamPrefix(tableId) {
5546
5598
  if (!/^[a-z][a-z0-9-]{0,63}$/.test(tableId)) {
@@ -5879,6 +5931,95 @@ var CommandShortcut = ({
5879
5931
  };
5880
5932
  CommandShortcut.displayName = "CommandShortcut";
5881
5933
 
5934
+ // src/data-table/facets.ts
5935
+ var MAX_LISTABLE_FACET_LENGTH = 300;
5936
+ var DEFAULT_FACET_LIMIT = 200;
5937
+ var MAX_FACET_LIMIT = 500;
5938
+ function facetText(raw) {
5939
+ if (raw === null || raw === void 0) return "";
5940
+ if (typeof raw === "object") {
5941
+ try {
5942
+ return JSON.stringify(raw);
5943
+ } catch {
5944
+ return String(raw);
5945
+ }
5946
+ }
5947
+ return String(raw);
5948
+ }
5949
+ function computeColumnFacets(args) {
5950
+ const { columnId: columnId2, rows, readValue: readValue2 } = args;
5951
+ const limit = Math.min(Math.max(args.limit ?? DEFAULT_FACET_LIMIT, 1), MAX_FACET_LIMIT);
5952
+ const counts = /* @__PURE__ */ new Map();
5953
+ const longValues = /* @__PURE__ */ new Set();
5954
+ let blank = 0;
5955
+ let maxLength = 0;
5956
+ for (const row of rows) {
5957
+ let raw;
5958
+ try {
5959
+ raw = readValue2(row, columnId2);
5960
+ } catch {
5961
+ raw = void 0;
5962
+ }
5963
+ const trimmed = facetText(raw).trim();
5964
+ if (trimmed === "") {
5965
+ blank += 1;
5966
+ continue;
5967
+ }
5968
+ if (trimmed.length > maxLength) maxLength = trimmed.length;
5969
+ if (trimmed.length > MAX_LISTABLE_FACET_LENGTH) {
5970
+ longValues.add(trimmed);
5971
+ continue;
5972
+ }
5973
+ counts.set(trimmed, (counts.get(trimmed) ?? 0) + 1);
5974
+ }
5975
+ const values = [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)).slice(0, limit);
5976
+ return {
5977
+ columnId: columnId2,
5978
+ totalRows: rows.length,
5979
+ filled: rows.length - blank,
5980
+ blank,
5981
+ distinctCount: counts.size + longValues.size,
5982
+ maxLength,
5983
+ unlistable: longValues.size,
5984
+ limit,
5985
+ truncated: counts.size > limit,
5986
+ values,
5987
+ answeredBy: "local",
5988
+ complete: args.complete ?? true
5989
+ };
5990
+ }
5991
+ function localFacetsAreComplete(loadedRowCount, total) {
5992
+ if (total === void 0 || !Number.isFinite(total) || total < 0) return false;
5993
+ return loadedRowCount >= total;
5994
+ }
5995
+ var NUMBERS = new Intl.NumberFormat("en-US");
5996
+ function n(value) {
5997
+ try {
5998
+ return NUMBERS.format(value);
5999
+ } catch {
6000
+ return String(value);
6001
+ }
6002
+ }
6003
+ function describeFacetSource(facets, noun = "row") {
6004
+ if (!facets) return "";
6005
+ const plural2 = `${noun}s`;
6006
+ if (!facets.complete) {
6007
+ return `These counts cover the ${n(facets.totalRows)} ${plural2} loaded here, not the whole table.`;
6008
+ }
6009
+ if (facets.truncated) {
6010
+ return `Showing the ${n(facets.values.length)} most common of ${n(facets.distinctCount)} values.`;
6011
+ }
6012
+ if (facets.unlistable > 0) {
6013
+ return `${n(facets.unlistable)} ${facets.unlistable === 1 ? "value is" : "values are"} too long to list. Match text instead to reach ${facets.unlistable === 1 ? "it" : "them"}.`;
6014
+ }
6015
+ return "";
6016
+ }
6017
+ var FACETS_LOADING_NOTICE = "Counting values\u2026";
6018
+ function describeFacetFailure(error) {
6019
+ const detail = error instanceof Error ? error.message : String(error ?? "");
6020
+ return detail ? `The values in this column could not be counted: ${detail} Match text instead.` : "The values in this column could not be counted. Match text instead.";
6021
+ }
6022
+
5882
6023
  // src/data-table/column-marker.ts
5883
6024
  var MARKER_ACCESSORS = {
5884
6025
  favorite: "favorite",
@@ -5946,6 +6087,10 @@ function ColumnHeaderCell({
5946
6087
  filterFooterHint,
5947
6088
  allowNegation = false,
5948
6089
  allowNullModes = false,
6090
+ facets,
6091
+ facetsLoading = false,
6092
+ facetsError,
6093
+ onFilterOpen,
5949
6094
  reorderColumn
5950
6095
  }) {
5951
6096
  const reorderEnabled = reorderColumn !== void 0;
@@ -5980,6 +6125,11 @@ function ColumnHeaderCell({
5980
6125
  setPendingNegation(filterValue?.negated ?? false);
5981
6126
  }
5982
6127
  }, [filterOpen, filterValue]);
6128
+ const notifyOpen = (0, import_react13.useRef)(onFilterOpen);
6129
+ notifyOpen.current = onFilterOpen;
6130
+ (0, import_react13.useEffect)(() => {
6131
+ if (filterOpen) notifyOpen.current?.();
6132
+ }, [filterOpen]);
5983
6133
  const applyFilter = () => {
5984
6134
  onFilterChange(draftFilter);
5985
6135
  setFilterOpen(false);
@@ -6074,6 +6224,9 @@ function ColumnHeaderCell({
6074
6224
  selectOptions,
6075
6225
  selectSingle,
6076
6226
  selectAllowCustom,
6227
+ facets: facets ?? null,
6228
+ facetsLoading,
6229
+ ...facetsError === void 0 ? {} : { facetsError },
6077
6230
  ...markerLabels ? {
6078
6231
  booleanLabels: {
6079
6232
  all: "All",
@@ -6298,8 +6451,43 @@ function FilterBody({
6298
6451
  selectSingle = false,
6299
6452
  selectAllowCustom = false,
6300
6453
  allowNullModes = false,
6301
- booleanLabels
6454
+ booleanLabels,
6455
+ facets = null,
6456
+ facetsLoading = false,
6457
+ facetsError
6302
6458
  }) {
6459
+ const checklist = facetsLoading || facetsError || facets && (facets.values.length > 0 || facets.blank > 0) ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6460
+ FacetChecklist,
6461
+ {
6462
+ facets,
6463
+ loading: facetsLoading,
6464
+ ...facetsError === void 0 ? {} : { error: facetsError },
6465
+ value,
6466
+ onChange
6467
+ }
6468
+ ) : null;
6469
+ if (checklist && kind === "select" && facets && facets.values.length > 0) {
6470
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "space-y-2", children: checklist });
6471
+ }
6472
+ if (checklist) {
6473
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "space-y-2", children: [
6474
+ checklist,
6475
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "h-px bg-border" }),
6476
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6477
+ FilterBody,
6478
+ {
6479
+ kind,
6480
+ value,
6481
+ onChange,
6482
+ selectOptions,
6483
+ selectSingle,
6484
+ selectAllowCustom,
6485
+ allowNullModes,
6486
+ ...booleanLabels === void 0 ? {} : { booleanLabels }
6487
+ }
6488
+ )
6489
+ ] });
6490
+ }
6303
6491
  if (kind === "text") {
6304
6492
  const text = value?.kind === "text" ? value.value : "";
6305
6493
  const mode = (value?.kind === "text" ? value.mode : void 0) ?? "contains";
@@ -6410,6 +6598,112 @@ function FilterBody({
6410
6598
  }
6411
6599
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(NumberFilterBody, { value, onChange });
6412
6600
  }
6601
+ function FacetChecklist({
6602
+ facets,
6603
+ loading,
6604
+ error,
6605
+ value,
6606
+ onChange
6607
+ }) {
6608
+ const [query, setQuery] = (0, import_react13.useState)("");
6609
+ const selected = value?.kind === "select" ? value.values ?? (value.value && value.value !== "__all__" ? [value.value] : []) : [];
6610
+ const selectedSet = new Set(selected);
6611
+ const rows = (0, import_react13.useMemo)(() => {
6612
+ if (!facets) return [];
6613
+ const listed = facets.values.map((entry) => ({
6614
+ value: entry.value,
6615
+ label: entry.value,
6616
+ count: entry.count
6617
+ }));
6618
+ const withBlank = facets.blank > 0 ? [{ value: "", label: "(empty)", count: facets.blank }, ...listed] : listed;
6619
+ const q = query.trim().toLowerCase();
6620
+ if (!q) return withBlank;
6621
+ return withBlank.filter((row) => row.label.toLowerCase().includes(q));
6622
+ }, [facets, query]);
6623
+ const toggle = (candidate) => {
6624
+ const next = new Set(selectedSet);
6625
+ if (next.has(candidate)) next.delete(candidate);
6626
+ else next.add(candidate);
6627
+ const values = [...next];
6628
+ onChange(
6629
+ values.length === 0 ? void 0 : { kind: "select", value: values[0] ?? "", values }
6630
+ );
6631
+ };
6632
+ const notice = error ?? (loading ? FACETS_LOADING_NOTICE : describeFacetSource(facets));
6633
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "space-y-1.5", children: [
6634
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Values" }),
6635
+ facets && facets.values.length + (facets.blank > 0 ? 1 : 0) > 8 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6636
+ Input,
6637
+ {
6638
+ "data-matrx-filter-autofocus": true,
6639
+ value: query,
6640
+ onChange: (event) => setQuery(event.target.value),
6641
+ placeholder: "Find a value\u2026",
6642
+ className: "h-7 text-sm",
6643
+ style: { fontSize: "16px" }
6644
+ }
6645
+ ) : null,
6646
+ rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { className: "max-h-56 space-y-0.5 overflow-y-auto", "data-matrx-facet-list": "true", children: rows.map((row) => {
6647
+ const checked = selectedSet.has(row.value);
6648
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
6649
+ "button",
6650
+ {
6651
+ type: "button",
6652
+ role: "checkbox",
6653
+ "aria-checked": checked,
6654
+ "data-matrx-facet-value": row.value === "" ? "__blank__" : row.value,
6655
+ onClick: () => toggle(row.value),
6656
+ className: cn(
6657
+ "flex w-full items-center gap-2 rounded px-1.5 py-1 text-left text-xs hover:bg-muted",
6658
+ checked && "bg-primary/10"
6659
+ ),
6660
+ children: [
6661
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6662
+ "span",
6663
+ {
6664
+ "aria-hidden": true,
6665
+ className: cn(
6666
+ "flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-[3px] border",
6667
+ checked ? "border-primary bg-primary text-primary-foreground" : "border-border"
6668
+ ),
6669
+ children: checked ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Check, { className: "h-2.5 w-2.5" }) : null
6670
+ }
6671
+ ),
6672
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: cn("min-w-0 flex-1 truncate", row.value === "" && "italic text-muted-foreground"), children: row.label }),
6673
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6674
+ "span",
6675
+ {
6676
+ "data-matrx-facet-count": String(row.count),
6677
+ className: "shrink-0 tabular-nums text-[11px] text-muted-foreground",
6678
+ children: row.count.toLocaleString("en-US")
6679
+ }
6680
+ )
6681
+ ]
6682
+ }
6683
+ ) }, row.value === "" ? "__blank__" : row.value);
6684
+ }) }) : loading ? null : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-1 text-[11px] text-muted-foreground", children: query.trim() ? "No value matches that." : "This column has no values yet." }),
6685
+ notice ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6686
+ "p",
6687
+ {
6688
+ "data-matrx-facet-notice": "true",
6689
+ className: cn(
6690
+ "px-1 text-[11px] leading-snug",
6691
+ error ? "text-amber-700 dark:text-amber-400" : "text-muted-foreground"
6692
+ ),
6693
+ children: notice
6694
+ }
6695
+ ) : null,
6696
+ selected.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6697
+ "button",
6698
+ {
6699
+ type: "button",
6700
+ onClick: () => onChange(void 0),
6701
+ className: "px-1 text-[11px] text-muted-foreground underline-offset-2 hover:underline",
6702
+ children: "Clear selection"
6703
+ }
6704
+ ) : null
6705
+ ] });
6706
+ }
6413
6707
  function SearchableSelectFilter({
6414
6708
  selected,
6415
6709
  options,
@@ -6596,8 +6890,8 @@ function NumberFilterBody({
6596
6890
  }
6597
6891
  function parseOptionalNumber(raw) {
6598
6892
  if (raw.trim() === "") return void 0;
6599
- const n = Number(raw.replace(/[^0-9.-]/g, ""));
6600
- return Number.isFinite(n) ? n : void 0;
6893
+ const n2 = Number(raw.replace(/[^0-9.-]/g, ""));
6894
+ return Number.isFinite(n2) ? n2 : void 0;
6601
6895
  }
6602
6896
 
6603
6897
  // src/data-table/ColumnResizeHandle.tsx
@@ -6790,115 +7084,1768 @@ function projectColumnDragOrder(orderedIds, preview2) {
6790
7084
  return order;
6791
7085
  }
6792
7086
 
6793
- // src/data-table/ranked-search.ts
6794
- function inferTableSearchRole(field) {
6795
- const key = field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/[\s-]+/g, "_");
6796
- if (key === "id" || key === "uuid" || key.endsWith("_id")) return "id";
6797
- if (/^(name|alias|label|title|key|slug|display_name|common_name|model_name)$/.test(key) || /_(name|label|title)$/.test(key)) return "name";
6798
- if (/^(description|summary|details|notes|blurb)$/.test(key) || key.endsWith("_description")) return "description";
6799
- return "other";
7087
+ // src/data-table/grouping.ts
7088
+ var EMPTY_GROUP_KEY = "empty";
7089
+ var DEFAULT_EMPTY_LABEL = "(empty)";
7090
+ function isBlankGroupValue(value) {
7091
+ return value === null || value === void 0 || value === "";
6800
7092
  }
6801
- function searchableFields(row, columns) {
6802
- const values = /* @__PURE__ */ new Map();
6803
- if (row !== null && typeof row === "object") {
6804
- for (const [key, value] of Object.entries(row)) values.set(key, stringifyCellValue(value));
7093
+ function groupKeyOf(value) {
7094
+ if (isBlankGroupValue(value)) return EMPTY_GROUP_KEY;
7095
+ switch (typeof value) {
7096
+ case "string":
7097
+ return `s:${value}`;
7098
+ case "number":
7099
+ return `n:${String(value)}`;
7100
+ case "boolean":
7101
+ return `b:${value ? "true" : "false"}`;
7102
+ case "bigint":
7103
+ return `i:${String(value)}`;
7104
+ case "symbol":
7105
+ return `y:${String(value)}`;
7106
+ case "function":
7107
+ return `f:${safeString(value)}`;
7108
+ default:
7109
+ break;
6805
7110
  }
6806
- for (const column of columns) {
6807
- if (column.accessorKey || column.accessorFn || column.filterValue) {
6808
- values.set(columnId(column), stringifyCellValue(getFilterValue(row, column)));
6809
- }
7111
+ if (value instanceof Date) {
7112
+ const time = value.getTime();
7113
+ return `d:${Number.isFinite(time) ? value.toISOString() : "invalid"}`;
6810
7114
  }
6811
- return values;
7115
+ return `o:${safeJson(value)}`;
6812
7116
  }
6813
- function resolveTableSearchScopes(rows, columns, options = {}) {
6814
- const fields = new Set(columns.filter((c) => c.accessorKey || c.accessorFn || c.filterValue).map(columnId));
6815
- for (const row of rows) if (row !== null && typeof row === "object") Object.keys(row).forEach((key) => fields.add(key));
6816
- const roles = new Set([...fields].map((key) => options.roles?.[key] ?? inferTableSearchRole(key)));
6817
- const scopes = [{ id: "all", label: "All" }];
6818
- for (const [id, label] of [["id", "ID"], ["name", "Name"], ["description", "Description"]]) {
6819
- if (roles.has(id)) scopes.push({ id, label });
7117
+ function safeString(value) {
7118
+ try {
7119
+ return String(value);
7120
+ } catch {
7121
+ return "[unreadable]";
6820
7122
  }
6821
- for (const scope of options.scopes ?? []) if (!scopes.some((s) => s.id === scope.id) && scope.fields?.length) scopes.push(scope);
6822
- return scopes;
6823
7123
  }
6824
- function rankTableSearchRows(rows, columns, query, options = {}, extraText) {
6825
- const q = query.trim().toLocaleLowerCase();
6826
- if (!q) return [...rows];
6827
- const scope = options.scope ?? "all";
6828
- const customScope = options.scopes?.find((s) => s.id === scope);
6829
- const mode = options.matchMode ?? "contains";
6830
- return rows.flatMap((row, index) => {
6831
- const fields = [...searchableFields(row, columns)].filter(([key]) => {
6832
- if (scope === "all") return true;
6833
- if (customScope) return customScope.fields?.includes(key) ?? false;
6834
- return (options.roles?.[key] ?? inferTableSearchRole(key)) === scope;
6835
- });
6836
- const extra = scope === "all" ? extraText?.(row) : void 0;
6837
- const texts = fields.map(([, value]) => value);
6838
- if (extra) texts.push(extra);
6839
- const matched = mode === "whole_words" ? matchesTableSearch(texts.join(" "), q, mode) : texts.some((value) => matchesTableSearch(value, q, mode));
6840
- if (!matched) return [];
6841
- let rank = 3;
6842
- for (const [key, value] of fields) {
6843
- if (!matchesTableSearch(value, q, mode)) continue;
6844
- const role = options.roles?.[key] ?? inferTableSearchRole(key);
6845
- if (role === "id" || role === "name") rank = Math.min(rank, value.trim().toLocaleLowerCase() === q ? 0 : 1);
6846
- else if (role === "description") rank = Math.min(rank, 2);
7124
+ function safeJson(value) {
7125
+ try {
7126
+ const json = JSON.stringify(value);
7127
+ return typeof json === "string" ? json : safeString(value);
7128
+ } catch {
7129
+ return safeString(value);
7130
+ }
7131
+ }
7132
+ var CURRENCY_AND_SPACE = /[\s,$£€¥]/g;
7133
+ function coerceGroupNumber(value) {
7134
+ if (isBlankGroupValue(value)) return null;
7135
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
7136
+ if (typeof value === "boolean") return value ? 1 : 0;
7137
+ if (typeof value === "bigint") {
7138
+ const asNumber = Number(value);
7139
+ return Number.isFinite(asNumber) ? asNumber : null;
7140
+ }
7141
+ if (typeof value !== "string") return null;
7142
+ const cleaned = value.replace(CURRENCY_AND_SPACE, "");
7143
+ if (cleaned === "" || cleaned === "-" || cleaned === "+") return null;
7144
+ const parsed = Number(cleaned);
7145
+ return Number.isFinite(parsed) ? parsed : null;
7146
+ }
7147
+ var NUMBER_FORMAT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
7148
+ function formatGroupNumber(value) {
7149
+ if (!Number.isFinite(value)) return "\u2014";
7150
+ try {
7151
+ return NUMBER_FORMAT.format(value);
7152
+ } catch {
7153
+ return String(value);
7154
+ }
7155
+ }
7156
+ function defaultLabelOf(value, emptyLabel) {
7157
+ if (isBlankGroupValue(value)) return emptyLabel;
7158
+ if (typeof value === "string") return value;
7159
+ if (typeof value === "number") return formatGroupNumber(value);
7160
+ if (typeof value === "boolean") return value ? "true" : "false";
7161
+ if (value instanceof Date) {
7162
+ const time = value.getTime();
7163
+ return Number.isFinite(time) ? value.toISOString() : "Invalid Date";
7164
+ }
7165
+ if (typeof value === "object") return safeJson(value);
7166
+ return safeString(value);
7167
+ }
7168
+ var AGGREGATE_NAMES = {
7169
+ count: { name: "Count", position: "before" },
7170
+ sum: { name: "Sum", position: "before" },
7171
+ average: { name: "Average", position: "before" },
7172
+ min: { name: "Min", position: "before" },
7173
+ max: { name: "Max", position: "before" },
7174
+ empty: { name: "empty", position: "after" },
7175
+ filled: { name: "filled", position: "after" },
7176
+ unique: { name: "unique", position: "after" },
7177
+ checked: { name: "checked", position: "after" },
7178
+ unchecked: { name: "unchecked", position: "after" },
7179
+ percentFilled: { name: "filled", position: "after" }
7180
+ };
7181
+ function aggregateLabel(kind, value, override) {
7182
+ const naming = AGGREGATE_NAMES[kind] ?? { name: kind, position: "before" };
7183
+ const name = override && override.trim() !== "" ? override : naming.name;
7184
+ const shown = value === null ? "\u2014" : kind === "percentFilled" ? `${formatGroupNumber(value)}%` : formatGroupNumber(value);
7185
+ return naming.position === "before" ? `${name} ${shown}` : `${shown} ${name}`;
7186
+ }
7187
+ function readValue(row, reader) {
7188
+ try {
7189
+ return reader(row);
7190
+ } catch {
7191
+ return null;
7192
+ }
7193
+ }
7194
+ function computeAggregate(rows, column) {
7195
+ const kind = column.spec.kind;
7196
+ const raw = rows.map((row) => readValue(row, column.readCell));
7197
+ const total = raw.length;
7198
+ const filled = raw.filter((value2) => !isBlankGroupValue(value2));
7199
+ const numbers = filled.map((value2) => coerceGroupNumber(value2)).filter((value2) => value2 !== null);
7200
+ let value;
7201
+ switch (kind) {
7202
+ case "count":
7203
+ value = total;
7204
+ break;
7205
+ case "sum":
7206
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => acc + n2, 0);
7207
+ break;
7208
+ case "average":
7209
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => acc + n2, 0) / numbers.length;
7210
+ break;
7211
+ case "min":
7212
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => n2 < acc ? n2 : acc);
7213
+ break;
7214
+ case "max":
7215
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => n2 > acc ? n2 : acc);
7216
+ break;
7217
+ case "empty":
7218
+ value = total - filled.length;
7219
+ break;
7220
+ case "filled":
7221
+ value = filled.length;
7222
+ break;
7223
+ case "unique": {
7224
+ const seen = /* @__PURE__ */ new Set();
7225
+ for (const entry of filled) seen.add(groupKeyOf(entry));
7226
+ value = seen.size;
7227
+ break;
6847
7228
  }
6848
- return [{ row, rank, index }];
6849
- }).sort((a, b) => a.rank - b.rank || a.index - b.index).map((item) => item.row);
7229
+ case "checked":
7230
+ value = filled.filter((entry) => entry === true).length;
7231
+ break;
7232
+ case "unchecked":
7233
+ value = filled.filter((entry) => entry === false).length;
7234
+ break;
7235
+ case "percentFilled":
7236
+ value = total === 0 ? null : filled.length / total * 100;
7237
+ break;
7238
+ default:
7239
+ value = null;
7240
+ break;
7241
+ }
7242
+ return { kind, value, label: aggregateLabel(kind, value, column.spec.label) };
7243
+ }
7244
+ function compareLabels(a, b) {
7245
+ if (a === b) return 0;
7246
+ return a < b ? -1 : 1;
7247
+ }
7248
+ function compareValues2(a, b) {
7249
+ const aNumber = typeof a.value === "number" ? a.value : null;
7250
+ const bNumber = typeof b.value === "number" ? b.value : null;
7251
+ if (aNumber !== null && bNumber !== null && aNumber !== bNumber) return aNumber < bNumber ? -1 : 1;
7252
+ if (typeof a.value === "boolean" && typeof b.value === "boolean" && a.value !== b.value) {
7253
+ return a.value ? 1 : -1;
7254
+ }
7255
+ return compareLabels(a.label, b.label);
7256
+ }
7257
+ function groupRows(options) {
7258
+ const rows = Array.isArray(options.rows) ? options.rows : [];
7259
+ const emptyLabel = options.emptyLabel ?? DEFAULT_EMPTY_LABEL;
7260
+ const order = options.order ?? "value-asc";
7261
+ const aggregates = options.aggregates ?? {};
7262
+ const labelFor = (value) => {
7263
+ if (!options.labelOf) return defaultLabelOf(value, emptyLabel);
7264
+ if (isBlankGroupValue(value)) {
7265
+ try {
7266
+ const custom = options.labelOf(value);
7267
+ return typeof custom === "string" && custom !== "" ? custom : emptyLabel;
7268
+ } catch {
7269
+ return emptyLabel;
7270
+ }
7271
+ }
7272
+ try {
7273
+ const custom = options.labelOf(value);
7274
+ return typeof custom === "string" ? custom : defaultLabelOf(value, emptyLabel);
7275
+ } catch {
7276
+ return defaultLabelOf(value, emptyLabel);
7277
+ }
7278
+ };
7279
+ const buckets = /* @__PURE__ */ new Map();
7280
+ for (const row of rows) {
7281
+ const raw = readValue(row, options.readCell);
7282
+ const blank = isBlankGroupValue(raw);
7283
+ const value = blank ? null : raw;
7284
+ const key = groupKeyOf(value);
7285
+ let bucket = buckets.get(key);
7286
+ if (!bucket) {
7287
+ bucket = { key, value, label: labelFor(value), rows: [], seen: buckets.size };
7288
+ buckets.set(key, bucket);
7289
+ }
7290
+ bucket.rows.push(row);
7291
+ }
7292
+ const ordered = [...buckets.values()].sort((a, b) => {
7293
+ const aBlank = a.key === EMPTY_GROUP_KEY;
7294
+ const bBlank = b.key === EMPTY_GROUP_KEY;
7295
+ if (aBlank !== bBlank) return aBlank ? 1 : -1;
7296
+ if (aBlank && bBlank) return 0;
7297
+ switch (order) {
7298
+ case "value-asc":
7299
+ return compareValues2(a, b) || a.seen - b.seen;
7300
+ case "value-desc":
7301
+ return -compareValues2(a, b) || a.seen - b.seen;
7302
+ case "count-desc":
7303
+ return b.rows.length - a.rows.length || a.seen - b.seen;
7304
+ case "first-seen":
7305
+ default:
7306
+ return a.seen - b.seen;
7307
+ }
7308
+ });
7309
+ const aggregateEntries = Object.entries(aggregates);
7310
+ return ordered.map((bucket, index) => {
7311
+ const results = {};
7312
+ for (const [columnId2, column] of aggregateEntries) {
7313
+ if (!column || typeof column.readCell !== "function" || !column.spec) continue;
7314
+ results[columnId2] = computeAggregate(bucket.rows, column);
7315
+ }
7316
+ return {
7317
+ key: bucket.key,
7318
+ value: bucket.value,
7319
+ label: bucket.label,
7320
+ rows: bucket.rows,
7321
+ aggregates: results,
7322
+ index
7323
+ };
7324
+ });
7325
+ }
7326
+ function toggleGroupCollapsed(collapsed, key) {
7327
+ const list = Array.isArray(collapsed) ? collapsed : [];
7328
+ return list.includes(key) ? list.filter((entry) => entry !== key) : [...list, key];
7329
+ }
7330
+ function isGroupCollapsed(collapsed, key) {
7331
+ return Array.isArray(collapsed) ? collapsed.includes(key) : false;
7332
+ }
7333
+ function flattenGroups(groups, collapsed) {
7334
+ const out = [];
7335
+ let dataIndex = 0;
7336
+ for (const group of Array.isArray(groups) ? groups : []) {
7337
+ out.push({ kind: "group", group });
7338
+ if (isGroupCollapsed(collapsed, group.key)) continue;
7339
+ for (const row of group.rows) {
7340
+ out.push({ kind: "row", group, row, dataIndex });
7341
+ dataIndex += 1;
7342
+ }
7343
+ }
7344
+ return out;
7345
+ }
7346
+ function groupSummary(group, rowNoun = "row") {
7347
+ const count = group?.rows?.length ?? 0;
7348
+ const noun = count === 1 ? rowNoun : `${rowNoun}s`;
7349
+ const parts = [`${formatGroupNumber(count)} ${noun}`];
7350
+ for (const result of Object.values(group?.aggregates ?? {})) {
7351
+ if (result?.label) parts.push(result.label);
7352
+ }
7353
+ return parts.join(" \xB7 ");
6850
7354
  }
6851
7355
 
6852
- // src/data-table/ColumnManager.tsx
6853
- var import_react15 = require("react");
6854
- var import_core2 = require("@dnd-kit/core");
6855
- var import_jsx_runtime33 = require("react/jsx-runtime");
6856
- function ColumnManager({
6857
- open,
6858
- onOpenChange,
6859
- columns,
6860
- order,
6861
- hidden,
6862
- onApply
6863
- }) {
6864
- const byId = (0, import_react15.useMemo)(
6865
- () => new Map(columns.map((column) => [columnId(column), column])),
6866
- [columns]
7356
+ // src/data-table/virtual-window.ts
7357
+ var DEFAULT_OVERSCAN = 6;
7358
+ function isNum(value) {
7359
+ return typeof value === "number" && Number.isFinite(value);
7360
+ }
7361
+ function toCount(value) {
7362
+ return isNum(value) && value > 0 ? Math.floor(value) : 0;
7363
+ }
7364
+ function toLength(value) {
7365
+ return isNum(value) && value > 0 ? value : 0;
7366
+ }
7367
+ function toOverscan(value) {
7368
+ if (!isNum(value)) return DEFAULT_OVERSCAN;
7369
+ return value > 0 ? Math.floor(value) : 0;
7370
+ }
7371
+ function toPinned(value, count) {
7372
+ if (!isNum(value) || value <= 0) return 0;
7373
+ return Math.min(Math.floor(value), count);
7374
+ }
7375
+ function assemble(args) {
7376
+ const { pinnedCount, start, end, padStart, body, total } = args;
7377
+ let padEnd = total - (padStart + body);
7378
+ if (!Number.isFinite(padEnd)) padEnd = 0;
7379
+ const sum = padStart + body + padEnd;
7380
+ if (sum !== total) padEnd = padEnd + (total - sum);
7381
+ const pinned = [];
7382
+ for (let i = 0; i < pinnedCount; i += 1) pinned.push(i);
7383
+ return { start, end, padStart, padEnd, total, pinned };
7384
+ }
7385
+ function emptyWindow(pinnedCount) {
7386
+ const pinned = [];
7387
+ for (let i = 0; i < pinnedCount; i += 1) pinned.push(i);
7388
+ return { start: pinnedCount, end: pinnedCount, padStart: 0, padEnd: 0, total: 0, pinned };
7389
+ }
7390
+ function band(firstVisible, lastVisible, scrollCount, overscan) {
7391
+ let first = firstVisible;
7392
+ if (first > scrollCount - 1) first = scrollCount - 1;
7393
+ if (first < 0) first = 0;
7394
+ let last = lastVisible;
7395
+ if (last < first + 1) last = first + 1;
7396
+ if (last > scrollCount) last = scrollCount;
7397
+ const start = Math.max(0, first - overscan);
7398
+ let end = Math.min(scrollCount, last + overscan);
7399
+ if (end < start) end = start;
7400
+ return { start, end };
7401
+ }
7402
+ function computeWindow(input) {
7403
+ const count = toCount(input?.count);
7404
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7405
+ if (count <= 0) return emptyWindow(0);
7406
+ const itemSize = toLength(input?.itemSize);
7407
+ const viewport = toLength(input?.viewport);
7408
+ const overscan = toOverscan(input?.overscan);
7409
+ const scrollCount = count - pinnedCount;
7410
+ if (scrollCount <= 0) return emptyWindow(pinnedCount);
7411
+ if (itemSize <= 0) {
7412
+ return assemble({ pinnedCount, start: pinnedCount, end: count, padStart: 0, body: 0, total: 0 });
7413
+ }
7414
+ const total = scrollCount * itemSize;
7415
+ const maxOffset = Math.max(0, total - viewport);
7416
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7417
+ const { start: relStart, end: relEnd } = band(
7418
+ Math.floor(offset / itemSize),
7419
+ Math.ceil((offset + viewport) / itemSize),
7420
+ scrollCount,
7421
+ overscan
6867
7422
  );
6868
- const [draftOrder, setDraftOrder] = (0, import_react15.useState)(order);
6869
- const [draftHidden, setDraftHidden] = (0, import_react15.useState)(() => new Set(hidden));
6870
- const [draggedColumnId, setDraggedColumnId] = (0, import_react15.useState)(null);
6871
- const [dropTarget, setDropTarget] = (0, import_react15.useState)(null);
6872
- (0, import_react15.useEffect)(() => {
6873
- if (!open) return;
6874
- setDraftOrder(order);
6875
- setDraftHidden(new Set(hidden));
6876
- }, [open, order, hidden]);
6877
- const move = (id, direction) => {
6878
- setDraftOrder((current) => {
6879
- const index = current.indexOf(id);
6880
- const target = index + direction;
6881
- if (index < 0 || target < 0 || target >= current.length) return current;
6882
- const next = [...current];
6883
- const displaced = next[target];
6884
- if (displaced === void 0) return current;
6885
- next[target] = id;
6886
- next[index] = displaced;
6887
- return next;
6888
- });
6889
- };
6890
- const moveTo = (id, targetId) => {
6891
- if (id === targetId) return;
6892
- setDraftOrder((current) => {
6893
- const from = current.indexOf(id);
6894
- const target = current.indexOf(targetId);
6895
- if (from < 0 || target < 0) return current;
6896
- const next = [...current];
6897
- next.splice(from, 1);
6898
- const targetAfterRemoval = next.indexOf(targetId);
6899
- next.splice(targetAfterRemoval, 0, id);
6900
- return next;
6901
- });
7423
+ return assemble({
7424
+ pinnedCount,
7425
+ start: pinnedCount + relStart,
7426
+ end: pinnedCount + relEnd,
7427
+ padStart: relStart * itemSize,
7428
+ body: (relEnd - relStart) * itemSize,
7429
+ total
7430
+ });
7431
+ }
7432
+ var PREFIX_CACHE = /* @__PURE__ */ new WeakMap();
7433
+ function prefixFor(sizeAt, count) {
7434
+ let byCount = PREFIX_CACHE.get(sizeAt);
7435
+ if (!byCount) {
7436
+ byCount = /* @__PURE__ */ new Map();
7437
+ PREFIX_CACHE.set(sizeAt, byCount);
7438
+ }
7439
+ const cached = byCount.get(count);
7440
+ if (cached) return cached;
7441
+ const prefix = new Float64Array(count + 1);
7442
+ let running = 0;
7443
+ for (let i = 0; i < count; i += 1) {
7444
+ let size = 0;
7445
+ try {
7446
+ size = sizeAt(i);
7447
+ } catch {
7448
+ size = 0;
7449
+ }
7450
+ running += isNum(size) && size > 0 ? size : 0;
7451
+ prefix[i + 1] = running;
7452
+ }
7453
+ byCount.set(count, prefix);
7454
+ return prefix;
7455
+ }
7456
+ function at(prefix, index) {
7457
+ return prefix[index] ?? 0;
7458
+ }
7459
+ function lastAtOrBefore(prefix, lo, hi, target) {
7460
+ let a = lo;
7461
+ let b = hi;
7462
+ while (a < b) {
7463
+ const mid = a + Math.ceil((b - a) / 2);
7464
+ if (at(prefix, mid) <= target) a = mid;
7465
+ else b = mid - 1;
7466
+ }
7467
+ return a;
7468
+ }
7469
+ function firstAtOrAfter(prefix, lo, hi, target) {
7470
+ let a = lo;
7471
+ let b = hi;
7472
+ while (a < b) {
7473
+ const mid = a + Math.floor((b - a) / 2);
7474
+ if (at(prefix, mid) >= target) b = mid;
7475
+ else a = mid + 1;
7476
+ }
7477
+ return a;
7478
+ }
7479
+ function computeVariableWindow(input) {
7480
+ const count = toCount(input?.count);
7481
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7482
+ if (count <= 0) return emptyWindow(0);
7483
+ const sizeAt = typeof input?.sizeAt === "function" ? input.sizeAt : () => 0;
7484
+ const viewport = toLength(input?.viewport);
7485
+ const overscan = toOverscan(input?.overscan);
7486
+ const scrollCount = count - pinnedCount;
7487
+ if (scrollCount <= 0) return emptyWindow(pinnedCount);
7488
+ const prefix = prefixFor(sizeAt, count);
7489
+ const pinnedLength = at(prefix, pinnedCount);
7490
+ const total = at(prefix, count) - pinnedLength;
7491
+ if (total <= 0) {
7492
+ return assemble({ pinnedCount, start: pinnedCount, end: count, padStart: 0, body: 0, total: total > 0 ? total : 0 });
7493
+ }
7494
+ const maxOffset = Math.max(0, total - viewport);
7495
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7496
+ const firstVisible = lastAtOrBefore(prefix, pinnedCount, count, pinnedLength + offset) - pinnedCount;
7497
+ const lastVisible = firstAtOrAfter(prefix, pinnedCount, count, pinnedLength + offset + viewport) - pinnedCount;
7498
+ const { start: relStart, end: relEnd } = band(firstVisible, lastVisible, scrollCount, overscan);
7499
+ const absStart = pinnedCount + relStart;
7500
+ const absEnd = pinnedCount + relEnd;
7501
+ return assemble({
7502
+ pinnedCount,
7503
+ start: absStart,
7504
+ end: absEnd,
7505
+ padStart: at(prefix, absStart) - pinnedLength,
7506
+ body: at(prefix, absEnd) - at(prefix, absStart),
7507
+ total
7508
+ });
7509
+ }
7510
+ function scrollOffsetForIndex(input) {
7511
+ const count = toCount(input?.count);
7512
+ if (count <= 0) return null;
7513
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7514
+ const itemSize = toLength(input?.itemSize);
7515
+ const viewport = toLength(input?.viewport);
7516
+ const scrollCount = count - pinnedCount;
7517
+ if (scrollCount <= 0 || itemSize <= 0) return null;
7518
+ const rawIndex = isNum(input?.index) ? Math.floor(input.index) : 0;
7519
+ const index = Math.min(Math.max(rawIndex, 0), count - 1);
7520
+ if (index < pinnedCount) return null;
7521
+ const total = scrollCount * itemSize;
7522
+ const maxOffset = Math.max(0, total - viewport);
7523
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7524
+ const itemStart = (index - pinnedCount) * itemSize;
7525
+ const itemEnd = itemStart + itemSize;
7526
+ if (itemStart >= offset && itemEnd <= offset + viewport) return null;
7527
+ const next = itemStart < offset ? itemStart : itemEnd - viewport;
7528
+ const clamped = Math.min(Math.max(next, 0), maxOffset);
7529
+ return clamped === offset ? null : clamped;
7530
+ }
7531
+
7532
+ // src/data-table/useSpreadsheetGrid.ts
7533
+ var import_react15 = require("react");
7534
+
7535
+ // src/data-table/grid-selection.ts
7536
+ function rangeBounds(range, rowIds, fieldNames) {
7537
+ const ra = rowIds.indexOf(range.anchor.rowId);
7538
+ const rf = rowIds.indexOf(range.focus.rowId);
7539
+ const ca = fieldNames.indexOf(range.anchor.fieldName);
7540
+ const cf = fieldNames.indexOf(range.focus.fieldName);
7541
+ if (ra === -1 || rf === -1 || ca === -1 || cf === -1) return null;
7542
+ return {
7543
+ r0: Math.min(ra, rf),
7544
+ r1: Math.max(ra, rf),
7545
+ c0: Math.min(ca, cf),
7546
+ c1: Math.max(ca, cf)
7547
+ };
7548
+ }
7549
+ function rangeSize(bounds) {
7550
+ const rows = bounds.r1 - bounds.r0 + 1;
7551
+ const cols = bounds.c1 - bounds.c0 + 1;
7552
+ return { rows, cols, cells: rows * cols };
7553
+ }
7554
+ function boundsContain(bounds, rowIndex, colIndex) {
7555
+ return rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
7556
+ }
7557
+ function cellsInRange(range, rowIds, fieldNames) {
7558
+ const b = rangeBounds(range, rowIds, fieldNames);
7559
+ if (!b) return [];
7560
+ const out = [];
7561
+ for (let r = b.r0; r <= b.r1; r += 1) {
7562
+ const rowId = rowIds[r];
7563
+ if (rowId === void 0) continue;
7564
+ for (let c = b.c0; c <= b.c1; c += 1) {
7565
+ const fieldName = fieldNames[c];
7566
+ if (fieldName === void 0) continue;
7567
+ out.push({ rowId, fieldName });
7568
+ }
7569
+ }
7570
+ return out;
7571
+ }
7572
+ function rangeRows(range, rowIds, fieldNames) {
7573
+ const b = rangeBounds(range, rowIds, fieldNames);
7574
+ if (!b) return [];
7575
+ const rows = [];
7576
+ for (let r = b.r0; r <= b.r1; r += 1) {
7577
+ const rowId = rowIds[r];
7578
+ if (rowId === void 0) continue;
7579
+ const row = [];
7580
+ for (let c = b.c0; c <= b.c1; c += 1) {
7581
+ const fieldName = fieldNames[c];
7582
+ if (fieldName === void 0) continue;
7583
+ row.push({ rowId, fieldName });
7584
+ }
7585
+ rows.push(row);
7586
+ }
7587
+ return rows;
7588
+ }
7589
+ function isSingleCellRange(range) {
7590
+ return sameCell(range.anchor, range.focus);
7591
+ }
7592
+ function rowRange(rowId, fieldNames) {
7593
+ const firstField = fieldNames[0];
7594
+ const lastField = fieldNames[fieldNames.length - 1];
7595
+ if (firstField === void 0 || lastField === void 0) return null;
7596
+ return {
7597
+ anchor: { rowId, fieldName: firstField },
7598
+ focus: { rowId, fieldName: lastField }
7599
+ };
7600
+ }
7601
+ function columnRange(fieldName, rowIds) {
7602
+ const firstRow = rowIds[0];
7603
+ const lastRow = rowIds[rowIds.length - 1];
7604
+ if (firstRow === void 0 || lastRow === void 0) return null;
7605
+ return {
7606
+ anchor: { rowId: firstRow, fieldName },
7607
+ focus: { rowId: lastRow, fieldName }
7608
+ };
7609
+ }
7610
+ function allRange(rowIds, fieldNames) {
7611
+ const firstRow = rowIds[0];
7612
+ const lastRow = rowIds[rowIds.length - 1];
7613
+ const firstField = fieldNames[0];
7614
+ const lastField = fieldNames[fieldNames.length - 1];
7615
+ if (firstRow === void 0 || lastRow === void 0 || firstField === void 0 || lastField === void 0) {
7616
+ return null;
7617
+ }
7618
+ return {
7619
+ anchor: { rowId: firstRow, fieldName: firstField },
7620
+ focus: { rowId: lastRow, fieldName: lastField }
7621
+ };
7622
+ }
7623
+ function sameCell(a, b) {
7624
+ if (!a || !b) return a === b;
7625
+ return a.rowId === b.rowId && a.fieldName === b.fieldName;
7626
+ }
7627
+ function moveSelection(current, move, rowIds, fieldNames) {
7628
+ if (rowIds.length === 0 || fieldNames.length === 0) return null;
7629
+ const firstRowId = rowIds[0];
7630
+ const firstFieldName = fieldNames[0];
7631
+ if (firstRowId === void 0 || firstFieldName === void 0) return null;
7632
+ const first = { rowId: firstRowId, fieldName: firstFieldName };
7633
+ if (!current) return first;
7634
+ let r = rowIds.indexOf(current.rowId);
7635
+ let c = fieldNames.indexOf(current.fieldName);
7636
+ if (r === -1 || c === -1) return first;
7637
+ const lastRow = rowIds.length - 1;
7638
+ const lastCol = fieldNames.length - 1;
7639
+ const clamp = (n2, max) => Math.min(Math.max(n2, 0), max);
7640
+ switch (move) {
7641
+ case "up":
7642
+ r = clamp(r - 1, lastRow);
7643
+ break;
7644
+ case "down":
7645
+ r = clamp(r + 1, lastRow);
7646
+ break;
7647
+ case "left":
7648
+ c = clamp(c - 1, lastCol);
7649
+ break;
7650
+ case "right":
7651
+ c = clamp(c + 1, lastCol);
7652
+ break;
7653
+ case "rowStart":
7654
+ c = 0;
7655
+ break;
7656
+ case "rowEnd":
7657
+ c = lastCol;
7658
+ break;
7659
+ case "gridStart":
7660
+ r = 0;
7661
+ c = 0;
7662
+ break;
7663
+ case "gridEnd":
7664
+ r = lastRow;
7665
+ c = lastCol;
7666
+ break;
7667
+ case "nextCell":
7668
+ if (c < lastCol) c += 1;
7669
+ else if (r < lastRow) {
7670
+ r += 1;
7671
+ c = 0;
7672
+ }
7673
+ break;
7674
+ case "prevCell":
7675
+ if (c > 0) c -= 1;
7676
+ else if (r > 0) {
7677
+ r -= 1;
7678
+ c = lastCol;
7679
+ }
7680
+ break;
7681
+ }
7682
+ const rowId = rowIds[r];
7683
+ const fieldName = fieldNames[c];
7684
+ if (rowId === void 0 || fieldName === void 0) return first;
7685
+ return { rowId, fieldName };
7686
+ }
7687
+ function isTypingKey(e) {
7688
+ if (e.ctrlKey || e.metaKey || e.altKey) return false;
7689
+ return e.key.length === 1 && e.key !== " ";
7690
+ }
7691
+ function classifyGridKey(e) {
7692
+ const mod = e.ctrlKey || e.metaKey;
7693
+ const arrowMove = {
7694
+ ArrowUp: mod ? "gridStart" : "up",
7695
+ ArrowDown: mod ? "gridEnd" : "down",
7696
+ ArrowLeft: mod ? "rowStart" : "left",
7697
+ ArrowRight: mod ? "rowEnd" : "right",
7698
+ Home: mod ? "gridStart" : "rowStart",
7699
+ End: mod ? "gridEnd" : "rowEnd"
7700
+ };
7701
+ const arrow = arrowMove[e.key];
7702
+ if (arrow !== void 0) {
7703
+ return { kind: e.shiftKey ? "extend" : "move", move: arrow };
7704
+ }
7705
+ switch (e.key) {
7706
+ case "Tab":
7707
+ return { kind: "move", move: e.shiftKey ? "prevCell" : "nextCell" };
7708
+ case "Enter":
7709
+ case "F2":
7710
+ return { kind: "edit" };
7711
+ case " ":
7712
+ if (e.shiftKey) return { kind: "selectRow" };
7713
+ if (mod) return { kind: "selectColumn" };
7714
+ return { kind: "edit" };
7715
+ case "a":
7716
+ case "A":
7717
+ if (mod) return { kind: "selectAll" };
7718
+ break;
7719
+ case "d":
7720
+ case "D":
7721
+ if (mod) return { kind: "fillDown" };
7722
+ break;
7723
+ case "Escape":
7724
+ return { kind: "escape" };
7725
+ case "Delete":
7726
+ case "Backspace":
7727
+ return { kind: "clearCell" };
7728
+ case "c":
7729
+ case "C":
7730
+ if (mod) return { kind: "copy" };
7731
+ break;
7732
+ case "x":
7733
+ case "X":
7734
+ if (mod) return { kind: "cut" };
7735
+ break;
7736
+ case "v":
7737
+ case "V":
7738
+ if (mod) return { kind: "paste" };
7739
+ break;
7740
+ }
7741
+ if (isTypingKey(e)) return { kind: "editSeeded", seed: e.key };
7742
+ return null;
7743
+ }
7744
+ function directClickKinds() {
7745
+ return ["checkbox", "rating", "select", "multiselect"];
7746
+ }
7747
+ function isDirectClickEditor(editorKind) {
7748
+ return editorKind !== void 0 && directClickKinds().includes(editorKind);
7749
+ }
7750
+ function cellDomKey(address) {
7751
+ return `${address.rowId}::${address.fieldName}`;
7752
+ }
7753
+
7754
+ // src/data-table/grid-clipboard.ts
7755
+ function cellClipboardText(value) {
7756
+ if (value === null || value === void 0) return "";
7757
+ if (typeof value === "object") return JSON.stringify(value);
7758
+ return String(value);
7759
+ }
7760
+ function tsvCell(text) {
7761
+ return /[\t\r\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
7762
+ }
7763
+ function gridToTsv(block) {
7764
+ return block.map((row) => row.map((cell) => tsvCell(cellClipboardText(cell))).join(" ")).join("\n");
7765
+ }
7766
+ function parseClipboardGrid(text) {
7767
+ const rows = [];
7768
+ let row = [];
7769
+ let cell = "";
7770
+ let i = 0;
7771
+ const n2 = text.length;
7772
+ while (i < n2) {
7773
+ const ch = text[i];
7774
+ if (cell === "" && ch === '"') {
7775
+ let j = i + 1;
7776
+ let out = "";
7777
+ let closed = false;
7778
+ while (j < n2) {
7779
+ const c = text[j];
7780
+ if (c === '"') {
7781
+ if (text[j + 1] === '"') {
7782
+ out += '"';
7783
+ j += 2;
7784
+ continue;
7785
+ }
7786
+ closed = true;
7787
+ j += 1;
7788
+ break;
7789
+ }
7790
+ out += c;
7791
+ j += 1;
7792
+ }
7793
+ if (closed && (j >= n2 || text[j] === " " || text[j] === "\n" || text[j] === "\r")) {
7794
+ cell = out;
7795
+ i = j;
7796
+ if (i >= n2) break;
7797
+ const d = text[i];
7798
+ if (d === " ") {
7799
+ row.push(cell);
7800
+ cell = "";
7801
+ i += 1;
7802
+ continue;
7803
+ }
7804
+ row.push(cell);
7805
+ rows.push(row);
7806
+ row = [];
7807
+ cell = "";
7808
+ i += d === "\r" && text[i + 1] === "\n" ? 2 : 1;
7809
+ continue;
7810
+ }
7811
+ cell += ch;
7812
+ i += 1;
7813
+ continue;
7814
+ }
7815
+ if (ch === " ") {
7816
+ row.push(cell);
7817
+ cell = "";
7818
+ i += 1;
7819
+ continue;
7820
+ }
7821
+ if (ch === "\n" || ch === "\r") {
7822
+ row.push(cell);
7823
+ rows.push(row);
7824
+ row = [];
7825
+ cell = "";
7826
+ i += ch === "\r" && text[i + 1] === "\n" ? 2 : 1;
7827
+ continue;
7828
+ }
7829
+ cell += ch;
7830
+ i += 1;
7831
+ }
7832
+ const endedOnBreak = n2 > 0 && (text[n2 - 1] === "\n" || text[n2 - 1] === "\r");
7833
+ if (!endedOnBreak) {
7834
+ row.push(cell);
7835
+ rows.push(row);
7836
+ } else if (row.length > 0) {
7837
+ row.push(cell);
7838
+ rows.push(row);
7839
+ }
7840
+ return rows.length === 0 ? [[""]] : rows;
7841
+ }
7842
+ function planPaste(anchor, block, rowIds, fieldNames) {
7843
+ const r0 = rowIds.indexOf(anchor.rowId);
7844
+ const c0 = fieldNames.indexOf(anchor.fieldName);
7845
+ if (r0 === -1 || c0 === -1) return null;
7846
+ const width = block.reduce((w, row) => Math.max(w, row.length), 0);
7847
+ const colsAvailable = fieldNames.length - c0;
7848
+ const colsUsed = Math.min(width, colsAvailable);
7849
+ const targetFields = fieldNames.slice(c0, c0 + colsUsed);
7850
+ const cells = [];
7851
+ const overflowRows = [];
7852
+ block.forEach((blockRow, dr) => {
7853
+ const rowId = rowIds[r0 + dr];
7854
+ if (rowId === void 0) {
7855
+ overflowRows.push(targetFields.map((_, dc) => blockRow[dc] ?? ""));
7856
+ return;
7857
+ }
7858
+ targetFields.forEach((fieldName, dc) => {
7859
+ if (dc >= blockRow.length) return;
7860
+ const raw = blockRow[dc];
7861
+ if (raw === void 0) return;
7862
+ cells.push({ rowId, fieldName, raw });
7863
+ });
7864
+ });
7865
+ return {
7866
+ cells,
7867
+ overflowRows,
7868
+ clippedColumns: Math.max(0, width - colsAvailable),
7869
+ fieldNames: targetFields
7870
+ };
7871
+ }
7872
+ function isSingleCell(block) {
7873
+ return block.length === 1 && block[0]?.length === 1;
7874
+ }
7875
+ function storedValuesEqual(a, b) {
7876
+ if (a === b) return true;
7877
+ if (a === null || b === null || a === void 0 || b === void 0) {
7878
+ return (a ?? null) === (b ?? null);
7879
+ }
7880
+ if (typeof a === "object" || typeof b === "object") {
7881
+ try {
7882
+ return JSON.stringify(a) === JSON.stringify(b);
7883
+ } catch {
7884
+ return false;
7885
+ }
7886
+ }
7887
+ return false;
7888
+ }
7889
+
7890
+ // src/data-table/fill.ts
7891
+ var NO_SERIES = { kind: "none", step: 0 };
7892
+ var MS_PER_DAY = 864e5;
7893
+ function plural(count, one, many) {
7894
+ return `${count} ${count === 1 ? one : many}`;
7895
+ }
7896
+ function isValidRect(rect) {
7897
+ return Number.isInteger(rect.top) && Number.isInteger(rect.left) && Number.isInteger(rect.bottom) && Number.isInteger(rect.right) && rect.top <= rect.bottom && rect.left <= rect.right;
7898
+ }
7899
+ function asBounds(rect) {
7900
+ return { r0: rect.top, r1: rect.bottom, c0: rect.left, c1: rect.right };
7901
+ }
7902
+ function directionOf(source, target) {
7903
+ if (!isValidRect(source) || !isValidRect(target)) return null;
7904
+ if (target.top === source.bottom + 1) return "down";
7905
+ if (target.bottom === source.top - 1) return "up";
7906
+ if (target.left === source.right + 1) return "right";
7907
+ if (target.right === source.left - 1) return "left";
7908
+ return null;
7909
+ }
7910
+ function tileIndex(i, a, b) {
7911
+ const n2 = b - a + 1;
7912
+ if (n2 <= 0) return a;
7913
+ if (i >= a && i <= b) return i;
7914
+ if (i > b) return a + (i - a) % n2;
7915
+ return b - (a - 1 - i) % n2;
7916
+ }
7917
+ function isFiniteNumber(v) {
7918
+ return typeof v === "number" && Number.isFinite(v);
7919
+ }
7920
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
7921
+ var DATE_ISH = /^\d{4}-\d{2}-\d{2}([T ]|$)/;
7922
+ function dateMs(v) {
7923
+ if (v instanceof Date) {
7924
+ const t = v.getTime();
7925
+ return Number.isFinite(t) ? t : null;
7926
+ }
7927
+ if (typeof v === "string" && DATE_ISH.test(v)) {
7928
+ const t = Date.parse(v);
7929
+ return Number.isFinite(t) ? t : null;
7930
+ }
7931
+ return null;
7932
+ }
7933
+ function detectSeries(values) {
7934
+ if (!Array.isArray(values) || values.length < 2) return NO_SERIES;
7935
+ if (values.every(isFiniteNumber)) {
7936
+ const nums = values;
7937
+ const first = nums[0];
7938
+ const second = nums[1];
7939
+ if (first === void 0 || second === void 0) return NO_SERIES;
7940
+ const step = second - first;
7941
+ for (let i = 1; i < nums.length; i += 1) {
7942
+ const prev = nums[i - 1];
7943
+ const cur = nums[i];
7944
+ if (prev === void 0 || cur === void 0) return NO_SERIES;
7945
+ if (cur - prev !== step) return NO_SERIES;
7946
+ }
7947
+ return { kind: "number", step };
7948
+ }
7949
+ const stamps = values.map(dateMs);
7950
+ if (stamps.every((t) => t !== null)) {
7951
+ const first = stamps[0];
7952
+ const second = stamps[1];
7953
+ if (first === void 0 || second === void 0) return NO_SERIES;
7954
+ const deltaMs = second - first;
7955
+ if (deltaMs % MS_PER_DAY !== 0) return NO_SERIES;
7956
+ for (let i = 1; i < stamps.length; i += 1) {
7957
+ const prev = stamps[i - 1];
7958
+ const cur = stamps[i];
7959
+ if (prev === void 0 || cur === void 0) return NO_SERIES;
7960
+ if (cur - prev !== deltaMs) return NO_SERIES;
7961
+ }
7962
+ return { kind: "date", step: deltaMs / MS_PER_DAY };
7963
+ }
7964
+ return NO_SERIES;
7965
+ }
7966
+ function formatDateLike(ms, sample) {
7967
+ const iso = new Date(ms).toISOString();
7968
+ if (typeof sample === "string" && DATE_ONLY.test(sample)) return iso.slice(0, 10);
7969
+ return iso;
7970
+ }
7971
+ function extrapolate(line, series, k) {
7972
+ const last = line[line.length - 1];
7973
+ if (series.kind === "number" && isFiniteNumber(last)) {
7974
+ return last + k * series.step;
7975
+ }
7976
+ if (series.kind === "date") {
7977
+ const base = dateMs(last);
7978
+ if (base === null) return jsonable(last);
7979
+ return formatDateLike(base + k * series.step * MS_PER_DAY, last);
7980
+ }
7981
+ return jsonable(last);
7982
+ }
7983
+ function jsonable(value) {
7984
+ if (value instanceof Date) {
7985
+ const t = value.getTime();
7986
+ return Number.isFinite(t) ? value.toISOString() : null;
7987
+ }
7988
+ if (value === void 0) return null;
7989
+ return value;
7990
+ }
7991
+ function emptyResult(summary) {
7992
+ return { patches: [], skipped: [], summary };
7993
+ }
7994
+ function fillRange(request) {
7995
+ const { source, target, columnIds, rowIds } = request;
7996
+ const mode = request.mode ?? "series";
7997
+ const isWritable = request.isColumnWritable ?? (() => true);
7998
+ const direction = directionOf(source, target);
7999
+ if (!direction) {
8000
+ return emptyResult(
8001
+ "Filled 0 cells because the fill area does not touch the selection."
8002
+ );
8003
+ }
8004
+ if (rowIds.length === 0 || columnIds.length === 0) {
8005
+ return emptyResult("Filled 0 cells because the table is empty.");
8006
+ }
8007
+ const read = (r, c) => {
8008
+ try {
8009
+ return { ok: true, value: request.valueAt(r, c) };
8010
+ } catch {
8011
+ return { ok: false, value: void 0 };
8012
+ }
8013
+ };
8014
+ const vertical = direction === "down" || direction === "up";
8015
+ const forward = direction === "down" || direction === "right";
8016
+ const fillStart = vertical ? source.top : source.left;
8017
+ const fillEnd = vertical ? source.bottom : source.right;
8018
+ const crossStart = vertical ? source.left : source.top;
8019
+ const crossEnd = vertical ? source.right : source.bottom;
8020
+ const maxFill = (vertical ? rowIds.length : columnIds.length) - 1;
8021
+ const maxCross = (vertical ? columnIds.length : rowIds.length) - 1;
8022
+ const rawFillFrom = vertical ? target.top : target.left;
8023
+ const rawFillTo = vertical ? target.bottom : target.right;
8024
+ const rawCrossFrom = vertical ? target.left : target.top;
8025
+ const rawCrossTo = vertical ? target.right : target.bottom;
8026
+ const fillFrom = Math.max(rawFillFrom, 0);
8027
+ const fillTo = Math.min(rawFillTo, maxFill);
8028
+ const crossFrom = Math.max(rawCrossFrom, 0);
8029
+ const crossTo = Math.min(rawCrossTo, maxCross);
8030
+ const requested = Math.max(rawFillTo - rawFillFrom + 1, 0) * Math.max(rawCrossTo - rawCrossFrom + 1, 0);
8031
+ const inside = Math.max(fillTo - fillFrom + 1, 0) * Math.max(crossTo - crossFrom + 1, 0);
8032
+ const outside = Math.max(requested - inside, 0);
8033
+ const sourceBounds = asBounds(source);
8034
+ const patches = [];
8035
+ const skipped = [];
8036
+ const touchedColumns = /* @__PURE__ */ new Set();
8037
+ const lines = /* @__PURE__ */ new Map();
8038
+ const lineFor = (crossIndex) => {
8039
+ const srcCross = tileIndex(crossIndex, crossStart, crossEnd);
8040
+ const cached = lines.get(srcCross);
8041
+ if (cached) return cached;
8042
+ const values = [];
8043
+ const reads = [];
8044
+ for (let f = fillStart; f <= fillEnd; f += 1) {
8045
+ const r = vertical ? f : srcCross;
8046
+ const c = vertical ? srcCross : f;
8047
+ const got = read(r, c);
8048
+ values.push(got.value);
8049
+ reads.push(got.ok);
8050
+ }
8051
+ const series = mode === "series" ? detectSeries(values) : NO_SERIES;
8052
+ const line = { values, reads, series };
8053
+ lines.set(srcCross, line);
8054
+ return line;
8055
+ };
8056
+ for (let f = fillFrom; f <= fillTo; f += 1) {
8057
+ for (let x = crossFrom; x <= crossTo; x += 1) {
8058
+ const rowIndex = vertical ? f : x;
8059
+ const colIndex = vertical ? x : f;
8060
+ const rowId = rowIds[rowIndex];
8061
+ const columnId2 = columnIds[colIndex];
8062
+ if (rowId === void 0 || columnId2 === void 0) continue;
8063
+ if (boundsContain(sourceBounds, rowIndex, colIndex)) continue;
8064
+ if (!isWritable(columnId2)) {
8065
+ skipped.push({
8066
+ rowId,
8067
+ columnId: columnId2,
8068
+ reason: `${columnId2} cannot be filled because that column is read-only.`
8069
+ });
8070
+ continue;
8071
+ }
8072
+ const line = lineFor(x);
8073
+ if (line.series.kind !== "none") {
8074
+ const k = forward ? f - fillEnd : fillStart - f;
8075
+ patches.push({ rowId, columnId: columnId2, value: jsonable(extrapolate(line.values, line.series, k)) });
8076
+ touchedColumns.add(columnId2);
8077
+ continue;
8078
+ }
8079
+ const srcFill = tileIndex(f, fillStart, fillEnd);
8080
+ const patternIndex = srcFill - fillStart;
8081
+ if (line.reads[patternIndex] !== true) {
8082
+ skipped.push({
8083
+ rowId,
8084
+ columnId: columnId2,
8085
+ reason: `The value to copy into ${columnId2} could not be read, so that cell was left alone.`
8086
+ });
8087
+ continue;
8088
+ }
8089
+ patches.push({ rowId, columnId: columnId2, value: jsonable(line.values[patternIndex]) });
8090
+ touchedColumns.add(columnId2);
8091
+ }
8092
+ }
8093
+ return { patches, skipped, summary: summarize(direction, patches, skipped, touchedColumns, outside) };
8094
+ }
8095
+ function summarize(direction, patches, skipped, touchedColumns, outside) {
8096
+ const parts = [];
8097
+ if (patches.length === 0) {
8098
+ parts.push("Filled 0 cells because there was nothing to fill.");
8099
+ } else if (direction === "down" || direction === "up") {
8100
+ const word = direction === "down" ? "down" : "up";
8101
+ parts.push(
8102
+ `Filled ${plural(patches.length, "cell", "cells")} ${word} ${plural(touchedColumns.size, "column", "columns")}.`
8103
+ );
8104
+ } else {
8105
+ const word = direction === "right" ? "right" : "left";
8106
+ parts.push(
8107
+ `Filled ${plural(patches.length, "cell", "cells")} ${word} across ${plural(touchedColumns.size, "column", "columns")}.`
8108
+ );
8109
+ }
8110
+ if (skipped.length > 0) {
8111
+ parts.push(`Skipped ${plural(skipped.length, "cell", "cells")} that could not be filled.`);
8112
+ }
8113
+ if (outside > 0) {
8114
+ parts.push(`Ignored ${plural(outside, "cell", "cells")} outside the table.`);
8115
+ }
8116
+ return parts.join(" ");
8117
+ }
8118
+ function fillDown(request) {
8119
+ const { range, ...rest } = request;
8120
+ if (!isValidRect(range)) {
8121
+ return emptyResult("Filled 0 cells because the selection is not a rectangle.");
8122
+ }
8123
+ if (range.top === range.bottom) {
8124
+ return emptyResult("Filled 0 cells because the selection is a single row.");
8125
+ }
8126
+ return fillRange({
8127
+ ...rest,
8128
+ source: { top: range.top, left: range.left, bottom: range.top, right: range.right },
8129
+ target: { top: range.top + 1, left: range.left, bottom: range.bottom, right: range.right }
8130
+ });
8131
+ }
8132
+ function fillTargetFor(source, toRow, toColumn) {
8133
+ if (!isValidRect(source)) return null;
8134
+ if (!Number.isInteger(toRow) || !Number.isInteger(toColumn)) return null;
8135
+ const below = toRow - source.bottom;
8136
+ const above = source.top - toRow;
8137
+ const rightOf = toColumn - source.right;
8138
+ const leftOf = source.left - toColumn;
8139
+ const vertical = Math.max(below, above, 0);
8140
+ const horizontal = Math.max(rightOf, leftOf, 0);
8141
+ if (vertical === 0 && horizontal === 0) return null;
8142
+ if (vertical >= horizontal) {
8143
+ if (below > 0) {
8144
+ return { top: source.bottom + 1, bottom: toRow, left: source.left, right: source.right };
8145
+ }
8146
+ return { top: toRow, bottom: source.top - 1, left: source.left, right: source.right };
8147
+ }
8148
+ if (rightOf > 0) {
8149
+ return { top: source.top, bottom: source.bottom, left: source.right + 1, right: toColumn };
8150
+ }
8151
+ return { top: source.top, bottom: source.bottom, left: toColumn, right: source.left - 1 };
8152
+ }
8153
+
8154
+ // src/data-table/cell-undo.ts
8155
+ var DEFAULT_UNDO_DEPTH = 100;
8156
+ function createUndoStack(depth = DEFAULT_UNDO_DEPTH) {
8157
+ const bounded = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 0;
8158
+ return { past: [], future: [], depth: bounded };
8159
+ }
8160
+ function pushUndo(stack, entry) {
8161
+ if (stack.depth <= 0) {
8162
+ return { past: [], future: [], depth: stack.depth };
8163
+ }
8164
+ const past = [...stack.past, entry];
8165
+ while (past.length > stack.depth) past.shift();
8166
+ return { past, future: [], depth: stack.depth };
8167
+ }
8168
+ function popUndo(stack) {
8169
+ const entry = stack.past[stack.past.length - 1];
8170
+ if (entry === void 0) return { stack, entry: null };
8171
+ return {
8172
+ stack: {
8173
+ past: stack.past.slice(0, -1),
8174
+ future: [...stack.future, entry],
8175
+ depth: stack.depth
8176
+ },
8177
+ entry
8178
+ };
8179
+ }
8180
+ function popRedo(stack) {
8181
+ const entry = stack.future[stack.future.length - 1];
8182
+ if (entry === void 0) return { stack, entry: null };
8183
+ const past = [...stack.past, entry];
8184
+ while (past.length > stack.depth) past.shift();
8185
+ return {
8186
+ stack: { past, future: stack.future.slice(0, -1), depth: stack.depth },
8187
+ entry
8188
+ };
8189
+ }
8190
+ function keyOf(patch) {
8191
+ return JSON.stringify([patch.rowId, patch.columnId]);
8192
+ }
8193
+ function undoEntryFor(before, after, label) {
8194
+ const priors = /* @__PURE__ */ new Map();
8195
+ for (const patch of before) priors.set(keyOf(patch), patch);
8196
+ const undo = after.map((patch) => {
8197
+ const prior = priors.get(keyOf(patch));
8198
+ return {
8199
+ rowId: patch.rowId,
8200
+ columnId: patch.columnId,
8201
+ value: prior ? prior.value : null
8202
+ };
8203
+ });
8204
+ return {
8205
+ label,
8206
+ undo,
8207
+ redo: after.map((patch) => ({
8208
+ rowId: patch.rowId,
8209
+ columnId: patch.columnId,
8210
+ value: patch.value
8211
+ }))
8212
+ };
8213
+ }
8214
+ function canUndo(stack) {
8215
+ return stack.depth > 0 && stack.past.length > 0;
8216
+ }
8217
+ function canRedo(stack) {
8218
+ return stack.depth > 0 && stack.future.length > 0;
8219
+ }
8220
+
8221
+ // src/data-table/useSpreadsheetGrid.ts
8222
+ function targetsAnEditor(target) {
8223
+ const element = target instanceof Element ? target : null;
8224
+ if (!element) return false;
8225
+ return element.closest("input, textarea, select, [contenteditable='true']") !== null;
8226
+ }
8227
+ function useSpreadsheetGrid(options) {
8228
+ const [range, setRange] = (0, import_react15.useState)(null);
8229
+ const [fillTarget, setFillTarget] = (0, import_react15.useState)(null);
8230
+ const [stack, setStack] = (0, import_react15.useState)(() => createUndoStack(options?.undoDepth ?? 100));
8231
+ const draggingRef = (0, import_react15.useRef)(null);
8232
+ const optionsRef = (0, import_react15.useRef)(options);
8233
+ optionsRef.current = options;
8234
+ const rowIds = options?.rowIds ?? [];
8235
+ const columnIds = (0, import_react15.useMemo)(
8236
+ () => options ? options.columnIds.filter((id) => id !== "") : [],
8237
+ [options]
8238
+ );
8239
+ (0, import_react15.useEffect)(() => {
8240
+ if (!range) return;
8241
+ const rowSet = new Set(rowIds);
8242
+ const colSet = new Set(columnIds);
8243
+ const alive = rowSet.has(range.anchor.rowId) && rowSet.has(range.focus.rowId) && colSet.has(range.anchor.fieldName) && colSet.has(range.focus.fieldName);
8244
+ if (!alive) setRange(null);
8245
+ }, [range, rowIds, columnIds]);
8246
+ const bounds = (0, import_react15.useMemo)(
8247
+ () => range ? rangeBounds(range, rowIds, columnIds) : null,
8248
+ [range, rowIds, columnIds]
8249
+ );
8250
+ const rowIndexById = (0, import_react15.useMemo)(() => {
8251
+ const map = /* @__PURE__ */ new Map();
8252
+ for (let i = 0; i < rowIds.length; i += 1) map.set(rowIds[i], i);
8253
+ return map;
8254
+ }, [rowIds]);
8255
+ const columnIndexById = (0, import_react15.useMemo)(() => {
8256
+ const map = /* @__PURE__ */ new Map();
8257
+ for (let i = 0; i < columnIds.length; i += 1) map.set(columnIds[i], i);
8258
+ return map;
8259
+ }, [columnIds]);
8260
+ const screenPatches = (0, import_react15.useCallback)(
8261
+ (patches) => {
8262
+ const validate = optionsRef.current?.validateCell;
8263
+ if (!validate) return { accepted: patches, refusals: [] };
8264
+ const accepted = [];
8265
+ const refusals = [];
8266
+ for (const patch of patches) {
8267
+ let reason;
8268
+ try {
8269
+ reason = validate({ rowId: patch.rowId, columnId: patch.columnId, value: patch.value });
8270
+ } catch {
8271
+ reason = void 0;
8272
+ }
8273
+ if (reason) refusals.push({ columnId: patch.columnId, reason });
8274
+ else accepted.push(patch);
8275
+ }
8276
+ return { accepted, refusals };
8277
+ },
8278
+ []
8279
+ );
8280
+ const applyPatches = (0, import_react15.useCallback)(
8281
+ async (patches, intent, label) => {
8282
+ const current = optionsRef.current;
8283
+ if (!current || patches.length === 0) return;
8284
+ const depth = current.undoDepth ?? 100;
8285
+ if (depth > 0 && intent !== "undo" && intent !== "redo") {
8286
+ const before = patches.map((patch) => ({
8287
+ rowId: patch.rowId,
8288
+ columnId: patch.columnId,
8289
+ value: current.valueAt(patch.rowId, patch.columnId) ?? null
8290
+ }));
8291
+ setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));
8292
+ }
8293
+ await current.onPatch(patches, intent);
8294
+ },
8295
+ []
8296
+ );
8297
+ const readValueAt = (0, import_react15.useCallback)((rowIndex, columnIndex) => {
8298
+ const current = optionsRef.current;
8299
+ const rowId = current?.rowIds[rowIndex];
8300
+ const columnId2 = current?.columnIds[columnIndex];
8301
+ if (!current || rowId === void 0 || columnId2 === void 0) return null;
8302
+ return current.valueAt(rowId, columnId2);
8303
+ }, []);
8304
+ const fillRequestBase = (0, import_react15.useCallback)(() => {
8305
+ const current = optionsRef.current;
8306
+ if (!current) return null;
8307
+ return {
8308
+ valueAt: readValueAt,
8309
+ columnIds: [...current.columnIds],
8310
+ rowIds: [...current.rowIds],
8311
+ isColumnWritable: current.isColumnWritable
8312
+ };
8313
+ }, [readValueAt]);
8314
+ const announceSkips = (0, import_react15.useCallback)(
8315
+ (skipped, summary) => {
8316
+ const current = optionsRef.current;
8317
+ if (!current?.notify) return;
8318
+ if (skipped.length === 0) {
8319
+ current.notify(summary);
8320
+ return;
8321
+ }
8322
+ const first = skipped[0];
8323
+ const reason = current.columnRefusal?.(first.columnId) ?? first.reason;
8324
+ const rest = skipped.length > 1 ? ` ${skipped.length - 1} more were left alone.` : "";
8325
+ current.notify(`${summary} ${reason}${rest}`);
8326
+ },
8327
+ []
8328
+ );
8329
+ const doFillDown = (0, import_react15.useCallback)(async () => {
8330
+ const base = fillRequestBase();
8331
+ if (!base || !bounds) return;
8332
+ const result = fillDown({
8333
+ ...base,
8334
+ range: { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 }
8335
+ });
8336
+ const screened = screenPatches(result.patches);
8337
+ await applyPatches(screened.accepted, "fill-down", "Filled down");
8338
+ announceSkips([...result.skipped, ...screened.refusals], result.summary);
8339
+ }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches]);
8340
+ const doClear = (0, import_react15.useCallback)(async () => {
8341
+ const current = optionsRef.current;
8342
+ if (!current || !bounds) return;
8343
+ const patches = [];
8344
+ const skipped = [];
8345
+ for (let r = bounds.r0; r <= bounds.r1; r += 1) {
8346
+ for (let c = bounds.c0; c <= bounds.c1; c += 1) {
8347
+ const rowId = current.rowIds[r];
8348
+ const columnId2 = current.columnIds[c];
8349
+ if (rowId === void 0 || columnId2 === void 0) continue;
8350
+ if (!current.isColumnWritable(columnId2)) {
8351
+ skipped.push({
8352
+ columnId: columnId2,
8353
+ reason: current.columnRefusal?.(columnId2) ?? `${columnId2} cannot be cleared.`
8354
+ });
8355
+ continue;
8356
+ }
8357
+ patches.push({ rowId, columnId: columnId2, value: null });
8358
+ }
8359
+ }
8360
+ const screened = screenPatches(patches);
8361
+ await applyPatches(screened.accepted, "clear", "Cleared");
8362
+ announceSkips(
8363
+ [...skipped, ...screened.refusals],
8364
+ `Cleared ${screened.accepted.length} cells.`
8365
+ );
8366
+ }, [bounds, applyPatches, announceSkips, screenPatches]);
8367
+ const doUndo = (0, import_react15.useCallback)(async () => {
8368
+ const taken = popUndo(stack);
8369
+ if (!taken.entry) return;
8370
+ setStack(taken.stack);
8371
+ await applyPatches(taken.entry.undo, "undo", taken.entry.label);
8372
+ optionsRef.current?.notify?.(taken.entry.label);
8373
+ }, [stack, applyPatches]);
8374
+ const doRedo = (0, import_react15.useCallback)(async () => {
8375
+ const taken = popRedo(stack);
8376
+ if (!taken.entry) return;
8377
+ setStack(taken.stack);
8378
+ await applyPatches(taken.entry.redo, "redo", taken.entry.label);
8379
+ }, [stack, applyPatches]);
8380
+ const move = (0, import_react15.useCallback)(
8381
+ (direction, extend) => {
8382
+ setRange((previous) => {
8383
+ const from = previous ? previous.focus : null;
8384
+ const next = moveSelection(from, direction, rowIds, columnIds);
8385
+ if (!next) return previous;
8386
+ if (extend && previous) return { anchor: previous.anchor, focus: next };
8387
+ return { anchor: next, focus: next };
8388
+ });
8389
+ },
8390
+ [rowIds, columnIds]
8391
+ );
8392
+ const onKeyDown = (0, import_react15.useCallback)(
8393
+ (event) => {
8394
+ const current = optionsRef.current;
8395
+ if (!current) return;
8396
+ if (targetsAnEditor(event.target)) return;
8397
+ const accel = event.metaKey || event.ctrlKey;
8398
+ if (accel && (event.key === "z" || event.key === "Z")) {
8399
+ event.preventDefault();
8400
+ if (event.shiftKey) void doRedo();
8401
+ else void doUndo();
8402
+ return;
8403
+ }
8404
+ if (accel && (event.key === "y" || event.key === "Y")) {
8405
+ event.preventDefault();
8406
+ void doRedo();
8407
+ return;
8408
+ }
8409
+ const action = classifyGridKey(event);
8410
+ if (!action) return;
8411
+ switch (action.kind) {
8412
+ case "move":
8413
+ event.preventDefault();
8414
+ move(action.move, false);
8415
+ return;
8416
+ case "extend":
8417
+ event.preventDefault();
8418
+ move(action.move, true);
8419
+ return;
8420
+ case "selectAll": {
8421
+ event.preventDefault();
8422
+ const all = allRange(rowIds, columnIds);
8423
+ if (all) setRange(all);
8424
+ return;
8425
+ }
8426
+ case "selectRow": {
8427
+ event.preventDefault();
8428
+ const focused = range?.focus;
8429
+ const next = focused ? rowRange(focused.rowId, columnIds) : null;
8430
+ if (next) setRange(next);
8431
+ return;
8432
+ }
8433
+ case "selectColumn": {
8434
+ event.preventDefault();
8435
+ const focused = range?.focus;
8436
+ const next = focused ? columnRange(focused.fieldName, rowIds) : null;
8437
+ if (next) setRange(next);
8438
+ return;
8439
+ }
8440
+ case "clearCell":
8441
+ event.preventDefault();
8442
+ void doClear();
8443
+ return;
8444
+ case "fillDown":
8445
+ event.preventDefault();
8446
+ void doFillDown();
8447
+ return;
8448
+ case "escape":
8449
+ setRange(null);
8450
+ return;
8451
+ case "edit":
8452
+ case "editSeeded": {
8453
+ const focused = range?.focus;
8454
+ if (!focused || !current.onEditRequest) return;
8455
+ const seed = action.kind === "editSeeded" ? action.seed : null;
8456
+ if (current.onEditRequest(focused, seed)) event.preventDefault();
8457
+ return;
8458
+ }
8459
+ default:
8460
+ return;
8461
+ }
8462
+ },
8463
+ [move, range, rowIds, columnIds, doClear, doFillDown, doUndo, doRedo]
8464
+ );
8465
+ const selectionAsTsv = (0, import_react15.useCallback)(() => {
8466
+ const current = optionsRef.current;
8467
+ if (!current || !range) return "";
8468
+ const grid = rangeRows(range, rowIds, columnIds).map(
8469
+ (row) => row.map((address) => current.valueAt(address.rowId, address.fieldName))
8470
+ );
8471
+ return gridToTsv(grid);
8472
+ }, [range, rowIds, columnIds]);
8473
+ const selectionAddresses = (0, import_react15.useCallback)(() => {
8474
+ if (!range || !bounds) return { rowIds: [], columnIds: [] };
8475
+ return {
8476
+ rowIds: rowIds.slice(bounds.r0, bounds.r1 + 1),
8477
+ columnIds: columnIds.slice(bounds.c0, bounds.c1 + 1)
8478
+ };
8479
+ }, [range, bounds, rowIds, columnIds]);
8480
+ const onCopy = (0, import_react15.useCallback)(
8481
+ (event) => {
8482
+ if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;
8483
+ const text = selectionAsTsv();
8484
+ if (!text) return;
8485
+ event.preventDefault();
8486
+ event.clipboardData?.setData("text/plain", text);
8487
+ },
8488
+ [range, selectionAsTsv]
8489
+ );
8490
+ const onCut = (0, import_react15.useCallback)(
8491
+ (event) => {
8492
+ if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;
8493
+ const text = selectionAsTsv();
8494
+ if (!text) return;
8495
+ event.preventDefault();
8496
+ event.clipboardData?.setData("text/plain", text);
8497
+ void doClear();
8498
+ },
8499
+ [range, selectionAsTsv, doClear]
8500
+ );
8501
+ const onPaste = (0, import_react15.useCallback)(
8502
+ (event) => {
8503
+ const current = optionsRef.current;
8504
+ if (!current || current.paste === false || !range || targetsAnEditor(event.target)) return;
8505
+ const text = event.clipboardData?.getData("text/plain") ?? "";
8506
+ if (!text) return;
8507
+ event.preventDefault();
8508
+ const block = parseClipboardGrid(text);
8509
+ const start = range.focus;
8510
+ const startRow = rowIds.indexOf(start.rowId);
8511
+ const startCol = columnIds.indexOf(start.fieldName);
8512
+ if (startRow === -1 || startCol === -1) return;
8513
+ const patches = [];
8514
+ const skipped = [];
8515
+ let outside = 0;
8516
+ for (let r = 0; r < block.length; r += 1) {
8517
+ const line = block[r] ?? [];
8518
+ for (let c = 0; c < line.length; c += 1) {
8519
+ const rowId = rowIds[startRow + r];
8520
+ const columnId2 = columnIds[startCol + c];
8521
+ if (rowId === void 0 || columnId2 === void 0) {
8522
+ outside += 1;
8523
+ continue;
8524
+ }
8525
+ if (!current.isColumnWritable(columnId2)) {
8526
+ skipped.push({
8527
+ columnId: columnId2,
8528
+ reason: current.columnRefusal?.(columnId2) ?? `${columnId2} cannot take a pasted value.`
8529
+ });
8530
+ continue;
8531
+ }
8532
+ patches.push({ rowId, columnId: columnId2, value: line[c] ?? null });
8533
+ }
8534
+ }
8535
+ const screened = screenPatches(patches);
8536
+ void applyPatches(screened.accepted, "paste", "Pasted");
8537
+ const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : "";
8538
+ announceSkips([...skipped, ...screened.refusals], `Pasted ${screened.accepted.length} cells.${tail}`);
8539
+ },
8540
+ [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches]
8541
+ );
8542
+ const cellProps = (0, import_react15.useCallback)(
8543
+ (rowId, columnId2) => {
8544
+ const address = { rowId, fieldName: columnId2 };
8545
+ const rowIndex = rowIndexById.get(rowId) ?? -1;
8546
+ const colIndex = columnIndexById.get(columnId2) ?? -1;
8547
+ const selected = bounds !== null && rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
8548
+ const active = range !== null && sameCell(range.focus, address);
8549
+ const inFillPreview = fillTarget !== null && rowIndex >= fillTarget.top && rowIndex <= fillTarget.bottom && colIndex >= fillTarget.left && colIndex <= fillTarget.right;
8550
+ return {
8551
+ "data-matrx-cell-row": rowId,
8552
+ "data-matrx-cell-col": columnId2,
8553
+ ...selected ? { "data-matrx-cell-selected": "true" } : {},
8554
+ ...active ? { "data-matrx-cell-active": "true" } : {},
8555
+ ...inFillPreview ? { "data-matrx-cell-fill-preview": "true" } : {},
8556
+ onMouseDown: (event) => {
8557
+ if (event.button !== 0) return;
8558
+ event.preventDefault();
8559
+ const origin = event.currentTarget;
8560
+ if (typeof HTMLElement !== "undefined" && origin instanceof HTMLElement) {
8561
+ origin.closest('[data-matrx-table-spreadsheet="true"]')?.focus({ preventScroll: true });
8562
+ }
8563
+ setRange(
8564
+ (previous) => event.shiftKey && previous ? { anchor: previous.anchor, focus: address } : { anchor: address, focus: address }
8565
+ );
8566
+ draggingRef.current = "select";
8567
+ },
8568
+ onMouseEnter: () => {
8569
+ if (draggingRef.current === "select") {
8570
+ setRange((previous) => previous ? { anchor: previous.anchor, focus: address } : previous);
8571
+ return;
8572
+ }
8573
+ if (draggingRef.current === "fill" && bounds) {
8574
+ setFillTarget(
8575
+ fillTargetFor(
8576
+ { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },
8577
+ rowIndex,
8578
+ colIndex
8579
+ )
8580
+ );
8581
+ }
8582
+ }
8583
+ };
8584
+ },
8585
+ [rowIndexById, columnIndexById, bounds, range, fillTarget]
8586
+ );
8587
+ const isFillHandleCell = (0, import_react15.useCallback)(
8588
+ (rowId, columnId2) => {
8589
+ const current = optionsRef.current;
8590
+ if (!current || current.fillHandle === false || !bounds) return false;
8591
+ return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId2;
8592
+ },
8593
+ [bounds, rowIds, columnIds]
8594
+ );
8595
+ const startFillDrag = (0, import_react15.useCallback)(
8596
+ (event) => {
8597
+ event.preventDefault();
8598
+ event.stopPropagation();
8599
+ draggingRef.current = "fill";
8600
+ },
8601
+ []
8602
+ );
8603
+ const fillTargetRef = (0, import_react15.useRef)(null);
8604
+ fillTargetRef.current = fillTarget;
8605
+ const boundsRef = (0, import_react15.useRef)(null);
8606
+ boundsRef.current = bounds;
8607
+ (0, import_react15.useEffect)(() => {
8608
+ if (!options) return;
8609
+ const commit = () => {
8610
+ const wasDragging = draggingRef.current;
8611
+ draggingRef.current = null;
8612
+ if (wasDragging !== "fill") return;
8613
+ const target = fillTargetRef.current;
8614
+ const source = boundsRef.current;
8615
+ setFillTarget(null);
8616
+ if (!target || !source) return;
8617
+ const base = fillRequestBase();
8618
+ if (!base) return;
8619
+ const result = fillRange({
8620
+ ...base,
8621
+ source: { top: source.r0, left: source.c0, bottom: source.r1, right: source.c1 },
8622
+ target
8623
+ });
8624
+ const screened = screenPatches(result.patches);
8625
+ void applyPatches(screened.accepted, "fill", "Filled");
8626
+ announceSkips([...result.skipped, ...screened.refusals], result.summary);
8627
+ };
8628
+ window.addEventListener("mouseup", commit);
8629
+ return () => window.removeEventListener("mouseup", commit);
8630
+ }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches]);
8631
+ return {
8632
+ range,
8633
+ bounds,
8634
+ enabled: options !== null,
8635
+ containerProps: {
8636
+ tabIndex: options ? 0 : -1,
8637
+ onKeyDown,
8638
+ onCopy,
8639
+ onCut,
8640
+ onPaste
8641
+ },
8642
+ cellProps,
8643
+ isFillHandleCell,
8644
+ startFillDrag,
8645
+ canUndo: stack.past.length > 0,
8646
+ canRedo: stack.future.length > 0,
8647
+ selectionTsv: selectionAsTsv,
8648
+ selectionAddresses
8649
+ };
8650
+ }
8651
+
8652
+ // src/data-table/coverage.ts
8653
+ var PARTIAL_BADGE = "Partial";
8654
+ var UNKNOWN_TOTAL_BADGE = "Unknown total";
8655
+ var NUMBERS2 = new Intl.NumberFormat("en-US");
8656
+ function formatNumber(value) {
8657
+ try {
8658
+ return NUMBERS2.format(value);
8659
+ } catch {
8660
+ return String(value);
8661
+ }
8662
+ }
8663
+ function pluralize(noun) {
8664
+ const word = noun.trim();
8665
+ if (!word) return "rows";
8666
+ const lower = word.toLowerCase();
8667
+ if (/[^aeiou]y$/.test(lower)) return `${word.slice(0, -1)}ies`;
8668
+ if (/(s|x|z|ch|sh)$/.test(lower)) return `${word}es`;
8669
+ return `${word}s`;
8670
+ }
8671
+ function quantity(value, noun) {
8672
+ return `${formatNumber(value)} ${value === 1 ? noun.trim() || "row" : pluralize(noun)}`;
8673
+ }
8674
+ function toWhole(value) {
8675
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
8676
+ }
8677
+ function toOptionalWhole(value) {
8678
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
8679
+ return Math.floor(value);
8680
+ }
8681
+ function describeCoverage(input) {
8682
+ const safe = input && typeof input === "object" ? input : { loaded: 0, answeredBy: "client" };
8683
+ const loaded = toWhole(safe.loaded);
8684
+ const matched = toOptionalWhole(safe.matched);
8685
+ const total = toOptionalWhole(safe.total);
8686
+ const cap = toOptionalWhole(safe.cap);
8687
+ const answeredBy = safe.answeredBy === "source" ? "source" : "client";
8688
+ const noun = typeof safe.noun === "string" && safe.noun.trim() ? safe.noun.trim() : "row";
8689
+ const reference = matched ?? total;
8690
+ const capReached = cap !== void 0 && cap > 0 && loaded >= cap;
8691
+ if (reference !== void 0 && loaded >= reference) {
8692
+ return {
8693
+ scope: "all",
8694
+ complete: true,
8695
+ count: { value: matched ?? reference, exact: true },
8696
+ notice: "",
8697
+ badge: ""
8698
+ };
8699
+ }
8700
+ if (answeredBy === "source" && matched !== void 0 && !capReached) {
8701
+ return { scope: "all", complete: true, count: { value: matched, exact: true }, notice: "", badge: "" };
8702
+ }
8703
+ if (capReached && cap !== void 0) {
8704
+ const rest = reference !== void 0 ? `${quantity(reference, noun)} in this table; the rest were not looked at.` : "Anything past that was not looked at.";
8705
+ return {
8706
+ scope: "capped",
8707
+ complete: false,
8708
+ count: { value: loaded, exact: false },
8709
+ notice: `This filter searched the first ${quantity(cap, noun)} only. ${rest}`,
8710
+ badge: PARTIAL_BADGE
8711
+ };
8712
+ }
8713
+ if (answeredBy === "client" && reference !== void 0) {
8714
+ return {
8715
+ scope: "loaded",
8716
+ complete: false,
8717
+ count: { value: loaded, exact: false },
8718
+ notice: `Filtering the ${quantity(loaded, noun)} loaded so far, not all ${quantity(reference, noun)} in this table.`,
8719
+ badge: PARTIAL_BADGE
8720
+ };
8721
+ }
8722
+ if (answeredBy === "source" && total !== void 0) {
8723
+ return {
8724
+ scope: "all",
8725
+ complete: false,
8726
+ count: { value: loaded, exact: false },
8727
+ notice: `Showing the ${quantity(loaded, noun)} loaded so far out of ${quantity(total, noun)} in this table.`,
8728
+ badge: PARTIAL_BADGE
8729
+ };
8730
+ }
8731
+ return {
8732
+ scope: answeredBy === "source" ? "all" : "loaded",
8733
+ complete: false,
8734
+ count: { value: loaded, exact: false },
8735
+ notice: `Showing the ${quantity(loaded, noun)} loaded so far. The total number of ${pluralize(noun)} in this table is not known.`,
8736
+ badge: UNKNOWN_TOTAL_BADGE
8737
+ };
8738
+ }
8739
+
8740
+ // src/data-table/ranked-search.ts
8741
+ function inferTableSearchRole(field) {
8742
+ const key = field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/[\s-]+/g, "_");
8743
+ if (key === "id" || key === "uuid" || key.endsWith("_id")) return "id";
8744
+ if (/^(name|alias|label|title|key|slug|display_name|common_name|model_name)$/.test(key) || /_(name|label|title)$/.test(key)) return "name";
8745
+ if (/^(description|summary|details|notes|blurb)$/.test(key) || key.endsWith("_description")) return "description";
8746
+ return "other";
8747
+ }
8748
+ function searchableFields(row, columns) {
8749
+ const values = /* @__PURE__ */ new Map();
8750
+ if (row !== null && typeof row === "object") {
8751
+ for (const [key, value] of Object.entries(row)) values.set(key, stringifyCellValue(value));
8752
+ }
8753
+ for (const column of columns) {
8754
+ if (column.accessorKey || column.accessorFn || column.filterValue) {
8755
+ values.set(columnId(column), stringifyCellValue(getFilterValue(row, column)));
8756
+ }
8757
+ }
8758
+ return values;
8759
+ }
8760
+ function resolveTableSearchScopes(rows, columns, options = {}) {
8761
+ const fields = new Set(columns.filter((c) => c.accessorKey || c.accessorFn || c.filterValue).map(columnId));
8762
+ for (const row of rows) if (row !== null && typeof row === "object") Object.keys(row).forEach((key) => fields.add(key));
8763
+ const roles = new Set([...fields].map((key) => options.roles?.[key] ?? inferTableSearchRole(key)));
8764
+ const scopes = [{ id: "all", label: "All" }];
8765
+ for (const [id, label] of [["id", "ID"], ["name", "Name"], ["description", "Description"]]) {
8766
+ if (roles.has(id)) scopes.push({ id, label });
8767
+ }
8768
+ for (const scope of options.scopes ?? []) if (!scopes.some((s) => s.id === scope.id) && scope.fields?.length) scopes.push(scope);
8769
+ return scopes;
8770
+ }
8771
+ function rankTableSearchRows(rows, columns, query, options = {}, extraText) {
8772
+ const q = query.trim().toLocaleLowerCase();
8773
+ if (!q) return [...rows];
8774
+ const scope = options.scope ?? "all";
8775
+ const customScope = options.scopes?.find((s) => s.id === scope);
8776
+ const mode = options.matchMode ?? "contains";
8777
+ return rows.flatMap((row, index) => {
8778
+ const fields = [...searchableFields(row, columns)].filter(([key]) => {
8779
+ if (scope === "all") return true;
8780
+ if (customScope) return customScope.fields?.includes(key) ?? false;
8781
+ return (options.roles?.[key] ?? inferTableSearchRole(key)) === scope;
8782
+ });
8783
+ const extra = scope === "all" ? extraText?.(row) : void 0;
8784
+ const texts = fields.map(([, value]) => value);
8785
+ if (extra) texts.push(extra);
8786
+ const matched = mode === "whole_words" ? matchesTableSearch(texts.join(" "), q, mode) : texts.some((value) => matchesTableSearch(value, q, mode));
8787
+ if (!matched) return [];
8788
+ let rank = 3;
8789
+ for (const [key, value] of fields) {
8790
+ if (!matchesTableSearch(value, q, mode)) continue;
8791
+ const role = options.roles?.[key] ?? inferTableSearchRole(key);
8792
+ if (role === "id" || role === "name") rank = Math.min(rank, value.trim().toLocaleLowerCase() === q ? 0 : 1);
8793
+ else if (role === "description") rank = Math.min(rank, 2);
8794
+ }
8795
+ return [{ row, rank, index }];
8796
+ }).sort((a, b) => a.rank - b.rank || a.index - b.index).map((item) => item.row);
8797
+ }
8798
+
8799
+ // src/data-table/ColumnManager.tsx
8800
+ var import_react16 = require("react");
8801
+ var import_core2 = require("@dnd-kit/core");
8802
+ var import_jsx_runtime33 = require("react/jsx-runtime");
8803
+ function ColumnManager({
8804
+ open,
8805
+ onOpenChange,
8806
+ columns,
8807
+ order,
8808
+ hidden,
8809
+ onApply
8810
+ }) {
8811
+ const byId = (0, import_react16.useMemo)(
8812
+ () => new Map(columns.map((column) => [columnId(column), column])),
8813
+ [columns]
8814
+ );
8815
+ const [draftOrder, setDraftOrder] = (0, import_react16.useState)(order);
8816
+ const [draftHidden, setDraftHidden] = (0, import_react16.useState)(() => new Set(hidden));
8817
+ const [draggedColumnId, setDraggedColumnId] = (0, import_react16.useState)(null);
8818
+ const [dropTarget, setDropTarget] = (0, import_react16.useState)(null);
8819
+ (0, import_react16.useEffect)(() => {
8820
+ if (!open) return;
8821
+ setDraftOrder(order);
8822
+ setDraftHidden(new Set(hidden));
8823
+ }, [open, order, hidden]);
8824
+ const move = (id, direction) => {
8825
+ setDraftOrder((current) => {
8826
+ const index = current.indexOf(id);
8827
+ const target = index + direction;
8828
+ if (index < 0 || target < 0 || target >= current.length) return current;
8829
+ const next = [...current];
8830
+ const displaced = next[target];
8831
+ if (displaced === void 0) return current;
8832
+ next[target] = id;
8833
+ next[index] = displaced;
8834
+ return next;
8835
+ });
8836
+ };
8837
+ const moveTo = (id, targetId) => {
8838
+ if (id === targetId) return;
8839
+ setDraftOrder((current) => {
8840
+ const from = current.indexOf(id);
8841
+ const target = current.indexOf(targetId);
8842
+ if (from < 0 || target < 0) return current;
8843
+ const next = [...current];
8844
+ next.splice(from, 1);
8845
+ const targetAfterRemoval = next.indexOf(targetId);
8846
+ next.splice(targetAfterRemoval, 0, id);
8847
+ return next;
8848
+ });
6902
8849
  };
6903
8850
  (0, import_core2.useDndMonitor)({
6904
8851
  onDragStart(event) {
@@ -7399,7 +9346,7 @@ function parseTableViewSnapshot(raw) {
7399
9346
  }
7400
9347
 
7401
9348
  // src/data-table/MatrxUuidCell.tsx
7402
- var import_react16 = require("react");
9349
+ var import_react17 = require("react");
7403
9350
  var import_jsx_runtime34 = require("react/jsx-runtime");
7404
9351
  var SHORT_LEN = 8;
7405
9352
  function shortUuid(id, len = SHORT_LEN) {
@@ -7439,11 +9386,11 @@ function MatrxUuidCell({
7439
9386
  }) {
7440
9387
  const { resolveEntityDoors, ResourcePeek } = useMatrxDataTableHost();
7441
9388
  const { notify, notice } = useTableNotice();
7442
- const [copied, setCopied] = (0, import_react16.useState)(false);
7443
- const [opening, setOpening] = (0, import_react16.useState)(false);
7444
- const [peekOpen, setPeekOpen] = (0, import_react16.useState)(false);
7445
- const timer = (0, import_react16.useRef)(null);
7446
- (0, import_react16.useEffect)(
9389
+ const [copied, setCopied] = (0, import_react17.useState)(false);
9390
+ const [opening, setOpening] = (0, import_react17.useState)(false);
9391
+ const [peekOpen, setPeekOpen] = (0, import_react17.useState)(false);
9392
+ const timer = (0, import_react17.useRef)(null);
9393
+ (0, import_react17.useEffect)(
7447
9394
  () => () => {
7448
9395
  if (timer.current) clearTimeout(timer.current);
7449
9396
  },
@@ -7738,7 +9685,7 @@ function DataRowInspector({
7738
9685
  }
7739
9686
 
7740
9687
  // src/data-table/DataRowWindow.tsx
7741
- var import_react17 = require("react");
9688
+ var import_react18 = require("react");
7742
9689
  var import_jsx_runtime36 = require("react/jsx-runtime");
7743
9690
  function DataRowWindow({
7744
9691
  isOpen,
@@ -7759,8 +9706,8 @@ function DataRowWindow({
7759
9706
  const hasEdit = editContent != null;
7760
9707
  const hasTabs = hasEdit && children == null;
7761
9708
  const initialTab = defaultTab ?? (hasEdit ? "edit" : "view");
7762
- const [tab, setTab] = (0, import_react17.useState)(initialTab);
7763
- (0, import_react17.useEffect)(() => {
9709
+ const [tab, setTab] = (0, import_react18.useState)(initialTab);
9710
+ (0, import_react18.useEffect)(() => {
7764
9711
  if (!isOpen) return;
7765
9712
  setTab(defaultTab ?? (hasEdit ? "edit" : "view"));
7766
9713
  }, [isOpen, windowId, hasEdit, defaultTab]);
@@ -7906,8 +9853,8 @@ function toNumber(value) {
7906
9853
  if (typeof value === "string") {
7907
9854
  const cleaned = value.replace(/[,\s$€£¥%]/g, "");
7908
9855
  if (cleaned === "") return null;
7909
- const n = Number(cleaned);
7910
- return Number.isFinite(n) ? n : null;
9856
+ const n2 = Number(cleaned);
9857
+ return Number.isFinite(n2) ? n2 : null;
7911
9858
  }
7912
9859
  return null;
7913
9860
  }
@@ -7945,14 +9892,14 @@ function toList(value) {
7945
9892
  }
7946
9893
  return null;
7947
9894
  }
7948
- function groupedNumber(n, options) {
9895
+ function groupedNumber(n2, options) {
7949
9896
  return new Intl.NumberFormat(void 0, {
7950
9897
  useGrouping: options.useGrouping !== false,
7951
9898
  ...options.precision != null ? {
7952
9899
  minimumFractionDigits: options.precision,
7953
9900
  maximumFractionDigits: options.precision
7954
9901
  } : { maximumFractionDigits: 10 }
7955
- }).format(n);
9902
+ }).format(n2);
7956
9903
  }
7957
9904
  function affix(text, options) {
7958
9905
  return `${options.prefix ?? ""}${text}${options.suffix ?? ""}`;
@@ -8086,8 +10033,8 @@ var DEFS = [
8086
10033
  numericAlign: true,
8087
10034
  optionKeys: ["useGrouping", "precision", "prefix", "suffix"],
8088
10035
  format: (v, o) => {
8089
- const n = toNumber(v);
8090
- return n === null ? null : affix(groupedNumber(n, o), o);
10036
+ const n2 = toNumber(v);
10037
+ return n2 === null ? null : affix(groupedNumber(n2, o), o);
8091
10038
  },
8092
10039
  parse: (raw) => toNumber(raw)
8093
10040
  },
@@ -8102,9 +10049,9 @@ var DEFS = [
8102
10049
  numericAlign: true,
8103
10050
  optionKeys: ["precision", "useGrouping", "prefix", "suffix"],
8104
10051
  format: (v, o) => {
8105
- const n = toNumber(v);
8106
- if (n === null) return null;
8107
- return affix(groupedNumber(n, { ...o, precision: o.precision ?? 2 }), o);
10052
+ const n2 = toNumber(v);
10053
+ if (n2 === null) return null;
10054
+ return affix(groupedNumber(n2, { ...o, precision: o.precision ?? 2 }), o);
8108
10055
  },
8109
10056
  parse: (raw) => toNumber(raw)
8110
10057
  },
@@ -8119,8 +10066,8 @@ var DEFS = [
8119
10066
  numericAlign: true,
8120
10067
  optionKeys: ["currency", "precision"],
8121
10068
  format: (v, o) => {
8122
- const n = toNumber(v);
8123
- if (n === null) return null;
10069
+ const n2 = toNumber(v);
10070
+ if (n2 === null) return null;
8124
10071
  const currency = (o.currency || "USD").toUpperCase();
8125
10072
  try {
8126
10073
  return new Intl.NumberFormat(void 0, {
@@ -8130,9 +10077,9 @@ var DEFS = [
8130
10077
  minimumFractionDigits: o.precision,
8131
10078
  maximumFractionDigits: o.precision
8132
10079
  } : {}
8133
- }).format(n);
10080
+ }).format(n2);
8134
10081
  } catch {
8135
- return `${groupedNumber(n, { precision: o.precision ?? 2 })} ${currency}`;
10082
+ return `${groupedNumber(n2, { precision: o.precision ?? 2 })} ${currency}`;
8136
10083
  }
8137
10084
  },
8138
10085
  parse: (raw) => toNumber(raw)
@@ -8148,9 +10095,9 @@ var DEFS = [
8148
10095
  numericAlign: true,
8149
10096
  optionKeys: ["percentScale", "precision"],
8150
10097
  format: (v, o) => {
8151
- const n = toNumber(v);
8152
- if (n === null) return null;
8153
- const shown = o.percentScale === "fraction" ? n * 100 : n;
10098
+ const n2 = toNumber(v);
10099
+ if (n2 === null) return null;
10100
+ const shown = o.percentScale === "fraction" ? n2 * 100 : n2;
8154
10101
  return `${groupedNumber(shown, o.precision === void 0 ? {} : { precision: o.precision })}%`;
8155
10102
  },
8156
10103
  parse: (raw) => toNumber(raw)
@@ -8166,9 +10113,9 @@ var DEFS = [
8166
10113
  numericAlign: true,
8167
10114
  optionKeys: ["durationUnit"],
8168
10115
  format: (v, o) => {
8169
- const n = toNumber(v);
8170
- if (n === null) return null;
8171
- return formatSignedDuration(n * DURATION_TO_SECONDS[o.durationUnit ?? "seconds"]);
10116
+ const n2 = toNumber(v);
10117
+ if (n2 === null) return null;
10118
+ return formatSignedDuration(n2 * DURATION_TO_SECONDS[o.durationUnit ?? "seconds"]);
8172
10119
  },
8173
10120
  parse: (raw) => toNumber(raw)
8174
10121
  },
@@ -8183,13 +10130,13 @@ var DEFS = [
8183
10130
  numericAlign: true,
8184
10131
  optionKeys: ["useGrouping", "prefix", "suffix"],
8185
10132
  format: (v, o) => {
8186
- const n = toNumber(v);
8187
- if (n === null) return null;
8188
- return affix(groupedNumber(Math.trunc(n), { ...o, precision: 0 }), o);
10133
+ const n2 = toNumber(v);
10134
+ if (n2 === null) return null;
10135
+ return affix(groupedNumber(Math.trunc(n2), { ...o, precision: 0 }), o);
8189
10136
  },
8190
10137
  parse: (raw) => {
8191
- const n = toNumber(raw);
8192
- return n === null ? null : Math.trunc(n);
10138
+ const n2 = toNumber(raw);
10139
+ return n2 === null ? null : Math.trunc(n2);
8193
10140
  }
8194
10141
  },
8195
10142
  {
@@ -8203,15 +10150,15 @@ var DEFS = [
8203
10150
  rich: true,
8204
10151
  optionKeys: ["ratingMax"],
8205
10152
  format: (v, o) => {
8206
- const n = toNumber(v);
8207
- if (n === null) return null;
10153
+ const n2 = toNumber(v);
10154
+ if (n2 === null) return null;
8208
10155
  const max = o.ratingMax ?? 5;
8209
- if (n < 0 || n > max) return null;
8210
- return `${n} / ${max}`;
10156
+ if (n2 < 0 || n2 > max) return null;
10157
+ return `${n2} / ${max}`;
8211
10158
  },
8212
10159
  parse: (raw) => {
8213
- const n = toNumber(raw);
8214
- return n === null ? null : Math.round(n);
10160
+ const n2 = toNumber(raw);
10161
+ return n2 === null ? null : Math.round(n2);
8215
10162
  }
8216
10163
  },
8217
10164
  {
@@ -8224,12 +10171,12 @@ var DEFS = [
8224
10171
  editor: "number",
8225
10172
  numericAlign: true,
8226
10173
  format: (v) => {
8227
- const n = toNumber(v);
8228
- return n === null ? null : (0, import_format3.formatFileSize)(n);
10174
+ const n2 = toNumber(v);
10175
+ return n2 === null ? null : (0, import_format3.formatFileSize)(n2);
8229
10176
  },
8230
10177
  parse: (raw) => {
8231
- const n = toNumber(raw);
8232
- return n === null ? null : Math.trunc(n);
10178
+ const n2 = toNumber(raw);
10179
+ return n2 === null ? null : Math.trunc(n2);
8233
10180
  }
8234
10181
  },
8235
10182
  // ── Choice ────────────────────────────────────────────────────────────────
@@ -8639,8 +10586,19 @@ function DirtySavePill({
8639
10586
  }
8640
10587
 
8641
10588
  // src/data-table/EditableTableCell.tsx
8642
- var import_react18 = require("react");
10589
+ var import_react19 = require("react");
8643
10590
  var import_jsx_runtime39 = require("react/jsx-runtime");
10591
+ function CellRefusal({ reason }) {
10592
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10593
+ "p",
10594
+ {
10595
+ role: "alert",
10596
+ "data-matrx-cell-refusal": "true",
10597
+ className: "mt-1 text-[11px] leading-snug text-amber-700 dark:text-amber-400",
10598
+ children: reason
10599
+ }
10600
+ );
10601
+ }
8644
10602
  function CellEditShell({
8645
10603
  href,
8646
10604
  display,
@@ -8687,12 +10645,17 @@ function EditableTableCell({
8687
10645
  className,
8688
10646
  href,
8689
10647
  editTrigger = "click",
8690
- editRequest
10648
+ editRequest,
10649
+ editSeed
8691
10650
  }) {
8692
- const [open, setOpen] = (0, import_react18.useState)(false);
8693
- (0, import_react18.useEffect)(() => {
10651
+ const [open, setOpen] = (0, import_react19.useState)(false);
10652
+ const [refusal, setRefusal] = (0, import_react19.useState)(null);
10653
+ (0, import_react19.useEffect)(() => {
8694
10654
  if (editRequest !== void 0) setOpen(true);
8695
10655
  }, [editRequest]);
10656
+ (0, import_react19.useEffect)(() => {
10657
+ if (!open) setRefusal(null);
10658
+ }, [open]);
8696
10659
  const usePencil = Boolean(href) || editTrigger === "pencil";
8697
10660
  if (editType === "string") {
8698
10661
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
@@ -8705,7 +10668,8 @@ function EditableTableCell({
8705
10668
  ...className === void 0 ? {} : { className },
8706
10669
  ...href === void 0 ? {} : { href },
8707
10670
  usePencil,
8708
- ...editRequest === void 0 ? {} : { editRequest }
10671
+ ...editRequest === void 0 ? {} : { editRequest },
10672
+ ...editSeed === void 0 || editSeed === null ? {} : { editSeed }
8709
10673
  }
8710
10674
  );
8711
10675
  }
@@ -8750,25 +10714,33 @@ function EditableTableCell({
8750
10714
  ) }) }),
8751
10715
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(TooltipContent, { children: "Edit value" })
8752
10716
  ] }),
8753
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10717
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
8754
10718
  PopoverContent,
8755
10719
  {
8756
10720
  align: "start",
8757
10721
  className: "w-56 p-2",
8758
10722
  onClick: (e) => e.stopPropagation(),
8759
- children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
8760
- TypedEditor,
8761
- {
8762
- editType,
8763
- value,
8764
- editOptions,
8765
- onCommit: (next) => {
8766
- onCommit(next);
8767
- setOpen(false);
8768
- },
8769
- onCancel: () => setOpen(false)
8770
- }
8771
- )
10723
+ children: [
10724
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10725
+ TypedEditor,
10726
+ {
10727
+ editType,
10728
+ value,
10729
+ editOptions,
10730
+ onCommit: (next) => {
10731
+ const refused = onCommit(next);
10732
+ if (typeof refused === "string" && refused) {
10733
+ setRefusal(refused);
10734
+ return;
10735
+ }
10736
+ setRefusal(null);
10737
+ setOpen(false);
10738
+ },
10739
+ onCancel: () => setOpen(false)
10740
+ }
10741
+ ),
10742
+ refusal ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CellRefusal, { reason: refusal }) : null
10743
+ ]
8772
10744
  }
8773
10745
  )
8774
10746
  ] });
@@ -8781,17 +10753,31 @@ function InlineStringEditor({
8781
10753
  className,
8782
10754
  href,
8783
10755
  usePencil,
8784
- editRequest
10756
+ editRequest,
10757
+ editSeed
8785
10758
  }) {
8786
- const [editing, setEditing] = (0, import_react18.useState)(false);
8787
- const [draft, setDraft] = (0, import_react18.useState)(value);
8788
- const handledEditRequest = (0, import_react18.useRef)(void 0);
8789
- (0, import_react18.useEffect)(() => {
10759
+ const [editing, setEditing] = (0, import_react19.useState)(false);
10760
+ const [draft, setDraft] = (0, import_react19.useState)(value);
10761
+ const [refusal, setRefusal] = (0, import_react19.useState)(null);
10762
+ const handledEditRequest = (0, import_react19.useRef)(void 0);
10763
+ const seedRef = (0, import_react19.useRef)(editSeed);
10764
+ seedRef.current = editSeed;
10765
+ (0, import_react19.useEffect)(() => {
8790
10766
  if (editRequest === void 0 || handledEditRequest.current === editRequest) return;
8791
10767
  handledEditRequest.current = editRequest;
8792
- setDraft(value);
10768
+ setDraft(seedRef.current ?? value);
10769
+ setRefusal(null);
8793
10770
  setEditing(true);
8794
10771
  }, [editRequest, value]);
10772
+ const commit = (next) => {
10773
+ const refused = onCommit(next);
10774
+ if (typeof refused === "string" && refused) {
10775
+ setRefusal(refused);
10776
+ return false;
10777
+ }
10778
+ setRefusal(null);
10779
+ return true;
10780
+ };
8795
10781
  if (!editing) {
8796
10782
  if (usePencil) {
8797
10783
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
@@ -8842,32 +10828,38 @@ function InlineStringEditor({
8842
10828
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(TooltipContent, { children: "Click to edit" })
8843
10829
  ] });
8844
10830
  }
8845
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
8846
- Input,
8847
- {
8848
- autoFocus: true,
8849
- value: draft,
8850
- onClick: (e) => e.stopPropagation(),
8851
- onChange: (e) => setDraft(e.target.value),
8852
- onBlur: () => {
8853
- onCommit(draft);
8854
- setEditing(false);
8855
- },
8856
- onKeyDown: (e) => {
8857
- if (e.key === "Enter") {
8858
- e.preventDefault();
8859
- onCommit(draft);
8860
- setEditing(false);
8861
- }
8862
- if (e.key === "Escape") {
8863
- e.preventDefault();
8864
- setEditing(false);
8865
- }
8866
- },
8867
- className: "h-7 text-sm",
8868
- style: { fontSize: "16px" }
8869
- }
8870
- );
10831
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "block w-full", children: [
10832
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10833
+ Input,
10834
+ {
10835
+ autoFocus: true,
10836
+ value: draft,
10837
+ onClick: (e) => e.stopPropagation(),
10838
+ onChange: (e) => {
10839
+ setDraft(e.target.value);
10840
+ if (refusal) setRefusal(null);
10841
+ },
10842
+ onBlur: () => {
10843
+ if (commit(draft)) setEditing(false);
10844
+ },
10845
+ onKeyDown: (e) => {
10846
+ if (e.key === "Enter") {
10847
+ e.preventDefault();
10848
+ if (commit(draft)) setEditing(false);
10849
+ }
10850
+ if (e.key === "Escape") {
10851
+ e.preventDefault();
10852
+ setRefusal(null);
10853
+ setEditing(false);
10854
+ }
10855
+ },
10856
+ "aria-invalid": refusal ? true : void 0,
10857
+ className: cn("h-7 text-sm", refusal && "border-amber-500 focus-visible:ring-amber-500"),
10858
+ style: { fontSize: "16px" }
10859
+ }
10860
+ ),
10861
+ refusal ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CellRefusal, { reason: refusal }) : null
10862
+ ] });
8871
10863
  }
8872
10864
  function TypedEditor({
8873
10865
  editType,
@@ -8942,8 +10934,8 @@ function TagsEditor({
8942
10934
  onCommit,
8943
10935
  onCancel
8944
10936
  }) {
8945
- const [tags, setTags] = (0, import_react18.useState)(value);
8946
- const [draft, setDraft] = (0, import_react18.useState)("");
10937
+ const [tags, setTags] = (0, import_react19.useState)(value);
10938
+ const [draft, setDraft] = (0, import_react19.useState)("");
8947
10939
  const add = (raw) => {
8948
10940
  const next = raw.trim();
8949
10941
  if (!next || tags.includes(next)) return;
@@ -9040,7 +11032,7 @@ function NumberEditor({
9040
11032
  onCommit,
9041
11033
  onCancel
9042
11034
  }) {
9043
- const [draft, setDraft] = (0, import_react18.useState)(String(value));
11035
+ const [draft, setDraft] = (0, import_react19.useState)(String(value));
9044
11036
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-2", children: [
9045
11037
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
9046
11038
  Input,
@@ -9052,8 +11044,8 @@ function NumberEditor({
9052
11044
  style: { fontSize: "16px" },
9053
11045
  onKeyDown: (e) => {
9054
11046
  if (e.key === "Enter") {
9055
- const n = Number(draft);
9056
- if (Number.isFinite(n)) onCommit(n);
11047
+ const n2 = Number(draft);
11048
+ if (Number.isFinite(n2)) onCommit(n2);
9057
11049
  }
9058
11050
  if (e.key === "Escape") onCancel();
9059
11051
  }
@@ -9080,8 +11072,8 @@ function NumberEditor({
9080
11072
  className: "h-7",
9081
11073
  "aria-label": "Apply value",
9082
11074
  onClick: () => {
9083
- const n = Number(draft);
9084
- if (Number.isFinite(n)) onCommit(n);
11075
+ const n2 = Number(draft);
11076
+ if (Number.isFinite(n2)) onCommit(n2);
9085
11077
  },
9086
11078
  children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Check, { className: "h-3.5 w-3.5" })
9087
11079
  }
@@ -9170,7 +11162,7 @@ function resetToolbarFacets(facets) {
9170
11162
  }
9171
11163
 
9172
11164
  // src/data-table/LayeredFilterBuilder.tsx
9173
- var import_react19 = require("react");
11165
+ var import_react20 = require("react");
9174
11166
  var import_jsx_runtime41 = require("react/jsx-runtime");
9175
11167
  function moveRule(rules, from, to) {
9176
11168
  if (from === to || to < 0 || to >= rules.length) return [...rules];
@@ -9187,8 +11179,8 @@ function LayeredFilterBuilder({
9187
11179
  maxRules = 20,
9188
11180
  label = "Advanced filters"
9189
11181
  }) {
9190
- const [open, setOpen] = (0, import_react19.useState)(false);
9191
- const [draggedIndex, setDraggedIndex] = (0, import_react19.useState)(null);
11182
+ const [open, setOpen] = (0, import_react20.useState)(false);
11183
+ const [draggedIndex, setDraggedIndex] = (0, import_react20.useState)(null);
9192
11184
  if (fields.length === 0) return null;
9193
11185
  const updateRule = (index, patch) => {
9194
11186
  onChange(
@@ -9669,7 +11661,7 @@ function TableToolbarAction({
9669
11661
  "aria-busy": busy || void 0,
9670
11662
  disabled,
9671
11663
  onClick,
9672
- children: (0, import_react20.isValidElement)(children) ? (0, import_react20.cloneElement)(children, { className: cn("h-5 w-5", children.props.className) }) : children
11664
+ children: (0, import_react21.isValidElement)(children) ? (0, import_react21.cloneElement)(children, { className: cn("h-5 w-5", children.props.className) }) : children
9673
11665
  }
9674
11666
  ) }) }),
9675
11667
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(TooltipContent, { children: tooltip ?? ariaLabel })
@@ -9732,7 +11724,7 @@ function MatrxDataTableWithUrlState({
9732
11724
  ...urlState.history === void 0 ? {} : { history: urlState.history },
9733
11725
  ...urlState.textHistory === void 0 ? {} : { textHistory: urlState.textHistory }
9734
11726
  });
9735
- (0, import_react20.useEffect)(() => {
11727
+ (0, import_react21.useEffect)(() => {
9736
11728
  if (activeUrlTableIds.has(urlState.id)) {
9737
11729
  throw new Error(
9738
11730
  `Duplicate MatrxDataTable urlState.id "${urlState.id}" on one page. Give every table a unique stable id.`
@@ -9823,6 +11815,11 @@ function opensRowHref(event) {
9823
11815
  function TableContextPassthrough({ children }) {
9824
11816
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_jsx_runtime42.Fragment, { children });
9825
11817
  }
11818
+ function sameWindow(a, b) {
11819
+ if (a === null || b === null) return a === b;
11820
+ return a.start === b.start && a.end === b.end && a.padStart === b.padStart && a.padEnd === b.padEnd && a.total === b.total && a.pinned.length === b.pinned.length;
11821
+ }
11822
+ var NO_GROUPING_VALUE = "__matrx_no_grouping__";
9826
11823
  function MatrxDataTableCore({
9827
11824
  data: suppliedData,
9828
11825
  columns,
@@ -9879,11 +11876,17 @@ function MatrxDataTableCore({
9879
11876
  mobileCardsBreakpoint = "sm",
9880
11877
  getRowHref,
9881
11878
  onRowOpen,
9882
- density
11879
+ density,
11880
+ grouping,
11881
+ virtualize,
11882
+ coverage,
11883
+ spreadsheet,
11884
+ facets,
11885
+ rowHeight: explicitRowHeight
9883
11886
  }) {
9884
11887
  const appendQuery = query?.mode === "controlled-append" ? query : null;
9885
11888
  const appendQueryKey = appendQuery?.pagination.queryKey ?? null;
9886
- const committedAppendQueryKey = (0, import_react20.useRef)(appendQueryKey);
11889
+ const committedAppendQueryKey = (0, import_react21.useRef)(appendQueryKey);
9887
11890
  const appendSourceChanged = appendQuery !== null && committedAppendQueryKey.current !== appendQueryKey;
9888
11891
  const data = (appendQuery ? appendQuery.pagination.rows : suppliedData) ?? [];
9889
11892
  const isLoading = appendQuery ? appendQuery.pagination.loading : suppliedLoading;
@@ -9895,10 +11898,10 @@ function MatrxDataTableCore({
9895
11898
  const Link = host.Link ?? DefaultTableLink;
9896
11899
  const SidePanelSurface = host.SidePanelSurface ?? DefaultTableSidePanel;
9897
11900
  const { notify: toast, notice } = useTableNotice();
9898
- const [savedViewError, setSavedViewError] = (0, import_react20.useState)(null);
9899
- const [toolbarActionError, setToolbarActionError] = (0, import_react20.useState)(null);
9900
- const [toolbarActionPending, setToolbarActionPending] = (0, import_react20.useState)({ refresh: false, add: false });
9901
- const toolbarActionPendingRef = (0, import_react20.useRef)({ refresh: false, add: false });
11901
+ const [savedViewError, setSavedViewError] = (0, import_react21.useState)(null);
11902
+ const [toolbarActionError, setToolbarActionError] = (0, import_react21.useState)(null);
11903
+ const [toolbarActionPending, setToolbarActionPending] = (0, import_react21.useState)({ refresh: false, add: false });
11904
+ const toolbarActionPendingRef = (0, import_react21.useRef)({ refresh: false, add: false });
9902
11905
  const CHECKBOX_TAP_AREA = "h-4 w-4 touch-manipulation";
9903
11906
  const mobileCardsClass = mobileCardsBreakpoint === "lg" ? "lg:hidden" : "sm:hidden";
9904
11907
  const tableWithCardsClass = mobileCardsBreakpoint === "lg" ? "max-lg:hidden" : "max-sm:hidden";
@@ -9911,7 +11914,7 @@ function MatrxDataTableCore({
9911
11914
  const sourceOwnsIntelligentSearch = sourceProcessing?.intelligentSearch === "source";
9912
11915
  const sourceOwnsSearch = remoteQuery?.mode === "controlled" || appendQuery !== null && sourceProcessing === void 0 || sourceProcessing?.search === "source" || sourceOwnsIntelligentSearch;
9913
11916
  const intelligentSearchEnabled = toolbar?.intelligentSearch !== void 0 && (sourceOwnsIntelligentSearch || remoteQuery === null && !sourceOwnsSearch);
9914
- const emitControlledQueryChange = (0, import_react20.useCallback)(
11917
+ const emitControlledQueryChange = (0, import_react21.useCallback)(
9915
11918
  (patch, options) => {
9916
11919
  if (!controlledQuery) return;
9917
11920
  controlledQuery.onStateChange(
@@ -9920,8 +11923,8 @@ function MatrxDataTableCore({
9920
11923
  },
9921
11924
  [controlledQuery]
9922
11925
  );
9923
- const [internalSearch, setInternalSearch] = (0, import_react20.useState)("");
9924
- const [draggedRowId, setDraggedRowId] = (0, import_react20.useState)(null);
11926
+ const [internalSearch, setInternalSearch] = (0, import_react21.useState)("");
11927
+ const [draggedRowId, setDraggedRowId] = (0, import_react21.useState)(null);
9925
11928
  const hierarchySensors = (0, import_core3.useSensors)(
9926
11929
  (0, import_core3.useSensor)(import_core3.PointerSensor, {
9927
11930
  activationConstraint: { distance: COLUMN_REORDER_ACTIVATION_DISTANCE }
@@ -9937,7 +11940,7 @@ function MatrxDataTableCore({
9937
11940
  }
9938
11941
  toolbar?.onSearchChange?.(v);
9939
11942
  };
9940
- const [internalSearchMatchMode, setInternalSearchMatchMode] = (0, import_react20.useState)(
11943
+ const [internalSearchMatchMode, setInternalSearchMatchMode] = (0, import_react21.useState)(
9941
11944
  toolbar?.searchMatch?.defaultMode ?? "contains"
9942
11945
  );
9943
11946
  const searchMatchMode = controlledQuery ? controlledQuery.state.searchMatchMode ?? toolbar?.searchMatch?.defaultMode ?? "contains" : internalSearchMatchMode;
@@ -9948,7 +11951,7 @@ function MatrxDataTableCore({
9948
11951
  }
9949
11952
  setInternalSearchMatchMode(mode);
9950
11953
  };
9951
- const [internalSearchScope, setInternalSearchScope] = (0, import_react20.useState)("all");
11954
+ const [internalSearchScope, setInternalSearchScope] = (0, import_react21.useState)("all");
9952
11955
  const searchScope = controlledQuery ? controlledQuery.state.searchScope ?? "all" : internalSearchScope;
9953
11956
  const setSearchScope = (scope) => {
9954
11957
  if (controlledQuery) {
@@ -9957,7 +11960,7 @@ function MatrxDataTableCore({
9957
11960
  }
9958
11961
  setInternalSearchScope(scope);
9959
11962
  };
9960
- const [internalAnyOf, setInternalAnyOf] = (0, import_react20.useState)("");
11963
+ const [internalAnyOf, setInternalAnyOf] = (0, import_react21.useState)("");
9961
11964
  const toolbarAnyOfControlled = toolbar?.anyOf?.value !== void 0;
9962
11965
  const anyOfValue = controlledQuery ? controlledQuery.state.anyOf : toolbarAnyOfControlled ? toolbar?.anyOf?.value ?? "" : internalAnyOf;
9963
11966
  const setAnyOfValue = (v) => {
@@ -9968,7 +11971,7 @@ function MatrxDataTableCore({
9968
11971
  }
9969
11972
  toolbar?.anyOf?.onChange?.(v);
9970
11973
  };
9971
- const [internalLayeredFilters, setInternalLayeredFilters] = (0, import_react20.useState)([]);
11974
+ const [internalLayeredFilters, setInternalLayeredFilters] = (0, import_react21.useState)([]);
9972
11975
  const layeredFilters = controlledQuery ? controlledQuery.state.layeredFilters ?? [] : internalLayeredFilters;
9973
11976
  const setLayeredFilters = (next) => {
9974
11977
  if (controlledQuery) {
@@ -9977,7 +11980,7 @@ function MatrxDataTableCore({
9977
11980
  }
9978
11981
  setInternalLayeredFilters(next);
9979
11982
  };
9980
- const [internalColumnFilters, setInternalColumnFilters] = (0, import_react20.useState)({});
11983
+ const [internalColumnFilters, setInternalColumnFilters] = (0, import_react21.useState)({});
9981
11984
  const columnFilters = controlledQuery ? controlledQuery.state.columnFilters : internalColumnFilters;
9982
11985
  const setColumnFilter = (id, next) => {
9983
11986
  const nextFilters = { ...columnFilters, [id]: next };
@@ -9990,7 +11993,7 @@ function MatrxDataTableCore({
9990
11993
  }
9991
11994
  setInternalColumnFilters(nextFilters);
9992
11995
  };
9993
- const [internalSort, setInternalSort] = (0, import_react20.useState)(defaultSort);
11996
+ const [internalSort, setInternalSort] = (0, import_react21.useState)(defaultSort);
9994
11997
  const sort = controlledQuery ? controlledQuery.state.sort : internalSort;
9995
11998
  const setSort = (next) => {
9996
11999
  if (controlledQuery) {
@@ -9999,7 +12002,7 @@ function MatrxDataTableCore({
9999
12002
  }
10000
12003
  setInternalSort(next);
10001
12004
  };
10002
- const [internalPage, setInternalPage] = (0, import_react20.useState)(1);
12005
+ const [internalPage, setInternalPage] = (0, import_react21.useState)(1);
10003
12006
  const page = controlledQuery ? controlledQuery.state.page : internalPage;
10004
12007
  const setPage = (next) => {
10005
12008
  if (controlledQuery) {
@@ -10008,7 +12011,7 @@ function MatrxDataTableCore({
10008
12011
  }
10009
12012
  setInternalPage(next);
10010
12013
  };
10011
- const [internalPageSize, setInternalPageSize] = (0, import_react20.useState)(
12014
+ const [internalPageSize, setInternalPageSize] = (0, import_react21.useState)(
10012
12015
  defaultPageSize
10013
12016
  );
10014
12017
  const pageSize = controlledQuery ? controlledQuery.state.pageSize : internalPageSize;
@@ -10019,28 +12022,28 @@ function MatrxDataTableCore({
10019
12022
  }
10020
12023
  setInternalPageSize(next);
10021
12024
  };
10022
- const [uncontrolledSelectedId, setUncontrolledSelectedId] = (0, import_react20.useState)(null);
12025
+ const [uncontrolledSelectedId, setUncontrolledSelectedId] = (0, import_react21.useState)(null);
10023
12026
  const rawSelectedId = controlledSelectedId !== void 0 ? controlledSelectedId : uncontrolledSelectedId;
10024
12027
  const selectedId = appendSourceChanged ? null : rawSelectedId;
10025
12028
  const setSelectedId = (id) => {
10026
12029
  if (controlledSelectedId === void 0) setUncontrolledSelectedId(id);
10027
12030
  onSelectedIdChange?.(id);
10028
12031
  };
10029
- const [uncontrolledWindowRowId, setUncontrolledWindowRowId] = (0, import_react20.useState)(null);
12032
+ const [uncontrolledWindowRowId, setUncontrolledWindowRowId] = (0, import_react21.useState)(null);
10030
12033
  const rawWindowRowId = controlledWindowRowId !== void 0 ? controlledWindowRowId : uncontrolledWindowRowId;
10031
12034
  const windowRowId = appendSourceChanged ? null : rawWindowRowId;
10032
12035
  const setWindowRowId = (id) => {
10033
12036
  if (controlledWindowRowId === void 0) setUncontrolledWindowRowId(id);
10034
12037
  onWindowRowIdChange?.(id);
10035
12038
  };
10036
- const [windowRowSnapshot, setWindowRowSnapshot] = (0, import_react20.useState)(null);
12039
+ const [windowRowSnapshot, setWindowRowSnapshot] = (0, import_react21.useState)(null);
10037
12040
  const editScopeKey = appendQueryKey === null ? "\0local" : `append:${appendQueryKey}`;
10038
- const [editsByScope, setEditsByScope] = (0, import_react20.useState)({});
10039
- const editRevisionRef = (0, import_react20.useRef)({});
10040
- const [savingByScope, setSavingByScope] = (0, import_react20.useState)({});
10041
- const savingScopesRef = (0, import_react20.useRef)(/* @__PURE__ */ new Set());
10042
- const [editRequest, setEditRequest] = (0, import_react20.useState)(null);
10043
- const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = (0, import_react20.useState)(false);
12041
+ const [editsByScope, setEditsByScope] = (0, import_react21.useState)({});
12042
+ const editRevisionRef = (0, import_react21.useRef)({});
12043
+ const [savingByScope, setSavingByScope] = (0, import_react21.useState)({});
12044
+ const savingScopesRef = (0, import_react21.useRef)(/* @__PURE__ */ new Set());
12045
+ const [editRequest, setEditRequest] = (0, import_react21.useState)(null);
12046
+ const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = (0, import_react21.useState)(false);
10044
12047
  const edits = editsByScope[editScopeKey]?.values ?? {};
10045
12048
  const saving = (savingByScope[editScopeKey] ?? 0) > 0;
10046
12049
  const setEdits = (next) => {
@@ -10107,7 +12110,7 @@ function MatrxDataTableCore({
10107
12110
  rows: Object.entries(draft.values).map(([rowId, columns2]) => ({ rowId, columns: columns2 }))
10108
12111
  }));
10109
12112
  const selection = appendSourceChanged && selectionConfig ? { ...selectionConfig, selectedIds: [] } : selectionConfig;
10110
- (0, import_react20.useEffect)(() => {
12113
+ (0, import_react21.useEffect)(() => {
10111
12114
  const previous = committedAppendQueryKey.current;
10112
12115
  committedAppendQueryKey.current = appendQueryKey;
10113
12116
  if (previous === null || appendQueryKey === null || previous === appendQueryKey) {
@@ -10125,14 +12128,14 @@ function MatrxDataTableCore({
10125
12128
  onWindowRowIdChange,
10126
12129
  selectionConfig
10127
12130
  ]);
10128
- const scrollRef = (0, import_react20.useRef)(null);
10129
- const tableRef = (0, import_react20.useRef)(null);
10130
- const tableHeaderRef = (0, import_react20.useRef)(null);
10131
- const cardsScrollRef = (0, import_react20.useRef)(null);
10132
- const [documentStickyHeader, setDocumentStickyHeader] = (0, import_react20.useState)(false);
10133
- const documentStickyHeaderRef = (0, import_react20.useRef)(false);
10134
- const previousAppendKeyForScrollReset = (0, import_react20.useRef)(appendQueryKey);
10135
- (0, import_react20.useLayoutEffect)(() => {
12131
+ const scrollRef = (0, import_react21.useRef)(null);
12132
+ const tableRef = (0, import_react21.useRef)(null);
12133
+ const tableHeaderRef = (0, import_react21.useRef)(null);
12134
+ const cardsScrollRef = (0, import_react21.useRef)(null);
12135
+ const [documentStickyHeader, setDocumentStickyHeader] = (0, import_react21.useState)(false);
12136
+ const documentStickyHeaderRef = (0, import_react21.useRef)(false);
12137
+ const previousAppendKeyForScrollReset = (0, import_react21.useRef)(appendQueryKey);
12138
+ (0, import_react21.useLayoutEffect)(() => {
10136
12139
  const previous = previousAppendKeyForScrollReset.current;
10137
12140
  previousAppendKeyForScrollReset.current = appendQueryKey;
10138
12141
  if (previous === null || appendQueryKey === null || previous === appendQueryKey) {
@@ -10157,13 +12160,13 @@ function MatrxDataTableCore({
10157
12160
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.thresholdPx !== void 0 ? { thresholdPx: appendScrollPolicy.thresholdPx } : {},
10158
12161
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.intentTimeoutMs !== void 0 ? { intentTimeoutMs: appendScrollPolicy.intentTimeoutMs } : {}
10159
12162
  });
10160
- const rootRef = (0, import_react20.useRef)(null);
12163
+ const rootRef = (0, import_react21.useRef)(null);
10161
12164
  useClippedContentGuard(rootRef, {
10162
12165
  label: typeof copy === "object" && copy.listLabel ? `Table: ${copy.listLabel}` : "MatrxDataTable"
10163
12166
  });
10164
- const [scrollHintRight, setScrollHintRight] = (0, import_react20.useState)(false);
10165
- const [scrollHintLeft, setScrollHintLeft] = (0, import_react20.useState)(false);
10166
- const updateScrollHint = (0, import_react20.useCallback)(() => {
12167
+ const [scrollHintRight, setScrollHintRight] = (0, import_react21.useState)(false);
12168
+ const [scrollHintLeft, setScrollHintLeft] = (0, import_react21.useState)(false);
12169
+ const updateScrollHint = (0, import_react21.useCallback)(() => {
10167
12170
  const el = scrollRef.current;
10168
12171
  if (!el) return;
10169
12172
  setScrollHintRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 2);
@@ -10177,7 +12180,7 @@ function MatrxDataTableCore({
10177
12180
  behavior: "smooth"
10178
12181
  });
10179
12182
  };
10180
- (0, import_react20.useEffect)(() => {
12183
+ (0, import_react21.useEffect)(() => {
10181
12184
  updateScrollHint();
10182
12185
  window.addEventListener("resize", updateScrollHint);
10183
12186
  const el = scrollRef.current;
@@ -10192,7 +12195,7 @@ function MatrxDataTableCore({
10192
12195
  observer?.disconnect();
10193
12196
  };
10194
12197
  }, [updateScrollHint, data, columns]);
10195
- (0, import_react20.useLayoutEffect)(() => {
12198
+ (0, import_react21.useLayoutEffect)(() => {
10196
12199
  const scroll = scrollRef.current;
10197
12200
  const table = tableRef.current;
10198
12201
  const header = tableHeaderRef.current;
@@ -10251,30 +12254,30 @@ function MatrxDataTableCore({
10251
12254
  window.removeEventListener("resize", requestUpdate);
10252
12255
  };
10253
12256
  }, [stickyHeader, data, columns]);
10254
- const [columnOrder, setColumnOrder] = (0, import_react20.useState)(() => columnState?.order ?? columns.map(columnId));
10255
- const [hiddenColumnIds, setHiddenColumnIds] = (0, import_react20.useState)(
12257
+ const [columnOrder, setColumnOrder] = (0, import_react21.useState)(() => columnState?.order ?? columns.map(columnId));
12258
+ const [hiddenColumnIds, setHiddenColumnIds] = (0, import_react21.useState)(
10256
12259
  () => new Set(columnState?.hidden ?? columns.filter((column) => column.hidden).map(columnId))
10257
12260
  );
10258
- const [columnWidths, setColumnWidths] = (0, import_react20.useState)(
12261
+ const [columnWidths, setColumnWidths] = (0, import_react21.useState)(
10259
12262
  () => normalizeColumnWidthsForColumns(columnState?.widths, columns)
10260
12263
  );
10261
- const [fixedTableGeometry, setFixedTableGeometry] = (0, import_react20.useState)(null);
10262
- const knownColumnIds = (0, import_react20.useRef)(new Set(columns.map(columnId)));
10263
- const draggedColumnId = (0, import_react20.useRef)(null);
10264
- const columnDragActive = (0, import_react20.useRef)(false);
10265
- const columnDragRaf = (0, import_react20.useRef)(null);
10266
- const columnDragScrollDirection = (0, import_react20.useRef)(0);
10267
- const [columnDragPreview, setColumnDragPreview] = (0, import_react20.useState)(null);
10268
- const stopColumnDragScroll = (0, import_react20.useCallback)(() => {
12264
+ const [fixedTableGeometry, setFixedTableGeometry] = (0, import_react21.useState)(null);
12265
+ const knownColumnIds = (0, import_react21.useRef)(new Set(columns.map(columnId)));
12266
+ const draggedColumnId = (0, import_react21.useRef)(null);
12267
+ const columnDragActive = (0, import_react21.useRef)(false);
12268
+ const columnDragRaf = (0, import_react21.useRef)(null);
12269
+ const columnDragScrollDirection = (0, import_react21.useRef)(0);
12270
+ const [columnDragPreview, setColumnDragPreview] = (0, import_react21.useState)(null);
12271
+ const stopColumnDragScroll = (0, import_react21.useCallback)(() => {
10269
12272
  if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
10270
12273
  columnDragRaf.current = null;
10271
12274
  columnDragScrollDirection.current = 0;
10272
12275
  }, []);
10273
- (0, import_react20.useEffect)(() => () => {
12276
+ (0, import_react21.useEffect)(() => () => {
10274
12277
  columnDragActive.current = false;
10275
12278
  stopColumnDragScroll();
10276
12279
  }, [stopColumnDragScroll]);
10277
- const startColumnDragScroll = (0, import_react20.useCallback)(() => {
12280
+ const startColumnDragScroll = (0, import_react21.useCallback)(() => {
10278
12281
  if (!columnDragActive.current || columnDragRaf.current !== null) return;
10279
12282
  const scrollStep = () => {
10280
12283
  const scroll = scrollRef.current;
@@ -10301,25 +12304,25 @@ function MatrxDataTableCore({
10301
12304
  };
10302
12305
  columnDragRaf.current = requestAnimationFrame(scrollStep);
10303
12306
  }, []);
10304
- const commitColumnState = (0, import_react20.useCallback)((order, hidden) => {
12307
+ const commitColumnState = (0, import_react21.useCallback)((order, hidden) => {
10305
12308
  if (columnState) columnState.onChange({ order, hidden: [...hidden] });
10306
12309
  else {
10307
12310
  setColumnOrder(order);
10308
12311
  setHiddenColumnIds(hidden);
10309
12312
  }
10310
12313
  }, [columnState]);
10311
- const [internalColumnManagerOpen, setInternalColumnManagerOpen] = (0, import_react20.useState)(false);
12314
+ const [internalColumnManagerOpen, setInternalColumnManagerOpen] = (0, import_react21.useState)(false);
10312
12315
  const columnManagerOpen = controlledColumnManager?.open ?? internalColumnManagerOpen;
10313
12316
  const setColumnManagerOpen = controlledColumnManager?.onOpenChange ?? setInternalColumnManagerOpen;
10314
- const columnIds = (0, import_react20.useMemo)(() => columns.map(columnId), [columns]);
10315
- const effectiveColumnOrder = (0, import_react20.useMemo)(() => columnState ? [...columnState.order.filter((id, index, values) => columnIds.includes(id) && values.indexOf(id) === index), ...columnIds.filter((id) => !columnState.order.includes(id))] : columnOrder, [columnIds, columnOrder, columnState]);
10316
- const effectiveHiddenColumnIds = (0, import_react20.useMemo)(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
10317
- const effectiveColumnWidths = (0, import_react20.useMemo)(
12317
+ const columnIds = (0, import_react21.useMemo)(() => columns.map(columnId), [columns]);
12318
+ const effectiveColumnOrder = (0, import_react21.useMemo)(() => columnState ? [...columnState.order.filter((id, index, values) => columnIds.includes(id) && values.indexOf(id) === index), ...columnIds.filter((id) => !columnState.order.includes(id))] : columnOrder, [columnIds, columnOrder, columnState]);
12319
+ const effectiveHiddenColumnIds = (0, import_react21.useMemo)(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
12320
+ const effectiveColumnWidths = (0, import_react21.useMemo)(
10318
12321
  () => normalizeColumnWidthsForColumns(columnState?.widths ?? columnWidths, columns),
10319
12322
  [columns, columnState?.widths, columnWidths]
10320
12323
  );
10321
- const resizeGestureSnapshotRef = (0, import_react20.useRef)(null);
10322
- const measureVisibleColumnWidths = (0, import_react20.useCallback)(() => {
12324
+ const resizeGestureSnapshotRef = (0, import_react21.useRef)(null);
12325
+ const measureVisibleColumnWidths = (0, import_react21.useCallback)(() => {
10323
12326
  const measured = {};
10324
12327
  const headers = tableRef.current ? Array.from(tableRef.current.querySelectorAll(
10325
12328
  "thead th[data-matrx-table-column-id]"
@@ -10333,7 +12336,7 @@ function MatrxDataTableCore({
10333
12336
  }
10334
12337
  return normalizeColumnWidthsForColumns(measured, columns);
10335
12338
  }, [columns]);
10336
- const measureFixedTableGeometry = (0, import_react20.useCallback)(() => {
12339
+ const measureFixedTableGeometry = (0, import_react21.useCallback)(() => {
10337
12340
  const table = tableRef.current;
10338
12341
  const headers = table ? Array.from(table.querySelectorAll("thead th")) : [];
10339
12342
  const widthFor = (selector) => {
@@ -10368,16 +12371,16 @@ function MatrxDataTableCore({
10368
12371
  ...actionsWidth === void 0 ? {} : { actionsWidth }
10369
12372
  };
10370
12373
  }, [resolvedDensity]);
10371
- const captureResizeGestureSnapshot = (0, import_react20.useCallback)(() => {
12374
+ const captureResizeGestureSnapshot = (0, import_react21.useCallback)(() => {
10372
12375
  resizeGestureSnapshotRef.current = {
10373
12376
  widths: measureVisibleColumnWidths(),
10374
12377
  geometry: measureFixedTableGeometry()
10375
12378
  };
10376
12379
  }, [measureFixedTableGeometry, measureVisibleColumnWidths]);
10377
- const clearResizeGestureSnapshot = (0, import_react20.useCallback)(() => {
12380
+ const clearResizeGestureSnapshot = (0, import_react21.useCallback)(() => {
10378
12381
  resizeGestureSnapshotRef.current = null;
10379
12382
  }, []);
10380
- const commitColumnWidths = (0, import_react20.useCallback)((next, captureInitialWidths = false) => {
12383
+ const commitColumnWidths = (0, import_react21.useCallback)((next, captureInitialWidths = false) => {
10381
12384
  const gestureSnapshot = captureInitialWidths ? resizeGestureSnapshotRef.current : null;
10382
12385
  resizeGestureSnapshotRef.current = null;
10383
12386
  const normalized = normalizeColumnWidthsForColumns(
@@ -10396,7 +12399,7 @@ function MatrxDataTableCore({
10396
12399
  }
10397
12400
  setColumnWidths(normalized);
10398
12401
  }, [columnIds, columnState, columns, measureFixedTableGeometry, measureVisibleColumnWidths]);
10399
- (0, import_react20.useEffect)(() => {
12402
+ (0, import_react21.useEffect)(() => {
10400
12403
  const ids = columns.map(columnId);
10401
12404
  if (columnState) return;
10402
12405
  const previousIds = knownColumnIds.current;
@@ -10415,31 +12418,31 @@ function MatrxDataTableCore({
10415
12418
  setColumnWidths((current) => normalizeColumnWidthsForColumns(current, columns));
10416
12419
  knownColumnIds.current = new Set(ids);
10417
12420
  }, [columns, columnState]);
10418
- (0, import_react20.useLayoutEffect)(() => {
12421
+ (0, import_react21.useLayoutEffect)(() => {
10419
12422
  for (const id of columnIds) {
10420
12423
  tableRef.current?.style.removeProperty(columnResizeCssVar(id));
10421
12424
  }
10422
12425
  });
10423
- const previewColumnOrder = (0, import_react20.useMemo)(
12426
+ const previewColumnOrder = (0, import_react21.useMemo)(
10424
12427
  () => projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
10425
12428
  [columnDragPreview, effectiveColumnOrder]
10426
12429
  );
10427
12430
  useColumnReorderAnimation(tableRef, previewColumnOrder.join("\0"), columnDragPreview !== null);
10428
- const visibleColumns = (0, import_react20.useMemo)(() => {
12431
+ const visibleColumns = (0, import_react21.useMemo)(() => {
10429
12432
  const byId = new Map(columns.map((column) => [columnId(column), column]));
10430
12433
  return previewColumnOrder.map((id) => byId.get(id)).filter(
10431
12434
  (column) => column !== void 0 && !effectiveHiddenColumnIds.has(columnId(column))
10432
12435
  );
10433
12436
  }, [columns, effectiveHiddenColumnIds, previewColumnOrder]);
10434
12437
  const usesMeasuredFixedLayout = visibleColumns.length > 0 && visibleColumns.every((column) => effectiveColumnWidths[columnId(column)] !== void 0);
10435
- (0, import_react20.useLayoutEffect)(() => {
12438
+ (0, import_react21.useLayoutEffect)(() => {
10436
12439
  if (!usesMeasuredFixedLayout) {
10437
12440
  if (fixedTableGeometry !== null) setFixedTableGeometry(null);
10438
12441
  return;
10439
12442
  }
10440
12443
  if (fixedTableGeometry === null) setFixedTableGeometry(measureFixedTableGeometry());
10441
12444
  }, [fixedTableGeometry, measureFixedTableGeometry, usesMeasuredFixedLayout]);
10442
- (0, import_react20.useLayoutEffect)(() => {
12445
+ (0, import_react21.useLayoutEffect)(() => {
10443
12446
  if (!usesMeasuredFixedLayout) return;
10444
12447
  const table = tableRef.current;
10445
12448
  if (!table) return;
@@ -10461,7 +12464,7 @@ function MatrxDataTableCore({
10461
12464
  setFixedTableGeometry(geometry);
10462
12465
  }, [resolvedDensity, measureFixedTableGeometry, usesMeasuredFixedLayout]);
10463
12466
  const fixedLayoutActive = usesMeasuredFixedLayout && fixedTableGeometry !== null;
10464
- const frozenColumns = (0, import_react20.useMemo)(() => {
12467
+ const frozenColumns = (0, import_react21.useMemo)(() => {
10465
12468
  if (!visibleColumns.some((column) => column.frozen)) return NO_FROZEN_COLUMNS;
10466
12469
  const ids = visibleColumns.map(columnId);
10467
12470
  const byId = new Map(visibleColumns.map((column) => [columnId(column), column]));
@@ -10503,15 +12506,15 @@ function MatrxDataTableCore({
10503
12506
  );
10504
12507
  return color ? CELL_TINT_CLASS[color] : void 0;
10505
12508
  };
10506
- const declaredVisibleCopyColumns = (0, import_react20.useMemo)(
12509
+ const declaredVisibleCopyColumns = (0, import_react21.useMemo)(
10507
12510
  () => visibleColumns.filter((column) => column.accessorFn !== void 0 || column.accessorKey !== void 0),
10508
12511
  [visibleColumns]
10509
12512
  );
10510
- const tableCopySourceNamespace = (0, import_react20.useId)();
10511
- const tableContextMenuInstanceId = (0, import_react20.useId)();
10512
- const tableDndContextId = (0, import_react20.useId)();
12513
+ const tableCopySourceNamespace = (0, import_react21.useId)();
12514
+ const tableContextMenuInstanceId = (0, import_react21.useId)();
12515
+ const tableDndContextId = (0, import_react21.useId)();
10513
12516
  const defaultCopyTitle = toolbar?.title || "Table";
10514
- const defaultCopy = (0, import_react20.useMemo)(() => ({
12517
+ const defaultCopy = (0, import_react21.useMemo)(() => ({
10515
12518
  label: defaultCopyTitle,
10516
12519
  listLabel: defaultCopyTitle,
10517
12520
  location: defaultCopyTitle,
@@ -10546,7 +12549,7 @@ function MatrxDataTableCore({
10546
12549
  serializer: (shaped) => buildViewAgentInput(effectiveCopy, shaped, data, { scope: "custom" })
10547
12550
  }))
10548
12551
  ] : [];
10549
- const filterMeta = (0, import_react20.useMemo)(() => {
12552
+ const filterMeta = (0, import_react21.useMemo)(() => {
10550
12553
  const meta = /* @__PURE__ */ new Map();
10551
12554
  for (const col of visibleColumns) {
10552
12555
  const id = columnId(col);
@@ -10554,14 +12557,14 @@ function MatrxDataTableCore({
10554
12557
  }
10555
12558
  return meta;
10556
12559
  }, [visibleColumns, data, remoteQuery]);
10557
- const intelligentSearchScopes = (0, import_react20.useMemo)(
12560
+ const intelligentSearchScopes = (0, import_react21.useMemo)(
10558
12561
  () => resolveTableSearchScopes(data, columns, {
10559
12562
  ...toolbar?.intelligentSearch?.roles === void 0 ? {} : { roles: toolbar.intelligentSearch.roles },
10560
12563
  ...toolbar?.intelligentSearch?.scopes === void 0 ? {} : { scopes: toolbar.intelligentSearch.scopes }
10561
12564
  }),
10562
12565
  [columns, data, toolbar?.intelligentSearch?.roles, toolbar?.intelligentSearch?.scopes]
10563
12566
  );
10564
- const processed = (0, import_react20.useMemo)(() => {
12567
+ const processed = (0, import_react21.useMemo)(() => {
10565
12568
  if (remoteQuery && !appendUsesLocalProcessing) return data;
10566
12569
  if (processLocalRows) {
10567
12570
  return processLocalRows(data, {
@@ -10624,8 +12627,8 @@ function MatrxDataTableCore({
10624
12627
  intelligentSearchEnabled,
10625
12628
  searchScope
10626
12629
  ]);
10627
- const lastNotifiedViewRows = (0, import_react20.useRef)(null);
10628
- (0, import_react20.useEffect)(() => {
12630
+ const lastNotifiedViewRows = (0, import_react21.useRef)(null);
12631
+ (0, import_react21.useEffect)(() => {
10629
12632
  if (!onViewChange) {
10630
12633
  lastNotifiedViewRows.current = null;
10631
12634
  return;
@@ -10634,7 +12637,7 @@ function MatrxDataTableCore({
10634
12637
  lastNotifiedViewRows.current = processed;
10635
12638
  if (changed) onViewChange(processed);
10636
12639
  }, [processed, onViewChange]);
10637
- (0, import_react20.useEffect)(() => {
12640
+ (0, import_react21.useEffect)(() => {
10638
12641
  if (controlledQuery) return;
10639
12642
  setInternalPage(1);
10640
12643
  }, [
@@ -10678,7 +12681,7 @@ function MatrxDataTableCore({
10678
12681
  numberedPageHasMore: !hidePagination && !isLoading && !isFetching && processed.length > 0 && safePage < pageCount && (remoteQuery?.mode === "controlled" || !remoteQuery && localPagination?.mode === "numbered"),
10679
12682
  onPageAdvance: setPage
10680
12683
  });
10681
- const paginated = (0, import_react20.useMemo)(() => {
12684
+ const paginated = (0, import_react21.useMemo)(() => {
10682
12685
  if (remoteQuery) return processed;
10683
12686
  if (hidePagination) return processed;
10684
12687
  if (localProgressiveEnabled) return [...progressiveRows.rows];
@@ -10686,15 +12689,351 @@ function MatrxDataTableCore({
10686
12689
  const start = (safePage - 1) * effectivePageSize;
10687
12690
  return processed.slice(start, start + effectivePageSize);
10688
12691
  }, [processed, safePage, effectivePageSize, pageSize, remoteQuery, hidePagination, localProgressiveEnabled, progressiveRows.rows]);
10689
- (0, import_react20.useEffect)(() => {
12692
+ const groupingColumnId = grouping?.columnId ?? null;
12693
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react21.useState)([]);
12694
+ const collapsedGroups = grouping?.collapsed ?? uncontrolledCollapsed;
12695
+ const setCollapsedGroups = (0, import_react21.useCallback)(
12696
+ (next) => {
12697
+ if (grouping?.onCollapsedChange) grouping.onCollapsedChange(next);
12698
+ else setUncontrolledCollapsed(next);
12699
+ },
12700
+ [grouping]
12701
+ );
12702
+ const groups = (0, import_react21.useMemo)(() => {
12703
+ if (!grouping || !groupingColumnId) return null;
12704
+ const groupColumn = columns.find((col) => columnId(col) === groupingColumnId);
12705
+ if (!groupColumn) return null;
12706
+ const aggregateColumns = {};
12707
+ for (const [aggColumnId, spec] of Object.entries(grouping.aggregates ?? {})) {
12708
+ const col = columns.find((c) => columnId(c) === aggColumnId);
12709
+ if (!col) continue;
12710
+ aggregateColumns[aggColumnId] = { spec, readCell: (row) => getCellValue(row, col) };
12711
+ }
12712
+ return groupRows({
12713
+ rows: paginated,
12714
+ columnId: groupingColumnId,
12715
+ readCell: (row) => grouping.readCell ? grouping.readCell(row, groupingColumnId) : getCellValue(row, groupColumn),
12716
+ aggregates: aggregateColumns,
12717
+ ...grouping.order ? { order: grouping.order } : {},
12718
+ ...grouping.emptyLabel ? { emptyLabel: grouping.emptyLabel } : {}
12719
+ });
12720
+ }, [grouping, groupingColumnId, columns, paginated]);
12721
+ const groupableColumns = (0, import_react21.useMemo)(() => {
12722
+ const allowed = grouping?.groupableColumnIds;
12723
+ return visibleColumns.filter((col) => {
12724
+ const id = columnId(col);
12725
+ if (allowed) return allowed.includes(id);
12726
+ return col.accessorKey !== void 0 || col.accessorFn !== void 0;
12727
+ });
12728
+ }, [visibleColumns, grouping?.groupableColumnIds]);
12729
+ const groupedRenderRows = (0, import_react21.useMemo)(
12730
+ () => groups ? flattenGroups(groups, collapsedGroups) : null,
12731
+ [groups, collapsedGroups]
12732
+ );
12733
+ const virtualizeConfig = virtualize === void 0 || virtualize === false ? null : virtualize === true ? {} : virtualize;
12734
+ const virtualRowHeight = explicitRowHeight ?? virtualizeConfig?.rowHeight ?? (resolvedDensity === "condensed" ? 32 : resolvedDensity === "spacious" ? 52 : 40);
12735
+ const virtualThreshold = virtualizeConfig?.threshold ?? 150;
12736
+ const bodyEntryCount = groupedRenderRows ? groupedRenderRows.length : paginated.length;
12737
+ const virtualizeActive = virtualizeConfig !== null && virtualizeConfig.enabled !== false && bodyEntryCount > virtualThreshold && !hierarchy && expandedDetail === void 0;
12738
+ const columnWindowWidth = virtualizeConfig?.columnWidth ?? 160;
12739
+ const frozenRunLength = (0, import_react21.useMemo)(() => {
12740
+ let run = 0;
12741
+ for (const col of visibleColumns) {
12742
+ if (frozenColumns.offsets[columnId(col)] === void 0) break;
12743
+ run += 1;
12744
+ }
12745
+ return run;
12746
+ }, [visibleColumns, frozenColumns]);
12747
+ const columnVirtualizeActive = virtualizeConfig !== null && virtualizeConfig.enabled !== false && virtualizeConfig.columns === true && visibleColumns.length - frozenRunLength > 30 && !hierarchy && mobileCards === void 0;
12748
+ const [rowWindow, setRowWindow] = (0, import_react21.useState)(null);
12749
+ const [columnWindow, setColumnWindow] = (0, import_react21.useState)(null);
12750
+ const windowInputs = (0, import_react21.useRef)({
12751
+ virtualizeActive: false,
12752
+ bodyEntryCount: 0,
12753
+ virtualRowHeight: 40,
12754
+ overscan: 8,
12755
+ columnVirtualizeActive: false,
12756
+ columnCount: 0,
12757
+ columnWindowWidth: 160,
12758
+ frozenRunLength: 0
12759
+ });
12760
+ windowInputs.current = {
12761
+ virtualizeActive,
12762
+ bodyEntryCount,
12763
+ virtualRowHeight,
12764
+ overscan: virtualizeConfig?.overscan ?? 8,
12765
+ columnVirtualizeActive,
12766
+ columnCount: visibleColumns.length,
12767
+ columnWindowWidth,
12768
+ frozenRunLength
12769
+ };
12770
+ const recomputeWindows = (0, import_react21.useCallback)(() => {
12771
+ const el = scrollRef.current;
12772
+ const input = windowInputs.current;
12773
+ const nextRow = input.virtualizeActive ? computeWindow({
12774
+ count: input.bodyEntryCount,
12775
+ itemSize: input.virtualRowHeight,
12776
+ viewport: el?.clientHeight || 600,
12777
+ offset: el?.scrollTop ?? 0,
12778
+ overscan: input.overscan
12779
+ }) : null;
12780
+ const nextColumn = input.columnVirtualizeActive ? computeWindow({
12781
+ count: input.columnCount,
12782
+ itemSize: input.columnWindowWidth,
12783
+ viewport: el?.clientWidth || 1200,
12784
+ offset: el?.scrollLeft ?? 0,
12785
+ overscan: Math.max(2, Math.round(input.overscan / 2)),
12786
+ pinnedStart: input.frozenRunLength
12787
+ }) : null;
12788
+ setRowWindow((prev) => sameWindow(prev, nextRow) ? prev : nextRow);
12789
+ setColumnWindow((prev) => sameWindow(prev, nextColumn) ? prev : nextColumn);
12790
+ }, []);
12791
+ const effectiveRowWindow = (0, import_react21.useMemo)(() => {
12792
+ if (!virtualizeActive) return null;
12793
+ if (rowWindow && rowWindow.end <= bodyEntryCount) return rowWindow;
12794
+ return computeWindow({
12795
+ count: bodyEntryCount,
12796
+ itemSize: virtualRowHeight,
12797
+ viewport: scrollRef.current?.clientHeight || 600,
12798
+ offset: scrollRef.current?.scrollTop ?? 0,
12799
+ overscan: virtualizeConfig?.overscan ?? 8
12800
+ });
12801
+ }, [virtualizeActive, rowWindow, bodyEntryCount, virtualRowHeight, virtualizeConfig?.overscan]);
12802
+ const effectiveColumnWindow = (0, import_react21.useMemo)(() => {
12803
+ if (!columnVirtualizeActive) return null;
12804
+ if (columnWindow && columnWindow.end <= visibleColumns.length) return columnWindow;
12805
+ return computeWindow({
12806
+ count: visibleColumns.length,
12807
+ itemSize: columnWindowWidth,
12808
+ viewport: scrollRef.current?.clientWidth || 1200,
12809
+ offset: scrollRef.current?.scrollLeft ?? 0,
12810
+ overscan: Math.max(2, Math.round((virtualizeConfig?.overscan ?? 8) / 2)),
12811
+ pinnedStart: frozenRunLength
12812
+ });
12813
+ }, [
12814
+ columnVirtualizeActive,
12815
+ columnWindow,
12816
+ visibleColumns.length,
12817
+ columnWindowWidth,
12818
+ virtualizeConfig?.overscan,
12819
+ frozenRunLength
12820
+ ]);
12821
+ const metricsWanted = virtualizeActive || columnVirtualizeActive;
12822
+ (0, import_react21.useEffect)(() => {
12823
+ recomputeWindows();
12824
+ }, [
12825
+ recomputeWindows,
12826
+ virtualizeActive,
12827
+ bodyEntryCount,
12828
+ virtualRowHeight,
12829
+ columnVirtualizeActive,
12830
+ visibleColumns.length,
12831
+ columnWindowWidth,
12832
+ frozenRunLength
12833
+ ]);
12834
+ (0, import_react21.useEffect)(() => {
12835
+ if (!metricsWanted) return;
12836
+ const el = scrollRef.current;
12837
+ if (!el) return;
12838
+ let frame = 0;
12839
+ const onScroll = () => {
12840
+ if (frame) return;
12841
+ frame = requestAnimationFrame(() => {
12842
+ frame = 0;
12843
+ recomputeWindows();
12844
+ });
12845
+ };
12846
+ el.addEventListener("scroll", onScroll, { passive: true });
12847
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(onScroll);
12848
+ observer?.observe(el);
12849
+ return () => {
12850
+ if (frame) cancelAnimationFrame(frame);
12851
+ el.removeEventListener("scroll", onScroll);
12852
+ observer?.disconnect();
12853
+ };
12854
+ }, [metricsWanted, recomputeWindows]);
12855
+ const renderedColumns = (0, import_react21.useMemo)(
12856
+ () => effectiveColumnWindow ? [
12857
+ ...visibleColumns.slice(0, frozenRunLength),
12858
+ ...visibleColumns.slice(effectiveColumnWindow.start, effectiveColumnWindow.end)
12859
+ ] : visibleColumns,
12860
+ [effectiveColumnWindow, visibleColumns, frozenRunLength]
12861
+ );
12862
+ const columnSpacers = effectiveColumnWindow ? { start: effectiveColumnWindow.padStart, end: effectiveColumnWindow.padEnd } : null;
12863
+ const windowedWidthFor = (0, import_react21.useCallback)(
12864
+ (id, declared) => {
12865
+ const resized = effectiveColumnWidths[id];
12866
+ if (!effectiveColumnWindow) return resized;
12867
+ if (frozenColumns.offsets[id] !== void 0) return resized;
12868
+ return resized ?? (typeof declared === "number" ? declared : columnWindowWidth);
12869
+ },
12870
+ [effectiveColumnWindow, effectiveColumnWidths, frozenColumns, columnWindowWidth]
12871
+ );
12872
+ const coverageResult = (0, import_react21.useMemo)(() => {
12873
+ if (!coverage) return null;
12874
+ return describeCoverage({
12875
+ loaded: processed.length,
12876
+ ...coverage.matched === void 0 ? {} : { matched: coverage.matched },
12877
+ ...coverage.total === void 0 ? {} : { total: coverage.total },
12878
+ ...coverage.cap === void 0 ? {} : { cap: coverage.cap },
12879
+ answeredBy: coverage.answeredBy ?? "client",
12880
+ ...coverage.noun === void 0 ? {} : { noun: coverage.noun }
12881
+ });
12882
+ }, [coverage, processed.length]);
12883
+ const bodyEntries = (0, import_react21.useMemo)(() => {
12884
+ const from = effectiveRowWindow ? effectiveRowWindow.start : 0;
12885
+ const to = effectiveRowWindow ? effectiveRowWindow.end : void 0;
12886
+ const windowed = groupedRenderRows ? groupedRenderRows.slice(from, to).map(
12887
+ (entry) => entry.kind === "group" ? {
12888
+ kind: "group",
12889
+ group: entry.group,
12890
+ collapsed: isGroupCollapsed(collapsedGroups, entry.group.key)
12891
+ } : { kind: "row", row: entry.row, index: entry.dataIndex ?? 0 }
12892
+ ) : paginated.slice(from, to).map((row, offset) => ({ kind: "row", row, index: from + offset }));
12893
+ if (!effectiveRowWindow) return windowed;
12894
+ if (effectiveRowWindow.padStart > 0)
12895
+ windowed.unshift({ kind: "spacer", height: effectiveRowWindow.padStart, place: "start" });
12896
+ if (effectiveRowWindow.padEnd > 0)
12897
+ windowed.push({ kind: "spacer", height: effectiveRowWindow.padEnd, place: "end" });
12898
+ return windowed;
12899
+ }, [groupedRenderRows, collapsedGroups, paginated, effectiveRowWindow]);
12900
+ const getRowIdRef = (0, import_react21.useRef)(getRowId);
12901
+ getRowIdRef.current = getRowId;
12902
+ const spreadsheetRowIds = (0, import_react21.useMemo)(
12903
+ () => paginated.map((row) => getRowIdRef.current(row)),
12904
+ [paginated]
12905
+ );
12906
+ const spreadsheetColumnIds = (0, import_react21.useMemo)(
12907
+ () => visibleColumns.map((col) => columnId(col)),
12908
+ [visibleColumns]
12909
+ );
12910
+ const spreadsheetRowById = (0, import_react21.useMemo)(() => {
12911
+ const map = /* @__PURE__ */ new Map();
12912
+ for (const row of paginated) map.set(getRowIdRef.current(row), row);
12913
+ return map;
12914
+ }, [paginated]);
12915
+ const spreadsheetColumnById = (0, import_react21.useMemo)(() => {
12916
+ const map = /* @__PURE__ */ new Map();
12917
+ for (const col of visibleColumns) map.set(columnId(col), col);
12918
+ return map;
12919
+ }, [visibleColumns]);
12920
+ const spreadsheetEnabled = Boolean(spreadsheet && spreadsheet.enabled !== false);
12921
+ const sheet = useSpreadsheetGrid(
12922
+ spreadsheetEnabled && spreadsheet ? {
12923
+ rowIds: spreadsheetRowIds,
12924
+ columnIds: spreadsheetColumnIds,
12925
+ valueAt: (rowId, colId) => {
12926
+ const row = spreadsheetRowById.get(rowId);
12927
+ const col = spreadsheetColumnById.get(colId);
12928
+ if (row === void 0 || col === void 0) return null;
12929
+ if (spreadsheet.readCell) return spreadsheet.readCell(row, colId);
12930
+ return getCellValue(applyRowEdits(row, edits[rowId]), col);
12931
+ },
12932
+ isColumnWritable: (colId) => {
12933
+ if (spreadsheet.writableColumnIds) return spreadsheet.writableColumnIds.includes(colId);
12934
+ const col = spreadsheetColumnById.get(colId);
12935
+ return Boolean(col && col.editable);
12936
+ },
12937
+ ...spreadsheet.columnRefusal ? { columnRefusal: spreadsheet.columnRefusal } : {},
12938
+ onPatch: spreadsheet.onPatch,
12939
+ ...spreadsheet.undoDepth === void 0 ? {} : { undoDepth: spreadsheet.undoDepth },
12940
+ ...spreadsheet.fillHandle === void 0 ? {} : { fillHandle: spreadsheet.fillHandle },
12941
+ ...spreadsheet.paste === void 0 ? {} : { paste: spreadsheet.paste },
12942
+ notify: spreadsheet.notify ?? ((message) => toast.success(message)),
12943
+ onEditRequest: (address, seed) => requestCellEditRef.current(address, seed),
12944
+ ...spreadsheet.validateCell ? { validateCell: spreadsheet.validateCell } : {}
12945
+ } : null
12946
+ );
12947
+ const sheetRef = (0, import_react21.useRef)(sheet);
12948
+ sheetRef.current = sheet;
12949
+ const searchValueRef = (0, import_react21.useRef)(searchValue);
12950
+ searchValueRef.current = searchValue;
12951
+ const facetsEnabled = Boolean(facets && facets.enabled !== false);
12952
+ const [facetState, setFacetState] = (0, import_react21.useState)({});
12953
+ const facetReadCell = (0, import_react21.useCallback)(
12954
+ (row, colId) => {
12955
+ if (facets?.readCell) return facets.readCell(row, colId);
12956
+ const col = visibleColumns.find((candidate) => columnId(candidate) === colId);
12957
+ return col ? getFilterValue(row, col) : void 0;
12958
+ },
12959
+ [facets, visibleColumns]
12960
+ );
12961
+ const facetInputsRef = (0, import_react21.useRef)({ data, facets, facetReadCell });
12962
+ facetInputsRef.current = { data, facets, facetReadCell };
12963
+ const facetRequestRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
12964
+ const requestFacets = (0, import_react21.useCallback)((colId) => {
12965
+ const { data: rows, facets: config, facetReadCell: readCell } = facetInputsRef.current;
12966
+ if (!config || config.enabled === false) return;
12967
+ const complete = localFacetsAreComplete(rows.length, config.totalRows);
12968
+ const limit = config.limit;
12969
+ if (complete || !config.source) {
12970
+ setFacetState((current) => ({
12971
+ ...current,
12972
+ [colId]: {
12973
+ loading: false,
12974
+ data: computeColumnFacets({
12975
+ columnId: colId,
12976
+ rows,
12977
+ readValue: readCell,
12978
+ ...limit === void 0 ? {} : { limit },
12979
+ complete
12980
+ })
12981
+ }
12982
+ }));
12983
+ return;
12984
+ }
12985
+ const ticket = (facetRequestRef.current.get(colId) ?? 0) + 1;
12986
+ facetRequestRef.current.set(colId, ticket);
12987
+ setFacetState((current) => ({
12988
+ ...current,
12989
+ [colId]: { loading: true, data: current[colId]?.data ?? null }
12990
+ }));
12991
+ void Promise.resolve().then(
12992
+ () => config.source?.({ columnId: colId, search: searchValueRef.current, limit: limit ?? 200 })
12993
+ ).then((answer) => {
12994
+ if (facetRequestRef.current.get(colId) !== ticket) return;
12995
+ if (!answer) {
12996
+ setFacetState((current) => ({
12997
+ ...current,
12998
+ [colId]: {
12999
+ loading: false,
13000
+ data: computeColumnFacets({
13001
+ columnId: colId,
13002
+ rows: facetInputsRef.current.data,
13003
+ readValue: facetInputsRef.current.facetReadCell,
13004
+ ...limit === void 0 ? {} : { limit },
13005
+ complete: false
13006
+ })
13007
+ }
13008
+ }));
13009
+ return;
13010
+ }
13011
+ setFacetState((current) => ({
13012
+ ...current,
13013
+ [colId]: { loading: false, data: { ...answer, answeredBy: "source", complete: true } }
13014
+ }));
13015
+ }).catch((error) => {
13016
+ if (facetRequestRef.current.get(colId) !== ticket) return;
13017
+ setFacetState((current) => ({
13018
+ ...current,
13019
+ [colId]: { loading: false, data: null, error: describeFacetFailure(error) }
13020
+ }));
13021
+ });
13022
+ }, []);
13023
+ (0, import_react21.useEffect)(() => {
13024
+ if (!facetsEnabled) return;
13025
+ setFacetState({});
13026
+ facetRequestRef.current = /* @__PURE__ */ new Map();
13027
+ }, [facetsEnabled, data, searchValue]);
13028
+ (0, import_react21.useEffect)(() => {
10690
13029
  if (!controlledQuery || controlledQuery.state.page === safePage) return;
10691
13030
  emitControlledQueryChange({ page: safePage });
10692
13031
  }, [controlledQuery, emitControlledQueryChange, safePage]);
10693
- const selectedRow = (0, import_react20.useMemo)(() => {
13032
+ const selectedRow = (0, import_react21.useMemo)(() => {
10694
13033
  if (!selectedId) return null;
10695
13034
  return data.find((r) => getRowId(r) === selectedId) ?? null;
10696
13035
  }, [data, selectedId, getRowId]);
10697
- const windowRow = (0, import_react20.useMemo)(() => {
13036
+ const windowRow = (0, import_react21.useMemo)(() => {
10698
13037
  if (!windowRowId) return null;
10699
13038
  return data.find((r) => getRowId(r) === windowRowId) ?? (windowRowSnapshot && windowRowSnapshot.queryKey === appendQueryKey && getRowId(windowRowSnapshot.row) === windowRowId ? windowRowSnapshot.row : null);
10700
13039
  }, [data, windowRowId, windowRowSnapshot, getRowId, appendQueryKey]);
@@ -10729,12 +13068,12 @@ function MatrxDataTableCore({
10729
13068
  return def !== void 0 && f.value !== def;
10730
13069
  });
10731
13070
  const hasActiveFilters = activeFilterCount > 0 || Boolean(searchValue.trim()) || intelligentSearchEnabled && searchScope !== "all" || Boolean(anyOfValue.trim()) || completeLayeredFilterRules(layeredFilters).length > 0 || facetActive;
10732
- const changeCount = (0, import_react20.useMemo)(() => {
10733
- let n = 0;
13071
+ const changeCount = (0, import_react21.useMemo)(() => {
13072
+ let n2 = 0;
10734
13073
  for (const fields of Object.values(edits)) {
10735
- n += Object.keys(fields).length;
13074
+ n2 += Object.keys(fields).length;
10736
13075
  }
10737
- return n;
13076
+ return n2;
10738
13077
  }, [edits]);
10739
13078
  const showSearch = toolbar?.search !== false;
10740
13079
  const openDetail = (row) => {
@@ -10813,6 +13152,23 @@ function MatrxDataTableCore({
10813
13152
  icon: void 0
10814
13153
  } : emptyState;
10815
13154
  const commitCell = (rowId, field, next) => {
13155
+ if (edit?.validate) {
13156
+ const row = data.find((candidate) => getRowId(candidate) === rowId);
13157
+ if (row) {
13158
+ let refusal;
13159
+ try {
13160
+ refusal = edit.validate({
13161
+ row: applyRowEdits(row, edits[rowId]),
13162
+ rowId,
13163
+ columnId: field,
13164
+ value: next
13165
+ });
13166
+ } catch {
13167
+ refusal = void 0;
13168
+ }
13169
+ if (refusal) return refusal;
13170
+ }
13171
+ }
10816
13172
  const scope = editScopeKey;
10817
13173
  const nextEdits = { [rowId]: { [field]: next } };
10818
13174
  const revision = setCellEdit(rowId, field, next);
@@ -10857,9 +13213,29 @@ function MatrxDataTableCore({
10857
13213
  setEditRequest((current) => ({
10858
13214
  rowId: getRowId(row),
10859
13215
  field,
10860
- nonce: (current?.nonce ?? 0) + 1
13216
+ nonce: (current?.nonce ?? 0) + 1,
13217
+ seed: null
13218
+ }));
13219
+ };
13220
+ const requestCellEdit = (address, seed) => {
13221
+ if (!editEnabled) return false;
13222
+ const row = data.find((candidate) => getRowId(candidate) === address.rowId);
13223
+ if (!row) return false;
13224
+ const column = visibleColumns.find(
13225
+ (candidate) => columnId(candidate) === address.fieldName
13226
+ );
13227
+ if (!column?.editable || !(column.editableIf?.(row) ?? true)) return false;
13228
+ const field = column.accessorKey ? String(column.accessorKey) : columnId(column);
13229
+ setEditRequest((current) => ({
13230
+ rowId: address.rowId,
13231
+ field,
13232
+ nonce: (current?.nonce ?? 0) + 1,
13233
+ seed
10861
13234
  }));
13235
+ return true;
10862
13236
  };
13237
+ const requestCellEditRef = (0, import_react21.useRef)(requestCellEdit);
13238
+ requestCellEditRef.current = requestCellEdit;
10863
13239
  const buildRecordControls = (row) => {
10864
13240
  const rowId = getRowId(row);
10865
13241
  const pending = edits[rowId];
@@ -10885,7 +13261,7 @@ function MatrxDataTableCore({
10885
13261
  };
10886
13262
  const hierarchyRows = hierarchy?.rows ?? data;
10887
13263
  const hierarchyGetParentId = hierarchy?.getParentId;
10888
- const hierarchyIndex = (0, import_react20.useMemo)(() => {
13264
+ const hierarchyIndex = (0, import_react21.useMemo)(() => {
10889
13265
  if (!hierarchyGetParentId) return null;
10890
13266
  const byId = /* @__PURE__ */ new Map();
10891
13267
  const parentById = /* @__PURE__ */ new Map();
@@ -10917,7 +13293,7 @@ function MatrxDataTableCore({
10917
13293
  return { byId, parentById, depthById };
10918
13294
  }, [hierarchyGetParentId, hierarchyRows, getRowId]);
10919
13295
  const draggedRow = draggedRowId && hierarchyIndex ? hierarchyIndex.byId.get(draggedRowId) : void 0;
10920
- const hierarchyBlockedIds = (0, import_react20.useMemo)(() => {
13296
+ const hierarchyBlockedIds = (0, import_react21.useMemo)(() => {
10921
13297
  if (!draggedRowId || !hierarchyIndex) return null;
10922
13298
  const childrenById = /* @__PURE__ */ new Map();
10923
13299
  for (const [id, parentId] of hierarchyIndex.parentById) {
@@ -11100,19 +13476,80 @@ function MatrxDataTableCore({
11100
13476
  setEdits({});
11101
13477
  edit?.onCancel?.();
11102
13478
  };
11103
- (0, import_react20.useEffect)(() => {
11104
- if (!host.rowContextRegistry || !contextMenu && !host.createDefaultRowContext) return;
11105
- return host.rowContextRegistry.register(tableContextMenuInstanceId, (rowId) => {
13479
+ const resolveMenuLevel = (target) => {
13480
+ const rowFor = (rowId) => {
11106
13481
  const row = data.find((candidate) => getRowId(candidate) === rowId);
11107
13482
  if (!row) return null;
11108
- const pending = edits[rowId];
11109
- const displayRow = applyRowEdits(row, pending);
11110
- const controls = buildRecordControls(displayRow);
11111
- return contextMenu?.resolveRowContext(displayRow, controls) ?? host.createDefaultRowContext?.(displayRow, controls) ?? null;
11112
- });
11113
- }, [buildRecordControls, contextMenu, data, getRowId, host.createDefaultRowContext, host.rowContextRegistry, tableContextMenuInstanceId]);
13483
+ return applyRowEdits(row, edits[rowId]);
13484
+ };
13485
+ const columnFor = (id) => visibleColumns.find((col) => columnId(col) === id);
13486
+ const labelFor = (col, id) => col?.label ?? (typeof col?.header === "string" ? col.header : id);
13487
+ const fromHost = (payload) => host.createDefaultMenuContext?.(payload) ?? null;
13488
+ switch (target.level) {
13489
+ case "row": {
13490
+ if (!target.rowId) return null;
13491
+ const displayRow = rowFor(target.rowId);
13492
+ if (!displayRow) return null;
13493
+ const controls = buildRecordControls(displayRow);
13494
+ return contextMenu?.resolveRowContext?.(displayRow, controls) ?? fromHost({ level: "row", row: displayRow, rowId: target.rowId, controls });
13495
+ }
13496
+ case "cell": {
13497
+ if (!target.rowId || !target.columnId) return null;
13498
+ const displayRow = rowFor(target.rowId);
13499
+ const col = columnFor(target.columnId);
13500
+ if (!displayRow || !col) return null;
13501
+ const controls = buildRecordControls(displayRow);
13502
+ const value = spreadsheet?.readCell ? spreadsheet.readCell(displayRow, target.columnId) : getCellValue(displayRow, col);
13503
+ const args = {
13504
+ row: displayRow,
13505
+ rowId: target.rowId,
13506
+ columnId: target.columnId,
13507
+ columnLabel: labelFor(col, target.columnId),
13508
+ value,
13509
+ text: stringifyCellValue(value),
13510
+ controls
13511
+ };
13512
+ return contextMenu?.resolveCellContext?.(args) ?? fromHost({ level: "cell", ...args });
13513
+ }
13514
+ case "column": {
13515
+ if (!target.columnId) return null;
13516
+ const col = columnFor(target.columnId);
13517
+ if (!col) return null;
13518
+ const args = {
13519
+ columnId: target.columnId,
13520
+ columnLabel: labelFor(col, target.columnId),
13521
+ values: paginated.map(
13522
+ (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
13523
+ )
13524
+ };
13525
+ return contextMenu?.resolveColumnContext?.(args) ?? fromHost({ level: "column", ...args });
13526
+ }
13527
+ case "selection": {
13528
+ const addresses = sheetRef.current.selectionAddresses();
13529
+ const cellCount = addresses.rowIds.length * addresses.columnIds.length;
13530
+ if (cellCount <= 1) {
13531
+ return resolveMenuLevel({ ...target, level: "cell" });
13532
+ }
13533
+ const args = { ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount };
13534
+ return contextMenu?.resolveSelectionContext?.(args) ?? fromHost({ level: "selection", ...args });
13535
+ }
13536
+ case "table": {
13537
+ const args = { rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) };
13538
+ return contextMenu?.resolveTableContext?.(args) ?? fromHost({ level: "table", ...args });
13539
+ }
13540
+ }
13541
+ };
13542
+ const resolveMenuLevelRef = (0, import_react21.useRef)(resolveMenuLevel);
13543
+ resolveMenuLevelRef.current = resolveMenuLevel;
13544
+ (0, import_react21.useEffect)(() => {
13545
+ if (!host.rowContextRegistry) return;
13546
+ return host.rowContextRegistry.register(
13547
+ tableContextMenuInstanceId,
13548
+ (target) => resolveMenuLevelRef.current(target)
13549
+ );
13550
+ }, [host.rowContextRegistry, tableContextMenuInstanceId]);
11114
13551
  const showActionsCol = windowEnabled || Boolean(rowActions) || showInlineRowCopy;
11115
- const selectedIdSet = (0, import_react20.useMemo)(
13552
+ const selectedIdSet = (0, import_react21.useMemo)(
11116
13553
  () => new Set(selection?.selectedIds ?? []),
11117
13554
  [selection?.selectedIds]
11118
13555
  );
@@ -11124,11 +13561,11 @@ function MatrxDataTableCore({
11124
13561
  );
11125
13562
  const allOnPageSelected = selectableRows.length > 0 && selectedOnPage.length === selectableRows.length;
11126
13563
  const someOnPageSelected = selectedOnPage.length > 0 && !allOnPageSelected;
11127
- const selectedRows = (0, import_react20.useMemo)(
13564
+ const selectedRows = (0, import_react21.useMemo)(
11128
13565
  () => data.filter((row) => selectedIdSet.has(getRowId(row))),
11129
13566
  [data, selectedIdSet, getRowId]
11130
13567
  );
11131
- const lastToggledIndex = (0, import_react20.useRef)(null);
13568
+ const lastToggledIndex = (0, import_react21.useRef)(null);
11132
13569
  const setSelectedIds = (next) => selection?.onSelectedIdsChange([...next]);
11133
13570
  const toggleRowSelected = (index, shiftKey) => {
11134
13571
  if (!selection) return;
@@ -11165,6 +13602,7 @@ function MatrxDataTableCore({
11165
13602
  const selectionNoun = selection?.noun ?? "row";
11166
13603
  const selectionCount = selection?.selectedIds.length ?? 0;
11167
13604
  const leadingCols = selection ? 1 : 0;
13605
+ const renderedColSpan = renderedColumns.length + leadingCols + (showActionsCol ? 1 : 0) + (columnSpacers ? (columnSpacers.start > 0 ? 1 : 0) + (columnSpacers.end > 0 ? 1 : 0) : 0);
11168
13606
  const renderedFacets = controlledQuery ? toolbar?.facets?.map(
11169
13607
  (facet) => facet.type === "button-group" ? {
11170
13608
  ...facet,
@@ -11191,14 +13629,25 @@ function MatrxDataTableCore({
11191
13629
  dirty: Boolean(rowEdits && field in rowEdits),
11192
13630
  onCommit: (next) => commitCell(id, field, next),
11193
13631
  ...href === void 0 ? {} : { href },
11194
- ...col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger },
11195
- ...editRequest?.rowId === id && editRequest.field === field ? { editRequest: editRequest.nonce } : {}
13632
+ ...spreadsheetEnabled ? {
13633
+ // 🚨 THE CLICK LAW, enforced where the two paths meet. In a grid
13634
+ // with spreadsheet gestures a single click SELECTS a cell — it
13635
+ // may never drop a person into a free-text buffer, because the
13636
+ // same click is how they start a range, and an editor that opens
13637
+ // under the pointer eats the drag and the next keystroke. So the
13638
+ // editor opens the way it does in Excel: Enter, F2, the hover
13639
+ // pencil, or simply typing. An explicit `editTrigger: "click"`
13640
+ // does not override this; the law is the table's, not the
13641
+ // column's.
13642
+ editTrigger: "pencil"
13643
+ } : col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger },
13644
+ ...editRequest?.rowId === id && editRequest.field === field ? { editRequest: editRequest.nonce, editSeed: editRequest.seed } : {}
11196
13645
  }
11197
13646
  );
11198
13647
  }
11199
13648
  return href ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Link, { href, onClick: (event) => event.stopPropagation(), className: "block w-full min-w-0 rounded outline-none focus-visible:ring-2 focus-visible:ring-ring", children: display }) : display;
11200
13649
  };
11201
- const currentViewSnapshot = (0, import_react20.useMemo)(
13650
+ const currentViewSnapshot = (0, import_react21.useMemo)(
11202
13651
  () => ({
11203
13652
  __kind: "matrx-table-view",
11204
13653
  version: 1,
@@ -11233,7 +13682,7 @@ function MatrxDataTableCore({
11233
13682
  sort
11234
13683
  ]
11235
13684
  );
11236
- const declaredDefaultViewSnapshot = (0, import_react20.useMemo)(
13685
+ const declaredDefaultViewSnapshot = (0, import_react21.useMemo)(
11237
13686
  () => defaultViewSnapshot ?? {
11238
13687
  __kind: "matrx-table-view",
11239
13688
  version: 1,
@@ -11252,7 +13701,7 @@ function MatrxDataTableCore({
11252
13701
  },
11253
13702
  [columns, defaultPageSize, defaultSort, defaultViewSnapshot]
11254
13703
  );
11255
- const applySavedView = (0, import_react20.useCallback)(
13704
+ const applySavedView = (0, import_react21.useCallback)(
11256
13705
  (rawSnapshot) => {
11257
13706
  const snapshot = parseTableViewSnapshot(rawSnapshot);
11258
13707
  if (!snapshot) {
@@ -11604,6 +14053,28 @@ function MatrxDataTableCore({
11604
14053
  }
11605
14054
  }
11606
14055
  ) : null,
14056
+ grouping?.onColumnIdChange ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14057
+ Select,
14058
+ {
14059
+ value: groupingColumnId ?? NO_GROUPING_VALUE,
14060
+ onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
14061
+ children: [
14062
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14063
+ SelectTrigger,
14064
+ {
14065
+ "data-matrx-table-group-by": true,
14066
+ "aria-label": "Group rows by a column",
14067
+ className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14068
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectValue, { placeholder: "No groups" })
14069
+ }
14070
+ ),
14071
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(SelectContent, { children: [
14072
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14073
+ groupableColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14074
+ ] })
14075
+ ]
14076
+ }
14077
+ ) : null,
11607
14078
  columns.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11608
14079
  TableToolbarAction,
11609
14080
  {
@@ -11671,65 +14142,6 @@ function MatrxDataTableCore({
11671
14142
  ]
11672
14143
  }
11673
14144
  ),
11674
- selection && selectionCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex shrink-0 flex-wrap items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-2 py-1.5", children: [
11675
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-xs font-medium text-foreground", children: [
11676
- (0, import_format5.formatCount)(selectionCount),
11677
- " ",
11678
- selectionNoun,
11679
- selectionCount === 1 ? "" : "s",
11680
- " selected"
11681
- ] }),
11682
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
11683
- Button,
11684
- {
11685
- type: "button",
11686
- variant: "ghost",
11687
- size: "sm",
11688
- className: "h-7 gap-1 px-2 text-xs",
11689
- onClick: () => {
11690
- lastToggledIndex.current = null;
11691
- selection.onSelectedIdsChange([]);
11692
- },
11693
- children: [
11694
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-3.5 w-3.5" }),
11695
- "Clear"
11696
- ]
11697
- }
11698
- ),
11699
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
11700
- effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11701
- CopyButtons,
11702
- {
11703
- size: "icon",
11704
- "data-matrx-table-icon-action": true,
11705
- sourceId: `${tableCopySourceNamespace}:selection`,
11706
- ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
11707
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
11708
- label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
11709
- ...copy === void 0 ? {} : {
11710
- human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
11711
- json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
11712
- },
11713
- agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
11714
- aiVariants: copySubsetVariantFor(
11715
- () => selectedRows,
11716
- "selected"
11717
- ),
11718
- export: {
11719
- items: [],
11720
- sheetRows: () => selectedRows.map(
11721
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
11722
- ),
11723
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
11724
- id: columnId(column),
11725
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
11726
- }))
11727
- }
11728
- }
11729
- ) : null,
11730
- selection.actions?.(selectedRows, selection.selectedIds)
11731
- ] })
11732
- ] }) : null,
11733
14145
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
11734
14146
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
11735
14147
  mobileCards ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
@@ -11837,12 +14249,33 @@ function MatrxDataTableCore({
11837
14249
  ]
11838
14250
  }
11839
14251
  ) : null,
14252
+ coverageResult && coverageResult.notice ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14253
+ "div",
14254
+ {
14255
+ "data-matrx-table-coverage": true,
14256
+ "data-matrx-table-coverage-scope": coverageResult.scope,
14257
+ role: "status",
14258
+ className: "mb-1 flex 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",
14259
+ children: [
14260
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "font-semibold", children: coverageResult.badge }),
14261
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { children: coverageResult.notice })
14262
+ ]
14263
+ }
14264
+ ) : null,
11840
14265
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
11841
14266
  "div",
11842
14267
  {
11843
14268
  ref: scrollRef,
11844
14269
  "data-matrx-table-scroll": true,
11845
14270
  onScroll: updateScrollHint,
14271
+ ...spreadsheetEnabled ? {
14272
+ tabIndex: sheet.containerProps.tabIndex,
14273
+ onKeyDown: sheet.containerProps.onKeyDown,
14274
+ onCopy: sheet.containerProps.onCopy,
14275
+ onCut: sheet.containerProps.onCut,
14276
+ onPaste: sheet.containerProps.onPaste,
14277
+ "data-matrx-table-spreadsheet": "true"
14278
+ } : {},
11846
14279
  "aria-busy": isLoading || isFetching,
11847
14280
  style: { containerType: "inline-size" },
11848
14281
  className: cn(
@@ -11900,7 +14333,14 @@ function MatrxDataTableCore({
11900
14333
  ref: tableHeaderRef,
11901
14334
  "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
11902
14335
  className: cn(
11903
- "z-10 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
14336
+ // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
14337
+ // cell is `sm:z-20` so it can slide under the columns to
14338
+ // its right; the header was `z-10`, so the frozen column's
14339
+ // cells scrolled straight OVER the header they belong to
14340
+ // and the top data row printed through the column names.
14341
+ // Only a table long enough to scroll shows it, which is why
14342
+ // it survived until a hundred-thousand-row screenshot.
14343
+ "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
11904
14344
  stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
11905
14345
  ),
11906
14346
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
@@ -11933,7 +14373,17 @@ function MatrxDataTableCore({
11933
14373
  )
11934
14374
  }
11935
14375
  ) : null,
11936
- visibleColumns.map((col) => {
14376
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14377
+ "th",
14378
+ {
14379
+ "aria-hidden": "true",
14380
+ "data-matrx-table-column-spacer": "start",
14381
+ className: "border-0 p-0",
14382
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14383
+ },
14384
+ "matrx-column-spacer-start"
14385
+ ) : null,
14386
+ renderedColumns.map((col) => {
11937
14387
  const id = columnId(col);
11938
14388
  const meta = filterMeta.get(id);
11939
14389
  const isSorted = sort?.id === id;
@@ -11949,6 +14399,7 @@ function MatrxDataTableCore({
11949
14399
  "th",
11950
14400
  {
11951
14401
  "data-matrx-table-column-id": id,
14402
+ "data-matrx-header-column-id": id,
11952
14403
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
11953
14404
  "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
11954
14405
  className: cn(
@@ -11976,7 +14427,7 @@ function MatrxDataTableCore({
11976
14427
  frozenEdgeId === id && FROZEN_EDGE_CLASS
11977
14428
  ),
11978
14429
  style: {
11979
- ...columnWidthVar(width, effectiveColumnWidths[id], id, columnResizable),
14430
+ ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
11980
14431
  ...frozenStyle(id)
11981
14432
  },
11982
14433
  children: [
@@ -11985,6 +14436,12 @@ function MatrxDataTableCore({
11985
14436
  {
11986
14437
  label: col.header,
11987
14438
  labelText: headerLabel,
14439
+ ...facetsEnabled ? {
14440
+ facets: facetState[id]?.data ?? null,
14441
+ facetsLoading: facetState[id]?.loading ?? false,
14442
+ ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
14443
+ onFilterOpen: () => requestFacets(id)
14444
+ } : {},
11988
14445
  sortable: col.sortable !== false,
11989
14446
  isSorted: Boolean(isSorted),
11990
14447
  sortDirection: sort?.direction ?? defaultSortDirection,
@@ -12040,6 +14497,16 @@ function MatrxDataTableCore({
12040
14497
  id
12041
14498
  );
12042
14499
  }),
14500
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14501
+ "th",
14502
+ {
14503
+ "aria-hidden": "true",
14504
+ "data-matrx-table-column-spacer": "end",
14505
+ className: "border-0 p-0",
14506
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14507
+ },
14508
+ "matrx-column-spacer-end"
14509
+ ) : null,
12043
14510
  showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12044
14511
  "th",
12045
14512
  {
@@ -12055,7 +14522,17 @@ function MatrxDataTableCore({
12055
14522
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tbody", { children: [
12056
14523
  isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { className: "border-b border-border/60", children: [
12057
14524
  selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
12058
- visibleColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14525
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14526
+ "td",
14527
+ {
14528
+ "aria-hidden": "true",
14529
+ "data-matrx-table-column-spacer": "start",
14530
+ className: "border-0 p-0",
14531
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14532
+ },
14533
+ "matrx-column-spacer-start"
14534
+ ) : null,
14535
+ renderedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12059
14536
  "td",
12060
14537
  {
12061
14538
  className: cn(
@@ -12067,11 +14544,21 @@ function MatrxDataTableCore({
12067
14544
  },
12068
14545
  columnId(col)
12069
14546
  )),
12070
- showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "ml-auto h-5 w-10" }) })
14547
+ showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Skeleton, { className: "ml-auto h-5 w-10" }) }),
14548
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14549
+ "td",
14550
+ {
14551
+ "aria-hidden": "true",
14552
+ "data-matrx-table-column-spacer": "end",
14553
+ className: "border-0 p-0",
14554
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14555
+ },
14556
+ "matrx-column-spacer-end"
14557
+ ) : null
12071
14558
  ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12072
14559
  "td",
12073
14560
  {
12074
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14561
+ colSpan: renderedColSpan,
12075
14562
  className: "px-0 py-12 text-center",
12076
14563
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "data-matrx-table-empty-state": true, className: "sticky left-0 flex flex-col items-center gap-2 whitespace-normal px-4", style: { width: "100cqw", maxWidth: "100%" }, children: [
12077
14564
  renderedEmptyState?.icon,
@@ -12080,7 +14567,43 @@ function MatrxDataTableCore({
12080
14567
  renderedEmptyState?.action
12081
14568
  ] })
12082
14569
  }
12083
- ) }) : paginated.map((row, index) => {
14570
+ ) }) : bodyEntries.map((entry) => {
14571
+ if (entry.kind === "spacer") {
14572
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14573
+ "tr",
14574
+ {
14575
+ "aria-hidden": "true",
14576
+ "data-matrx-table-virtual-spacer": entry.place,
14577
+ style: { height: `${entry.height}px` },
14578
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14579
+ "td",
14580
+ {
14581
+ className: "border-0 p-0",
14582
+ colSpan: renderedColSpan
14583
+ }
14584
+ )
14585
+ },
14586
+ `matrx-virtual-spacer-${entry.place}`
14587
+ );
14588
+ }
14589
+ if (entry.kind === "group") {
14590
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14591
+ TableGroupHeaderRow,
14592
+ {
14593
+ group: entry.group,
14594
+ collapsed: entry.collapsed,
14595
+ columns: renderedColumns,
14596
+ spacers: columnSpacers,
14597
+ leadingCols,
14598
+ showActionsCol,
14599
+ rowNoun: grouping?.rowNoun ?? "row",
14600
+ onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
14601
+ },
14602
+ `matrx-group-${entry.group.key}`
14603
+ );
14604
+ }
14605
+ const row = entry.row;
14606
+ const index = entry.index;
12084
14607
  const id = getRowId(row);
12085
14608
  const isSelected = selectedId === id;
12086
14609
  const rowEdits = edits[id];
@@ -12091,6 +14614,7 @@ function MatrxDataTableCore({
12091
14614
  "tr",
12092
14615
  {
12093
14616
  "data-row-id": id,
14617
+ style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
12094
14618
  "data-state": isSelected ? "selected" : void 0,
12095
14619
  onClick: (e) => {
12096
14620
  if (rowEventTargetsInteractiveDescendant(e.target)) return;
@@ -12163,15 +14687,40 @@ function MatrxDataTableCore({
12163
14687
  ) : null
12164
14688
  }
12165
14689
  ) : null,
12166
- visibleColumns.map((col, colIdx) => {
14690
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14691
+ "td",
14692
+ {
14693
+ "aria-hidden": "true",
14694
+ "data-matrx-table-column-spacer": "start",
14695
+ className: "border-0 p-0",
14696
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14697
+ },
14698
+ "matrx-column-spacer-start"
14699
+ ) : null,
14700
+ renderedColumns.map((col, colIdx) => {
12167
14701
  const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
12168
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14702
+ const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
14703
+ const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
14704
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
12169
14705
  "td",
12170
14706
  {
12171
14707
  "data-matrx-table-column-id": columnId(col),
12172
14708
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
14709
+ ...sheetCell ? {
14710
+ "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
14711
+ "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
14712
+ ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
14713
+ ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
14714
+ ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
14715
+ onMouseDown: sheetCell.onMouseDown,
14716
+ onMouseEnter: sheetCell.onMouseEnter
14717
+ } : {},
12173
14718
  className: cn(
12174
14719
  "py-1.5 align-middle lg:py-0.5",
14720
+ sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
14721
+ sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
14722
+ sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
14723
+ carriesFillHandle && "relative",
12175
14724
  col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
12176
14725
  // nowrap (NOT truncate — truncate clips the cell and
12177
14726
  // defeats w-max, killing the horizontal scroll).
@@ -12193,42 +14742,64 @@ function MatrxDataTableCore({
12193
14742
  style: {
12194
14743
  ...columnWidthVar(
12195
14744
  resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
12196
- effectiveColumnWidths[columnId(col)],
14745
+ windowedWidthFor(columnId(col), col.width),
12197
14746
  columnId(col),
12198
14747
  resizableColumns && col.resizable !== false
12199
14748
  ),
12200
14749
  ...frozenStyle(columnId(col))
12201
14750
  },
12202
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
12203
- "div",
12204
- {
12205
- className: cn(
12206
- colIdx === 0 && hierarchy && "flex min-w-0 items-center"
12207
- ),
12208
- children: [
12209
- colIdx === 0 && hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12210
- HierarchyDragHandle,
12211
- {
12212
- id,
12213
- label: hierarchy.itemLabel?.(row) ?? "row",
12214
- disabled: !(hierarchy.canReparent?.(row) ?? true)
12215
- }
12216
- ) : null,
12217
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12218
- "div",
12219
- {
12220
- "data-matrx-cell-content": true,
12221
- 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]",
12222
- children: renderRecordCell(row, col, index)
12223
- }
12224
- )
12225
- ]
12226
- }
12227
- )
14751
+ children: [
14752
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
14753
+ "div",
14754
+ {
14755
+ className: cn(
14756
+ colIdx === 0 && hierarchy && "flex min-w-0 items-center"
14757
+ ),
14758
+ children: [
14759
+ colIdx === 0 && hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14760
+ HierarchyDragHandle,
14761
+ {
14762
+ id,
14763
+ label: hierarchy.itemLabel?.(row) ?? "row",
14764
+ disabled: !(hierarchy.canReparent?.(row) ?? true)
14765
+ }
14766
+ ) : null,
14767
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14768
+ "div",
14769
+ {
14770
+ "data-matrx-cell-content": true,
14771
+ 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]",
14772
+ children: renderRecordCell(row, col, index)
14773
+ }
14774
+ )
14775
+ ]
14776
+ }
14777
+ ),
14778
+ carriesFillHandle ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14779
+ "button",
14780
+ {
14781
+ type: "button",
14782
+ "data-matrx-table-fill-handle": true,
14783
+ "aria-label": "Drag to fill the cells below or beside this selection",
14784
+ onMouseDown: sheet.startFillDrag,
14785
+ className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
14786
+ }
14787
+ ) : null
14788
+ ]
12228
14789
  },
12229
14790
  columnId(col)
12230
14791
  );
12231
14792
  }),
14793
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14794
+ "td",
14795
+ {
14796
+ "aria-hidden": "true",
14797
+ "data-matrx-table-column-spacer": "end",
14798
+ className: "border-0 p-0",
14799
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14800
+ },
14801
+ "matrx-column-spacer-end"
14802
+ ) : null,
12232
14803
  showActionsCol && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12233
14804
  "td",
12234
14805
  {
@@ -12291,7 +14862,7 @@ function MatrxDataTableCore({
12291
14862
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12292
14863
  "td",
12293
14864
  {
12294
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14865
+ colSpan: renderedColSpan,
12295
14866
  className: "p-4",
12296
14867
  children: expandedDetail.render(displayRow, controls)
12297
14868
  }
@@ -12299,7 +14870,7 @@ function MatrxDataTableCore({
12299
14870
  },
12300
14871
  `${id}:expanded-detail`
12301
14872
  ) : null;
12302
- return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react20.Fragment, { children: [
14873
+ return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
12303
14874
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12304
14875
  HierarchyDroppableRow,
12305
14876
  {
@@ -12309,7 +14880,7 @@ function MatrxDataTableCore({
12309
14880
  }
12310
14881
  ),
12311
14882
  inlineDetail
12312
- ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react20.Fragment, { children: [
14883
+ ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react21.Fragment, { children: [
12313
14884
  wrappedRow,
12314
14885
  inlineDetail
12315
14886
  ] }, id);
@@ -12317,7 +14888,7 @@ function MatrxDataTableCore({
12317
14888
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12318
14889
  "td",
12319
14890
  {
12320
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14891
+ colSpan: renderedColSpan,
12321
14892
  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",
12322
14893
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
12323
14894
  "div",
@@ -12342,7 +14913,17 @@ function MatrxDataTableCore({
12342
14913
  ] }),
12343
14914
  footerRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("tr", { children: [
12344
14915
  selection ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-2" }) : null,
12345
- visibleColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14916
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14917
+ "td",
14918
+ {
14919
+ "aria-hidden": "true",
14920
+ "data-matrx-table-column-spacer": "start",
14921
+ className: "border-0 p-0",
14922
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14923
+ },
14924
+ "matrx-column-spacer-start"
14925
+ ) : null,
14926
+ renderedColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12346
14927
  "td",
12347
14928
  {
12348
14929
  className: cn(
@@ -12354,7 +14935,7 @@ function MatrxDataTableCore({
12354
14935
  ),
12355
14936
  style: columnWidthVar(
12356
14937
  resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
12357
- effectiveColumnWidths[columnId(column)],
14938
+ windowedWidthFor(columnId(column), column.width),
12358
14939
  columnId(column),
12359
14940
  resizableColumns && column.resizable !== false
12360
14941
  ),
@@ -12362,6 +14943,16 @@ function MatrxDataTableCore({
12362
14943
  },
12363
14944
  columnId(column)
12364
14945
  )),
14946
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
14947
+ "td",
14948
+ {
14949
+ "aria-hidden": "true",
14950
+ "data-matrx-table-column-spacer": "end",
14951
+ className: "border-0 p-0",
14952
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14953
+ },
14954
+ "matrx-column-spacer-end"
14955
+ ) : null,
12365
14956
  showActionsCol ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", {}) : null
12366
14957
  ] }, `footer-${index}`)) }) : null
12367
14958
  ]
@@ -12525,8 +15116,8 @@ function MatrxDataTableCore({
12525
15116
  itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
12526
15117
  currentPage: safePage,
12527
15118
  onPageChange: setPage,
12528
- onItemsPerPageChange: (n) => {
12529
- setPageSize(n);
15119
+ onItemsPerPageChange: (n2) => {
15120
+ setPageSize(n2);
12530
15121
  if (!controlledQuery) setPage(1);
12531
15122
  },
12532
15123
  pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
@@ -12536,7 +15127,73 @@ function MatrxDataTableCore({
12536
15127
  compact: true,
12537
15128
  hideEntriesInfo: false
12538
15129
  }
12539
- ) }) : null
15130
+ ) }) : null,
15131
+ selection && selectionCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15132
+ "div",
15133
+ {
15134
+ "data-matrx-table-bulk-bar": true,
15135
+ 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",
15136
+ children: [
15137
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-xs font-medium text-foreground", children: [
15138
+ (0, import_format5.formatCount)(selectionCount),
15139
+ " ",
15140
+ selectionNoun,
15141
+ selectionCount === 1 ? "" : "s",
15142
+ " selected"
15143
+ ] }),
15144
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15145
+ Button,
15146
+ {
15147
+ type: "button",
15148
+ variant: "ghost",
15149
+ size: "sm",
15150
+ className: "h-7 gap-1 px-2 text-xs",
15151
+ onClick: () => {
15152
+ lastToggledIndex.current = null;
15153
+ selection.onSelectedIdsChange([]);
15154
+ },
15155
+ children: [
15156
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(X, { className: "h-3.5 w-3.5" }),
15157
+ "Clear"
15158
+ ]
15159
+ }
15160
+ ),
15161
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
15162
+ effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15163
+ CopyButtons,
15164
+ {
15165
+ size: "icon",
15166
+ "data-matrx-table-icon-action": true,
15167
+ sourceId: `${tableCopySourceNamespace}:selection`,
15168
+ ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
15169
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
15170
+ label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
15171
+ ...copy === void 0 ? {} : {
15172
+ human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
15173
+ json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
15174
+ },
15175
+ agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
15176
+ aiVariants: copySubsetVariantFor(
15177
+ () => selectedRows,
15178
+ "selected"
15179
+ ),
15180
+ export: {
15181
+ items: [],
15182
+ sheetRows: () => selectedRows.map(
15183
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
15184
+ ),
15185
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
15186
+ id: columnId(column),
15187
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
15188
+ }))
15189
+ }
15190
+ }
15191
+ ) : null,
15192
+ selection.actions?.(selectedRows, selection.selectedIds)
15193
+ ] })
15194
+ ]
15195
+ }
15196
+ ) : null
12540
15197
  ] }),
12541
15198
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12542
15199
  ColumnManager,
@@ -12818,7 +15475,7 @@ function HierarchyDragIndicatorLayer({
12818
15475
  scrollRef,
12819
15476
  resolve: resolve2
12820
15477
  }) {
12821
- const [indicator, setIndicator] = (0, import_react20.useState)(
15478
+ const [indicator, setIndicator] = (0, import_react21.useState)(
12822
15479
  null
12823
15480
  );
12824
15481
  (0, import_core3.useDndMonitor)({
@@ -12922,9 +15579,9 @@ function HierarchyDroppableRow({
12922
15579
  children
12923
15580
  }) {
12924
15581
  const { setNodeRef } = (0, import_core3.useDroppable)({ id, disabled });
12925
- if (!(0, import_react20.isValidElement)(children) || children.type !== "tr") return children;
15582
+ if (!(0, import_react21.isValidElement)(children) || children.type !== "tr") return children;
12926
15583
  const row = children;
12927
- return (0, import_react20.cloneElement)(row, {
15584
+ return (0, import_react21.cloneElement)(row, {
12928
15585
  ref: setNodeRef,
12929
15586
  className: row.props.className
12930
15587
  });
@@ -12959,6 +15616,71 @@ function normalizeColumnWidthsForColumns(widths, columns) {
12959
15616
  })
12960
15617
  );
12961
15618
  }
15619
+ function TableGroupHeaderRow({
15620
+ group,
15621
+ collapsed,
15622
+ columns,
15623
+ spacers,
15624
+ leadingCols,
15625
+ showActionsCol,
15626
+ rowNoun,
15627
+ onToggle
15628
+ }) {
15629
+ const count = group.rows.length;
15630
+ const summary = `${count.toLocaleString()} ${count === 1 ? rowNoun : `${rowNoun}s`}`;
15631
+ const firstId = columns[0] ? columnId(columns[0]) : null;
15632
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
15633
+ "tr",
15634
+ {
15635
+ "data-matrx-table-group-row": group.key,
15636
+ "data-matrx-table-group-collapsed": collapsed ? "true" : "false",
15637
+ className: "border-y border-border bg-muted/40",
15638
+ children: [
15639
+ Array.from({ length: leadingCols }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-1.5" }, `group-lead-${i}`)),
15640
+ spacers && spacers.start > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { "aria-hidden": "true", className: "border-0 p-0", style: { width: `${spacers.start}px` } }) : null,
15641
+ columns.map((col) => {
15642
+ const id = columnId(col);
15643
+ const aggregate = group.aggregates[id];
15644
+ if (id === firstId) {
15645
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: cn("px-2 py-1.5 align-middle", col.mobileHidden && "max-sm:hidden"), children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-1.5", children: [
15646
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15647
+ "button",
15648
+ {
15649
+ type: "button",
15650
+ onClick: onToggle,
15651
+ "aria-expanded": !collapsed,
15652
+ "aria-label": `${collapsed ? "Show" : "Hide"} ${group.label}`,
15653
+ className: "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground",
15654
+ children: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ChevronDown, { className: "h-4 w-4" })
15655
+ }
15656
+ ),
15657
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "truncate text-sm font-semibold text-foreground", children: group.label }),
15658
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "whitespace-nowrap text-xs text-muted-foreground", children: summary })
15659
+ ] }) }, id);
15660
+ }
15661
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
15662
+ "td",
15663
+ {
15664
+ className: cn(
15665
+ "px-2 py-1.5 align-middle text-xs font-medium text-muted-foreground",
15666
+ // A subtotal lines up with the numbers it is a subtotal OF.
15667
+ // Left-aligned under a right-aligned money column it reads as a
15668
+ // different column's value.
15669
+ alignForFormat(col) === "center" && "text-center",
15670
+ alignForFormat(col) === "right" && "text-right",
15671
+ col.mobileHidden && "max-sm:hidden"
15672
+ ),
15673
+ children: aggregate ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-matrx-table-group-aggregate": id, className: "whitespace-nowrap", children: aggregate.label }) : null
15674
+ },
15675
+ id
15676
+ );
15677
+ }),
15678
+ spacers && spacers.end > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { "aria-hidden": "true", className: "border-0 p-0", style: { width: `${spacers.end}px` } }) : null,
15679
+ showActionsCol ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("td", { className: "px-2 py-1.5" }) : null
15680
+ ]
15681
+ }
15682
+ );
15683
+ }
12962
15684
  function columnWidthVar(width, resizedWidth, id, resizable) {
12963
15685
  if (width === void 0 && resizedWidth === void 0 && !resizable) return void 0;
12964
15686
  const inferredWidth = resizable && inferTableSearchRole(id) === "name" ? "12rem" : "auto";
@@ -12968,7 +15690,7 @@ function columnWidthVar(width, resizedWidth, id, resizable) {
12968
15690
  };
12969
15691
  }
12970
15692
  function DefaultJsonCell({ value }) {
12971
- const [open, setOpen] = (0, import_react20.useState)(false);
15693
+ const [open, setOpen] = (0, import_react21.useState)(false);
12972
15694
  const host = useMatrxDataTableHost();
12973
15695
  const Viewer = host.JsonViewer;
12974
15696
  const text = JSON.stringify(value, null, 2);
@@ -13051,7 +15773,7 @@ function resolveStringTitle(title, fallback) {
13051
15773
  }
13052
15774
 
13053
15775
  // src/data-table/SavedViewsControl.tsx
13054
- var import_react21 = require("react");
15776
+ var import_react22 = require("react");
13055
15777
 
13056
15778
  // src/dropdown-menu.tsx
13057
15779
  var DropdownMenuPrimitive = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
@@ -13194,11 +15916,11 @@ function SavedViewsControl({
13194
15916
  onUpdate,
13195
15917
  onReload
13196
15918
  }) {
13197
- const unsavedChangesId = (0, import_react21.useId)();
13198
- const [saveOpen, setSaveOpen] = (0, import_react21.useState)(false);
13199
- const [name, setName] = (0, import_react21.useState)("");
13200
- const [saving, setSaving] = (0, import_react21.useState)(false);
13201
- const [actionError, setActionError] = (0, import_react21.useState)(null);
15919
+ const unsavedChangesId = (0, import_react22.useId)();
15920
+ const [saveOpen, setSaveOpen] = (0, import_react22.useState)(false);
15921
+ const [name, setName] = (0, import_react22.useState)("");
15922
+ const [saving, setSaving] = (0, import_react22.useState)(false);
15923
+ const [actionError, setActionError] = (0, import_react22.useState)(null);
13202
15924
  const visibleError = actionError ?? error;
13203
15925
  const save = async () => {
13204
15926
  const next = name.trim();