@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
@@ -5,13 +5,13 @@ import {
5
5
  cloneElement as cloneElement2,
6
6
  Fragment as Fragment6,
7
7
  isValidElement as isValidElement2,
8
- useCallback as useCallback7,
9
- useEffect as useEffect15,
8
+ useCallback as useCallback8,
9
+ useEffect as useEffect16,
10
10
  useId as useId2,
11
11
  useLayoutEffect as useLayoutEffect2,
12
- useMemo as useMemo7,
13
- useRef as useRef13,
14
- useState as useState21
12
+ useMemo as useMemo8,
13
+ useRef as useRef15,
14
+ useState as useState22
15
15
  } from "react";
16
16
  import {
17
17
  DndContext,
@@ -5139,10 +5139,10 @@ function passesColumnFilter(value, filter) {
5139
5139
  if (raw === "") return false;
5140
5140
  const normalized = raw.replace(/[^0-9.-]/g, "");
5141
5141
  if (normalized === "") return false;
5142
- const n = typeof value === "number" ? value : Number(normalized);
5143
- if (!Number.isFinite(n)) return false;
5144
- if (filter.min !== void 0 && n < filter.min) return false;
5145
- if (filter.max !== void 0 && n > filter.max) return false;
5142
+ const n2 = typeof value === "number" ? value : Number(normalized);
5143
+ if (!Number.isFinite(n2)) return false;
5144
+ if (filter.min !== void 0 && n2 < filter.min) return false;
5145
+ if (filter.max !== void 0 && n2 > filter.max) return false;
5146
5146
  return true;
5147
5147
  }
5148
5148
  }
@@ -5205,26 +5205,26 @@ function filterAndSortRows(rows, columns, columnFilters, sort, globalSearch, any
5205
5205
  return result;
5206
5206
  }
5207
5207
  function countActiveColumnFilters(filters) {
5208
- let n = 0;
5208
+ let n2 = 0;
5209
5209
  for (const f of Object.values(filters)) {
5210
5210
  if (!f) continue;
5211
5211
  switch (f.kind) {
5212
5212
  case "text":
5213
- if (f.value.trim() || f.mode && f.mode !== "contains") n += 1;
5213
+ if (f.value.trim() || f.mode && f.mode !== "contains") n2 += 1;
5214
5214
  break;
5215
5215
  case "select":
5216
5216
  if (f.values !== void 0 || f.value && f.value !== "__all__")
5217
- n += 1;
5217
+ n2 += 1;
5218
5218
  break;
5219
5219
  case "boolean":
5220
- n += 1;
5220
+ n2 += 1;
5221
5221
  break;
5222
5222
  case "number":
5223
- if (f.min !== void 0 || f.max !== void 0) n += 1;
5223
+ if (f.min !== void 0 || f.max !== void 0) n2 += 1;
5224
5224
  break;
5225
5225
  }
5226
5226
  }
5227
- return n;
5227
+ return n2;
5228
5228
  }
5229
5229
  function isColumnFilterActive(filter) {
5230
5230
  if (!filter) return false;
@@ -5479,11 +5479,11 @@ import {
5479
5479
  } from "@ai-matrx/kit/url-state";
5480
5480
  function parseSort(raw) {
5481
5481
  if (!raw) return null;
5482
- const at = raw.lastIndexOf(".");
5483
- if (at <= 0) return null;
5484
- const direction = raw.slice(at + 1);
5482
+ const at2 = raw.lastIndexOf(".");
5483
+ if (at2 <= 0) return null;
5484
+ const direction = raw.slice(at2 + 1);
5485
5485
  if (direction !== "asc" && direction !== "desc") return null;
5486
- return { id: raw.slice(0, at), direction };
5486
+ return { id: raw.slice(0, at2), direction };
5487
5487
  }
5488
5488
  function parseObject(raw, fallback) {
5489
5489
  if (!raw) return fallback;
@@ -5506,8 +5506,8 @@ function parseLayeredFilters(raw) {
5506
5506
  }
5507
5507
  }
5508
5508
  function positiveInt(raw, fallback) {
5509
- const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
5510
- return Number.isFinite(n) && n > 0 ? n : fallback;
5509
+ const n2 = raw ? Number.parseInt(raw, 10) : Number.NaN;
5510
+ return Number.isFinite(n2) && n2 > 0 ? n2 : fallback;
5511
5511
  }
5512
5512
  function tableUrlParamPrefix(tableId) {
5513
5513
  if (!/^[a-z][a-z0-9-]{0,63}$/.test(tableId)) {
@@ -5708,6 +5708,7 @@ import {
5708
5708
  forwardRef as forwardRef12,
5709
5709
  useEffect as useEffect9,
5710
5710
  useMemo as useMemo5,
5711
+ useRef as useRef10,
5711
5712
  useState as useState14
5712
5713
  } from "react";
5713
5714
  import { useDraggable } from "@dnd-kit/core";
@@ -5855,6 +5856,95 @@ var CommandShortcut = ({
5855
5856
  };
5856
5857
  CommandShortcut.displayName = "CommandShortcut";
5857
5858
 
5859
+ // src/data-table/facets.ts
5860
+ var MAX_LISTABLE_FACET_LENGTH = 300;
5861
+ var DEFAULT_FACET_LIMIT = 200;
5862
+ var MAX_FACET_LIMIT = 500;
5863
+ function facetText(raw) {
5864
+ if (raw === null || raw === void 0) return "";
5865
+ if (typeof raw === "object") {
5866
+ try {
5867
+ return JSON.stringify(raw);
5868
+ } catch {
5869
+ return String(raw);
5870
+ }
5871
+ }
5872
+ return String(raw);
5873
+ }
5874
+ function computeColumnFacets(args) {
5875
+ const { columnId: columnId2, rows, readValue: readValue2 } = args;
5876
+ const limit = Math.min(Math.max(args.limit ?? DEFAULT_FACET_LIMIT, 1), MAX_FACET_LIMIT);
5877
+ const counts = /* @__PURE__ */ new Map();
5878
+ const longValues = /* @__PURE__ */ new Set();
5879
+ let blank = 0;
5880
+ let maxLength = 0;
5881
+ for (const row of rows) {
5882
+ let raw;
5883
+ try {
5884
+ raw = readValue2(row, columnId2);
5885
+ } catch {
5886
+ raw = void 0;
5887
+ }
5888
+ const trimmed = facetText(raw).trim();
5889
+ if (trimmed === "") {
5890
+ blank += 1;
5891
+ continue;
5892
+ }
5893
+ if (trimmed.length > maxLength) maxLength = trimmed.length;
5894
+ if (trimmed.length > MAX_LISTABLE_FACET_LENGTH) {
5895
+ longValues.add(trimmed);
5896
+ continue;
5897
+ }
5898
+ counts.set(trimmed, (counts.get(trimmed) ?? 0) + 1);
5899
+ }
5900
+ const values = [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)).slice(0, limit);
5901
+ return {
5902
+ columnId: columnId2,
5903
+ totalRows: rows.length,
5904
+ filled: rows.length - blank,
5905
+ blank,
5906
+ distinctCount: counts.size + longValues.size,
5907
+ maxLength,
5908
+ unlistable: longValues.size,
5909
+ limit,
5910
+ truncated: counts.size > limit,
5911
+ values,
5912
+ answeredBy: "local",
5913
+ complete: args.complete ?? true
5914
+ };
5915
+ }
5916
+ function localFacetsAreComplete(loadedRowCount, total) {
5917
+ if (total === void 0 || !Number.isFinite(total) || total < 0) return false;
5918
+ return loadedRowCount >= total;
5919
+ }
5920
+ var NUMBERS = new Intl.NumberFormat("en-US");
5921
+ function n(value) {
5922
+ try {
5923
+ return NUMBERS.format(value);
5924
+ } catch {
5925
+ return String(value);
5926
+ }
5927
+ }
5928
+ function describeFacetSource(facets, noun = "row") {
5929
+ if (!facets) return "";
5930
+ const plural2 = `${noun}s`;
5931
+ if (!facets.complete) {
5932
+ return `These counts cover the ${n(facets.totalRows)} ${plural2} loaded here, not the whole table.`;
5933
+ }
5934
+ if (facets.truncated) {
5935
+ return `Showing the ${n(facets.values.length)} most common of ${n(facets.distinctCount)} values.`;
5936
+ }
5937
+ if (facets.unlistable > 0) {
5938
+ 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"}.`;
5939
+ }
5940
+ return "";
5941
+ }
5942
+ var FACETS_LOADING_NOTICE = "Counting values\u2026";
5943
+ function describeFacetFailure(error) {
5944
+ const detail = error instanceof Error ? error.message : String(error ?? "");
5945
+ 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.";
5946
+ }
5947
+
5858
5948
  // src/data-table/column-marker.ts
5859
5949
  var MARKER_ACCESSORS = {
5860
5950
  favorite: "favorite",
@@ -5922,6 +6012,10 @@ function ColumnHeaderCell({
5922
6012
  filterFooterHint,
5923
6013
  allowNegation = false,
5924
6014
  allowNullModes = false,
6015
+ facets,
6016
+ facetsLoading = false,
6017
+ facetsError,
6018
+ onFilterOpen,
5925
6019
  reorderColumn
5926
6020
  }) {
5927
6021
  const reorderEnabled = reorderColumn !== void 0;
@@ -5956,6 +6050,11 @@ function ColumnHeaderCell({
5956
6050
  setPendingNegation(filterValue?.negated ?? false);
5957
6051
  }
5958
6052
  }, [filterOpen, filterValue]);
6053
+ const notifyOpen = useRef10(onFilterOpen);
6054
+ notifyOpen.current = onFilterOpen;
6055
+ useEffect9(() => {
6056
+ if (filterOpen) notifyOpen.current?.();
6057
+ }, [filterOpen]);
5959
6058
  const applyFilter = () => {
5960
6059
  onFilterChange(draftFilter);
5961
6060
  setFilterOpen(false);
@@ -6050,6 +6149,9 @@ function ColumnHeaderCell({
6050
6149
  selectOptions,
6051
6150
  selectSingle,
6052
6151
  selectAllowCustom,
6152
+ facets: facets ?? null,
6153
+ facetsLoading,
6154
+ ...facetsError === void 0 ? {} : { facetsError },
6053
6155
  ...markerLabels ? {
6054
6156
  booleanLabels: {
6055
6157
  all: "All",
@@ -6274,8 +6376,43 @@ function FilterBody({
6274
6376
  selectSingle = false,
6275
6377
  selectAllowCustom = false,
6276
6378
  allowNullModes = false,
6277
- booleanLabels
6379
+ booleanLabels,
6380
+ facets = null,
6381
+ facetsLoading = false,
6382
+ facetsError
6278
6383
  }) {
6384
+ const checklist = facetsLoading || facetsError || facets && (facets.values.length > 0 || facets.blank > 0) ? /* @__PURE__ */ jsx31(
6385
+ FacetChecklist,
6386
+ {
6387
+ facets,
6388
+ loading: facetsLoading,
6389
+ ...facetsError === void 0 ? {} : { error: facetsError },
6390
+ value,
6391
+ onChange
6392
+ }
6393
+ ) : null;
6394
+ if (checklist && kind === "select" && facets && facets.values.length > 0) {
6395
+ return /* @__PURE__ */ jsx31("div", { className: "space-y-2", children: checklist });
6396
+ }
6397
+ if (checklist) {
6398
+ return /* @__PURE__ */ jsxs22("div", { className: "space-y-2", children: [
6399
+ checklist,
6400
+ /* @__PURE__ */ jsx31("div", { className: "h-px bg-border" }),
6401
+ /* @__PURE__ */ jsx31(
6402
+ FilterBody,
6403
+ {
6404
+ kind,
6405
+ value,
6406
+ onChange,
6407
+ selectOptions,
6408
+ selectSingle,
6409
+ selectAllowCustom,
6410
+ allowNullModes,
6411
+ ...booleanLabels === void 0 ? {} : { booleanLabels }
6412
+ }
6413
+ )
6414
+ ] });
6415
+ }
6279
6416
  if (kind === "text") {
6280
6417
  const text = value?.kind === "text" ? value.value : "";
6281
6418
  const mode = (value?.kind === "text" ? value.mode : void 0) ?? "contains";
@@ -6386,6 +6523,112 @@ function FilterBody({
6386
6523
  }
6387
6524
  return /* @__PURE__ */ jsx31(NumberFilterBody, { value, onChange });
6388
6525
  }
6526
+ function FacetChecklist({
6527
+ facets,
6528
+ loading,
6529
+ error,
6530
+ value,
6531
+ onChange
6532
+ }) {
6533
+ const [query, setQuery] = useState14("");
6534
+ const selected = value?.kind === "select" ? value.values ?? (value.value && value.value !== "__all__" ? [value.value] : []) : [];
6535
+ const selectedSet = new Set(selected);
6536
+ const rows = useMemo5(() => {
6537
+ if (!facets) return [];
6538
+ const listed = facets.values.map((entry) => ({
6539
+ value: entry.value,
6540
+ label: entry.value,
6541
+ count: entry.count
6542
+ }));
6543
+ const withBlank = facets.blank > 0 ? [{ value: "", label: "(empty)", count: facets.blank }, ...listed] : listed;
6544
+ const q = query.trim().toLowerCase();
6545
+ if (!q) return withBlank;
6546
+ return withBlank.filter((row) => row.label.toLowerCase().includes(q));
6547
+ }, [facets, query]);
6548
+ const toggle = (candidate) => {
6549
+ const next = new Set(selectedSet);
6550
+ if (next.has(candidate)) next.delete(candidate);
6551
+ else next.add(candidate);
6552
+ const values = [...next];
6553
+ onChange(
6554
+ values.length === 0 ? void 0 : { kind: "select", value: values[0] ?? "", values }
6555
+ );
6556
+ };
6557
+ const notice = error ?? (loading ? FACETS_LOADING_NOTICE : describeFacetSource(facets));
6558
+ return /* @__PURE__ */ jsxs22("div", { className: "space-y-1.5", children: [
6559
+ /* @__PURE__ */ jsx31("p", { className: "px-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Values" }),
6560
+ facets && facets.values.length + (facets.blank > 0 ? 1 : 0) > 8 ? /* @__PURE__ */ jsx31(
6561
+ Input,
6562
+ {
6563
+ "data-matrx-filter-autofocus": true,
6564
+ value: query,
6565
+ onChange: (event) => setQuery(event.target.value),
6566
+ placeholder: "Find a value\u2026",
6567
+ className: "h-7 text-sm",
6568
+ style: { fontSize: "16px" }
6569
+ }
6570
+ ) : null,
6571
+ rows.length > 0 ? /* @__PURE__ */ jsx31("ul", { className: "max-h-56 space-y-0.5 overflow-y-auto", "data-matrx-facet-list": "true", children: rows.map((row) => {
6572
+ const checked = selectedSet.has(row.value);
6573
+ return /* @__PURE__ */ jsx31("li", { children: /* @__PURE__ */ jsxs22(
6574
+ "button",
6575
+ {
6576
+ type: "button",
6577
+ role: "checkbox",
6578
+ "aria-checked": checked,
6579
+ "data-matrx-facet-value": row.value === "" ? "__blank__" : row.value,
6580
+ onClick: () => toggle(row.value),
6581
+ className: cn(
6582
+ "flex w-full items-center gap-2 rounded px-1.5 py-1 text-left text-xs hover:bg-muted",
6583
+ checked && "bg-primary/10"
6584
+ ),
6585
+ children: [
6586
+ /* @__PURE__ */ jsx31(
6587
+ "span",
6588
+ {
6589
+ "aria-hidden": true,
6590
+ className: cn(
6591
+ "flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-[3px] border",
6592
+ checked ? "border-primary bg-primary text-primary-foreground" : "border-border"
6593
+ ),
6594
+ children: checked ? /* @__PURE__ */ jsx31(Check, { className: "h-2.5 w-2.5" }) : null
6595
+ }
6596
+ ),
6597
+ /* @__PURE__ */ jsx31("span", { className: cn("min-w-0 flex-1 truncate", row.value === "" && "italic text-muted-foreground"), children: row.label }),
6598
+ /* @__PURE__ */ jsx31(
6599
+ "span",
6600
+ {
6601
+ "data-matrx-facet-count": String(row.count),
6602
+ className: "shrink-0 tabular-nums text-[11px] text-muted-foreground",
6603
+ children: row.count.toLocaleString("en-US")
6604
+ }
6605
+ )
6606
+ ]
6607
+ }
6608
+ ) }, row.value === "" ? "__blank__" : row.value);
6609
+ }) }) : loading ? null : /* @__PURE__ */ jsx31("p", { className: "px-1 text-[11px] text-muted-foreground", children: query.trim() ? "No value matches that." : "This column has no values yet." }),
6610
+ notice ? /* @__PURE__ */ jsx31(
6611
+ "p",
6612
+ {
6613
+ "data-matrx-facet-notice": "true",
6614
+ className: cn(
6615
+ "px-1 text-[11px] leading-snug",
6616
+ error ? "text-amber-700 dark:text-amber-400" : "text-muted-foreground"
6617
+ ),
6618
+ children: notice
6619
+ }
6620
+ ) : null,
6621
+ selected.length > 0 ? /* @__PURE__ */ jsx31(
6622
+ "button",
6623
+ {
6624
+ type: "button",
6625
+ onClick: () => onChange(void 0),
6626
+ className: "px-1 text-[11px] text-muted-foreground underline-offset-2 hover:underline",
6627
+ children: "Clear selection"
6628
+ }
6629
+ ) : null
6630
+ ] });
6631
+ }
6389
6632
  function SearchableSelectFilter({
6390
6633
  selected,
6391
6634
  options,
@@ -6572,15 +6815,15 @@ function NumberFilterBody({
6572
6815
  }
6573
6816
  function parseOptionalNumber(raw) {
6574
6817
  if (raw.trim() === "") return void 0;
6575
- const n = Number(raw.replace(/[^0-9.-]/g, ""));
6576
- return Number.isFinite(n) ? n : void 0;
6818
+ const n2 = Number(raw.replace(/[^0-9.-]/g, ""));
6819
+ return Number.isFinite(n2) ? n2 : void 0;
6577
6820
  }
6578
6821
 
6579
6822
  // src/data-table/ColumnResizeHandle.tsx
6580
6823
  import {
6581
6824
  useCallback as useCallback6,
6582
6825
  useEffect as useEffect10,
6583
- useRef as useRef10,
6826
+ useRef as useRef11,
6584
6827
  useState as useState15
6585
6828
  } from "react";
6586
6829
 
@@ -6624,11 +6867,11 @@ function ColumnResizeHandle({
6624
6867
  onResizeCancel,
6625
6868
  onResizeCommit
6626
6869
  }) {
6627
- const handleRef = useRef10(null);
6628
- const activeResizeRef = useRef10(null);
6629
- const previewRafRef = useRef10(null);
6630
- const queuedWidthRef = useRef10(null);
6631
- const keyboardWidthRef = useRef10(null);
6870
+ const handleRef = useRef11(null);
6871
+ const activeResizeRef = useRef11(null);
6872
+ const previewRafRef = useRef11(null);
6873
+ const queuedWidthRef = useRef11(null);
6874
+ const keyboardWidthRef = useRef11(null);
6632
6875
  const [announcedWidth, setAnnouncedWidth] = useState15(null);
6633
6876
  const bounds = resolveColumnResizeBounds(minWidth, maxWidth);
6634
6877
  const cssVar = columnResizeCssVar(columnId2);
@@ -6771,115 +7014,1768 @@ function projectColumnDragOrder(orderedIds, preview2) {
6771
7014
  return order;
6772
7015
  }
6773
7016
 
6774
- // src/data-table/ranked-search.ts
6775
- function inferTableSearchRole(field) {
6776
- const key = field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/[\s-]+/g, "_");
6777
- if (key === "id" || key === "uuid" || key.endsWith("_id")) return "id";
6778
- if (/^(name|alias|label|title|key|slug|display_name|common_name|model_name)$/.test(key) || /_(name|label|title)$/.test(key)) return "name";
6779
- if (/^(description|summary|details|notes|blurb)$/.test(key) || key.endsWith("_description")) return "description";
6780
- return "other";
7017
+ // src/data-table/grouping.ts
7018
+ var EMPTY_GROUP_KEY = "empty";
7019
+ var DEFAULT_EMPTY_LABEL = "(empty)";
7020
+ function isBlankGroupValue(value) {
7021
+ return value === null || value === void 0 || value === "";
6781
7022
  }
6782
- function searchableFields(row, columns) {
6783
- const values = /* @__PURE__ */ new Map();
6784
- if (row !== null && typeof row === "object") {
6785
- for (const [key, value] of Object.entries(row)) values.set(key, stringifyCellValue(value));
7023
+ function groupKeyOf(value) {
7024
+ if (isBlankGroupValue(value)) return EMPTY_GROUP_KEY;
7025
+ switch (typeof value) {
7026
+ case "string":
7027
+ return `s:${value}`;
7028
+ case "number":
7029
+ return `n:${String(value)}`;
7030
+ case "boolean":
7031
+ return `b:${value ? "true" : "false"}`;
7032
+ case "bigint":
7033
+ return `i:${String(value)}`;
7034
+ case "symbol":
7035
+ return `y:${String(value)}`;
7036
+ case "function":
7037
+ return `f:${safeString(value)}`;
7038
+ default:
7039
+ break;
6786
7040
  }
6787
- for (const column of columns) {
6788
- if (column.accessorKey || column.accessorFn || column.filterValue) {
6789
- values.set(columnId(column), stringifyCellValue(getFilterValue(row, column)));
6790
- }
7041
+ if (value instanceof Date) {
7042
+ const time = value.getTime();
7043
+ return `d:${Number.isFinite(time) ? value.toISOString() : "invalid"}`;
6791
7044
  }
6792
- return values;
7045
+ return `o:${safeJson(value)}`;
6793
7046
  }
6794
- function resolveTableSearchScopes(rows, columns, options = {}) {
6795
- const fields = new Set(columns.filter((c) => c.accessorKey || c.accessorFn || c.filterValue).map(columnId));
6796
- for (const row of rows) if (row !== null && typeof row === "object") Object.keys(row).forEach((key) => fields.add(key));
6797
- const roles = new Set([...fields].map((key) => options.roles?.[key] ?? inferTableSearchRole(key)));
6798
- const scopes = [{ id: "all", label: "All" }];
6799
- for (const [id, label] of [["id", "ID"], ["name", "Name"], ["description", "Description"]]) {
6800
- if (roles.has(id)) scopes.push({ id, label });
7047
+ function safeString(value) {
7048
+ try {
7049
+ return String(value);
7050
+ } catch {
7051
+ return "[unreadable]";
6801
7052
  }
6802
- for (const scope of options.scopes ?? []) if (!scopes.some((s) => s.id === scope.id) && scope.fields?.length) scopes.push(scope);
6803
- return scopes;
6804
7053
  }
6805
- function rankTableSearchRows(rows, columns, query, options = {}, extraText) {
6806
- const q = query.trim().toLocaleLowerCase();
6807
- if (!q) return [...rows];
6808
- const scope = options.scope ?? "all";
6809
- const customScope = options.scopes?.find((s) => s.id === scope);
6810
- const mode = options.matchMode ?? "contains";
6811
- return rows.flatMap((row, index) => {
6812
- const fields = [...searchableFields(row, columns)].filter(([key]) => {
6813
- if (scope === "all") return true;
6814
- if (customScope) return customScope.fields?.includes(key) ?? false;
6815
- return (options.roles?.[key] ?? inferTableSearchRole(key)) === scope;
6816
- });
6817
- const extra = scope === "all" ? extraText?.(row) : void 0;
6818
- const texts = fields.map(([, value]) => value);
6819
- if (extra) texts.push(extra);
6820
- const matched = mode === "whole_words" ? matchesTableSearch(texts.join(" "), q, mode) : texts.some((value) => matchesTableSearch(value, q, mode));
6821
- if (!matched) return [];
6822
- let rank = 3;
6823
- for (const [key, value] of fields) {
6824
- if (!matchesTableSearch(value, q, mode)) continue;
6825
- const role = options.roles?.[key] ?? inferTableSearchRole(key);
6826
- if (role === "id" || role === "name") rank = Math.min(rank, value.trim().toLocaleLowerCase() === q ? 0 : 1);
6827
- else if (role === "description") rank = Math.min(rank, 2);
7054
+ function safeJson(value) {
7055
+ try {
7056
+ const json = JSON.stringify(value);
7057
+ return typeof json === "string" ? json : safeString(value);
7058
+ } catch {
7059
+ return safeString(value);
7060
+ }
7061
+ }
7062
+ var CURRENCY_AND_SPACE = /[\s,$£€¥]/g;
7063
+ function coerceGroupNumber(value) {
7064
+ if (isBlankGroupValue(value)) return null;
7065
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
7066
+ if (typeof value === "boolean") return value ? 1 : 0;
7067
+ if (typeof value === "bigint") {
7068
+ const asNumber = Number(value);
7069
+ return Number.isFinite(asNumber) ? asNumber : null;
7070
+ }
7071
+ if (typeof value !== "string") return null;
7072
+ const cleaned = value.replace(CURRENCY_AND_SPACE, "");
7073
+ if (cleaned === "" || cleaned === "-" || cleaned === "+") return null;
7074
+ const parsed = Number(cleaned);
7075
+ return Number.isFinite(parsed) ? parsed : null;
7076
+ }
7077
+ var NUMBER_FORMAT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
7078
+ function formatGroupNumber(value) {
7079
+ if (!Number.isFinite(value)) return "\u2014";
7080
+ try {
7081
+ return NUMBER_FORMAT.format(value);
7082
+ } catch {
7083
+ return String(value);
7084
+ }
7085
+ }
7086
+ function defaultLabelOf(value, emptyLabel) {
7087
+ if (isBlankGroupValue(value)) return emptyLabel;
7088
+ if (typeof value === "string") return value;
7089
+ if (typeof value === "number") return formatGroupNumber(value);
7090
+ if (typeof value === "boolean") return value ? "true" : "false";
7091
+ if (value instanceof Date) {
7092
+ const time = value.getTime();
7093
+ return Number.isFinite(time) ? value.toISOString() : "Invalid Date";
7094
+ }
7095
+ if (typeof value === "object") return safeJson(value);
7096
+ return safeString(value);
7097
+ }
7098
+ var AGGREGATE_NAMES = {
7099
+ count: { name: "Count", position: "before" },
7100
+ sum: { name: "Sum", position: "before" },
7101
+ average: { name: "Average", position: "before" },
7102
+ min: { name: "Min", position: "before" },
7103
+ max: { name: "Max", position: "before" },
7104
+ empty: { name: "empty", position: "after" },
7105
+ filled: { name: "filled", position: "after" },
7106
+ unique: { name: "unique", position: "after" },
7107
+ checked: { name: "checked", position: "after" },
7108
+ unchecked: { name: "unchecked", position: "after" },
7109
+ percentFilled: { name: "filled", position: "after" }
7110
+ };
7111
+ function aggregateLabel(kind, value, override) {
7112
+ const naming = AGGREGATE_NAMES[kind] ?? { name: kind, position: "before" };
7113
+ const name = override && override.trim() !== "" ? override : naming.name;
7114
+ const shown = value === null ? "\u2014" : kind === "percentFilled" ? `${formatGroupNumber(value)}%` : formatGroupNumber(value);
7115
+ return naming.position === "before" ? `${name} ${shown}` : `${shown} ${name}`;
7116
+ }
7117
+ function readValue(row, reader) {
7118
+ try {
7119
+ return reader(row);
7120
+ } catch {
7121
+ return null;
7122
+ }
7123
+ }
7124
+ function computeAggregate(rows, column) {
7125
+ const kind = column.spec.kind;
7126
+ const raw = rows.map((row) => readValue(row, column.readCell));
7127
+ const total = raw.length;
7128
+ const filled = raw.filter((value2) => !isBlankGroupValue(value2));
7129
+ const numbers = filled.map((value2) => coerceGroupNumber(value2)).filter((value2) => value2 !== null);
7130
+ let value;
7131
+ switch (kind) {
7132
+ case "count":
7133
+ value = total;
7134
+ break;
7135
+ case "sum":
7136
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => acc + n2, 0);
7137
+ break;
7138
+ case "average":
7139
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => acc + n2, 0) / numbers.length;
7140
+ break;
7141
+ case "min":
7142
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => n2 < acc ? n2 : acc);
7143
+ break;
7144
+ case "max":
7145
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n2) => n2 > acc ? n2 : acc);
7146
+ break;
7147
+ case "empty":
7148
+ value = total - filled.length;
7149
+ break;
7150
+ case "filled":
7151
+ value = filled.length;
7152
+ break;
7153
+ case "unique": {
7154
+ const seen = /* @__PURE__ */ new Set();
7155
+ for (const entry of filled) seen.add(groupKeyOf(entry));
7156
+ value = seen.size;
7157
+ break;
6828
7158
  }
6829
- return [{ row, rank, index }];
6830
- }).sort((a, b) => a.rank - b.rank || a.index - b.index).map((item) => item.row);
7159
+ case "checked":
7160
+ value = filled.filter((entry) => entry === true).length;
7161
+ break;
7162
+ case "unchecked":
7163
+ value = filled.filter((entry) => entry === false).length;
7164
+ break;
7165
+ case "percentFilled":
7166
+ value = total === 0 ? null : filled.length / total * 100;
7167
+ break;
7168
+ default:
7169
+ value = null;
7170
+ break;
7171
+ }
7172
+ return { kind, value, label: aggregateLabel(kind, value, column.spec.label) };
7173
+ }
7174
+ function compareLabels(a, b) {
7175
+ if (a === b) return 0;
7176
+ return a < b ? -1 : 1;
7177
+ }
7178
+ function compareValues2(a, b) {
7179
+ const aNumber = typeof a.value === "number" ? a.value : null;
7180
+ const bNumber = typeof b.value === "number" ? b.value : null;
7181
+ if (aNumber !== null && bNumber !== null && aNumber !== bNumber) return aNumber < bNumber ? -1 : 1;
7182
+ if (typeof a.value === "boolean" && typeof b.value === "boolean" && a.value !== b.value) {
7183
+ return a.value ? 1 : -1;
7184
+ }
7185
+ return compareLabels(a.label, b.label);
7186
+ }
7187
+ function groupRows(options) {
7188
+ const rows = Array.isArray(options.rows) ? options.rows : [];
7189
+ const emptyLabel = options.emptyLabel ?? DEFAULT_EMPTY_LABEL;
7190
+ const order = options.order ?? "value-asc";
7191
+ const aggregates = options.aggregates ?? {};
7192
+ const labelFor = (value) => {
7193
+ if (!options.labelOf) return defaultLabelOf(value, emptyLabel);
7194
+ if (isBlankGroupValue(value)) {
7195
+ try {
7196
+ const custom = options.labelOf(value);
7197
+ return typeof custom === "string" && custom !== "" ? custom : emptyLabel;
7198
+ } catch {
7199
+ return emptyLabel;
7200
+ }
7201
+ }
7202
+ try {
7203
+ const custom = options.labelOf(value);
7204
+ return typeof custom === "string" ? custom : defaultLabelOf(value, emptyLabel);
7205
+ } catch {
7206
+ return defaultLabelOf(value, emptyLabel);
7207
+ }
7208
+ };
7209
+ const buckets = /* @__PURE__ */ new Map();
7210
+ for (const row of rows) {
7211
+ const raw = readValue(row, options.readCell);
7212
+ const blank = isBlankGroupValue(raw);
7213
+ const value = blank ? null : raw;
7214
+ const key = groupKeyOf(value);
7215
+ let bucket = buckets.get(key);
7216
+ if (!bucket) {
7217
+ bucket = { key, value, label: labelFor(value), rows: [], seen: buckets.size };
7218
+ buckets.set(key, bucket);
7219
+ }
7220
+ bucket.rows.push(row);
7221
+ }
7222
+ const ordered = [...buckets.values()].sort((a, b) => {
7223
+ const aBlank = a.key === EMPTY_GROUP_KEY;
7224
+ const bBlank = b.key === EMPTY_GROUP_KEY;
7225
+ if (aBlank !== bBlank) return aBlank ? 1 : -1;
7226
+ if (aBlank && bBlank) return 0;
7227
+ switch (order) {
7228
+ case "value-asc":
7229
+ return compareValues2(a, b) || a.seen - b.seen;
7230
+ case "value-desc":
7231
+ return -compareValues2(a, b) || a.seen - b.seen;
7232
+ case "count-desc":
7233
+ return b.rows.length - a.rows.length || a.seen - b.seen;
7234
+ case "first-seen":
7235
+ default:
7236
+ return a.seen - b.seen;
7237
+ }
7238
+ });
7239
+ const aggregateEntries = Object.entries(aggregates);
7240
+ return ordered.map((bucket, index) => {
7241
+ const results = {};
7242
+ for (const [columnId2, column] of aggregateEntries) {
7243
+ if (!column || typeof column.readCell !== "function" || !column.spec) continue;
7244
+ results[columnId2] = computeAggregate(bucket.rows, column);
7245
+ }
7246
+ return {
7247
+ key: bucket.key,
7248
+ value: bucket.value,
7249
+ label: bucket.label,
7250
+ rows: bucket.rows,
7251
+ aggregates: results,
7252
+ index
7253
+ };
7254
+ });
7255
+ }
7256
+ function toggleGroupCollapsed(collapsed, key) {
7257
+ const list = Array.isArray(collapsed) ? collapsed : [];
7258
+ return list.includes(key) ? list.filter((entry) => entry !== key) : [...list, key];
7259
+ }
7260
+ function isGroupCollapsed(collapsed, key) {
7261
+ return Array.isArray(collapsed) ? collapsed.includes(key) : false;
7262
+ }
7263
+ function flattenGroups(groups, collapsed) {
7264
+ const out = [];
7265
+ let dataIndex = 0;
7266
+ for (const group of Array.isArray(groups) ? groups : []) {
7267
+ out.push({ kind: "group", group });
7268
+ if (isGroupCollapsed(collapsed, group.key)) continue;
7269
+ for (const row of group.rows) {
7270
+ out.push({ kind: "row", group, row, dataIndex });
7271
+ dataIndex += 1;
7272
+ }
7273
+ }
7274
+ return out;
7275
+ }
7276
+ function groupSummary(group, rowNoun = "row") {
7277
+ const count = group?.rows?.length ?? 0;
7278
+ const noun = count === 1 ? rowNoun : `${rowNoun}s`;
7279
+ const parts = [`${formatGroupNumber(count)} ${noun}`];
7280
+ for (const result of Object.values(group?.aggregates ?? {})) {
7281
+ if (result?.label) parts.push(result.label);
7282
+ }
7283
+ return parts.join(" \xB7 ");
6831
7284
  }
6832
7285
 
6833
- // src/data-table/ColumnManager.tsx
6834
- import { useEffect as useEffect11, useMemo as useMemo6, useState as useState16 } from "react";
6835
- import { useDndMonitor, useDraggable as useDraggable2, useDroppable } from "@dnd-kit/core";
6836
- import { jsx as jsx33, jsxs as jsxs23 } from "react/jsx-runtime";
6837
- function ColumnManager({
6838
- open,
6839
- onOpenChange,
6840
- columns,
6841
- order,
6842
- hidden,
6843
- onApply
6844
- }) {
6845
- const byId = useMemo6(
6846
- () => new Map(columns.map((column) => [columnId(column), column])),
6847
- [columns]
7286
+ // src/data-table/virtual-window.ts
7287
+ var DEFAULT_OVERSCAN = 6;
7288
+ function isNum(value) {
7289
+ return typeof value === "number" && Number.isFinite(value);
7290
+ }
7291
+ function toCount(value) {
7292
+ return isNum(value) && value > 0 ? Math.floor(value) : 0;
7293
+ }
7294
+ function toLength(value) {
7295
+ return isNum(value) && value > 0 ? value : 0;
7296
+ }
7297
+ function toOverscan(value) {
7298
+ if (!isNum(value)) return DEFAULT_OVERSCAN;
7299
+ return value > 0 ? Math.floor(value) : 0;
7300
+ }
7301
+ function toPinned(value, count) {
7302
+ if (!isNum(value) || value <= 0) return 0;
7303
+ return Math.min(Math.floor(value), count);
7304
+ }
7305
+ function assemble(args) {
7306
+ const { pinnedCount, start, end, padStart, body, total } = args;
7307
+ let padEnd = total - (padStart + body);
7308
+ if (!Number.isFinite(padEnd)) padEnd = 0;
7309
+ const sum = padStart + body + padEnd;
7310
+ if (sum !== total) padEnd = padEnd + (total - sum);
7311
+ const pinned = [];
7312
+ for (let i = 0; i < pinnedCount; i += 1) pinned.push(i);
7313
+ return { start, end, padStart, padEnd, total, pinned };
7314
+ }
7315
+ function emptyWindow(pinnedCount) {
7316
+ const pinned = [];
7317
+ for (let i = 0; i < pinnedCount; i += 1) pinned.push(i);
7318
+ return { start: pinnedCount, end: pinnedCount, padStart: 0, padEnd: 0, total: 0, pinned };
7319
+ }
7320
+ function band(firstVisible, lastVisible, scrollCount, overscan) {
7321
+ let first = firstVisible;
7322
+ if (first > scrollCount - 1) first = scrollCount - 1;
7323
+ if (first < 0) first = 0;
7324
+ let last = lastVisible;
7325
+ if (last < first + 1) last = first + 1;
7326
+ if (last > scrollCount) last = scrollCount;
7327
+ const start = Math.max(0, first - overscan);
7328
+ let end = Math.min(scrollCount, last + overscan);
7329
+ if (end < start) end = start;
7330
+ return { start, end };
7331
+ }
7332
+ function computeWindow(input) {
7333
+ const count = toCount(input?.count);
7334
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7335
+ if (count <= 0) return emptyWindow(0);
7336
+ const itemSize = toLength(input?.itemSize);
7337
+ const viewport = toLength(input?.viewport);
7338
+ const overscan = toOverscan(input?.overscan);
7339
+ const scrollCount = count - pinnedCount;
7340
+ if (scrollCount <= 0) return emptyWindow(pinnedCount);
7341
+ if (itemSize <= 0) {
7342
+ return assemble({ pinnedCount, start: pinnedCount, end: count, padStart: 0, body: 0, total: 0 });
7343
+ }
7344
+ const total = scrollCount * itemSize;
7345
+ const maxOffset = Math.max(0, total - viewport);
7346
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7347
+ const { start: relStart, end: relEnd } = band(
7348
+ Math.floor(offset / itemSize),
7349
+ Math.ceil((offset + viewport) / itemSize),
7350
+ scrollCount,
7351
+ overscan
6848
7352
  );
6849
- const [draftOrder, setDraftOrder] = useState16(order);
6850
- const [draftHidden, setDraftHidden] = useState16(() => new Set(hidden));
6851
- const [draggedColumnId, setDraggedColumnId] = useState16(null);
6852
- const [dropTarget, setDropTarget] = useState16(null);
6853
- useEffect11(() => {
6854
- if (!open) return;
6855
- setDraftOrder(order);
6856
- setDraftHidden(new Set(hidden));
6857
- }, [open, order, hidden]);
6858
- const move = (id, direction) => {
6859
- setDraftOrder((current) => {
6860
- const index = current.indexOf(id);
6861
- const target = index + direction;
6862
- if (index < 0 || target < 0 || target >= current.length) return current;
6863
- const next = [...current];
6864
- const displaced = next[target];
6865
- if (displaced === void 0) return current;
6866
- next[target] = id;
6867
- next[index] = displaced;
6868
- return next;
6869
- });
6870
- };
6871
- const moveTo = (id, targetId) => {
6872
- if (id === targetId) return;
6873
- setDraftOrder((current) => {
6874
- const from = current.indexOf(id);
6875
- const target = current.indexOf(targetId);
6876
- if (from < 0 || target < 0) return current;
6877
- const next = [...current];
6878
- next.splice(from, 1);
6879
- const targetAfterRemoval = next.indexOf(targetId);
6880
- next.splice(targetAfterRemoval, 0, id);
6881
- return next;
6882
- });
7353
+ return assemble({
7354
+ pinnedCount,
7355
+ start: pinnedCount + relStart,
7356
+ end: pinnedCount + relEnd,
7357
+ padStart: relStart * itemSize,
7358
+ body: (relEnd - relStart) * itemSize,
7359
+ total
7360
+ });
7361
+ }
7362
+ var PREFIX_CACHE = /* @__PURE__ */ new WeakMap();
7363
+ function prefixFor(sizeAt, count) {
7364
+ let byCount = PREFIX_CACHE.get(sizeAt);
7365
+ if (!byCount) {
7366
+ byCount = /* @__PURE__ */ new Map();
7367
+ PREFIX_CACHE.set(sizeAt, byCount);
7368
+ }
7369
+ const cached = byCount.get(count);
7370
+ if (cached) return cached;
7371
+ const prefix = new Float64Array(count + 1);
7372
+ let running = 0;
7373
+ for (let i = 0; i < count; i += 1) {
7374
+ let size = 0;
7375
+ try {
7376
+ size = sizeAt(i);
7377
+ } catch {
7378
+ size = 0;
7379
+ }
7380
+ running += isNum(size) && size > 0 ? size : 0;
7381
+ prefix[i + 1] = running;
7382
+ }
7383
+ byCount.set(count, prefix);
7384
+ return prefix;
7385
+ }
7386
+ function at(prefix, index) {
7387
+ return prefix[index] ?? 0;
7388
+ }
7389
+ function lastAtOrBefore(prefix, lo, hi, target) {
7390
+ let a = lo;
7391
+ let b = hi;
7392
+ while (a < b) {
7393
+ const mid = a + Math.ceil((b - a) / 2);
7394
+ if (at(prefix, mid) <= target) a = mid;
7395
+ else b = mid - 1;
7396
+ }
7397
+ return a;
7398
+ }
7399
+ function firstAtOrAfter(prefix, lo, hi, target) {
7400
+ let a = lo;
7401
+ let b = hi;
7402
+ while (a < b) {
7403
+ const mid = a + Math.floor((b - a) / 2);
7404
+ if (at(prefix, mid) >= target) b = mid;
7405
+ else a = mid + 1;
7406
+ }
7407
+ return a;
7408
+ }
7409
+ function computeVariableWindow(input) {
7410
+ const count = toCount(input?.count);
7411
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7412
+ if (count <= 0) return emptyWindow(0);
7413
+ const sizeAt = typeof input?.sizeAt === "function" ? input.sizeAt : () => 0;
7414
+ const viewport = toLength(input?.viewport);
7415
+ const overscan = toOverscan(input?.overscan);
7416
+ const scrollCount = count - pinnedCount;
7417
+ if (scrollCount <= 0) return emptyWindow(pinnedCount);
7418
+ const prefix = prefixFor(sizeAt, count);
7419
+ const pinnedLength = at(prefix, pinnedCount);
7420
+ const total = at(prefix, count) - pinnedLength;
7421
+ if (total <= 0) {
7422
+ return assemble({ pinnedCount, start: pinnedCount, end: count, padStart: 0, body: 0, total: total > 0 ? total : 0 });
7423
+ }
7424
+ const maxOffset = Math.max(0, total - viewport);
7425
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7426
+ const firstVisible = lastAtOrBefore(prefix, pinnedCount, count, pinnedLength + offset) - pinnedCount;
7427
+ const lastVisible = firstAtOrAfter(prefix, pinnedCount, count, pinnedLength + offset + viewport) - pinnedCount;
7428
+ const { start: relStart, end: relEnd } = band(firstVisible, lastVisible, scrollCount, overscan);
7429
+ const absStart = pinnedCount + relStart;
7430
+ const absEnd = pinnedCount + relEnd;
7431
+ return assemble({
7432
+ pinnedCount,
7433
+ start: absStart,
7434
+ end: absEnd,
7435
+ padStart: at(prefix, absStart) - pinnedLength,
7436
+ body: at(prefix, absEnd) - at(prefix, absStart),
7437
+ total
7438
+ });
7439
+ }
7440
+ function scrollOffsetForIndex(input) {
7441
+ const count = toCount(input?.count);
7442
+ if (count <= 0) return null;
7443
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7444
+ const itemSize = toLength(input?.itemSize);
7445
+ const viewport = toLength(input?.viewport);
7446
+ const scrollCount = count - pinnedCount;
7447
+ if (scrollCount <= 0 || itemSize <= 0) return null;
7448
+ const rawIndex = isNum(input?.index) ? Math.floor(input.index) : 0;
7449
+ const index = Math.min(Math.max(rawIndex, 0), count - 1);
7450
+ if (index < pinnedCount) return null;
7451
+ const total = scrollCount * itemSize;
7452
+ const maxOffset = Math.max(0, total - viewport);
7453
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7454
+ const itemStart = (index - pinnedCount) * itemSize;
7455
+ const itemEnd = itemStart + itemSize;
7456
+ if (itemStart >= offset && itemEnd <= offset + viewport) return null;
7457
+ const next = itemStart < offset ? itemStart : itemEnd - viewport;
7458
+ const clamped = Math.min(Math.max(next, 0), maxOffset);
7459
+ return clamped === offset ? null : clamped;
7460
+ }
7461
+
7462
+ // src/data-table/useSpreadsheetGrid.ts
7463
+ import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo6, useRef as useRef12, useState as useState16 } from "react";
7464
+
7465
+ // src/data-table/grid-selection.ts
7466
+ function rangeBounds(range, rowIds, fieldNames) {
7467
+ const ra = rowIds.indexOf(range.anchor.rowId);
7468
+ const rf = rowIds.indexOf(range.focus.rowId);
7469
+ const ca = fieldNames.indexOf(range.anchor.fieldName);
7470
+ const cf = fieldNames.indexOf(range.focus.fieldName);
7471
+ if (ra === -1 || rf === -1 || ca === -1 || cf === -1) return null;
7472
+ return {
7473
+ r0: Math.min(ra, rf),
7474
+ r1: Math.max(ra, rf),
7475
+ c0: Math.min(ca, cf),
7476
+ c1: Math.max(ca, cf)
7477
+ };
7478
+ }
7479
+ function rangeSize(bounds) {
7480
+ const rows = bounds.r1 - bounds.r0 + 1;
7481
+ const cols = bounds.c1 - bounds.c0 + 1;
7482
+ return { rows, cols, cells: rows * cols };
7483
+ }
7484
+ function boundsContain(bounds, rowIndex, colIndex) {
7485
+ return rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
7486
+ }
7487
+ function cellsInRange(range, rowIds, fieldNames) {
7488
+ const b = rangeBounds(range, rowIds, fieldNames);
7489
+ if (!b) return [];
7490
+ const out = [];
7491
+ for (let r = b.r0; r <= b.r1; r += 1) {
7492
+ const rowId = rowIds[r];
7493
+ if (rowId === void 0) continue;
7494
+ for (let c = b.c0; c <= b.c1; c += 1) {
7495
+ const fieldName = fieldNames[c];
7496
+ if (fieldName === void 0) continue;
7497
+ out.push({ rowId, fieldName });
7498
+ }
7499
+ }
7500
+ return out;
7501
+ }
7502
+ function rangeRows(range, rowIds, fieldNames) {
7503
+ const b = rangeBounds(range, rowIds, fieldNames);
7504
+ if (!b) return [];
7505
+ const rows = [];
7506
+ for (let r = b.r0; r <= b.r1; r += 1) {
7507
+ const rowId = rowIds[r];
7508
+ if (rowId === void 0) continue;
7509
+ const row = [];
7510
+ for (let c = b.c0; c <= b.c1; c += 1) {
7511
+ const fieldName = fieldNames[c];
7512
+ if (fieldName === void 0) continue;
7513
+ row.push({ rowId, fieldName });
7514
+ }
7515
+ rows.push(row);
7516
+ }
7517
+ return rows;
7518
+ }
7519
+ function isSingleCellRange(range) {
7520
+ return sameCell(range.anchor, range.focus);
7521
+ }
7522
+ function rowRange(rowId, fieldNames) {
7523
+ const firstField = fieldNames[0];
7524
+ const lastField = fieldNames[fieldNames.length - 1];
7525
+ if (firstField === void 0 || lastField === void 0) return null;
7526
+ return {
7527
+ anchor: { rowId, fieldName: firstField },
7528
+ focus: { rowId, fieldName: lastField }
7529
+ };
7530
+ }
7531
+ function columnRange(fieldName, rowIds) {
7532
+ const firstRow = rowIds[0];
7533
+ const lastRow = rowIds[rowIds.length - 1];
7534
+ if (firstRow === void 0 || lastRow === void 0) return null;
7535
+ return {
7536
+ anchor: { rowId: firstRow, fieldName },
7537
+ focus: { rowId: lastRow, fieldName }
7538
+ };
7539
+ }
7540
+ function allRange(rowIds, fieldNames) {
7541
+ const firstRow = rowIds[0];
7542
+ const lastRow = rowIds[rowIds.length - 1];
7543
+ const firstField = fieldNames[0];
7544
+ const lastField = fieldNames[fieldNames.length - 1];
7545
+ if (firstRow === void 0 || lastRow === void 0 || firstField === void 0 || lastField === void 0) {
7546
+ return null;
7547
+ }
7548
+ return {
7549
+ anchor: { rowId: firstRow, fieldName: firstField },
7550
+ focus: { rowId: lastRow, fieldName: lastField }
7551
+ };
7552
+ }
7553
+ function sameCell(a, b) {
7554
+ if (!a || !b) return a === b;
7555
+ return a.rowId === b.rowId && a.fieldName === b.fieldName;
7556
+ }
7557
+ function moveSelection(current, move, rowIds, fieldNames) {
7558
+ if (rowIds.length === 0 || fieldNames.length === 0) return null;
7559
+ const firstRowId = rowIds[0];
7560
+ const firstFieldName = fieldNames[0];
7561
+ if (firstRowId === void 0 || firstFieldName === void 0) return null;
7562
+ const first = { rowId: firstRowId, fieldName: firstFieldName };
7563
+ if (!current) return first;
7564
+ let r = rowIds.indexOf(current.rowId);
7565
+ let c = fieldNames.indexOf(current.fieldName);
7566
+ if (r === -1 || c === -1) return first;
7567
+ const lastRow = rowIds.length - 1;
7568
+ const lastCol = fieldNames.length - 1;
7569
+ const clamp = (n2, max) => Math.min(Math.max(n2, 0), max);
7570
+ switch (move) {
7571
+ case "up":
7572
+ r = clamp(r - 1, lastRow);
7573
+ break;
7574
+ case "down":
7575
+ r = clamp(r + 1, lastRow);
7576
+ break;
7577
+ case "left":
7578
+ c = clamp(c - 1, lastCol);
7579
+ break;
7580
+ case "right":
7581
+ c = clamp(c + 1, lastCol);
7582
+ break;
7583
+ case "rowStart":
7584
+ c = 0;
7585
+ break;
7586
+ case "rowEnd":
7587
+ c = lastCol;
7588
+ break;
7589
+ case "gridStart":
7590
+ r = 0;
7591
+ c = 0;
7592
+ break;
7593
+ case "gridEnd":
7594
+ r = lastRow;
7595
+ c = lastCol;
7596
+ break;
7597
+ case "nextCell":
7598
+ if (c < lastCol) c += 1;
7599
+ else if (r < lastRow) {
7600
+ r += 1;
7601
+ c = 0;
7602
+ }
7603
+ break;
7604
+ case "prevCell":
7605
+ if (c > 0) c -= 1;
7606
+ else if (r > 0) {
7607
+ r -= 1;
7608
+ c = lastCol;
7609
+ }
7610
+ break;
7611
+ }
7612
+ const rowId = rowIds[r];
7613
+ const fieldName = fieldNames[c];
7614
+ if (rowId === void 0 || fieldName === void 0) return first;
7615
+ return { rowId, fieldName };
7616
+ }
7617
+ function isTypingKey(e) {
7618
+ if (e.ctrlKey || e.metaKey || e.altKey) return false;
7619
+ return e.key.length === 1 && e.key !== " ";
7620
+ }
7621
+ function classifyGridKey(e) {
7622
+ const mod = e.ctrlKey || e.metaKey;
7623
+ const arrowMove = {
7624
+ ArrowUp: mod ? "gridStart" : "up",
7625
+ ArrowDown: mod ? "gridEnd" : "down",
7626
+ ArrowLeft: mod ? "rowStart" : "left",
7627
+ ArrowRight: mod ? "rowEnd" : "right",
7628
+ Home: mod ? "gridStart" : "rowStart",
7629
+ End: mod ? "gridEnd" : "rowEnd"
7630
+ };
7631
+ const arrow = arrowMove[e.key];
7632
+ if (arrow !== void 0) {
7633
+ return { kind: e.shiftKey ? "extend" : "move", move: arrow };
7634
+ }
7635
+ switch (e.key) {
7636
+ case "Tab":
7637
+ return { kind: "move", move: e.shiftKey ? "prevCell" : "nextCell" };
7638
+ case "Enter":
7639
+ case "F2":
7640
+ return { kind: "edit" };
7641
+ case " ":
7642
+ if (e.shiftKey) return { kind: "selectRow" };
7643
+ if (mod) return { kind: "selectColumn" };
7644
+ return { kind: "edit" };
7645
+ case "a":
7646
+ case "A":
7647
+ if (mod) return { kind: "selectAll" };
7648
+ break;
7649
+ case "d":
7650
+ case "D":
7651
+ if (mod) return { kind: "fillDown" };
7652
+ break;
7653
+ case "Escape":
7654
+ return { kind: "escape" };
7655
+ case "Delete":
7656
+ case "Backspace":
7657
+ return { kind: "clearCell" };
7658
+ case "c":
7659
+ case "C":
7660
+ if (mod) return { kind: "copy" };
7661
+ break;
7662
+ case "x":
7663
+ case "X":
7664
+ if (mod) return { kind: "cut" };
7665
+ break;
7666
+ case "v":
7667
+ case "V":
7668
+ if (mod) return { kind: "paste" };
7669
+ break;
7670
+ }
7671
+ if (isTypingKey(e)) return { kind: "editSeeded", seed: e.key };
7672
+ return null;
7673
+ }
7674
+ function directClickKinds() {
7675
+ return ["checkbox", "rating", "select", "multiselect"];
7676
+ }
7677
+ function isDirectClickEditor(editorKind) {
7678
+ return editorKind !== void 0 && directClickKinds().includes(editorKind);
7679
+ }
7680
+ function cellDomKey(address) {
7681
+ return `${address.rowId}::${address.fieldName}`;
7682
+ }
7683
+
7684
+ // src/data-table/grid-clipboard.ts
7685
+ function cellClipboardText(value) {
7686
+ if (value === null || value === void 0) return "";
7687
+ if (typeof value === "object") return JSON.stringify(value);
7688
+ return String(value);
7689
+ }
7690
+ function tsvCell(text) {
7691
+ return /[\t\r\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
7692
+ }
7693
+ function gridToTsv(block) {
7694
+ return block.map((row) => row.map((cell) => tsvCell(cellClipboardText(cell))).join(" ")).join("\n");
7695
+ }
7696
+ function parseClipboardGrid(text) {
7697
+ const rows = [];
7698
+ let row = [];
7699
+ let cell = "";
7700
+ let i = 0;
7701
+ const n2 = text.length;
7702
+ while (i < n2) {
7703
+ const ch = text[i];
7704
+ if (cell === "" && ch === '"') {
7705
+ let j = i + 1;
7706
+ let out = "";
7707
+ let closed = false;
7708
+ while (j < n2) {
7709
+ const c = text[j];
7710
+ if (c === '"') {
7711
+ if (text[j + 1] === '"') {
7712
+ out += '"';
7713
+ j += 2;
7714
+ continue;
7715
+ }
7716
+ closed = true;
7717
+ j += 1;
7718
+ break;
7719
+ }
7720
+ out += c;
7721
+ j += 1;
7722
+ }
7723
+ if (closed && (j >= n2 || text[j] === " " || text[j] === "\n" || text[j] === "\r")) {
7724
+ cell = out;
7725
+ i = j;
7726
+ if (i >= n2) break;
7727
+ const d = text[i];
7728
+ if (d === " ") {
7729
+ row.push(cell);
7730
+ cell = "";
7731
+ i += 1;
7732
+ continue;
7733
+ }
7734
+ row.push(cell);
7735
+ rows.push(row);
7736
+ row = [];
7737
+ cell = "";
7738
+ i += d === "\r" && text[i + 1] === "\n" ? 2 : 1;
7739
+ continue;
7740
+ }
7741
+ cell += ch;
7742
+ i += 1;
7743
+ continue;
7744
+ }
7745
+ if (ch === " ") {
7746
+ row.push(cell);
7747
+ cell = "";
7748
+ i += 1;
7749
+ continue;
7750
+ }
7751
+ if (ch === "\n" || ch === "\r") {
7752
+ row.push(cell);
7753
+ rows.push(row);
7754
+ row = [];
7755
+ cell = "";
7756
+ i += ch === "\r" && text[i + 1] === "\n" ? 2 : 1;
7757
+ continue;
7758
+ }
7759
+ cell += ch;
7760
+ i += 1;
7761
+ }
7762
+ const endedOnBreak = n2 > 0 && (text[n2 - 1] === "\n" || text[n2 - 1] === "\r");
7763
+ if (!endedOnBreak) {
7764
+ row.push(cell);
7765
+ rows.push(row);
7766
+ } else if (row.length > 0) {
7767
+ row.push(cell);
7768
+ rows.push(row);
7769
+ }
7770
+ return rows.length === 0 ? [[""]] : rows;
7771
+ }
7772
+ function planPaste(anchor, block, rowIds, fieldNames) {
7773
+ const r0 = rowIds.indexOf(anchor.rowId);
7774
+ const c0 = fieldNames.indexOf(anchor.fieldName);
7775
+ if (r0 === -1 || c0 === -1) return null;
7776
+ const width = block.reduce((w, row) => Math.max(w, row.length), 0);
7777
+ const colsAvailable = fieldNames.length - c0;
7778
+ const colsUsed = Math.min(width, colsAvailable);
7779
+ const targetFields = fieldNames.slice(c0, c0 + colsUsed);
7780
+ const cells = [];
7781
+ const overflowRows = [];
7782
+ block.forEach((blockRow, dr) => {
7783
+ const rowId = rowIds[r0 + dr];
7784
+ if (rowId === void 0) {
7785
+ overflowRows.push(targetFields.map((_, dc) => blockRow[dc] ?? ""));
7786
+ return;
7787
+ }
7788
+ targetFields.forEach((fieldName, dc) => {
7789
+ if (dc >= blockRow.length) return;
7790
+ const raw = blockRow[dc];
7791
+ if (raw === void 0) return;
7792
+ cells.push({ rowId, fieldName, raw });
7793
+ });
7794
+ });
7795
+ return {
7796
+ cells,
7797
+ overflowRows,
7798
+ clippedColumns: Math.max(0, width - colsAvailable),
7799
+ fieldNames: targetFields
7800
+ };
7801
+ }
7802
+ function isSingleCell(block) {
7803
+ return block.length === 1 && block[0]?.length === 1;
7804
+ }
7805
+ function storedValuesEqual(a, b) {
7806
+ if (a === b) return true;
7807
+ if (a === null || b === null || a === void 0 || b === void 0) {
7808
+ return (a ?? null) === (b ?? null);
7809
+ }
7810
+ if (typeof a === "object" || typeof b === "object") {
7811
+ try {
7812
+ return JSON.stringify(a) === JSON.stringify(b);
7813
+ } catch {
7814
+ return false;
7815
+ }
7816
+ }
7817
+ return false;
7818
+ }
7819
+
7820
+ // src/data-table/fill.ts
7821
+ var NO_SERIES = { kind: "none", step: 0 };
7822
+ var MS_PER_DAY = 864e5;
7823
+ function plural(count, one, many) {
7824
+ return `${count} ${count === 1 ? one : many}`;
7825
+ }
7826
+ function isValidRect(rect) {
7827
+ 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;
7828
+ }
7829
+ function asBounds(rect) {
7830
+ return { r0: rect.top, r1: rect.bottom, c0: rect.left, c1: rect.right };
7831
+ }
7832
+ function directionOf(source, target) {
7833
+ if (!isValidRect(source) || !isValidRect(target)) return null;
7834
+ if (target.top === source.bottom + 1) return "down";
7835
+ if (target.bottom === source.top - 1) return "up";
7836
+ if (target.left === source.right + 1) return "right";
7837
+ if (target.right === source.left - 1) return "left";
7838
+ return null;
7839
+ }
7840
+ function tileIndex(i, a, b) {
7841
+ const n2 = b - a + 1;
7842
+ if (n2 <= 0) return a;
7843
+ if (i >= a && i <= b) return i;
7844
+ if (i > b) return a + (i - a) % n2;
7845
+ return b - (a - 1 - i) % n2;
7846
+ }
7847
+ function isFiniteNumber(v) {
7848
+ return typeof v === "number" && Number.isFinite(v);
7849
+ }
7850
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
7851
+ var DATE_ISH = /^\d{4}-\d{2}-\d{2}([T ]|$)/;
7852
+ function dateMs(v) {
7853
+ if (v instanceof Date) {
7854
+ const t = v.getTime();
7855
+ return Number.isFinite(t) ? t : null;
7856
+ }
7857
+ if (typeof v === "string" && DATE_ISH.test(v)) {
7858
+ const t = Date.parse(v);
7859
+ return Number.isFinite(t) ? t : null;
7860
+ }
7861
+ return null;
7862
+ }
7863
+ function detectSeries(values) {
7864
+ if (!Array.isArray(values) || values.length < 2) return NO_SERIES;
7865
+ if (values.every(isFiniteNumber)) {
7866
+ const nums = values;
7867
+ const first = nums[0];
7868
+ const second = nums[1];
7869
+ if (first === void 0 || second === void 0) return NO_SERIES;
7870
+ const step = second - first;
7871
+ for (let i = 1; i < nums.length; i += 1) {
7872
+ const prev = nums[i - 1];
7873
+ const cur = nums[i];
7874
+ if (prev === void 0 || cur === void 0) return NO_SERIES;
7875
+ if (cur - prev !== step) return NO_SERIES;
7876
+ }
7877
+ return { kind: "number", step };
7878
+ }
7879
+ const stamps = values.map(dateMs);
7880
+ if (stamps.every((t) => t !== null)) {
7881
+ const first = stamps[0];
7882
+ const second = stamps[1];
7883
+ if (first === void 0 || second === void 0) return NO_SERIES;
7884
+ const deltaMs = second - first;
7885
+ if (deltaMs % MS_PER_DAY !== 0) return NO_SERIES;
7886
+ for (let i = 1; i < stamps.length; i += 1) {
7887
+ const prev = stamps[i - 1];
7888
+ const cur = stamps[i];
7889
+ if (prev === void 0 || cur === void 0) return NO_SERIES;
7890
+ if (cur - prev !== deltaMs) return NO_SERIES;
7891
+ }
7892
+ return { kind: "date", step: deltaMs / MS_PER_DAY };
7893
+ }
7894
+ return NO_SERIES;
7895
+ }
7896
+ function formatDateLike(ms, sample) {
7897
+ const iso = new Date(ms).toISOString();
7898
+ if (typeof sample === "string" && DATE_ONLY.test(sample)) return iso.slice(0, 10);
7899
+ return iso;
7900
+ }
7901
+ function extrapolate(line, series, k) {
7902
+ const last = line[line.length - 1];
7903
+ if (series.kind === "number" && isFiniteNumber(last)) {
7904
+ return last + k * series.step;
7905
+ }
7906
+ if (series.kind === "date") {
7907
+ const base = dateMs(last);
7908
+ if (base === null) return jsonable(last);
7909
+ return formatDateLike(base + k * series.step * MS_PER_DAY, last);
7910
+ }
7911
+ return jsonable(last);
7912
+ }
7913
+ function jsonable(value) {
7914
+ if (value instanceof Date) {
7915
+ const t = value.getTime();
7916
+ return Number.isFinite(t) ? value.toISOString() : null;
7917
+ }
7918
+ if (value === void 0) return null;
7919
+ return value;
7920
+ }
7921
+ function emptyResult(summary) {
7922
+ return { patches: [], skipped: [], summary };
7923
+ }
7924
+ function fillRange(request) {
7925
+ const { source, target, columnIds, rowIds } = request;
7926
+ const mode = request.mode ?? "series";
7927
+ const isWritable = request.isColumnWritable ?? (() => true);
7928
+ const direction = directionOf(source, target);
7929
+ if (!direction) {
7930
+ return emptyResult(
7931
+ "Filled 0 cells because the fill area does not touch the selection."
7932
+ );
7933
+ }
7934
+ if (rowIds.length === 0 || columnIds.length === 0) {
7935
+ return emptyResult("Filled 0 cells because the table is empty.");
7936
+ }
7937
+ const read = (r, c) => {
7938
+ try {
7939
+ return { ok: true, value: request.valueAt(r, c) };
7940
+ } catch {
7941
+ return { ok: false, value: void 0 };
7942
+ }
7943
+ };
7944
+ const vertical = direction === "down" || direction === "up";
7945
+ const forward = direction === "down" || direction === "right";
7946
+ const fillStart = vertical ? source.top : source.left;
7947
+ const fillEnd = vertical ? source.bottom : source.right;
7948
+ const crossStart = vertical ? source.left : source.top;
7949
+ const crossEnd = vertical ? source.right : source.bottom;
7950
+ const maxFill = (vertical ? rowIds.length : columnIds.length) - 1;
7951
+ const maxCross = (vertical ? columnIds.length : rowIds.length) - 1;
7952
+ const rawFillFrom = vertical ? target.top : target.left;
7953
+ const rawFillTo = vertical ? target.bottom : target.right;
7954
+ const rawCrossFrom = vertical ? target.left : target.top;
7955
+ const rawCrossTo = vertical ? target.right : target.bottom;
7956
+ const fillFrom = Math.max(rawFillFrom, 0);
7957
+ const fillTo = Math.min(rawFillTo, maxFill);
7958
+ const crossFrom = Math.max(rawCrossFrom, 0);
7959
+ const crossTo = Math.min(rawCrossTo, maxCross);
7960
+ const requested = Math.max(rawFillTo - rawFillFrom + 1, 0) * Math.max(rawCrossTo - rawCrossFrom + 1, 0);
7961
+ const inside = Math.max(fillTo - fillFrom + 1, 0) * Math.max(crossTo - crossFrom + 1, 0);
7962
+ const outside = Math.max(requested - inside, 0);
7963
+ const sourceBounds = asBounds(source);
7964
+ const patches = [];
7965
+ const skipped = [];
7966
+ const touchedColumns = /* @__PURE__ */ new Set();
7967
+ const lines = /* @__PURE__ */ new Map();
7968
+ const lineFor = (crossIndex) => {
7969
+ const srcCross = tileIndex(crossIndex, crossStart, crossEnd);
7970
+ const cached = lines.get(srcCross);
7971
+ if (cached) return cached;
7972
+ const values = [];
7973
+ const reads = [];
7974
+ for (let f = fillStart; f <= fillEnd; f += 1) {
7975
+ const r = vertical ? f : srcCross;
7976
+ const c = vertical ? srcCross : f;
7977
+ const got = read(r, c);
7978
+ values.push(got.value);
7979
+ reads.push(got.ok);
7980
+ }
7981
+ const series = mode === "series" ? detectSeries(values) : NO_SERIES;
7982
+ const line = { values, reads, series };
7983
+ lines.set(srcCross, line);
7984
+ return line;
7985
+ };
7986
+ for (let f = fillFrom; f <= fillTo; f += 1) {
7987
+ for (let x = crossFrom; x <= crossTo; x += 1) {
7988
+ const rowIndex = vertical ? f : x;
7989
+ const colIndex = vertical ? x : f;
7990
+ const rowId = rowIds[rowIndex];
7991
+ const columnId2 = columnIds[colIndex];
7992
+ if (rowId === void 0 || columnId2 === void 0) continue;
7993
+ if (boundsContain(sourceBounds, rowIndex, colIndex)) continue;
7994
+ if (!isWritable(columnId2)) {
7995
+ skipped.push({
7996
+ rowId,
7997
+ columnId: columnId2,
7998
+ reason: `${columnId2} cannot be filled because that column is read-only.`
7999
+ });
8000
+ continue;
8001
+ }
8002
+ const line = lineFor(x);
8003
+ if (line.series.kind !== "none") {
8004
+ const k = forward ? f - fillEnd : fillStart - f;
8005
+ patches.push({ rowId, columnId: columnId2, value: jsonable(extrapolate(line.values, line.series, k)) });
8006
+ touchedColumns.add(columnId2);
8007
+ continue;
8008
+ }
8009
+ const srcFill = tileIndex(f, fillStart, fillEnd);
8010
+ const patternIndex = srcFill - fillStart;
8011
+ if (line.reads[patternIndex] !== true) {
8012
+ skipped.push({
8013
+ rowId,
8014
+ columnId: columnId2,
8015
+ reason: `The value to copy into ${columnId2} could not be read, so that cell was left alone.`
8016
+ });
8017
+ continue;
8018
+ }
8019
+ patches.push({ rowId, columnId: columnId2, value: jsonable(line.values[patternIndex]) });
8020
+ touchedColumns.add(columnId2);
8021
+ }
8022
+ }
8023
+ return { patches, skipped, summary: summarize(direction, patches, skipped, touchedColumns, outside) };
8024
+ }
8025
+ function summarize(direction, patches, skipped, touchedColumns, outside) {
8026
+ const parts = [];
8027
+ if (patches.length === 0) {
8028
+ parts.push("Filled 0 cells because there was nothing to fill.");
8029
+ } else if (direction === "down" || direction === "up") {
8030
+ const word = direction === "down" ? "down" : "up";
8031
+ parts.push(
8032
+ `Filled ${plural(patches.length, "cell", "cells")} ${word} ${plural(touchedColumns.size, "column", "columns")}.`
8033
+ );
8034
+ } else {
8035
+ const word = direction === "right" ? "right" : "left";
8036
+ parts.push(
8037
+ `Filled ${plural(patches.length, "cell", "cells")} ${word} across ${plural(touchedColumns.size, "column", "columns")}.`
8038
+ );
8039
+ }
8040
+ if (skipped.length > 0) {
8041
+ parts.push(`Skipped ${plural(skipped.length, "cell", "cells")} that could not be filled.`);
8042
+ }
8043
+ if (outside > 0) {
8044
+ parts.push(`Ignored ${plural(outside, "cell", "cells")} outside the table.`);
8045
+ }
8046
+ return parts.join(" ");
8047
+ }
8048
+ function fillDown(request) {
8049
+ const { range, ...rest } = request;
8050
+ if (!isValidRect(range)) {
8051
+ return emptyResult("Filled 0 cells because the selection is not a rectangle.");
8052
+ }
8053
+ if (range.top === range.bottom) {
8054
+ return emptyResult("Filled 0 cells because the selection is a single row.");
8055
+ }
8056
+ return fillRange({
8057
+ ...rest,
8058
+ source: { top: range.top, left: range.left, bottom: range.top, right: range.right },
8059
+ target: { top: range.top + 1, left: range.left, bottom: range.bottom, right: range.right }
8060
+ });
8061
+ }
8062
+ function fillTargetFor(source, toRow, toColumn) {
8063
+ if (!isValidRect(source)) return null;
8064
+ if (!Number.isInteger(toRow) || !Number.isInteger(toColumn)) return null;
8065
+ const below = toRow - source.bottom;
8066
+ const above = source.top - toRow;
8067
+ const rightOf = toColumn - source.right;
8068
+ const leftOf = source.left - toColumn;
8069
+ const vertical = Math.max(below, above, 0);
8070
+ const horizontal = Math.max(rightOf, leftOf, 0);
8071
+ if (vertical === 0 && horizontal === 0) return null;
8072
+ if (vertical >= horizontal) {
8073
+ if (below > 0) {
8074
+ return { top: source.bottom + 1, bottom: toRow, left: source.left, right: source.right };
8075
+ }
8076
+ return { top: toRow, bottom: source.top - 1, left: source.left, right: source.right };
8077
+ }
8078
+ if (rightOf > 0) {
8079
+ return { top: source.top, bottom: source.bottom, left: source.right + 1, right: toColumn };
8080
+ }
8081
+ return { top: source.top, bottom: source.bottom, left: toColumn, right: source.left - 1 };
8082
+ }
8083
+
8084
+ // src/data-table/cell-undo.ts
8085
+ var DEFAULT_UNDO_DEPTH = 100;
8086
+ function createUndoStack(depth = DEFAULT_UNDO_DEPTH) {
8087
+ const bounded = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 0;
8088
+ return { past: [], future: [], depth: bounded };
8089
+ }
8090
+ function pushUndo(stack, entry) {
8091
+ if (stack.depth <= 0) {
8092
+ return { past: [], future: [], depth: stack.depth };
8093
+ }
8094
+ const past = [...stack.past, entry];
8095
+ while (past.length > stack.depth) past.shift();
8096
+ return { past, future: [], depth: stack.depth };
8097
+ }
8098
+ function popUndo(stack) {
8099
+ const entry = stack.past[stack.past.length - 1];
8100
+ if (entry === void 0) return { stack, entry: null };
8101
+ return {
8102
+ stack: {
8103
+ past: stack.past.slice(0, -1),
8104
+ future: [...stack.future, entry],
8105
+ depth: stack.depth
8106
+ },
8107
+ entry
8108
+ };
8109
+ }
8110
+ function popRedo(stack) {
8111
+ const entry = stack.future[stack.future.length - 1];
8112
+ if (entry === void 0) return { stack, entry: null };
8113
+ const past = [...stack.past, entry];
8114
+ while (past.length > stack.depth) past.shift();
8115
+ return {
8116
+ stack: { past, future: stack.future.slice(0, -1), depth: stack.depth },
8117
+ entry
8118
+ };
8119
+ }
8120
+ function keyOf(patch) {
8121
+ return JSON.stringify([patch.rowId, patch.columnId]);
8122
+ }
8123
+ function undoEntryFor(before, after, label) {
8124
+ const priors = /* @__PURE__ */ new Map();
8125
+ for (const patch of before) priors.set(keyOf(patch), patch);
8126
+ const undo = after.map((patch) => {
8127
+ const prior = priors.get(keyOf(patch));
8128
+ return {
8129
+ rowId: patch.rowId,
8130
+ columnId: patch.columnId,
8131
+ value: prior ? prior.value : null
8132
+ };
8133
+ });
8134
+ return {
8135
+ label,
8136
+ undo,
8137
+ redo: after.map((patch) => ({
8138
+ rowId: patch.rowId,
8139
+ columnId: patch.columnId,
8140
+ value: patch.value
8141
+ }))
8142
+ };
8143
+ }
8144
+ function canUndo(stack) {
8145
+ return stack.depth > 0 && stack.past.length > 0;
8146
+ }
8147
+ function canRedo(stack) {
8148
+ return stack.depth > 0 && stack.future.length > 0;
8149
+ }
8150
+
8151
+ // src/data-table/useSpreadsheetGrid.ts
8152
+ function targetsAnEditor(target) {
8153
+ const element = target instanceof Element ? target : null;
8154
+ if (!element) return false;
8155
+ return element.closest("input, textarea, select, [contenteditable='true']") !== null;
8156
+ }
8157
+ function useSpreadsheetGrid(options) {
8158
+ const [range, setRange] = useState16(null);
8159
+ const [fillTarget, setFillTarget] = useState16(null);
8160
+ const [stack, setStack] = useState16(() => createUndoStack(options?.undoDepth ?? 100));
8161
+ const draggingRef = useRef12(null);
8162
+ const optionsRef = useRef12(options);
8163
+ optionsRef.current = options;
8164
+ const rowIds = options?.rowIds ?? [];
8165
+ const columnIds = useMemo6(
8166
+ () => options ? options.columnIds.filter((id) => id !== "") : [],
8167
+ [options]
8168
+ );
8169
+ useEffect11(() => {
8170
+ if (!range) return;
8171
+ const rowSet = new Set(rowIds);
8172
+ const colSet = new Set(columnIds);
8173
+ const alive = rowSet.has(range.anchor.rowId) && rowSet.has(range.focus.rowId) && colSet.has(range.anchor.fieldName) && colSet.has(range.focus.fieldName);
8174
+ if (!alive) setRange(null);
8175
+ }, [range, rowIds, columnIds]);
8176
+ const bounds = useMemo6(
8177
+ () => range ? rangeBounds(range, rowIds, columnIds) : null,
8178
+ [range, rowIds, columnIds]
8179
+ );
8180
+ const rowIndexById = useMemo6(() => {
8181
+ const map = /* @__PURE__ */ new Map();
8182
+ for (let i = 0; i < rowIds.length; i += 1) map.set(rowIds[i], i);
8183
+ return map;
8184
+ }, [rowIds]);
8185
+ const columnIndexById = useMemo6(() => {
8186
+ const map = /* @__PURE__ */ new Map();
8187
+ for (let i = 0; i < columnIds.length; i += 1) map.set(columnIds[i], i);
8188
+ return map;
8189
+ }, [columnIds]);
8190
+ const screenPatches = useCallback7(
8191
+ (patches) => {
8192
+ const validate = optionsRef.current?.validateCell;
8193
+ if (!validate) return { accepted: patches, refusals: [] };
8194
+ const accepted = [];
8195
+ const refusals = [];
8196
+ for (const patch of patches) {
8197
+ let reason;
8198
+ try {
8199
+ reason = validate({ rowId: patch.rowId, columnId: patch.columnId, value: patch.value });
8200
+ } catch {
8201
+ reason = void 0;
8202
+ }
8203
+ if (reason) refusals.push({ columnId: patch.columnId, reason });
8204
+ else accepted.push(patch);
8205
+ }
8206
+ return { accepted, refusals };
8207
+ },
8208
+ []
8209
+ );
8210
+ const applyPatches = useCallback7(
8211
+ async (patches, intent, label) => {
8212
+ const current = optionsRef.current;
8213
+ if (!current || patches.length === 0) return;
8214
+ const depth = current.undoDepth ?? 100;
8215
+ if (depth > 0 && intent !== "undo" && intent !== "redo") {
8216
+ const before = patches.map((patch) => ({
8217
+ rowId: patch.rowId,
8218
+ columnId: patch.columnId,
8219
+ value: current.valueAt(patch.rowId, patch.columnId) ?? null
8220
+ }));
8221
+ setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));
8222
+ }
8223
+ await current.onPatch(patches, intent);
8224
+ },
8225
+ []
8226
+ );
8227
+ const readValueAt = useCallback7((rowIndex, columnIndex) => {
8228
+ const current = optionsRef.current;
8229
+ const rowId = current?.rowIds[rowIndex];
8230
+ const columnId2 = current?.columnIds[columnIndex];
8231
+ if (!current || rowId === void 0 || columnId2 === void 0) return null;
8232
+ return current.valueAt(rowId, columnId2);
8233
+ }, []);
8234
+ const fillRequestBase = useCallback7(() => {
8235
+ const current = optionsRef.current;
8236
+ if (!current) return null;
8237
+ return {
8238
+ valueAt: readValueAt,
8239
+ columnIds: [...current.columnIds],
8240
+ rowIds: [...current.rowIds],
8241
+ isColumnWritable: current.isColumnWritable
8242
+ };
8243
+ }, [readValueAt]);
8244
+ const announceSkips = useCallback7(
8245
+ (skipped, summary) => {
8246
+ const current = optionsRef.current;
8247
+ if (!current?.notify) return;
8248
+ if (skipped.length === 0) {
8249
+ current.notify(summary);
8250
+ return;
8251
+ }
8252
+ const first = skipped[0];
8253
+ const reason = current.columnRefusal?.(first.columnId) ?? first.reason;
8254
+ const rest = skipped.length > 1 ? ` ${skipped.length - 1} more were left alone.` : "";
8255
+ current.notify(`${summary} ${reason}${rest}`);
8256
+ },
8257
+ []
8258
+ );
8259
+ const doFillDown = useCallback7(async () => {
8260
+ const base = fillRequestBase();
8261
+ if (!base || !bounds) return;
8262
+ const result = fillDown({
8263
+ ...base,
8264
+ range: { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 }
8265
+ });
8266
+ const screened = screenPatches(result.patches);
8267
+ await applyPatches(screened.accepted, "fill-down", "Filled down");
8268
+ announceSkips([...result.skipped, ...screened.refusals], result.summary);
8269
+ }, [fillRequestBase, bounds, applyPatches, announceSkips, screenPatches]);
8270
+ const doClear = useCallback7(async () => {
8271
+ const current = optionsRef.current;
8272
+ if (!current || !bounds) return;
8273
+ const patches = [];
8274
+ const skipped = [];
8275
+ for (let r = bounds.r0; r <= bounds.r1; r += 1) {
8276
+ for (let c = bounds.c0; c <= bounds.c1; c += 1) {
8277
+ const rowId = current.rowIds[r];
8278
+ const columnId2 = current.columnIds[c];
8279
+ if (rowId === void 0 || columnId2 === void 0) continue;
8280
+ if (!current.isColumnWritable(columnId2)) {
8281
+ skipped.push({
8282
+ columnId: columnId2,
8283
+ reason: current.columnRefusal?.(columnId2) ?? `${columnId2} cannot be cleared.`
8284
+ });
8285
+ continue;
8286
+ }
8287
+ patches.push({ rowId, columnId: columnId2, value: null });
8288
+ }
8289
+ }
8290
+ const screened = screenPatches(patches);
8291
+ await applyPatches(screened.accepted, "clear", "Cleared");
8292
+ announceSkips(
8293
+ [...skipped, ...screened.refusals],
8294
+ `Cleared ${screened.accepted.length} cells.`
8295
+ );
8296
+ }, [bounds, applyPatches, announceSkips, screenPatches]);
8297
+ const doUndo = useCallback7(async () => {
8298
+ const taken = popUndo(stack);
8299
+ if (!taken.entry) return;
8300
+ setStack(taken.stack);
8301
+ await applyPatches(taken.entry.undo, "undo", taken.entry.label);
8302
+ optionsRef.current?.notify?.(taken.entry.label);
8303
+ }, [stack, applyPatches]);
8304
+ const doRedo = useCallback7(async () => {
8305
+ const taken = popRedo(stack);
8306
+ if (!taken.entry) return;
8307
+ setStack(taken.stack);
8308
+ await applyPatches(taken.entry.redo, "redo", taken.entry.label);
8309
+ }, [stack, applyPatches]);
8310
+ const move = useCallback7(
8311
+ (direction, extend) => {
8312
+ setRange((previous) => {
8313
+ const from = previous ? previous.focus : null;
8314
+ const next = moveSelection(from, direction, rowIds, columnIds);
8315
+ if (!next) return previous;
8316
+ if (extend && previous) return { anchor: previous.anchor, focus: next };
8317
+ return { anchor: next, focus: next };
8318
+ });
8319
+ },
8320
+ [rowIds, columnIds]
8321
+ );
8322
+ const onKeyDown = useCallback7(
8323
+ (event) => {
8324
+ const current = optionsRef.current;
8325
+ if (!current) return;
8326
+ if (targetsAnEditor(event.target)) return;
8327
+ const accel = event.metaKey || event.ctrlKey;
8328
+ if (accel && (event.key === "z" || event.key === "Z")) {
8329
+ event.preventDefault();
8330
+ if (event.shiftKey) void doRedo();
8331
+ else void doUndo();
8332
+ return;
8333
+ }
8334
+ if (accel && (event.key === "y" || event.key === "Y")) {
8335
+ event.preventDefault();
8336
+ void doRedo();
8337
+ return;
8338
+ }
8339
+ const action = classifyGridKey(event);
8340
+ if (!action) return;
8341
+ switch (action.kind) {
8342
+ case "move":
8343
+ event.preventDefault();
8344
+ move(action.move, false);
8345
+ return;
8346
+ case "extend":
8347
+ event.preventDefault();
8348
+ move(action.move, true);
8349
+ return;
8350
+ case "selectAll": {
8351
+ event.preventDefault();
8352
+ const all = allRange(rowIds, columnIds);
8353
+ if (all) setRange(all);
8354
+ return;
8355
+ }
8356
+ case "selectRow": {
8357
+ event.preventDefault();
8358
+ const focused = range?.focus;
8359
+ const next = focused ? rowRange(focused.rowId, columnIds) : null;
8360
+ if (next) setRange(next);
8361
+ return;
8362
+ }
8363
+ case "selectColumn": {
8364
+ event.preventDefault();
8365
+ const focused = range?.focus;
8366
+ const next = focused ? columnRange(focused.fieldName, rowIds) : null;
8367
+ if (next) setRange(next);
8368
+ return;
8369
+ }
8370
+ case "clearCell":
8371
+ event.preventDefault();
8372
+ void doClear();
8373
+ return;
8374
+ case "fillDown":
8375
+ event.preventDefault();
8376
+ void doFillDown();
8377
+ return;
8378
+ case "escape":
8379
+ setRange(null);
8380
+ return;
8381
+ case "edit":
8382
+ case "editSeeded": {
8383
+ const focused = range?.focus;
8384
+ if (!focused || !current.onEditRequest) return;
8385
+ const seed = action.kind === "editSeeded" ? action.seed : null;
8386
+ if (current.onEditRequest(focused, seed)) event.preventDefault();
8387
+ return;
8388
+ }
8389
+ default:
8390
+ return;
8391
+ }
8392
+ },
8393
+ [move, range, rowIds, columnIds, doClear, doFillDown, doUndo, doRedo]
8394
+ );
8395
+ const selectionAsTsv = useCallback7(() => {
8396
+ const current = optionsRef.current;
8397
+ if (!current || !range) return "";
8398
+ const grid = rangeRows(range, rowIds, columnIds).map(
8399
+ (row) => row.map((address) => current.valueAt(address.rowId, address.fieldName))
8400
+ );
8401
+ return gridToTsv(grid);
8402
+ }, [range, rowIds, columnIds]);
8403
+ const selectionAddresses = useCallback7(() => {
8404
+ if (!range || !bounds) return { rowIds: [], columnIds: [] };
8405
+ return {
8406
+ rowIds: rowIds.slice(bounds.r0, bounds.r1 + 1),
8407
+ columnIds: columnIds.slice(bounds.c0, bounds.c1 + 1)
8408
+ };
8409
+ }, [range, bounds, rowIds, columnIds]);
8410
+ const onCopy = useCallback7(
8411
+ (event) => {
8412
+ if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;
8413
+ const text = selectionAsTsv();
8414
+ if (!text) return;
8415
+ event.preventDefault();
8416
+ event.clipboardData?.setData("text/plain", text);
8417
+ },
8418
+ [range, selectionAsTsv]
8419
+ );
8420
+ const onCut = useCallback7(
8421
+ (event) => {
8422
+ if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;
8423
+ const text = selectionAsTsv();
8424
+ if (!text) return;
8425
+ event.preventDefault();
8426
+ event.clipboardData?.setData("text/plain", text);
8427
+ void doClear();
8428
+ },
8429
+ [range, selectionAsTsv, doClear]
8430
+ );
8431
+ const onPaste = useCallback7(
8432
+ (event) => {
8433
+ const current = optionsRef.current;
8434
+ if (!current || current.paste === false || !range || targetsAnEditor(event.target)) return;
8435
+ const text = event.clipboardData?.getData("text/plain") ?? "";
8436
+ if (!text) return;
8437
+ event.preventDefault();
8438
+ const block = parseClipboardGrid(text);
8439
+ const start = range.focus;
8440
+ const startRow = rowIds.indexOf(start.rowId);
8441
+ const startCol = columnIds.indexOf(start.fieldName);
8442
+ if (startRow === -1 || startCol === -1) return;
8443
+ const patches = [];
8444
+ const skipped = [];
8445
+ let outside = 0;
8446
+ for (let r = 0; r < block.length; r += 1) {
8447
+ const line = block[r] ?? [];
8448
+ for (let c = 0; c < line.length; c += 1) {
8449
+ const rowId = rowIds[startRow + r];
8450
+ const columnId2 = columnIds[startCol + c];
8451
+ if (rowId === void 0 || columnId2 === void 0) {
8452
+ outside += 1;
8453
+ continue;
8454
+ }
8455
+ if (!current.isColumnWritable(columnId2)) {
8456
+ skipped.push({
8457
+ columnId: columnId2,
8458
+ reason: current.columnRefusal?.(columnId2) ?? `${columnId2} cannot take a pasted value.`
8459
+ });
8460
+ continue;
8461
+ }
8462
+ patches.push({ rowId, columnId: columnId2, value: line[c] ?? null });
8463
+ }
8464
+ }
8465
+ const screened = screenPatches(patches);
8466
+ void applyPatches(screened.accepted, "paste", "Pasted");
8467
+ const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : "";
8468
+ announceSkips([...skipped, ...screened.refusals], `Pasted ${screened.accepted.length} cells.${tail}`);
8469
+ },
8470
+ [range, rowIds, columnIds, applyPatches, announceSkips, screenPatches]
8471
+ );
8472
+ const cellProps = useCallback7(
8473
+ (rowId, columnId2) => {
8474
+ const address = { rowId, fieldName: columnId2 };
8475
+ const rowIndex = rowIndexById.get(rowId) ?? -1;
8476
+ const colIndex = columnIndexById.get(columnId2) ?? -1;
8477
+ const selected = bounds !== null && rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
8478
+ const active = range !== null && sameCell(range.focus, address);
8479
+ const inFillPreview = fillTarget !== null && rowIndex >= fillTarget.top && rowIndex <= fillTarget.bottom && colIndex >= fillTarget.left && colIndex <= fillTarget.right;
8480
+ return {
8481
+ "data-matrx-cell-row": rowId,
8482
+ "data-matrx-cell-col": columnId2,
8483
+ ...selected ? { "data-matrx-cell-selected": "true" } : {},
8484
+ ...active ? { "data-matrx-cell-active": "true" } : {},
8485
+ ...inFillPreview ? { "data-matrx-cell-fill-preview": "true" } : {},
8486
+ onMouseDown: (event) => {
8487
+ if (event.button !== 0) return;
8488
+ event.preventDefault();
8489
+ const origin = event.currentTarget;
8490
+ if (typeof HTMLElement !== "undefined" && origin instanceof HTMLElement) {
8491
+ origin.closest('[data-matrx-table-spreadsheet="true"]')?.focus({ preventScroll: true });
8492
+ }
8493
+ setRange(
8494
+ (previous) => event.shiftKey && previous ? { anchor: previous.anchor, focus: address } : { anchor: address, focus: address }
8495
+ );
8496
+ draggingRef.current = "select";
8497
+ },
8498
+ onMouseEnter: () => {
8499
+ if (draggingRef.current === "select") {
8500
+ setRange((previous) => previous ? { anchor: previous.anchor, focus: address } : previous);
8501
+ return;
8502
+ }
8503
+ if (draggingRef.current === "fill" && bounds) {
8504
+ setFillTarget(
8505
+ fillTargetFor(
8506
+ { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },
8507
+ rowIndex,
8508
+ colIndex
8509
+ )
8510
+ );
8511
+ }
8512
+ }
8513
+ };
8514
+ },
8515
+ [rowIndexById, columnIndexById, bounds, range, fillTarget]
8516
+ );
8517
+ const isFillHandleCell = useCallback7(
8518
+ (rowId, columnId2) => {
8519
+ const current = optionsRef.current;
8520
+ if (!current || current.fillHandle === false || !bounds) return false;
8521
+ return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId2;
8522
+ },
8523
+ [bounds, rowIds, columnIds]
8524
+ );
8525
+ const startFillDrag = useCallback7(
8526
+ (event) => {
8527
+ event.preventDefault();
8528
+ event.stopPropagation();
8529
+ draggingRef.current = "fill";
8530
+ },
8531
+ []
8532
+ );
8533
+ const fillTargetRef = useRef12(null);
8534
+ fillTargetRef.current = fillTarget;
8535
+ const boundsRef = useRef12(null);
8536
+ boundsRef.current = bounds;
8537
+ useEffect11(() => {
8538
+ if (!options) return;
8539
+ const commit = () => {
8540
+ const wasDragging = draggingRef.current;
8541
+ draggingRef.current = null;
8542
+ if (wasDragging !== "fill") return;
8543
+ const target = fillTargetRef.current;
8544
+ const source = boundsRef.current;
8545
+ setFillTarget(null);
8546
+ if (!target || !source) return;
8547
+ const base = fillRequestBase();
8548
+ if (!base) return;
8549
+ const result = fillRange({
8550
+ ...base,
8551
+ source: { top: source.r0, left: source.c0, bottom: source.r1, right: source.c1 },
8552
+ target
8553
+ });
8554
+ const screened = screenPatches(result.patches);
8555
+ void applyPatches(screened.accepted, "fill", "Filled");
8556
+ announceSkips([...result.skipped, ...screened.refusals], result.summary);
8557
+ };
8558
+ window.addEventListener("mouseup", commit);
8559
+ return () => window.removeEventListener("mouseup", commit);
8560
+ }, [options, fillRequestBase, applyPatches, announceSkips, screenPatches]);
8561
+ return {
8562
+ range,
8563
+ bounds,
8564
+ enabled: options !== null,
8565
+ containerProps: {
8566
+ tabIndex: options ? 0 : -1,
8567
+ onKeyDown,
8568
+ onCopy,
8569
+ onCut,
8570
+ onPaste
8571
+ },
8572
+ cellProps,
8573
+ isFillHandleCell,
8574
+ startFillDrag,
8575
+ canUndo: stack.past.length > 0,
8576
+ canRedo: stack.future.length > 0,
8577
+ selectionTsv: selectionAsTsv,
8578
+ selectionAddresses
8579
+ };
8580
+ }
8581
+
8582
+ // src/data-table/coverage.ts
8583
+ var PARTIAL_BADGE = "Partial";
8584
+ var UNKNOWN_TOTAL_BADGE = "Unknown total";
8585
+ var NUMBERS2 = new Intl.NumberFormat("en-US");
8586
+ function formatNumber(value) {
8587
+ try {
8588
+ return NUMBERS2.format(value);
8589
+ } catch {
8590
+ return String(value);
8591
+ }
8592
+ }
8593
+ function pluralize(noun) {
8594
+ const word = noun.trim();
8595
+ if (!word) return "rows";
8596
+ const lower = word.toLowerCase();
8597
+ if (/[^aeiou]y$/.test(lower)) return `${word.slice(0, -1)}ies`;
8598
+ if (/(s|x|z|ch|sh)$/.test(lower)) return `${word}es`;
8599
+ return `${word}s`;
8600
+ }
8601
+ function quantity(value, noun) {
8602
+ return `${formatNumber(value)} ${value === 1 ? noun.trim() || "row" : pluralize(noun)}`;
8603
+ }
8604
+ function toWhole(value) {
8605
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
8606
+ }
8607
+ function toOptionalWhole(value) {
8608
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
8609
+ return Math.floor(value);
8610
+ }
8611
+ function describeCoverage(input) {
8612
+ const safe = input && typeof input === "object" ? input : { loaded: 0, answeredBy: "client" };
8613
+ const loaded = toWhole(safe.loaded);
8614
+ const matched = toOptionalWhole(safe.matched);
8615
+ const total = toOptionalWhole(safe.total);
8616
+ const cap = toOptionalWhole(safe.cap);
8617
+ const answeredBy = safe.answeredBy === "source" ? "source" : "client";
8618
+ const noun = typeof safe.noun === "string" && safe.noun.trim() ? safe.noun.trim() : "row";
8619
+ const reference = matched ?? total;
8620
+ const capReached = cap !== void 0 && cap > 0 && loaded >= cap;
8621
+ if (reference !== void 0 && loaded >= reference) {
8622
+ return {
8623
+ scope: "all",
8624
+ complete: true,
8625
+ count: { value: matched ?? reference, exact: true },
8626
+ notice: "",
8627
+ badge: ""
8628
+ };
8629
+ }
8630
+ if (answeredBy === "source" && matched !== void 0 && !capReached) {
8631
+ return { scope: "all", complete: true, count: { value: matched, exact: true }, notice: "", badge: "" };
8632
+ }
8633
+ if (capReached && cap !== void 0) {
8634
+ const rest = reference !== void 0 ? `${quantity(reference, noun)} in this table; the rest were not looked at.` : "Anything past that was not looked at.";
8635
+ return {
8636
+ scope: "capped",
8637
+ complete: false,
8638
+ count: { value: loaded, exact: false },
8639
+ notice: `This filter searched the first ${quantity(cap, noun)} only. ${rest}`,
8640
+ badge: PARTIAL_BADGE
8641
+ };
8642
+ }
8643
+ if (answeredBy === "client" && reference !== void 0) {
8644
+ return {
8645
+ scope: "loaded",
8646
+ complete: false,
8647
+ count: { value: loaded, exact: false },
8648
+ notice: `Filtering the ${quantity(loaded, noun)} loaded so far, not all ${quantity(reference, noun)} in this table.`,
8649
+ badge: PARTIAL_BADGE
8650
+ };
8651
+ }
8652
+ if (answeredBy === "source" && total !== void 0) {
8653
+ return {
8654
+ scope: "all",
8655
+ complete: false,
8656
+ count: { value: loaded, exact: false },
8657
+ notice: `Showing the ${quantity(loaded, noun)} loaded so far out of ${quantity(total, noun)} in this table.`,
8658
+ badge: PARTIAL_BADGE
8659
+ };
8660
+ }
8661
+ return {
8662
+ scope: answeredBy === "source" ? "all" : "loaded",
8663
+ complete: false,
8664
+ count: { value: loaded, exact: false },
8665
+ notice: `Showing the ${quantity(loaded, noun)} loaded so far. The total number of ${pluralize(noun)} in this table is not known.`,
8666
+ badge: UNKNOWN_TOTAL_BADGE
8667
+ };
8668
+ }
8669
+
8670
+ // src/data-table/ranked-search.ts
8671
+ function inferTableSearchRole(field) {
8672
+ const key = field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/[\s-]+/g, "_");
8673
+ if (key === "id" || key === "uuid" || key.endsWith("_id")) return "id";
8674
+ if (/^(name|alias|label|title|key|slug|display_name|common_name|model_name)$/.test(key) || /_(name|label|title)$/.test(key)) return "name";
8675
+ if (/^(description|summary|details|notes|blurb)$/.test(key) || key.endsWith("_description")) return "description";
8676
+ return "other";
8677
+ }
8678
+ function searchableFields(row, columns) {
8679
+ const values = /* @__PURE__ */ new Map();
8680
+ if (row !== null && typeof row === "object") {
8681
+ for (const [key, value] of Object.entries(row)) values.set(key, stringifyCellValue(value));
8682
+ }
8683
+ for (const column of columns) {
8684
+ if (column.accessorKey || column.accessorFn || column.filterValue) {
8685
+ values.set(columnId(column), stringifyCellValue(getFilterValue(row, column)));
8686
+ }
8687
+ }
8688
+ return values;
8689
+ }
8690
+ function resolveTableSearchScopes(rows, columns, options = {}) {
8691
+ const fields = new Set(columns.filter((c) => c.accessorKey || c.accessorFn || c.filterValue).map(columnId));
8692
+ for (const row of rows) if (row !== null && typeof row === "object") Object.keys(row).forEach((key) => fields.add(key));
8693
+ const roles = new Set([...fields].map((key) => options.roles?.[key] ?? inferTableSearchRole(key)));
8694
+ const scopes = [{ id: "all", label: "All" }];
8695
+ for (const [id, label] of [["id", "ID"], ["name", "Name"], ["description", "Description"]]) {
8696
+ if (roles.has(id)) scopes.push({ id, label });
8697
+ }
8698
+ for (const scope of options.scopes ?? []) if (!scopes.some((s) => s.id === scope.id) && scope.fields?.length) scopes.push(scope);
8699
+ return scopes;
8700
+ }
8701
+ function rankTableSearchRows(rows, columns, query, options = {}, extraText) {
8702
+ const q = query.trim().toLocaleLowerCase();
8703
+ if (!q) return [...rows];
8704
+ const scope = options.scope ?? "all";
8705
+ const customScope = options.scopes?.find((s) => s.id === scope);
8706
+ const mode = options.matchMode ?? "contains";
8707
+ return rows.flatMap((row, index) => {
8708
+ const fields = [...searchableFields(row, columns)].filter(([key]) => {
8709
+ if (scope === "all") return true;
8710
+ if (customScope) return customScope.fields?.includes(key) ?? false;
8711
+ return (options.roles?.[key] ?? inferTableSearchRole(key)) === scope;
8712
+ });
8713
+ const extra = scope === "all" ? extraText?.(row) : void 0;
8714
+ const texts = fields.map(([, value]) => value);
8715
+ if (extra) texts.push(extra);
8716
+ const matched = mode === "whole_words" ? matchesTableSearch(texts.join(" "), q, mode) : texts.some((value) => matchesTableSearch(value, q, mode));
8717
+ if (!matched) return [];
8718
+ let rank = 3;
8719
+ for (const [key, value] of fields) {
8720
+ if (!matchesTableSearch(value, q, mode)) continue;
8721
+ const role = options.roles?.[key] ?? inferTableSearchRole(key);
8722
+ if (role === "id" || role === "name") rank = Math.min(rank, value.trim().toLocaleLowerCase() === q ? 0 : 1);
8723
+ else if (role === "description") rank = Math.min(rank, 2);
8724
+ }
8725
+ return [{ row, rank, index }];
8726
+ }).sort((a, b) => a.rank - b.rank || a.index - b.index).map((item) => item.row);
8727
+ }
8728
+
8729
+ // src/data-table/ColumnManager.tsx
8730
+ import { useEffect as useEffect12, useMemo as useMemo7, useState as useState17 } from "react";
8731
+ import { useDndMonitor, useDraggable as useDraggable2, useDroppable } from "@dnd-kit/core";
8732
+ import { jsx as jsx33, jsxs as jsxs23 } from "react/jsx-runtime";
8733
+ function ColumnManager({
8734
+ open,
8735
+ onOpenChange,
8736
+ columns,
8737
+ order,
8738
+ hidden,
8739
+ onApply
8740
+ }) {
8741
+ const byId = useMemo7(
8742
+ () => new Map(columns.map((column) => [columnId(column), column])),
8743
+ [columns]
8744
+ );
8745
+ const [draftOrder, setDraftOrder] = useState17(order);
8746
+ const [draftHidden, setDraftHidden] = useState17(() => new Set(hidden));
8747
+ const [draggedColumnId, setDraggedColumnId] = useState17(null);
8748
+ const [dropTarget, setDropTarget] = useState17(null);
8749
+ useEffect12(() => {
8750
+ if (!open) return;
8751
+ setDraftOrder(order);
8752
+ setDraftHidden(new Set(hidden));
8753
+ }, [open, order, hidden]);
8754
+ const move = (id, direction) => {
8755
+ setDraftOrder((current) => {
8756
+ const index = current.indexOf(id);
8757
+ const target = index + direction;
8758
+ if (index < 0 || target < 0 || target >= current.length) return current;
8759
+ const next = [...current];
8760
+ const displaced = next[target];
8761
+ if (displaced === void 0) return current;
8762
+ next[target] = id;
8763
+ next[index] = displaced;
8764
+ return next;
8765
+ });
8766
+ };
8767
+ const moveTo = (id, targetId) => {
8768
+ if (id === targetId) return;
8769
+ setDraftOrder((current) => {
8770
+ const from = current.indexOf(id);
8771
+ const target = current.indexOf(targetId);
8772
+ if (from < 0 || target < 0) return current;
8773
+ const next = [...current];
8774
+ next.splice(from, 1);
8775
+ const targetAfterRemoval = next.indexOf(targetId);
8776
+ next.splice(targetAfterRemoval, 0, id);
8777
+ return next;
8778
+ });
6883
8779
  };
6884
8780
  useDndMonitor({
6885
8781
  onDragStart(event) {
@@ -7381,9 +9277,9 @@ function parseTableViewSnapshot(raw) {
7381
9277
 
7382
9278
  // src/data-table/MatrxUuidCell.tsx
7383
9279
  import {
7384
- useEffect as useEffect12,
7385
- useRef as useRef11,
7386
- useState as useState17
9280
+ useEffect as useEffect13,
9281
+ useRef as useRef13,
9282
+ useState as useState18
7387
9283
  } from "react";
7388
9284
  import { jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
7389
9285
  var SHORT_LEN = 8;
@@ -7424,11 +9320,11 @@ function MatrxUuidCell({
7424
9320
  }) {
7425
9321
  const { resolveEntityDoors, ResourcePeek } = useMatrxDataTableHost();
7426
9322
  const { notify, notice } = useTableNotice();
7427
- const [copied, setCopied] = useState17(false);
7428
- const [opening, setOpening] = useState17(false);
7429
- const [peekOpen, setPeekOpen] = useState17(false);
7430
- const timer = useRef11(null);
7431
- useEffect12(
9323
+ const [copied, setCopied] = useState18(false);
9324
+ const [opening, setOpening] = useState18(false);
9325
+ const [peekOpen, setPeekOpen] = useState18(false);
9326
+ const timer = useRef13(null);
9327
+ useEffect13(
7432
9328
  () => () => {
7433
9329
  if (timer.current) clearTimeout(timer.current);
7434
9330
  },
@@ -7723,7 +9619,7 @@ function DataRowInspector({
7723
9619
  }
7724
9620
 
7725
9621
  // src/data-table/DataRowWindow.tsx
7726
- import { useEffect as useEffect13, useState as useState18 } from "react";
9622
+ import { useEffect as useEffect14, useState as useState19 } from "react";
7727
9623
  import { jsx as jsx36, jsxs as jsxs26 } from "react/jsx-runtime";
7728
9624
  function DataRowWindow({
7729
9625
  isOpen,
@@ -7744,8 +9640,8 @@ function DataRowWindow({
7744
9640
  const hasEdit = editContent != null;
7745
9641
  const hasTabs = hasEdit && children == null;
7746
9642
  const initialTab = defaultTab ?? (hasEdit ? "edit" : "view");
7747
- const [tab, setTab] = useState18(initialTab);
7748
- useEffect13(() => {
9643
+ const [tab, setTab] = useState19(initialTab);
9644
+ useEffect14(() => {
7749
9645
  if (!isOpen) return;
7750
9646
  setTab(defaultTab ?? (hasEdit ? "edit" : "view"));
7751
9647
  }, [isOpen, windowId, hasEdit, defaultTab]);
@@ -7895,8 +9791,8 @@ function toNumber(value) {
7895
9791
  if (typeof value === "string") {
7896
9792
  const cleaned = value.replace(/[,\s$€£¥%]/g, "");
7897
9793
  if (cleaned === "") return null;
7898
- const n = Number(cleaned);
7899
- return Number.isFinite(n) ? n : null;
9794
+ const n2 = Number(cleaned);
9795
+ return Number.isFinite(n2) ? n2 : null;
7900
9796
  }
7901
9797
  return null;
7902
9798
  }
@@ -7934,14 +9830,14 @@ function toList(value) {
7934
9830
  }
7935
9831
  return null;
7936
9832
  }
7937
- function groupedNumber(n, options) {
9833
+ function groupedNumber(n2, options) {
7938
9834
  return new Intl.NumberFormat(void 0, {
7939
9835
  useGrouping: options.useGrouping !== false,
7940
9836
  ...options.precision != null ? {
7941
9837
  minimumFractionDigits: options.precision,
7942
9838
  maximumFractionDigits: options.precision
7943
9839
  } : { maximumFractionDigits: 10 }
7944
- }).format(n);
9840
+ }).format(n2);
7945
9841
  }
7946
9842
  function affix(text, options) {
7947
9843
  return `${options.prefix ?? ""}${text}${options.suffix ?? ""}`;
@@ -8075,8 +9971,8 @@ var DEFS = [
8075
9971
  numericAlign: true,
8076
9972
  optionKeys: ["useGrouping", "precision", "prefix", "suffix"],
8077
9973
  format: (v, o) => {
8078
- const n = toNumber(v);
8079
- return n === null ? null : affix(groupedNumber(n, o), o);
9974
+ const n2 = toNumber(v);
9975
+ return n2 === null ? null : affix(groupedNumber(n2, o), o);
8080
9976
  },
8081
9977
  parse: (raw) => toNumber(raw)
8082
9978
  },
@@ -8091,9 +9987,9 @@ var DEFS = [
8091
9987
  numericAlign: true,
8092
9988
  optionKeys: ["precision", "useGrouping", "prefix", "suffix"],
8093
9989
  format: (v, o) => {
8094
- const n = toNumber(v);
8095
- if (n === null) return null;
8096
- return affix(groupedNumber(n, { ...o, precision: o.precision ?? 2 }), o);
9990
+ const n2 = toNumber(v);
9991
+ if (n2 === null) return null;
9992
+ return affix(groupedNumber(n2, { ...o, precision: o.precision ?? 2 }), o);
8097
9993
  },
8098
9994
  parse: (raw) => toNumber(raw)
8099
9995
  },
@@ -8108,8 +10004,8 @@ var DEFS = [
8108
10004
  numericAlign: true,
8109
10005
  optionKeys: ["currency", "precision"],
8110
10006
  format: (v, o) => {
8111
- const n = toNumber(v);
8112
- if (n === null) return null;
10007
+ const n2 = toNumber(v);
10008
+ if (n2 === null) return null;
8113
10009
  const currency = (o.currency || "USD").toUpperCase();
8114
10010
  try {
8115
10011
  return new Intl.NumberFormat(void 0, {
@@ -8119,9 +10015,9 @@ var DEFS = [
8119
10015
  minimumFractionDigits: o.precision,
8120
10016
  maximumFractionDigits: o.precision
8121
10017
  } : {}
8122
- }).format(n);
10018
+ }).format(n2);
8123
10019
  } catch {
8124
- return `${groupedNumber(n, { precision: o.precision ?? 2 })} ${currency}`;
10020
+ return `${groupedNumber(n2, { precision: o.precision ?? 2 })} ${currency}`;
8125
10021
  }
8126
10022
  },
8127
10023
  parse: (raw) => toNumber(raw)
@@ -8137,9 +10033,9 @@ var DEFS = [
8137
10033
  numericAlign: true,
8138
10034
  optionKeys: ["percentScale", "precision"],
8139
10035
  format: (v, o) => {
8140
- const n = toNumber(v);
8141
- if (n === null) return null;
8142
- const shown = o.percentScale === "fraction" ? n * 100 : n;
10036
+ const n2 = toNumber(v);
10037
+ if (n2 === null) return null;
10038
+ const shown = o.percentScale === "fraction" ? n2 * 100 : n2;
8143
10039
  return `${groupedNumber(shown, o.precision === void 0 ? {} : { precision: o.precision })}%`;
8144
10040
  },
8145
10041
  parse: (raw) => toNumber(raw)
@@ -8155,9 +10051,9 @@ var DEFS = [
8155
10051
  numericAlign: true,
8156
10052
  optionKeys: ["durationUnit"],
8157
10053
  format: (v, o) => {
8158
- const n = toNumber(v);
8159
- if (n === null) return null;
8160
- return formatSignedDuration(n * DURATION_TO_SECONDS[o.durationUnit ?? "seconds"]);
10054
+ const n2 = toNumber(v);
10055
+ if (n2 === null) return null;
10056
+ return formatSignedDuration(n2 * DURATION_TO_SECONDS[o.durationUnit ?? "seconds"]);
8161
10057
  },
8162
10058
  parse: (raw) => toNumber(raw)
8163
10059
  },
@@ -8172,13 +10068,13 @@ var DEFS = [
8172
10068
  numericAlign: true,
8173
10069
  optionKeys: ["useGrouping", "prefix", "suffix"],
8174
10070
  format: (v, o) => {
8175
- const n = toNumber(v);
8176
- if (n === null) return null;
8177
- return affix(groupedNumber(Math.trunc(n), { ...o, precision: 0 }), o);
10071
+ const n2 = toNumber(v);
10072
+ if (n2 === null) return null;
10073
+ return affix(groupedNumber(Math.trunc(n2), { ...o, precision: 0 }), o);
8178
10074
  },
8179
10075
  parse: (raw) => {
8180
- const n = toNumber(raw);
8181
- return n === null ? null : Math.trunc(n);
10076
+ const n2 = toNumber(raw);
10077
+ return n2 === null ? null : Math.trunc(n2);
8182
10078
  }
8183
10079
  },
8184
10080
  {
@@ -8192,15 +10088,15 @@ var DEFS = [
8192
10088
  rich: true,
8193
10089
  optionKeys: ["ratingMax"],
8194
10090
  format: (v, o) => {
8195
- const n = toNumber(v);
8196
- if (n === null) return null;
10091
+ const n2 = toNumber(v);
10092
+ if (n2 === null) return null;
8197
10093
  const max = o.ratingMax ?? 5;
8198
- if (n < 0 || n > max) return null;
8199
- return `${n} / ${max}`;
10094
+ if (n2 < 0 || n2 > max) return null;
10095
+ return `${n2} / ${max}`;
8200
10096
  },
8201
10097
  parse: (raw) => {
8202
- const n = toNumber(raw);
8203
- return n === null ? null : Math.round(n);
10098
+ const n2 = toNumber(raw);
10099
+ return n2 === null ? null : Math.round(n2);
8204
10100
  }
8205
10101
  },
8206
10102
  {
@@ -8213,12 +10109,12 @@ var DEFS = [
8213
10109
  editor: "number",
8214
10110
  numericAlign: true,
8215
10111
  format: (v) => {
8216
- const n = toNumber(v);
8217
- return n === null ? null : formatFileSize(n);
10112
+ const n2 = toNumber(v);
10113
+ return n2 === null ? null : formatFileSize(n2);
8218
10114
  },
8219
10115
  parse: (raw) => {
8220
- const n = toNumber(raw);
8221
- return n === null ? null : Math.trunc(n);
10116
+ const n2 = toNumber(raw);
10117
+ return n2 === null ? null : Math.trunc(n2);
8222
10118
  }
8223
10119
  },
8224
10120
  // ── Choice ────────────────────────────────────────────────────────────────
@@ -8628,8 +10524,19 @@ function DirtySavePill({
8628
10524
  }
8629
10525
 
8630
10526
  // src/data-table/EditableTableCell.tsx
8631
- import { useEffect as useEffect14, useRef as useRef12, useState as useState19 } from "react";
10527
+ import { useEffect as useEffect15, useRef as useRef14, useState as useState20 } from "react";
8632
10528
  import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
10529
+ function CellRefusal({ reason }) {
10530
+ return /* @__PURE__ */ jsx39(
10531
+ "p",
10532
+ {
10533
+ role: "alert",
10534
+ "data-matrx-cell-refusal": "true",
10535
+ className: "mt-1 text-[11px] leading-snug text-amber-700 dark:text-amber-400",
10536
+ children: reason
10537
+ }
10538
+ );
10539
+ }
8633
10540
  function CellEditShell({
8634
10541
  href,
8635
10542
  display,
@@ -8676,12 +10583,17 @@ function EditableTableCell({
8676
10583
  className,
8677
10584
  href,
8678
10585
  editTrigger = "click",
8679
- editRequest
10586
+ editRequest,
10587
+ editSeed
8680
10588
  }) {
8681
- const [open, setOpen] = useState19(false);
8682
- useEffect14(() => {
10589
+ const [open, setOpen] = useState20(false);
10590
+ const [refusal, setRefusal] = useState20(null);
10591
+ useEffect15(() => {
8683
10592
  if (editRequest !== void 0) setOpen(true);
8684
10593
  }, [editRequest]);
10594
+ useEffect15(() => {
10595
+ if (!open) setRefusal(null);
10596
+ }, [open]);
8685
10597
  const usePencil = Boolean(href) || editTrigger === "pencil";
8686
10598
  if (editType === "string") {
8687
10599
  return /* @__PURE__ */ jsx39(
@@ -8694,7 +10606,8 @@ function EditableTableCell({
8694
10606
  ...className === void 0 ? {} : { className },
8695
10607
  ...href === void 0 ? {} : { href },
8696
10608
  usePencil,
8697
- ...editRequest === void 0 ? {} : { editRequest }
10609
+ ...editRequest === void 0 ? {} : { editRequest },
10610
+ ...editSeed === void 0 || editSeed === null ? {} : { editSeed }
8698
10611
  }
8699
10612
  );
8700
10613
  }
@@ -8739,25 +10652,33 @@ function EditableTableCell({
8739
10652
  ) }) }),
8740
10653
  /* @__PURE__ */ jsx39(TooltipContent, { children: "Edit value" })
8741
10654
  ] }),
8742
- /* @__PURE__ */ jsx39(
10655
+ /* @__PURE__ */ jsxs29(
8743
10656
  PopoverContent,
8744
10657
  {
8745
10658
  align: "start",
8746
- className: "w-56 p-2",
8747
- onClick: (e) => e.stopPropagation(),
8748
- children: /* @__PURE__ */ jsx39(
8749
- TypedEditor,
8750
- {
8751
- editType,
8752
- value,
8753
- editOptions,
8754
- onCommit: (next) => {
8755
- onCommit(next);
8756
- setOpen(false);
8757
- },
8758
- onCancel: () => setOpen(false)
8759
- }
8760
- )
10659
+ className: "w-56 p-2",
10660
+ onClick: (e) => e.stopPropagation(),
10661
+ children: [
10662
+ /* @__PURE__ */ jsx39(
10663
+ TypedEditor,
10664
+ {
10665
+ editType,
10666
+ value,
10667
+ editOptions,
10668
+ onCommit: (next) => {
10669
+ const refused = onCommit(next);
10670
+ if (typeof refused === "string" && refused) {
10671
+ setRefusal(refused);
10672
+ return;
10673
+ }
10674
+ setRefusal(null);
10675
+ setOpen(false);
10676
+ },
10677
+ onCancel: () => setOpen(false)
10678
+ }
10679
+ ),
10680
+ refusal ? /* @__PURE__ */ jsx39(CellRefusal, { reason: refusal }) : null
10681
+ ]
8761
10682
  }
8762
10683
  )
8763
10684
  ] });
@@ -8770,17 +10691,31 @@ function InlineStringEditor({
8770
10691
  className,
8771
10692
  href,
8772
10693
  usePencil,
8773
- editRequest
10694
+ editRequest,
10695
+ editSeed
8774
10696
  }) {
8775
- const [editing, setEditing] = useState19(false);
8776
- const [draft, setDraft] = useState19(value);
8777
- const handledEditRequest = useRef12(void 0);
8778
- useEffect14(() => {
10697
+ const [editing, setEditing] = useState20(false);
10698
+ const [draft, setDraft] = useState20(value);
10699
+ const [refusal, setRefusal] = useState20(null);
10700
+ const handledEditRequest = useRef14(void 0);
10701
+ const seedRef = useRef14(editSeed);
10702
+ seedRef.current = editSeed;
10703
+ useEffect15(() => {
8779
10704
  if (editRequest === void 0 || handledEditRequest.current === editRequest) return;
8780
10705
  handledEditRequest.current = editRequest;
8781
- setDraft(value);
10706
+ setDraft(seedRef.current ?? value);
10707
+ setRefusal(null);
8782
10708
  setEditing(true);
8783
10709
  }, [editRequest, value]);
10710
+ const commit = (next) => {
10711
+ const refused = onCommit(next);
10712
+ if (typeof refused === "string" && refused) {
10713
+ setRefusal(refused);
10714
+ return false;
10715
+ }
10716
+ setRefusal(null);
10717
+ return true;
10718
+ };
8784
10719
  if (!editing) {
8785
10720
  if (usePencil) {
8786
10721
  return /* @__PURE__ */ jsx39(
@@ -8831,32 +10766,38 @@ function InlineStringEditor({
8831
10766
  /* @__PURE__ */ jsx39(TooltipContent, { children: "Click to edit" })
8832
10767
  ] });
8833
10768
  }
8834
- return /* @__PURE__ */ jsx39(
8835
- Input,
8836
- {
8837
- autoFocus: true,
8838
- value: draft,
8839
- onClick: (e) => e.stopPropagation(),
8840
- onChange: (e) => setDraft(e.target.value),
8841
- onBlur: () => {
8842
- onCommit(draft);
8843
- setEditing(false);
8844
- },
8845
- onKeyDown: (e) => {
8846
- if (e.key === "Enter") {
8847
- e.preventDefault();
8848
- onCommit(draft);
8849
- setEditing(false);
8850
- }
8851
- if (e.key === "Escape") {
8852
- e.preventDefault();
8853
- setEditing(false);
8854
- }
8855
- },
8856
- className: "h-7 text-sm",
8857
- style: { fontSize: "16px" }
8858
- }
8859
- );
10769
+ return /* @__PURE__ */ jsxs29("span", { className: "block w-full", children: [
10770
+ /* @__PURE__ */ jsx39(
10771
+ Input,
10772
+ {
10773
+ autoFocus: true,
10774
+ value: draft,
10775
+ onClick: (e) => e.stopPropagation(),
10776
+ onChange: (e) => {
10777
+ setDraft(e.target.value);
10778
+ if (refusal) setRefusal(null);
10779
+ },
10780
+ onBlur: () => {
10781
+ if (commit(draft)) setEditing(false);
10782
+ },
10783
+ onKeyDown: (e) => {
10784
+ if (e.key === "Enter") {
10785
+ e.preventDefault();
10786
+ if (commit(draft)) setEditing(false);
10787
+ }
10788
+ if (e.key === "Escape") {
10789
+ e.preventDefault();
10790
+ setRefusal(null);
10791
+ setEditing(false);
10792
+ }
10793
+ },
10794
+ "aria-invalid": refusal ? true : void 0,
10795
+ className: cn("h-7 text-sm", refusal && "border-amber-500 focus-visible:ring-amber-500"),
10796
+ style: { fontSize: "16px" }
10797
+ }
10798
+ ),
10799
+ refusal ? /* @__PURE__ */ jsx39(CellRefusal, { reason: refusal }) : null
10800
+ ] });
8860
10801
  }
8861
10802
  function TypedEditor({
8862
10803
  editType,
@@ -8931,8 +10872,8 @@ function TagsEditor({
8931
10872
  onCommit,
8932
10873
  onCancel
8933
10874
  }) {
8934
- const [tags, setTags] = useState19(value);
8935
- const [draft, setDraft] = useState19("");
10875
+ const [tags, setTags] = useState20(value);
10876
+ const [draft, setDraft] = useState20("");
8936
10877
  const add = (raw) => {
8937
10878
  const next = raw.trim();
8938
10879
  if (!next || tags.includes(next)) return;
@@ -9029,7 +10970,7 @@ function NumberEditor({
9029
10970
  onCommit,
9030
10971
  onCancel
9031
10972
  }) {
9032
- const [draft, setDraft] = useState19(String(value));
10973
+ const [draft, setDraft] = useState20(String(value));
9033
10974
  return /* @__PURE__ */ jsxs29("div", { className: "flex flex-col gap-2", children: [
9034
10975
  /* @__PURE__ */ jsx39(
9035
10976
  Input,
@@ -9041,8 +10982,8 @@ function NumberEditor({
9041
10982
  style: { fontSize: "16px" },
9042
10983
  onKeyDown: (e) => {
9043
10984
  if (e.key === "Enter") {
9044
- const n = Number(draft);
9045
- if (Number.isFinite(n)) onCommit(n);
10985
+ const n2 = Number(draft);
10986
+ if (Number.isFinite(n2)) onCommit(n2);
9046
10987
  }
9047
10988
  if (e.key === "Escape") onCancel();
9048
10989
  }
@@ -9069,8 +11010,8 @@ function NumberEditor({
9069
11010
  className: "h-7",
9070
11011
  "aria-label": "Apply value",
9071
11012
  onClick: () => {
9072
- const n = Number(draft);
9073
- if (Number.isFinite(n)) onCommit(n);
11013
+ const n2 = Number(draft);
11014
+ if (Number.isFinite(n2)) onCommit(n2);
9074
11015
  },
9075
11016
  children: /* @__PURE__ */ jsx39(Check, { className: "h-3.5 w-3.5" })
9076
11017
  }
@@ -9159,7 +11100,7 @@ function resetToolbarFacets(facets) {
9159
11100
  }
9160
11101
 
9161
11102
  // src/data-table/LayeredFilterBuilder.tsx
9162
- import { useState as useState20 } from "react";
11103
+ import { useState as useState21 } from "react";
9163
11104
  import { Fragment as Fragment5, jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
9164
11105
  function moveRule(rules, from, to) {
9165
11106
  if (from === to || to < 0 || to >= rules.length) return [...rules];
@@ -9176,8 +11117,8 @@ function LayeredFilterBuilder({
9176
11117
  maxRules = 20,
9177
11118
  label = "Advanced filters"
9178
11119
  }) {
9179
- const [open, setOpen] = useState20(false);
9180
- const [draggedIndex, setDraggedIndex] = useState20(null);
11120
+ const [open, setOpen] = useState21(false);
11121
+ const [draggedIndex, setDraggedIndex] = useState21(null);
9181
11122
  if (fields.length === 0) return null;
9182
11123
  const updateRule = (index, patch) => {
9183
11124
  onChange(
@@ -9721,7 +11662,7 @@ function MatrxDataTableWithUrlState({
9721
11662
  ...urlState.history === void 0 ? {} : { history: urlState.history },
9722
11663
  ...urlState.textHistory === void 0 ? {} : { textHistory: urlState.textHistory }
9723
11664
  });
9724
- useEffect15(() => {
11665
+ useEffect16(() => {
9725
11666
  if (activeUrlTableIds.has(urlState.id)) {
9726
11667
  throw new Error(
9727
11668
  `Duplicate MatrxDataTable urlState.id "${urlState.id}" on one page. Give every table a unique stable id.`
@@ -9812,6 +11753,11 @@ function opensRowHref(event) {
9812
11753
  function TableContextPassthrough({ children }) {
9813
11754
  return /* @__PURE__ */ jsx42(Fragment7, { children });
9814
11755
  }
11756
+ function sameWindow(a, b) {
11757
+ if (a === null || b === null) return a === b;
11758
+ 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;
11759
+ }
11760
+ var NO_GROUPING_VALUE = "__matrx_no_grouping__";
9815
11761
  function MatrxDataTableCore({
9816
11762
  data: suppliedData,
9817
11763
  columns,
@@ -9868,11 +11814,17 @@ function MatrxDataTableCore({
9868
11814
  mobileCardsBreakpoint = "sm",
9869
11815
  getRowHref,
9870
11816
  onRowOpen,
9871
- density
11817
+ density,
11818
+ grouping,
11819
+ virtualize,
11820
+ coverage,
11821
+ spreadsheet,
11822
+ facets,
11823
+ rowHeight: explicitRowHeight
9872
11824
  }) {
9873
11825
  const appendQuery = query?.mode === "controlled-append" ? query : null;
9874
11826
  const appendQueryKey = appendQuery?.pagination.queryKey ?? null;
9875
- const committedAppendQueryKey = useRef13(appendQueryKey);
11827
+ const committedAppendQueryKey = useRef15(appendQueryKey);
9876
11828
  const appendSourceChanged = appendQuery !== null && committedAppendQueryKey.current !== appendQueryKey;
9877
11829
  const data = (appendQuery ? appendQuery.pagination.rows : suppliedData) ?? [];
9878
11830
  const isLoading = appendQuery ? appendQuery.pagination.loading : suppliedLoading;
@@ -9884,10 +11836,10 @@ function MatrxDataTableCore({
9884
11836
  const Link = host.Link ?? DefaultTableLink;
9885
11837
  const SidePanelSurface = host.SidePanelSurface ?? DefaultTableSidePanel;
9886
11838
  const { notify: toast, notice } = useTableNotice();
9887
- const [savedViewError, setSavedViewError] = useState21(null);
9888
- const [toolbarActionError, setToolbarActionError] = useState21(null);
9889
- const [toolbarActionPending, setToolbarActionPending] = useState21({ refresh: false, add: false });
9890
- const toolbarActionPendingRef = useRef13({ refresh: false, add: false });
11839
+ const [savedViewError, setSavedViewError] = useState22(null);
11840
+ const [toolbarActionError, setToolbarActionError] = useState22(null);
11841
+ const [toolbarActionPending, setToolbarActionPending] = useState22({ refresh: false, add: false });
11842
+ const toolbarActionPendingRef = useRef15({ refresh: false, add: false });
9891
11843
  const CHECKBOX_TAP_AREA = "h-4 w-4 touch-manipulation";
9892
11844
  const mobileCardsClass = mobileCardsBreakpoint === "lg" ? "lg:hidden" : "sm:hidden";
9893
11845
  const tableWithCardsClass = mobileCardsBreakpoint === "lg" ? "max-lg:hidden" : "max-sm:hidden";
@@ -9900,7 +11852,7 @@ function MatrxDataTableCore({
9900
11852
  const sourceOwnsIntelligentSearch = sourceProcessing?.intelligentSearch === "source";
9901
11853
  const sourceOwnsSearch = remoteQuery?.mode === "controlled" || appendQuery !== null && sourceProcessing === void 0 || sourceProcessing?.search === "source" || sourceOwnsIntelligentSearch;
9902
11854
  const intelligentSearchEnabled = toolbar?.intelligentSearch !== void 0 && (sourceOwnsIntelligentSearch || remoteQuery === null && !sourceOwnsSearch);
9903
- const emitControlledQueryChange = useCallback7(
11855
+ const emitControlledQueryChange = useCallback8(
9904
11856
  (patch, options) => {
9905
11857
  if (!controlledQuery) return;
9906
11858
  controlledQuery.onStateChange(
@@ -9909,8 +11861,8 @@ function MatrxDataTableCore({
9909
11861
  },
9910
11862
  [controlledQuery]
9911
11863
  );
9912
- const [internalSearch, setInternalSearch] = useState21("");
9913
- const [draggedRowId, setDraggedRowId] = useState21(null);
11864
+ const [internalSearch, setInternalSearch] = useState22("");
11865
+ const [draggedRowId, setDraggedRowId] = useState22(null);
9914
11866
  const hierarchySensors = useSensors(
9915
11867
  useSensor(PointerSensor, {
9916
11868
  activationConstraint: { distance: COLUMN_REORDER_ACTIVATION_DISTANCE }
@@ -9926,7 +11878,7 @@ function MatrxDataTableCore({
9926
11878
  }
9927
11879
  toolbar?.onSearchChange?.(v);
9928
11880
  };
9929
- const [internalSearchMatchMode, setInternalSearchMatchMode] = useState21(
11881
+ const [internalSearchMatchMode, setInternalSearchMatchMode] = useState22(
9930
11882
  toolbar?.searchMatch?.defaultMode ?? "contains"
9931
11883
  );
9932
11884
  const searchMatchMode = controlledQuery ? controlledQuery.state.searchMatchMode ?? toolbar?.searchMatch?.defaultMode ?? "contains" : internalSearchMatchMode;
@@ -9937,7 +11889,7 @@ function MatrxDataTableCore({
9937
11889
  }
9938
11890
  setInternalSearchMatchMode(mode);
9939
11891
  };
9940
- const [internalSearchScope, setInternalSearchScope] = useState21("all");
11892
+ const [internalSearchScope, setInternalSearchScope] = useState22("all");
9941
11893
  const searchScope = controlledQuery ? controlledQuery.state.searchScope ?? "all" : internalSearchScope;
9942
11894
  const setSearchScope = (scope) => {
9943
11895
  if (controlledQuery) {
@@ -9946,7 +11898,7 @@ function MatrxDataTableCore({
9946
11898
  }
9947
11899
  setInternalSearchScope(scope);
9948
11900
  };
9949
- const [internalAnyOf, setInternalAnyOf] = useState21("");
11901
+ const [internalAnyOf, setInternalAnyOf] = useState22("");
9950
11902
  const toolbarAnyOfControlled = toolbar?.anyOf?.value !== void 0;
9951
11903
  const anyOfValue = controlledQuery ? controlledQuery.state.anyOf : toolbarAnyOfControlled ? toolbar?.anyOf?.value ?? "" : internalAnyOf;
9952
11904
  const setAnyOfValue = (v) => {
@@ -9957,7 +11909,7 @@ function MatrxDataTableCore({
9957
11909
  }
9958
11910
  toolbar?.anyOf?.onChange?.(v);
9959
11911
  };
9960
- const [internalLayeredFilters, setInternalLayeredFilters] = useState21([]);
11912
+ const [internalLayeredFilters, setInternalLayeredFilters] = useState22([]);
9961
11913
  const layeredFilters = controlledQuery ? controlledQuery.state.layeredFilters ?? [] : internalLayeredFilters;
9962
11914
  const setLayeredFilters = (next) => {
9963
11915
  if (controlledQuery) {
@@ -9966,7 +11918,7 @@ function MatrxDataTableCore({
9966
11918
  }
9967
11919
  setInternalLayeredFilters(next);
9968
11920
  };
9969
- const [internalColumnFilters, setInternalColumnFilters] = useState21({});
11921
+ const [internalColumnFilters, setInternalColumnFilters] = useState22({});
9970
11922
  const columnFilters = controlledQuery ? controlledQuery.state.columnFilters : internalColumnFilters;
9971
11923
  const setColumnFilter = (id, next) => {
9972
11924
  const nextFilters = { ...columnFilters, [id]: next };
@@ -9979,7 +11931,7 @@ function MatrxDataTableCore({
9979
11931
  }
9980
11932
  setInternalColumnFilters(nextFilters);
9981
11933
  };
9982
- const [internalSort, setInternalSort] = useState21(defaultSort);
11934
+ const [internalSort, setInternalSort] = useState22(defaultSort);
9983
11935
  const sort = controlledQuery ? controlledQuery.state.sort : internalSort;
9984
11936
  const setSort = (next) => {
9985
11937
  if (controlledQuery) {
@@ -9988,7 +11940,7 @@ function MatrxDataTableCore({
9988
11940
  }
9989
11941
  setInternalSort(next);
9990
11942
  };
9991
- const [internalPage, setInternalPage] = useState21(1);
11943
+ const [internalPage, setInternalPage] = useState22(1);
9992
11944
  const page = controlledQuery ? controlledQuery.state.page : internalPage;
9993
11945
  const setPage = (next) => {
9994
11946
  if (controlledQuery) {
@@ -9997,7 +11949,7 @@ function MatrxDataTableCore({
9997
11949
  }
9998
11950
  setInternalPage(next);
9999
11951
  };
10000
- const [internalPageSize, setInternalPageSize] = useState21(
11952
+ const [internalPageSize, setInternalPageSize] = useState22(
10001
11953
  defaultPageSize
10002
11954
  );
10003
11955
  const pageSize = controlledQuery ? controlledQuery.state.pageSize : internalPageSize;
@@ -10008,28 +11960,28 @@ function MatrxDataTableCore({
10008
11960
  }
10009
11961
  setInternalPageSize(next);
10010
11962
  };
10011
- const [uncontrolledSelectedId, setUncontrolledSelectedId] = useState21(null);
11963
+ const [uncontrolledSelectedId, setUncontrolledSelectedId] = useState22(null);
10012
11964
  const rawSelectedId = controlledSelectedId !== void 0 ? controlledSelectedId : uncontrolledSelectedId;
10013
11965
  const selectedId = appendSourceChanged ? null : rawSelectedId;
10014
11966
  const setSelectedId = (id) => {
10015
11967
  if (controlledSelectedId === void 0) setUncontrolledSelectedId(id);
10016
11968
  onSelectedIdChange?.(id);
10017
11969
  };
10018
- const [uncontrolledWindowRowId, setUncontrolledWindowRowId] = useState21(null);
11970
+ const [uncontrolledWindowRowId, setUncontrolledWindowRowId] = useState22(null);
10019
11971
  const rawWindowRowId = controlledWindowRowId !== void 0 ? controlledWindowRowId : uncontrolledWindowRowId;
10020
11972
  const windowRowId = appendSourceChanged ? null : rawWindowRowId;
10021
11973
  const setWindowRowId = (id) => {
10022
11974
  if (controlledWindowRowId === void 0) setUncontrolledWindowRowId(id);
10023
11975
  onWindowRowIdChange?.(id);
10024
11976
  };
10025
- const [windowRowSnapshot, setWindowRowSnapshot] = useState21(null);
11977
+ const [windowRowSnapshot, setWindowRowSnapshot] = useState22(null);
10026
11978
  const editScopeKey = appendQueryKey === null ? "\0local" : `append:${appendQueryKey}`;
10027
- const [editsByScope, setEditsByScope] = useState21({});
10028
- const editRevisionRef = useRef13({});
10029
- const [savingByScope, setSavingByScope] = useState21({});
10030
- const savingScopesRef = useRef13(/* @__PURE__ */ new Set());
10031
- const [editRequest, setEditRequest] = useState21(null);
10032
- const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = useState21(false);
11979
+ const [editsByScope, setEditsByScope] = useState22({});
11980
+ const editRevisionRef = useRef15({});
11981
+ const [savingByScope, setSavingByScope] = useState22({});
11982
+ const savingScopesRef = useRef15(/* @__PURE__ */ new Set());
11983
+ const [editRequest, setEditRequest] = useState22(null);
11984
+ const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = useState22(false);
10033
11985
  const edits = editsByScope[editScopeKey]?.values ?? {};
10034
11986
  const saving = (savingByScope[editScopeKey] ?? 0) > 0;
10035
11987
  const setEdits = (next) => {
@@ -10096,7 +12048,7 @@ function MatrxDataTableCore({
10096
12048
  rows: Object.entries(draft.values).map(([rowId, columns2]) => ({ rowId, columns: columns2 }))
10097
12049
  }));
10098
12050
  const selection = appendSourceChanged && selectionConfig ? { ...selectionConfig, selectedIds: [] } : selectionConfig;
10099
- useEffect15(() => {
12051
+ useEffect16(() => {
10100
12052
  const previous = committedAppendQueryKey.current;
10101
12053
  committedAppendQueryKey.current = appendQueryKey;
10102
12054
  if (previous === null || appendQueryKey === null || previous === appendQueryKey) {
@@ -10114,13 +12066,13 @@ function MatrxDataTableCore({
10114
12066
  onWindowRowIdChange,
10115
12067
  selectionConfig
10116
12068
  ]);
10117
- const scrollRef = useRef13(null);
10118
- const tableRef = useRef13(null);
10119
- const tableHeaderRef = useRef13(null);
10120
- const cardsScrollRef = useRef13(null);
10121
- const [documentStickyHeader, setDocumentStickyHeader] = useState21(false);
10122
- const documentStickyHeaderRef = useRef13(false);
10123
- const previousAppendKeyForScrollReset = useRef13(appendQueryKey);
12069
+ const scrollRef = useRef15(null);
12070
+ const tableRef = useRef15(null);
12071
+ const tableHeaderRef = useRef15(null);
12072
+ const cardsScrollRef = useRef15(null);
12073
+ const [documentStickyHeader, setDocumentStickyHeader] = useState22(false);
12074
+ const documentStickyHeaderRef = useRef15(false);
12075
+ const previousAppendKeyForScrollReset = useRef15(appendQueryKey);
10124
12076
  useLayoutEffect2(() => {
10125
12077
  const previous = previousAppendKeyForScrollReset.current;
10126
12078
  previousAppendKeyForScrollReset.current = appendQueryKey;
@@ -10146,13 +12098,13 @@ function MatrxDataTableCore({
10146
12098
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.thresholdPx !== void 0 ? { thresholdPx: appendScrollPolicy.thresholdPx } : {},
10147
12099
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.intentTimeoutMs !== void 0 ? { intentTimeoutMs: appendScrollPolicy.intentTimeoutMs } : {}
10148
12100
  });
10149
- const rootRef = useRef13(null);
12101
+ const rootRef = useRef15(null);
10150
12102
  useClippedContentGuard(rootRef, {
10151
12103
  label: typeof copy === "object" && copy.listLabel ? `Table: ${copy.listLabel}` : "MatrxDataTable"
10152
12104
  });
10153
- const [scrollHintRight, setScrollHintRight] = useState21(false);
10154
- const [scrollHintLeft, setScrollHintLeft] = useState21(false);
10155
- const updateScrollHint = useCallback7(() => {
12105
+ const [scrollHintRight, setScrollHintRight] = useState22(false);
12106
+ const [scrollHintLeft, setScrollHintLeft] = useState22(false);
12107
+ const updateScrollHint = useCallback8(() => {
10156
12108
  const el = scrollRef.current;
10157
12109
  if (!el) return;
10158
12110
  setScrollHintRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 2);
@@ -10166,7 +12118,7 @@ function MatrxDataTableCore({
10166
12118
  behavior: "smooth"
10167
12119
  });
10168
12120
  };
10169
- useEffect15(() => {
12121
+ useEffect16(() => {
10170
12122
  updateScrollHint();
10171
12123
  window.addEventListener("resize", updateScrollHint);
10172
12124
  const el = scrollRef.current;
@@ -10240,30 +12192,30 @@ function MatrxDataTableCore({
10240
12192
  window.removeEventListener("resize", requestUpdate);
10241
12193
  };
10242
12194
  }, [stickyHeader, data, columns]);
10243
- const [columnOrder, setColumnOrder] = useState21(() => columnState?.order ?? columns.map(columnId));
10244
- const [hiddenColumnIds, setHiddenColumnIds] = useState21(
12195
+ const [columnOrder, setColumnOrder] = useState22(() => columnState?.order ?? columns.map(columnId));
12196
+ const [hiddenColumnIds, setHiddenColumnIds] = useState22(
10245
12197
  () => new Set(columnState?.hidden ?? columns.filter((column) => column.hidden).map(columnId))
10246
12198
  );
10247
- const [columnWidths, setColumnWidths] = useState21(
12199
+ const [columnWidths, setColumnWidths] = useState22(
10248
12200
  () => normalizeColumnWidthsForColumns(columnState?.widths, columns)
10249
12201
  );
10250
- const [fixedTableGeometry, setFixedTableGeometry] = useState21(null);
10251
- const knownColumnIds = useRef13(new Set(columns.map(columnId)));
10252
- const draggedColumnId = useRef13(null);
10253
- const columnDragActive = useRef13(false);
10254
- const columnDragRaf = useRef13(null);
10255
- const columnDragScrollDirection = useRef13(0);
10256
- const [columnDragPreview, setColumnDragPreview] = useState21(null);
10257
- const stopColumnDragScroll = useCallback7(() => {
12202
+ const [fixedTableGeometry, setFixedTableGeometry] = useState22(null);
12203
+ const knownColumnIds = useRef15(new Set(columns.map(columnId)));
12204
+ const draggedColumnId = useRef15(null);
12205
+ const columnDragActive = useRef15(false);
12206
+ const columnDragRaf = useRef15(null);
12207
+ const columnDragScrollDirection = useRef15(0);
12208
+ const [columnDragPreview, setColumnDragPreview] = useState22(null);
12209
+ const stopColumnDragScroll = useCallback8(() => {
10258
12210
  if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
10259
12211
  columnDragRaf.current = null;
10260
12212
  columnDragScrollDirection.current = 0;
10261
12213
  }, []);
10262
- useEffect15(() => () => {
12214
+ useEffect16(() => () => {
10263
12215
  columnDragActive.current = false;
10264
12216
  stopColumnDragScroll();
10265
12217
  }, [stopColumnDragScroll]);
10266
- const startColumnDragScroll = useCallback7(() => {
12218
+ const startColumnDragScroll = useCallback8(() => {
10267
12219
  if (!columnDragActive.current || columnDragRaf.current !== null) return;
10268
12220
  const scrollStep = () => {
10269
12221
  const scroll = scrollRef.current;
@@ -10290,25 +12242,25 @@ function MatrxDataTableCore({
10290
12242
  };
10291
12243
  columnDragRaf.current = requestAnimationFrame(scrollStep);
10292
12244
  }, []);
10293
- const commitColumnState = useCallback7((order, hidden) => {
12245
+ const commitColumnState = useCallback8((order, hidden) => {
10294
12246
  if (columnState) columnState.onChange({ order, hidden: [...hidden] });
10295
12247
  else {
10296
12248
  setColumnOrder(order);
10297
12249
  setHiddenColumnIds(hidden);
10298
12250
  }
10299
12251
  }, [columnState]);
10300
- const [internalColumnManagerOpen, setInternalColumnManagerOpen] = useState21(false);
12252
+ const [internalColumnManagerOpen, setInternalColumnManagerOpen] = useState22(false);
10301
12253
  const columnManagerOpen = controlledColumnManager?.open ?? internalColumnManagerOpen;
10302
12254
  const setColumnManagerOpen = controlledColumnManager?.onOpenChange ?? setInternalColumnManagerOpen;
10303
- const columnIds = useMemo7(() => columns.map(columnId), [columns]);
10304
- const effectiveColumnOrder = useMemo7(() => 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]);
10305
- const effectiveHiddenColumnIds = useMemo7(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
10306
- const effectiveColumnWidths = useMemo7(
12255
+ const columnIds = useMemo8(() => columns.map(columnId), [columns]);
12256
+ const effectiveColumnOrder = useMemo8(() => 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]);
12257
+ const effectiveHiddenColumnIds = useMemo8(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
12258
+ const effectiveColumnWidths = useMemo8(
10307
12259
  () => normalizeColumnWidthsForColumns(columnState?.widths ?? columnWidths, columns),
10308
12260
  [columns, columnState?.widths, columnWidths]
10309
12261
  );
10310
- const resizeGestureSnapshotRef = useRef13(null);
10311
- const measureVisibleColumnWidths = useCallback7(() => {
12262
+ const resizeGestureSnapshotRef = useRef15(null);
12263
+ const measureVisibleColumnWidths = useCallback8(() => {
10312
12264
  const measured = {};
10313
12265
  const headers = tableRef.current ? Array.from(tableRef.current.querySelectorAll(
10314
12266
  "thead th[data-matrx-table-column-id]"
@@ -10322,7 +12274,7 @@ function MatrxDataTableCore({
10322
12274
  }
10323
12275
  return normalizeColumnWidthsForColumns(measured, columns);
10324
12276
  }, [columns]);
10325
- const measureFixedTableGeometry = useCallback7(() => {
12277
+ const measureFixedTableGeometry = useCallback8(() => {
10326
12278
  const table = tableRef.current;
10327
12279
  const headers = table ? Array.from(table.querySelectorAll("thead th")) : [];
10328
12280
  const widthFor = (selector) => {
@@ -10357,16 +12309,16 @@ function MatrxDataTableCore({
10357
12309
  ...actionsWidth === void 0 ? {} : { actionsWidth }
10358
12310
  };
10359
12311
  }, [resolvedDensity]);
10360
- const captureResizeGestureSnapshot = useCallback7(() => {
12312
+ const captureResizeGestureSnapshot = useCallback8(() => {
10361
12313
  resizeGestureSnapshotRef.current = {
10362
12314
  widths: measureVisibleColumnWidths(),
10363
12315
  geometry: measureFixedTableGeometry()
10364
12316
  };
10365
12317
  }, [measureFixedTableGeometry, measureVisibleColumnWidths]);
10366
- const clearResizeGestureSnapshot = useCallback7(() => {
12318
+ const clearResizeGestureSnapshot = useCallback8(() => {
10367
12319
  resizeGestureSnapshotRef.current = null;
10368
12320
  }, []);
10369
- const commitColumnWidths = useCallback7((next, captureInitialWidths = false) => {
12321
+ const commitColumnWidths = useCallback8((next, captureInitialWidths = false) => {
10370
12322
  const gestureSnapshot = captureInitialWidths ? resizeGestureSnapshotRef.current : null;
10371
12323
  resizeGestureSnapshotRef.current = null;
10372
12324
  const normalized = normalizeColumnWidthsForColumns(
@@ -10385,7 +12337,7 @@ function MatrxDataTableCore({
10385
12337
  }
10386
12338
  setColumnWidths(normalized);
10387
12339
  }, [columnIds, columnState, columns, measureFixedTableGeometry, measureVisibleColumnWidths]);
10388
- useEffect15(() => {
12340
+ useEffect16(() => {
10389
12341
  const ids = columns.map(columnId);
10390
12342
  if (columnState) return;
10391
12343
  const previousIds = knownColumnIds.current;
@@ -10409,12 +12361,12 @@ function MatrxDataTableCore({
10409
12361
  tableRef.current?.style.removeProperty(columnResizeCssVar(id));
10410
12362
  }
10411
12363
  });
10412
- const previewColumnOrder = useMemo7(
12364
+ const previewColumnOrder = useMemo8(
10413
12365
  () => projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
10414
12366
  [columnDragPreview, effectiveColumnOrder]
10415
12367
  );
10416
12368
  useColumnReorderAnimation(tableRef, previewColumnOrder.join("\0"), columnDragPreview !== null);
10417
- const visibleColumns = useMemo7(() => {
12369
+ const visibleColumns = useMemo8(() => {
10418
12370
  const byId = new Map(columns.map((column) => [columnId(column), column]));
10419
12371
  return previewColumnOrder.map((id) => byId.get(id)).filter(
10420
12372
  (column) => column !== void 0 && !effectiveHiddenColumnIds.has(columnId(column))
@@ -10450,7 +12402,7 @@ function MatrxDataTableCore({
10450
12402
  setFixedTableGeometry(geometry);
10451
12403
  }, [resolvedDensity, measureFixedTableGeometry, usesMeasuredFixedLayout]);
10452
12404
  const fixedLayoutActive = usesMeasuredFixedLayout && fixedTableGeometry !== null;
10453
- const frozenColumns = useMemo7(() => {
12405
+ const frozenColumns = useMemo8(() => {
10454
12406
  if (!visibleColumns.some((column) => column.frozen)) return NO_FROZEN_COLUMNS;
10455
12407
  const ids = visibleColumns.map(columnId);
10456
12408
  const byId = new Map(visibleColumns.map((column) => [columnId(column), column]));
@@ -10492,7 +12444,7 @@ function MatrxDataTableCore({
10492
12444
  );
10493
12445
  return color ? CELL_TINT_CLASS[color] : void 0;
10494
12446
  };
10495
- const declaredVisibleCopyColumns = useMemo7(
12447
+ const declaredVisibleCopyColumns = useMemo8(
10496
12448
  () => visibleColumns.filter((column) => column.accessorFn !== void 0 || column.accessorKey !== void 0),
10497
12449
  [visibleColumns]
10498
12450
  );
@@ -10500,7 +12452,7 @@ function MatrxDataTableCore({
10500
12452
  const tableContextMenuInstanceId = useId2();
10501
12453
  const tableDndContextId = useId2();
10502
12454
  const defaultCopyTitle = toolbar?.title || "Table";
10503
- const defaultCopy = useMemo7(() => ({
12455
+ const defaultCopy = useMemo8(() => ({
10504
12456
  label: defaultCopyTitle,
10505
12457
  listLabel: defaultCopyTitle,
10506
12458
  location: defaultCopyTitle,
@@ -10535,7 +12487,7 @@ function MatrxDataTableCore({
10535
12487
  serializer: (shaped) => buildViewAgentInput(effectiveCopy, shaped, data, { scope: "custom" })
10536
12488
  }))
10537
12489
  ] : [];
10538
- const filterMeta = useMemo7(() => {
12490
+ const filterMeta = useMemo8(() => {
10539
12491
  const meta = /* @__PURE__ */ new Map();
10540
12492
  for (const col of visibleColumns) {
10541
12493
  const id = columnId(col);
@@ -10543,14 +12495,14 @@ function MatrxDataTableCore({
10543
12495
  }
10544
12496
  return meta;
10545
12497
  }, [visibleColumns, data, remoteQuery]);
10546
- const intelligentSearchScopes = useMemo7(
12498
+ const intelligentSearchScopes = useMemo8(
10547
12499
  () => resolveTableSearchScopes(data, columns, {
10548
12500
  ...toolbar?.intelligentSearch?.roles === void 0 ? {} : { roles: toolbar.intelligentSearch.roles },
10549
12501
  ...toolbar?.intelligentSearch?.scopes === void 0 ? {} : { scopes: toolbar.intelligentSearch.scopes }
10550
12502
  }),
10551
12503
  [columns, data, toolbar?.intelligentSearch?.roles, toolbar?.intelligentSearch?.scopes]
10552
12504
  );
10553
- const processed = useMemo7(() => {
12505
+ const processed = useMemo8(() => {
10554
12506
  if (remoteQuery && !appendUsesLocalProcessing) return data;
10555
12507
  if (processLocalRows) {
10556
12508
  return processLocalRows(data, {
@@ -10613,8 +12565,8 @@ function MatrxDataTableCore({
10613
12565
  intelligentSearchEnabled,
10614
12566
  searchScope
10615
12567
  ]);
10616
- const lastNotifiedViewRows = useRef13(null);
10617
- useEffect15(() => {
12568
+ const lastNotifiedViewRows = useRef15(null);
12569
+ useEffect16(() => {
10618
12570
  if (!onViewChange) {
10619
12571
  lastNotifiedViewRows.current = null;
10620
12572
  return;
@@ -10623,7 +12575,7 @@ function MatrxDataTableCore({
10623
12575
  lastNotifiedViewRows.current = processed;
10624
12576
  if (changed) onViewChange(processed);
10625
12577
  }, [processed, onViewChange]);
10626
- useEffect15(() => {
12578
+ useEffect16(() => {
10627
12579
  if (controlledQuery) return;
10628
12580
  setInternalPage(1);
10629
12581
  }, [
@@ -10667,7 +12619,7 @@ function MatrxDataTableCore({
10667
12619
  numberedPageHasMore: !hidePagination && !isLoading && !isFetching && processed.length > 0 && safePage < pageCount && (remoteQuery?.mode === "controlled" || !remoteQuery && localPagination?.mode === "numbered"),
10668
12620
  onPageAdvance: setPage
10669
12621
  });
10670
- const paginated = useMemo7(() => {
12622
+ const paginated = useMemo8(() => {
10671
12623
  if (remoteQuery) return processed;
10672
12624
  if (hidePagination) return processed;
10673
12625
  if (localProgressiveEnabled) return [...progressiveRows.rows];
@@ -10675,15 +12627,351 @@ function MatrxDataTableCore({
10675
12627
  const start = (safePage - 1) * effectivePageSize;
10676
12628
  return processed.slice(start, start + effectivePageSize);
10677
12629
  }, [processed, safePage, effectivePageSize, pageSize, remoteQuery, hidePagination, localProgressiveEnabled, progressiveRows.rows]);
10678
- useEffect15(() => {
12630
+ const groupingColumnId = grouping?.columnId ?? null;
12631
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState22([]);
12632
+ const collapsedGroups = grouping?.collapsed ?? uncontrolledCollapsed;
12633
+ const setCollapsedGroups = useCallback8(
12634
+ (next) => {
12635
+ if (grouping?.onCollapsedChange) grouping.onCollapsedChange(next);
12636
+ else setUncontrolledCollapsed(next);
12637
+ },
12638
+ [grouping]
12639
+ );
12640
+ const groups = useMemo8(() => {
12641
+ if (!grouping || !groupingColumnId) return null;
12642
+ const groupColumn = columns.find((col) => columnId(col) === groupingColumnId);
12643
+ if (!groupColumn) return null;
12644
+ const aggregateColumns = {};
12645
+ for (const [aggColumnId, spec] of Object.entries(grouping.aggregates ?? {})) {
12646
+ const col = columns.find((c) => columnId(c) === aggColumnId);
12647
+ if (!col) continue;
12648
+ aggregateColumns[aggColumnId] = { spec, readCell: (row) => getCellValue(row, col) };
12649
+ }
12650
+ return groupRows({
12651
+ rows: paginated,
12652
+ columnId: groupingColumnId,
12653
+ readCell: (row) => grouping.readCell ? grouping.readCell(row, groupingColumnId) : getCellValue(row, groupColumn),
12654
+ aggregates: aggregateColumns,
12655
+ ...grouping.order ? { order: grouping.order } : {},
12656
+ ...grouping.emptyLabel ? { emptyLabel: grouping.emptyLabel } : {}
12657
+ });
12658
+ }, [grouping, groupingColumnId, columns, paginated]);
12659
+ const groupableColumns = useMemo8(() => {
12660
+ const allowed = grouping?.groupableColumnIds;
12661
+ return visibleColumns.filter((col) => {
12662
+ const id = columnId(col);
12663
+ if (allowed) return allowed.includes(id);
12664
+ return col.accessorKey !== void 0 || col.accessorFn !== void 0;
12665
+ });
12666
+ }, [visibleColumns, grouping?.groupableColumnIds]);
12667
+ const groupedRenderRows = useMemo8(
12668
+ () => groups ? flattenGroups(groups, collapsedGroups) : null,
12669
+ [groups, collapsedGroups]
12670
+ );
12671
+ const virtualizeConfig = virtualize === void 0 || virtualize === false ? null : virtualize === true ? {} : virtualize;
12672
+ const virtualRowHeight = explicitRowHeight ?? virtualizeConfig?.rowHeight ?? (resolvedDensity === "condensed" ? 32 : resolvedDensity === "spacious" ? 52 : 40);
12673
+ const virtualThreshold = virtualizeConfig?.threshold ?? 150;
12674
+ const bodyEntryCount = groupedRenderRows ? groupedRenderRows.length : paginated.length;
12675
+ const virtualizeActive = virtualizeConfig !== null && virtualizeConfig.enabled !== false && bodyEntryCount > virtualThreshold && !hierarchy && expandedDetail === void 0;
12676
+ const columnWindowWidth = virtualizeConfig?.columnWidth ?? 160;
12677
+ const frozenRunLength = useMemo8(() => {
12678
+ let run = 0;
12679
+ for (const col of visibleColumns) {
12680
+ if (frozenColumns.offsets[columnId(col)] === void 0) break;
12681
+ run += 1;
12682
+ }
12683
+ return run;
12684
+ }, [visibleColumns, frozenColumns]);
12685
+ const columnVirtualizeActive = virtualizeConfig !== null && virtualizeConfig.enabled !== false && virtualizeConfig.columns === true && visibleColumns.length - frozenRunLength > 30 && !hierarchy && mobileCards === void 0;
12686
+ const [rowWindow, setRowWindow] = useState22(null);
12687
+ const [columnWindow, setColumnWindow] = useState22(null);
12688
+ const windowInputs = useRef15({
12689
+ virtualizeActive: false,
12690
+ bodyEntryCount: 0,
12691
+ virtualRowHeight: 40,
12692
+ overscan: 8,
12693
+ columnVirtualizeActive: false,
12694
+ columnCount: 0,
12695
+ columnWindowWidth: 160,
12696
+ frozenRunLength: 0
12697
+ });
12698
+ windowInputs.current = {
12699
+ virtualizeActive,
12700
+ bodyEntryCount,
12701
+ virtualRowHeight,
12702
+ overscan: virtualizeConfig?.overscan ?? 8,
12703
+ columnVirtualizeActive,
12704
+ columnCount: visibleColumns.length,
12705
+ columnWindowWidth,
12706
+ frozenRunLength
12707
+ };
12708
+ const recomputeWindows = useCallback8(() => {
12709
+ const el = scrollRef.current;
12710
+ const input = windowInputs.current;
12711
+ const nextRow = input.virtualizeActive ? computeWindow({
12712
+ count: input.bodyEntryCount,
12713
+ itemSize: input.virtualRowHeight,
12714
+ viewport: el?.clientHeight || 600,
12715
+ offset: el?.scrollTop ?? 0,
12716
+ overscan: input.overscan
12717
+ }) : null;
12718
+ const nextColumn = input.columnVirtualizeActive ? computeWindow({
12719
+ count: input.columnCount,
12720
+ itemSize: input.columnWindowWidth,
12721
+ viewport: el?.clientWidth || 1200,
12722
+ offset: el?.scrollLeft ?? 0,
12723
+ overscan: Math.max(2, Math.round(input.overscan / 2)),
12724
+ pinnedStart: input.frozenRunLength
12725
+ }) : null;
12726
+ setRowWindow((prev) => sameWindow(prev, nextRow) ? prev : nextRow);
12727
+ setColumnWindow((prev) => sameWindow(prev, nextColumn) ? prev : nextColumn);
12728
+ }, []);
12729
+ const effectiveRowWindow = useMemo8(() => {
12730
+ if (!virtualizeActive) return null;
12731
+ if (rowWindow && rowWindow.end <= bodyEntryCount) return rowWindow;
12732
+ return computeWindow({
12733
+ count: bodyEntryCount,
12734
+ itemSize: virtualRowHeight,
12735
+ viewport: scrollRef.current?.clientHeight || 600,
12736
+ offset: scrollRef.current?.scrollTop ?? 0,
12737
+ overscan: virtualizeConfig?.overscan ?? 8
12738
+ });
12739
+ }, [virtualizeActive, rowWindow, bodyEntryCount, virtualRowHeight, virtualizeConfig?.overscan]);
12740
+ const effectiveColumnWindow = useMemo8(() => {
12741
+ if (!columnVirtualizeActive) return null;
12742
+ if (columnWindow && columnWindow.end <= visibleColumns.length) return columnWindow;
12743
+ return computeWindow({
12744
+ count: visibleColumns.length,
12745
+ itemSize: columnWindowWidth,
12746
+ viewport: scrollRef.current?.clientWidth || 1200,
12747
+ offset: scrollRef.current?.scrollLeft ?? 0,
12748
+ overscan: Math.max(2, Math.round((virtualizeConfig?.overscan ?? 8) / 2)),
12749
+ pinnedStart: frozenRunLength
12750
+ });
12751
+ }, [
12752
+ columnVirtualizeActive,
12753
+ columnWindow,
12754
+ visibleColumns.length,
12755
+ columnWindowWidth,
12756
+ virtualizeConfig?.overscan,
12757
+ frozenRunLength
12758
+ ]);
12759
+ const metricsWanted = virtualizeActive || columnVirtualizeActive;
12760
+ useEffect16(() => {
12761
+ recomputeWindows();
12762
+ }, [
12763
+ recomputeWindows,
12764
+ virtualizeActive,
12765
+ bodyEntryCount,
12766
+ virtualRowHeight,
12767
+ columnVirtualizeActive,
12768
+ visibleColumns.length,
12769
+ columnWindowWidth,
12770
+ frozenRunLength
12771
+ ]);
12772
+ useEffect16(() => {
12773
+ if (!metricsWanted) return;
12774
+ const el = scrollRef.current;
12775
+ if (!el) return;
12776
+ let frame = 0;
12777
+ const onScroll = () => {
12778
+ if (frame) return;
12779
+ frame = requestAnimationFrame(() => {
12780
+ frame = 0;
12781
+ recomputeWindows();
12782
+ });
12783
+ };
12784
+ el.addEventListener("scroll", onScroll, { passive: true });
12785
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(onScroll);
12786
+ observer?.observe(el);
12787
+ return () => {
12788
+ if (frame) cancelAnimationFrame(frame);
12789
+ el.removeEventListener("scroll", onScroll);
12790
+ observer?.disconnect();
12791
+ };
12792
+ }, [metricsWanted, recomputeWindows]);
12793
+ const renderedColumns = useMemo8(
12794
+ () => effectiveColumnWindow ? [
12795
+ ...visibleColumns.slice(0, frozenRunLength),
12796
+ ...visibleColumns.slice(effectiveColumnWindow.start, effectiveColumnWindow.end)
12797
+ ] : visibleColumns,
12798
+ [effectiveColumnWindow, visibleColumns, frozenRunLength]
12799
+ );
12800
+ const columnSpacers = effectiveColumnWindow ? { start: effectiveColumnWindow.padStart, end: effectiveColumnWindow.padEnd } : null;
12801
+ const windowedWidthFor = useCallback8(
12802
+ (id, declared) => {
12803
+ const resized = effectiveColumnWidths[id];
12804
+ if (!effectiveColumnWindow) return resized;
12805
+ if (frozenColumns.offsets[id] !== void 0) return resized;
12806
+ return resized ?? (typeof declared === "number" ? declared : columnWindowWidth);
12807
+ },
12808
+ [effectiveColumnWindow, effectiveColumnWidths, frozenColumns, columnWindowWidth]
12809
+ );
12810
+ const coverageResult = useMemo8(() => {
12811
+ if (!coverage) return null;
12812
+ return describeCoverage({
12813
+ loaded: processed.length,
12814
+ ...coverage.matched === void 0 ? {} : { matched: coverage.matched },
12815
+ ...coverage.total === void 0 ? {} : { total: coverage.total },
12816
+ ...coverage.cap === void 0 ? {} : { cap: coverage.cap },
12817
+ answeredBy: coverage.answeredBy ?? "client",
12818
+ ...coverage.noun === void 0 ? {} : { noun: coverage.noun }
12819
+ });
12820
+ }, [coverage, processed.length]);
12821
+ const bodyEntries = useMemo8(() => {
12822
+ const from = effectiveRowWindow ? effectiveRowWindow.start : 0;
12823
+ const to = effectiveRowWindow ? effectiveRowWindow.end : void 0;
12824
+ const windowed = groupedRenderRows ? groupedRenderRows.slice(from, to).map(
12825
+ (entry) => entry.kind === "group" ? {
12826
+ kind: "group",
12827
+ group: entry.group,
12828
+ collapsed: isGroupCollapsed(collapsedGroups, entry.group.key)
12829
+ } : { kind: "row", row: entry.row, index: entry.dataIndex ?? 0 }
12830
+ ) : paginated.slice(from, to).map((row, offset) => ({ kind: "row", row, index: from + offset }));
12831
+ if (!effectiveRowWindow) return windowed;
12832
+ if (effectiveRowWindow.padStart > 0)
12833
+ windowed.unshift({ kind: "spacer", height: effectiveRowWindow.padStart, place: "start" });
12834
+ if (effectiveRowWindow.padEnd > 0)
12835
+ windowed.push({ kind: "spacer", height: effectiveRowWindow.padEnd, place: "end" });
12836
+ return windowed;
12837
+ }, [groupedRenderRows, collapsedGroups, paginated, effectiveRowWindow]);
12838
+ const getRowIdRef = useRef15(getRowId);
12839
+ getRowIdRef.current = getRowId;
12840
+ const spreadsheetRowIds = useMemo8(
12841
+ () => paginated.map((row) => getRowIdRef.current(row)),
12842
+ [paginated]
12843
+ );
12844
+ const spreadsheetColumnIds = useMemo8(
12845
+ () => visibleColumns.map((col) => columnId(col)),
12846
+ [visibleColumns]
12847
+ );
12848
+ const spreadsheetRowById = useMemo8(() => {
12849
+ const map = /* @__PURE__ */ new Map();
12850
+ for (const row of paginated) map.set(getRowIdRef.current(row), row);
12851
+ return map;
12852
+ }, [paginated]);
12853
+ const spreadsheetColumnById = useMemo8(() => {
12854
+ const map = /* @__PURE__ */ new Map();
12855
+ for (const col of visibleColumns) map.set(columnId(col), col);
12856
+ return map;
12857
+ }, [visibleColumns]);
12858
+ const spreadsheetEnabled = Boolean(spreadsheet && spreadsheet.enabled !== false);
12859
+ const sheet = useSpreadsheetGrid(
12860
+ spreadsheetEnabled && spreadsheet ? {
12861
+ rowIds: spreadsheetRowIds,
12862
+ columnIds: spreadsheetColumnIds,
12863
+ valueAt: (rowId, colId) => {
12864
+ const row = spreadsheetRowById.get(rowId);
12865
+ const col = spreadsheetColumnById.get(colId);
12866
+ if (row === void 0 || col === void 0) return null;
12867
+ if (spreadsheet.readCell) return spreadsheet.readCell(row, colId);
12868
+ return getCellValue(applyRowEdits(row, edits[rowId]), col);
12869
+ },
12870
+ isColumnWritable: (colId) => {
12871
+ if (spreadsheet.writableColumnIds) return spreadsheet.writableColumnIds.includes(colId);
12872
+ const col = spreadsheetColumnById.get(colId);
12873
+ return Boolean(col && col.editable);
12874
+ },
12875
+ ...spreadsheet.columnRefusal ? { columnRefusal: spreadsheet.columnRefusal } : {},
12876
+ onPatch: spreadsheet.onPatch,
12877
+ ...spreadsheet.undoDepth === void 0 ? {} : { undoDepth: spreadsheet.undoDepth },
12878
+ ...spreadsheet.fillHandle === void 0 ? {} : { fillHandle: spreadsheet.fillHandle },
12879
+ ...spreadsheet.paste === void 0 ? {} : { paste: spreadsheet.paste },
12880
+ notify: spreadsheet.notify ?? ((message) => toast.success(message)),
12881
+ onEditRequest: (address, seed) => requestCellEditRef.current(address, seed),
12882
+ ...spreadsheet.validateCell ? { validateCell: spreadsheet.validateCell } : {}
12883
+ } : null
12884
+ );
12885
+ const sheetRef = useRef15(sheet);
12886
+ sheetRef.current = sheet;
12887
+ const searchValueRef = useRef15(searchValue);
12888
+ searchValueRef.current = searchValue;
12889
+ const facetsEnabled = Boolean(facets && facets.enabled !== false);
12890
+ const [facetState, setFacetState] = useState22({});
12891
+ const facetReadCell = useCallback8(
12892
+ (row, colId) => {
12893
+ if (facets?.readCell) return facets.readCell(row, colId);
12894
+ const col = visibleColumns.find((candidate) => columnId(candidate) === colId);
12895
+ return col ? getFilterValue(row, col) : void 0;
12896
+ },
12897
+ [facets, visibleColumns]
12898
+ );
12899
+ const facetInputsRef = useRef15({ data, facets, facetReadCell });
12900
+ facetInputsRef.current = { data, facets, facetReadCell };
12901
+ const facetRequestRef = useRef15(/* @__PURE__ */ new Map());
12902
+ const requestFacets = useCallback8((colId) => {
12903
+ const { data: rows, facets: config, facetReadCell: readCell } = facetInputsRef.current;
12904
+ if (!config || config.enabled === false) return;
12905
+ const complete = localFacetsAreComplete(rows.length, config.totalRows);
12906
+ const limit = config.limit;
12907
+ if (complete || !config.source) {
12908
+ setFacetState((current) => ({
12909
+ ...current,
12910
+ [colId]: {
12911
+ loading: false,
12912
+ data: computeColumnFacets({
12913
+ columnId: colId,
12914
+ rows,
12915
+ readValue: readCell,
12916
+ ...limit === void 0 ? {} : { limit },
12917
+ complete
12918
+ })
12919
+ }
12920
+ }));
12921
+ return;
12922
+ }
12923
+ const ticket = (facetRequestRef.current.get(colId) ?? 0) + 1;
12924
+ facetRequestRef.current.set(colId, ticket);
12925
+ setFacetState((current) => ({
12926
+ ...current,
12927
+ [colId]: { loading: true, data: current[colId]?.data ?? null }
12928
+ }));
12929
+ void Promise.resolve().then(
12930
+ () => config.source?.({ columnId: colId, search: searchValueRef.current, limit: limit ?? 200 })
12931
+ ).then((answer) => {
12932
+ if (facetRequestRef.current.get(colId) !== ticket) return;
12933
+ if (!answer) {
12934
+ setFacetState((current) => ({
12935
+ ...current,
12936
+ [colId]: {
12937
+ loading: false,
12938
+ data: computeColumnFacets({
12939
+ columnId: colId,
12940
+ rows: facetInputsRef.current.data,
12941
+ readValue: facetInputsRef.current.facetReadCell,
12942
+ ...limit === void 0 ? {} : { limit },
12943
+ complete: false
12944
+ })
12945
+ }
12946
+ }));
12947
+ return;
12948
+ }
12949
+ setFacetState((current) => ({
12950
+ ...current,
12951
+ [colId]: { loading: false, data: { ...answer, answeredBy: "source", complete: true } }
12952
+ }));
12953
+ }).catch((error) => {
12954
+ if (facetRequestRef.current.get(colId) !== ticket) return;
12955
+ setFacetState((current) => ({
12956
+ ...current,
12957
+ [colId]: { loading: false, data: null, error: describeFacetFailure(error) }
12958
+ }));
12959
+ });
12960
+ }, []);
12961
+ useEffect16(() => {
12962
+ if (!facetsEnabled) return;
12963
+ setFacetState({});
12964
+ facetRequestRef.current = /* @__PURE__ */ new Map();
12965
+ }, [facetsEnabled, data, searchValue]);
12966
+ useEffect16(() => {
10679
12967
  if (!controlledQuery || controlledQuery.state.page === safePage) return;
10680
12968
  emitControlledQueryChange({ page: safePage });
10681
12969
  }, [controlledQuery, emitControlledQueryChange, safePage]);
10682
- const selectedRow = useMemo7(() => {
12970
+ const selectedRow = useMemo8(() => {
10683
12971
  if (!selectedId) return null;
10684
12972
  return data.find((r) => getRowId(r) === selectedId) ?? null;
10685
12973
  }, [data, selectedId, getRowId]);
10686
- const windowRow = useMemo7(() => {
12974
+ const windowRow = useMemo8(() => {
10687
12975
  if (!windowRowId) return null;
10688
12976
  return data.find((r) => getRowId(r) === windowRowId) ?? (windowRowSnapshot && windowRowSnapshot.queryKey === appendQueryKey && getRowId(windowRowSnapshot.row) === windowRowId ? windowRowSnapshot.row : null);
10689
12977
  }, [data, windowRowId, windowRowSnapshot, getRowId, appendQueryKey]);
@@ -10718,12 +13006,12 @@ function MatrxDataTableCore({
10718
13006
  return def !== void 0 && f.value !== def;
10719
13007
  });
10720
13008
  const hasActiveFilters = activeFilterCount > 0 || Boolean(searchValue.trim()) || intelligentSearchEnabled && searchScope !== "all" || Boolean(anyOfValue.trim()) || completeLayeredFilterRules(layeredFilters).length > 0 || facetActive;
10721
- const changeCount = useMemo7(() => {
10722
- let n = 0;
13009
+ const changeCount = useMemo8(() => {
13010
+ let n2 = 0;
10723
13011
  for (const fields of Object.values(edits)) {
10724
- n += Object.keys(fields).length;
13012
+ n2 += Object.keys(fields).length;
10725
13013
  }
10726
- return n;
13014
+ return n2;
10727
13015
  }, [edits]);
10728
13016
  const showSearch = toolbar?.search !== false;
10729
13017
  const openDetail = (row) => {
@@ -10802,6 +13090,23 @@ function MatrxDataTableCore({
10802
13090
  icon: void 0
10803
13091
  } : emptyState;
10804
13092
  const commitCell = (rowId, field, next) => {
13093
+ if (edit?.validate) {
13094
+ const row = data.find((candidate) => getRowId(candidate) === rowId);
13095
+ if (row) {
13096
+ let refusal;
13097
+ try {
13098
+ refusal = edit.validate({
13099
+ row: applyRowEdits(row, edits[rowId]),
13100
+ rowId,
13101
+ columnId: field,
13102
+ value: next
13103
+ });
13104
+ } catch {
13105
+ refusal = void 0;
13106
+ }
13107
+ if (refusal) return refusal;
13108
+ }
13109
+ }
10805
13110
  const scope = editScopeKey;
10806
13111
  const nextEdits = { [rowId]: { [field]: next } };
10807
13112
  const revision = setCellEdit(rowId, field, next);
@@ -10846,9 +13151,29 @@ function MatrxDataTableCore({
10846
13151
  setEditRequest((current) => ({
10847
13152
  rowId: getRowId(row),
10848
13153
  field,
10849
- nonce: (current?.nonce ?? 0) + 1
13154
+ nonce: (current?.nonce ?? 0) + 1,
13155
+ seed: null
13156
+ }));
13157
+ };
13158
+ const requestCellEdit = (address, seed) => {
13159
+ if (!editEnabled) return false;
13160
+ const row = data.find((candidate) => getRowId(candidate) === address.rowId);
13161
+ if (!row) return false;
13162
+ const column = visibleColumns.find(
13163
+ (candidate) => columnId(candidate) === address.fieldName
13164
+ );
13165
+ if (!column?.editable || !(column.editableIf?.(row) ?? true)) return false;
13166
+ const field = column.accessorKey ? String(column.accessorKey) : columnId(column);
13167
+ setEditRequest((current) => ({
13168
+ rowId: address.rowId,
13169
+ field,
13170
+ nonce: (current?.nonce ?? 0) + 1,
13171
+ seed
10850
13172
  }));
13173
+ return true;
10851
13174
  };
13175
+ const requestCellEditRef = useRef15(requestCellEdit);
13176
+ requestCellEditRef.current = requestCellEdit;
10852
13177
  const buildRecordControls = (row) => {
10853
13178
  const rowId = getRowId(row);
10854
13179
  const pending = edits[rowId];
@@ -10874,7 +13199,7 @@ function MatrxDataTableCore({
10874
13199
  };
10875
13200
  const hierarchyRows = hierarchy?.rows ?? data;
10876
13201
  const hierarchyGetParentId = hierarchy?.getParentId;
10877
- const hierarchyIndex = useMemo7(() => {
13202
+ const hierarchyIndex = useMemo8(() => {
10878
13203
  if (!hierarchyGetParentId) return null;
10879
13204
  const byId = /* @__PURE__ */ new Map();
10880
13205
  const parentById = /* @__PURE__ */ new Map();
@@ -10906,7 +13231,7 @@ function MatrxDataTableCore({
10906
13231
  return { byId, parentById, depthById };
10907
13232
  }, [hierarchyGetParentId, hierarchyRows, getRowId]);
10908
13233
  const draggedRow = draggedRowId && hierarchyIndex ? hierarchyIndex.byId.get(draggedRowId) : void 0;
10909
- const hierarchyBlockedIds = useMemo7(() => {
13234
+ const hierarchyBlockedIds = useMemo8(() => {
10910
13235
  if (!draggedRowId || !hierarchyIndex) return null;
10911
13236
  const childrenById = /* @__PURE__ */ new Map();
10912
13237
  for (const [id, parentId] of hierarchyIndex.parentById) {
@@ -11089,19 +13414,80 @@ function MatrxDataTableCore({
11089
13414
  setEdits({});
11090
13415
  edit?.onCancel?.();
11091
13416
  };
11092
- useEffect15(() => {
11093
- if (!host.rowContextRegistry || !contextMenu && !host.createDefaultRowContext) return;
11094
- return host.rowContextRegistry.register(tableContextMenuInstanceId, (rowId) => {
13417
+ const resolveMenuLevel = (target) => {
13418
+ const rowFor = (rowId) => {
11095
13419
  const row = data.find((candidate) => getRowId(candidate) === rowId);
11096
13420
  if (!row) return null;
11097
- const pending = edits[rowId];
11098
- const displayRow = applyRowEdits(row, pending);
11099
- const controls = buildRecordControls(displayRow);
11100
- return contextMenu?.resolveRowContext(displayRow, controls) ?? host.createDefaultRowContext?.(displayRow, controls) ?? null;
11101
- });
11102
- }, [buildRecordControls, contextMenu, data, getRowId, host.createDefaultRowContext, host.rowContextRegistry, tableContextMenuInstanceId]);
13421
+ return applyRowEdits(row, edits[rowId]);
13422
+ };
13423
+ const columnFor = (id) => visibleColumns.find((col) => columnId(col) === id);
13424
+ const labelFor = (col, id) => col?.label ?? (typeof col?.header === "string" ? col.header : id);
13425
+ const fromHost = (payload) => host.createDefaultMenuContext?.(payload) ?? null;
13426
+ switch (target.level) {
13427
+ case "row": {
13428
+ if (!target.rowId) return null;
13429
+ const displayRow = rowFor(target.rowId);
13430
+ if (!displayRow) return null;
13431
+ const controls = buildRecordControls(displayRow);
13432
+ return contextMenu?.resolveRowContext?.(displayRow, controls) ?? fromHost({ level: "row", row: displayRow, rowId: target.rowId, controls });
13433
+ }
13434
+ case "cell": {
13435
+ if (!target.rowId || !target.columnId) return null;
13436
+ const displayRow = rowFor(target.rowId);
13437
+ const col = columnFor(target.columnId);
13438
+ if (!displayRow || !col) return null;
13439
+ const controls = buildRecordControls(displayRow);
13440
+ const value = spreadsheet?.readCell ? spreadsheet.readCell(displayRow, target.columnId) : getCellValue(displayRow, col);
13441
+ const args = {
13442
+ row: displayRow,
13443
+ rowId: target.rowId,
13444
+ columnId: target.columnId,
13445
+ columnLabel: labelFor(col, target.columnId),
13446
+ value,
13447
+ text: stringifyCellValue(value),
13448
+ controls
13449
+ };
13450
+ return contextMenu?.resolveCellContext?.(args) ?? fromHost({ level: "cell", ...args });
13451
+ }
13452
+ case "column": {
13453
+ if (!target.columnId) return null;
13454
+ const col = columnFor(target.columnId);
13455
+ if (!col) return null;
13456
+ const args = {
13457
+ columnId: target.columnId,
13458
+ columnLabel: labelFor(col, target.columnId),
13459
+ values: paginated.map(
13460
+ (row) => getCellValue(applyRowEdits(row, edits[getRowId(row)]), col)
13461
+ )
13462
+ };
13463
+ return contextMenu?.resolveColumnContext?.(args) ?? fromHost({ level: "column", ...args });
13464
+ }
13465
+ case "selection": {
13466
+ const addresses = sheetRef.current.selectionAddresses();
13467
+ const cellCount = addresses.rowIds.length * addresses.columnIds.length;
13468
+ if (cellCount <= 1) {
13469
+ return resolveMenuLevel({ ...target, level: "cell" });
13470
+ }
13471
+ const args = { ...addresses, tsv: sheetRef.current.selectionTsv(), cellCount };
13472
+ return contextMenu?.resolveSelectionContext?.(args) ?? fromHost({ level: "selection", ...args });
13473
+ }
13474
+ case "table": {
13475
+ const args = { rowCount: data.length, columnIds: visibleColumns.map((col) => columnId(col)) };
13476
+ return contextMenu?.resolveTableContext?.(args) ?? fromHost({ level: "table", ...args });
13477
+ }
13478
+ }
13479
+ };
13480
+ const resolveMenuLevelRef = useRef15(resolveMenuLevel);
13481
+ resolveMenuLevelRef.current = resolveMenuLevel;
13482
+ useEffect16(() => {
13483
+ if (!host.rowContextRegistry) return;
13484
+ return host.rowContextRegistry.register(
13485
+ tableContextMenuInstanceId,
13486
+ (target) => resolveMenuLevelRef.current(target)
13487
+ );
13488
+ }, [host.rowContextRegistry, tableContextMenuInstanceId]);
11103
13489
  const showActionsCol = windowEnabled || Boolean(rowActions) || showInlineRowCopy;
11104
- const selectedIdSet = useMemo7(
13490
+ const selectedIdSet = useMemo8(
11105
13491
  () => new Set(selection?.selectedIds ?? []),
11106
13492
  [selection?.selectedIds]
11107
13493
  );
@@ -11113,11 +13499,11 @@ function MatrxDataTableCore({
11113
13499
  );
11114
13500
  const allOnPageSelected = selectableRows.length > 0 && selectedOnPage.length === selectableRows.length;
11115
13501
  const someOnPageSelected = selectedOnPage.length > 0 && !allOnPageSelected;
11116
- const selectedRows = useMemo7(
13502
+ const selectedRows = useMemo8(
11117
13503
  () => data.filter((row) => selectedIdSet.has(getRowId(row))),
11118
13504
  [data, selectedIdSet, getRowId]
11119
13505
  );
11120
- const lastToggledIndex = useRef13(null);
13506
+ const lastToggledIndex = useRef15(null);
11121
13507
  const setSelectedIds = (next) => selection?.onSelectedIdsChange([...next]);
11122
13508
  const toggleRowSelected = (index, shiftKey) => {
11123
13509
  if (!selection) return;
@@ -11154,6 +13540,7 @@ function MatrxDataTableCore({
11154
13540
  const selectionNoun = selection?.noun ?? "row";
11155
13541
  const selectionCount = selection?.selectedIds.length ?? 0;
11156
13542
  const leadingCols = selection ? 1 : 0;
13543
+ const renderedColSpan = renderedColumns.length + leadingCols + (showActionsCol ? 1 : 0) + (columnSpacers ? (columnSpacers.start > 0 ? 1 : 0) + (columnSpacers.end > 0 ? 1 : 0) : 0);
11157
13544
  const renderedFacets = controlledQuery ? toolbar?.facets?.map(
11158
13545
  (facet) => facet.type === "button-group" ? {
11159
13546
  ...facet,
@@ -11180,14 +13567,25 @@ function MatrxDataTableCore({
11180
13567
  dirty: Boolean(rowEdits && field in rowEdits),
11181
13568
  onCommit: (next) => commitCell(id, field, next),
11182
13569
  ...href === void 0 ? {} : { href },
11183
- ...col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger },
11184
- ...editRequest?.rowId === id && editRequest.field === field ? { editRequest: editRequest.nonce } : {}
13570
+ ...spreadsheetEnabled ? {
13571
+ // 🚨 THE CLICK LAW, enforced where the two paths meet. In a grid
13572
+ // with spreadsheet gestures a single click SELECTS a cell — it
13573
+ // may never drop a person into a free-text buffer, because the
13574
+ // same click is how they start a range, and an editor that opens
13575
+ // under the pointer eats the drag and the next keystroke. So the
13576
+ // editor opens the way it does in Excel: Enter, F2, the hover
13577
+ // pencil, or simply typing. An explicit `editTrigger: "click"`
13578
+ // does not override this; the law is the table's, not the
13579
+ // column's.
13580
+ editTrigger: "pencil"
13581
+ } : col.editTrigger === void 0 ? {} : { editTrigger: col.editTrigger },
13582
+ ...editRequest?.rowId === id && editRequest.field === field ? { editRequest: editRequest.nonce, editSeed: editRequest.seed } : {}
11185
13583
  }
11186
13584
  );
11187
13585
  }
11188
13586
  return href ? /* @__PURE__ */ jsx42(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;
11189
13587
  };
11190
- const currentViewSnapshot = useMemo7(
13588
+ const currentViewSnapshot = useMemo8(
11191
13589
  () => ({
11192
13590
  __kind: "matrx-table-view",
11193
13591
  version: 1,
@@ -11222,7 +13620,7 @@ function MatrxDataTableCore({
11222
13620
  sort
11223
13621
  ]
11224
13622
  );
11225
- const declaredDefaultViewSnapshot = useMemo7(
13623
+ const declaredDefaultViewSnapshot = useMemo8(
11226
13624
  () => defaultViewSnapshot ?? {
11227
13625
  __kind: "matrx-table-view",
11228
13626
  version: 1,
@@ -11241,7 +13639,7 @@ function MatrxDataTableCore({
11241
13639
  },
11242
13640
  [columns, defaultPageSize, defaultSort, defaultViewSnapshot]
11243
13641
  );
11244
- const applySavedView = useCallback7(
13642
+ const applySavedView = useCallback8(
11245
13643
  (rawSnapshot) => {
11246
13644
  const snapshot = parseTableViewSnapshot(rawSnapshot);
11247
13645
  if (!snapshot) {
@@ -11593,6 +13991,28 @@ function MatrxDataTableCore({
11593
13991
  }
11594
13992
  }
11595
13993
  ) : null,
13994
+ grouping?.onColumnIdChange ? /* @__PURE__ */ jsxs32(
13995
+ Select,
13996
+ {
13997
+ value: groupingColumnId ?? NO_GROUPING_VALUE,
13998
+ onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
13999
+ children: [
14000
+ /* @__PURE__ */ jsx42(
14001
+ SelectTrigger,
14002
+ {
14003
+ "data-matrx-table-group-by": true,
14004
+ "aria-label": "Group rows by a column",
14005
+ className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
14006
+ children: /* @__PURE__ */ jsx42(SelectValue, { placeholder: "No groups" })
14007
+ }
14008
+ ),
14009
+ /* @__PURE__ */ jsxs32(SelectContent, { children: [
14010
+ /* @__PURE__ */ jsx42(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
14011
+ groupableColumns.map((col) => /* @__PURE__ */ jsx42(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
14012
+ ] })
14013
+ ]
14014
+ }
14015
+ ) : null,
11596
14016
  columns.length > 0 ? /* @__PURE__ */ jsx42(
11597
14017
  TableToolbarAction,
11598
14018
  {
@@ -11660,65 +14080,6 @@ function MatrxDataTableCore({
11660
14080
  ]
11661
14081
  }
11662
14082
  ),
11663
- selection && selectionCount > 0 ? /* @__PURE__ */ jsxs32("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: [
11664
- /* @__PURE__ */ jsxs32("span", { className: "text-xs font-medium text-foreground", children: [
11665
- formatCount3(selectionCount),
11666
- " ",
11667
- selectionNoun,
11668
- selectionCount === 1 ? "" : "s",
11669
- " selected"
11670
- ] }),
11671
- /* @__PURE__ */ jsxs32(
11672
- Button,
11673
- {
11674
- type: "button",
11675
- variant: "ghost",
11676
- size: "sm",
11677
- className: "h-7 gap-1 px-2 text-xs",
11678
- onClick: () => {
11679
- lastToggledIndex.current = null;
11680
- selection.onSelectedIdsChange([]);
11681
- },
11682
- children: [
11683
- /* @__PURE__ */ jsx42(X, { className: "h-3.5 w-3.5" }),
11684
- "Clear"
11685
- ]
11686
- }
11687
- ),
11688
- /* @__PURE__ */ jsxs32("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
11689
- effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ jsx42(
11690
- CopyButtons,
11691
- {
11692
- size: "icon",
11693
- "data-matrx-table-icon-action": true,
11694
- sourceId: `${tableCopySourceNamespace}:selection`,
11695
- ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
11696
- ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
11697
- label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
11698
- ...copy === void 0 ? {} : {
11699
- human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
11700
- json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
11701
- },
11702
- agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
11703
- aiVariants: copySubsetVariantFor(
11704
- () => selectedRows,
11705
- "selected"
11706
- ),
11707
- export: {
11708
- items: [],
11709
- sheetRows: () => selectedRows.map(
11710
- (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
11711
- ),
11712
- sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
11713
- id: columnId(column),
11714
- label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
11715
- }))
11716
- }
11717
- }
11718
- ) : null,
11719
- selection.actions?.(selectedRows, selection.selectedIds)
11720
- ] })
11721
- ] }) : null,
11722
14083
  /* @__PURE__ */ jsxs32("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
11723
14084
  /* @__PURE__ */ jsxs32("div", { className: "relative min-h-0 flex-1", children: [
11724
14085
  mobileCards ? /* @__PURE__ */ jsxs32(
@@ -11826,12 +14187,33 @@ function MatrxDataTableCore({
11826
14187
  ]
11827
14188
  }
11828
14189
  ) : null,
14190
+ coverageResult && coverageResult.notice ? /* @__PURE__ */ jsxs32(
14191
+ "div",
14192
+ {
14193
+ "data-matrx-table-coverage": true,
14194
+ "data-matrx-table-coverage-scope": coverageResult.scope,
14195
+ role: "status",
14196
+ 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",
14197
+ children: [
14198
+ /* @__PURE__ */ jsx42("span", { className: "font-semibold", children: coverageResult.badge }),
14199
+ /* @__PURE__ */ jsx42("span", { children: coverageResult.notice })
14200
+ ]
14201
+ }
14202
+ ) : null,
11829
14203
  /* @__PURE__ */ jsxs32(
11830
14204
  "div",
11831
14205
  {
11832
14206
  ref: scrollRef,
11833
14207
  "data-matrx-table-scroll": true,
11834
14208
  onScroll: updateScrollHint,
14209
+ ...spreadsheetEnabled ? {
14210
+ tabIndex: sheet.containerProps.tabIndex,
14211
+ onKeyDown: sheet.containerProps.onKeyDown,
14212
+ onCopy: sheet.containerProps.onCopy,
14213
+ onCut: sheet.containerProps.onCut,
14214
+ onPaste: sheet.containerProps.onPaste,
14215
+ "data-matrx-table-spreadsheet": "true"
14216
+ } : {},
11835
14217
  "aria-busy": isLoading || isFetching,
11836
14218
  style: { containerType: "inline-size" },
11837
14219
  className: cn(
@@ -11889,7 +14271,14 @@ function MatrxDataTableCore({
11889
14271
  ref: tableHeaderRef,
11890
14272
  "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
11891
14273
  className: cn(
11892
- "z-10 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
14274
+ // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
14275
+ // cell is `sm:z-20` so it can slide under the columns to
14276
+ // its right; the header was `z-10`, so the frozen column's
14277
+ // cells scrolled straight OVER the header they belong to
14278
+ // and the top data row printed through the column names.
14279
+ // Only a table long enough to scroll shows it, which is why
14280
+ // it survived until a hundred-thousand-row screenshot.
14281
+ "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
11893
14282
  stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
11894
14283
  ),
11895
14284
  children: /* @__PURE__ */ jsxs32("tr", { children: [
@@ -11922,7 +14311,17 @@ function MatrxDataTableCore({
11922
14311
  )
11923
14312
  }
11924
14313
  ) : null,
11925
- visibleColumns.map((col) => {
14314
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14315
+ "th",
14316
+ {
14317
+ "aria-hidden": "true",
14318
+ "data-matrx-table-column-spacer": "start",
14319
+ className: "border-0 p-0",
14320
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14321
+ },
14322
+ "matrx-column-spacer-start"
14323
+ ) : null,
14324
+ renderedColumns.map((col) => {
11926
14325
  const id = columnId(col);
11927
14326
  const meta = filterMeta.get(id);
11928
14327
  const isSorted = sort?.id === id;
@@ -11938,6 +14337,7 @@ function MatrxDataTableCore({
11938
14337
  "th",
11939
14338
  {
11940
14339
  "data-matrx-table-column-id": id,
14340
+ "data-matrx-header-column-id": id,
11941
14341
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
11942
14342
  "aria-sort": isSorted ? sort?.direction === "asc" ? "ascending" : "descending" : void 0,
11943
14343
  className: cn(
@@ -11965,7 +14365,7 @@ function MatrxDataTableCore({
11965
14365
  frozenEdgeId === id && FROZEN_EDGE_CLASS
11966
14366
  ),
11967
14367
  style: {
11968
- ...columnWidthVar(width, effectiveColumnWidths[id], id, columnResizable),
14368
+ ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
11969
14369
  ...frozenStyle(id)
11970
14370
  },
11971
14371
  children: [
@@ -11974,6 +14374,12 @@ function MatrxDataTableCore({
11974
14374
  {
11975
14375
  label: col.header,
11976
14376
  labelText: headerLabel,
14377
+ ...facetsEnabled ? {
14378
+ facets: facetState[id]?.data ?? null,
14379
+ facetsLoading: facetState[id]?.loading ?? false,
14380
+ ...facetState[id]?.error === void 0 ? {} : { facetsError: facetState[id]?.error },
14381
+ onFilterOpen: () => requestFacets(id)
14382
+ } : {},
11977
14383
  sortable: col.sortable !== false,
11978
14384
  isSorted: Boolean(isSorted),
11979
14385
  sortDirection: sort?.direction ?? defaultSortDirection,
@@ -12029,6 +14435,16 @@ function MatrxDataTableCore({
12029
14435
  id
12030
14436
  );
12031
14437
  }),
14438
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14439
+ "th",
14440
+ {
14441
+ "aria-hidden": "true",
14442
+ "data-matrx-table-column-spacer": "end",
14443
+ className: "border-0 p-0",
14444
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14445
+ },
14446
+ "matrx-column-spacer-end"
14447
+ ) : null,
12032
14448
  showActionsCol && /* @__PURE__ */ jsx42(
12033
14449
  "th",
12034
14450
  {
@@ -12044,7 +14460,17 @@ function MatrxDataTableCore({
12044
14460
  /* @__PURE__ */ jsxs32("tbody", { children: [
12045
14461
  isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs32("tr", { className: "border-b border-border/60", children: [
12046
14462
  selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2", style: fixedUtilityWidth(fixedTableGeometry?.selectionWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "h-3.5 w-3.5" }) }) : null,
12047
- visibleColumns.map((col) => /* @__PURE__ */ jsx42(
14463
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14464
+ "td",
14465
+ {
14466
+ "aria-hidden": "true",
14467
+ "data-matrx-table-column-spacer": "start",
14468
+ className: "border-0 p-0",
14469
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14470
+ },
14471
+ "matrx-column-spacer-start"
14472
+ ) : null,
14473
+ renderedColumns.map((col) => /* @__PURE__ */ jsx42(
12048
14474
  "td",
12049
14475
  {
12050
14476
  className: cn(
@@ -12056,11 +14482,21 @@ function MatrxDataTableCore({
12056
14482
  },
12057
14483
  columnId(col)
12058
14484
  )),
12059
- showActionsCol && /* @__PURE__ */ jsx42("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "ml-auto h-5 w-10" }) })
14485
+ showActionsCol && /* @__PURE__ */ jsx42("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "ml-auto h-5 w-10" }) }),
14486
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14487
+ "td",
14488
+ {
14489
+ "aria-hidden": "true",
14490
+ "data-matrx-table-column-spacer": "end",
14491
+ className: "border-0 p-0",
14492
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14493
+ },
14494
+ "matrx-column-spacer-end"
14495
+ ) : null
12060
14496
  ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ jsx42("tr", { children: /* @__PURE__ */ jsx42(
12061
14497
  "td",
12062
14498
  {
12063
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14499
+ colSpan: renderedColSpan,
12064
14500
  className: "px-0 py-12 text-center",
12065
14501
  children: /* @__PURE__ */ jsxs32("div", { "data-matrx-table-empty-state": true, className: "sticky left-0 flex flex-col items-center gap-2 whitespace-normal px-4", style: { width: "100cqw", maxWidth: "100%" }, children: [
12066
14502
  renderedEmptyState?.icon,
@@ -12069,7 +14505,43 @@ function MatrxDataTableCore({
12069
14505
  renderedEmptyState?.action
12070
14506
  ] })
12071
14507
  }
12072
- ) }) : paginated.map((row, index) => {
14508
+ ) }) : bodyEntries.map((entry) => {
14509
+ if (entry.kind === "spacer") {
14510
+ return /* @__PURE__ */ jsx42(
14511
+ "tr",
14512
+ {
14513
+ "aria-hidden": "true",
14514
+ "data-matrx-table-virtual-spacer": entry.place,
14515
+ style: { height: `${entry.height}px` },
14516
+ children: /* @__PURE__ */ jsx42(
14517
+ "td",
14518
+ {
14519
+ className: "border-0 p-0",
14520
+ colSpan: renderedColSpan
14521
+ }
14522
+ )
14523
+ },
14524
+ `matrx-virtual-spacer-${entry.place}`
14525
+ );
14526
+ }
14527
+ if (entry.kind === "group") {
14528
+ return /* @__PURE__ */ jsx42(
14529
+ TableGroupHeaderRow,
14530
+ {
14531
+ group: entry.group,
14532
+ collapsed: entry.collapsed,
14533
+ columns: renderedColumns,
14534
+ spacers: columnSpacers,
14535
+ leadingCols,
14536
+ showActionsCol,
14537
+ rowNoun: grouping?.rowNoun ?? "row",
14538
+ onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
14539
+ },
14540
+ `matrx-group-${entry.group.key}`
14541
+ );
14542
+ }
14543
+ const row = entry.row;
14544
+ const index = entry.index;
12073
14545
  const id = getRowId(row);
12074
14546
  const isSelected = selectedId === id;
12075
14547
  const rowEdits = edits[id];
@@ -12080,6 +14552,7 @@ function MatrxDataTableCore({
12080
14552
  "tr",
12081
14553
  {
12082
14554
  "data-row-id": id,
14555
+ style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
12083
14556
  "data-state": isSelected ? "selected" : void 0,
12084
14557
  onClick: (e) => {
12085
14558
  if (rowEventTargetsInteractiveDescendant(e.target)) return;
@@ -12152,15 +14625,40 @@ function MatrxDataTableCore({
12152
14625
  ) : null
12153
14626
  }
12154
14627
  ) : null,
12155
- visibleColumns.map((col, colIdx) => {
14628
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14629
+ "td",
14630
+ {
14631
+ "aria-hidden": "true",
14632
+ "data-matrx-table-column-spacer": "start",
14633
+ className: "border-0 p-0",
14634
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14635
+ },
14636
+ "matrx-column-spacer-start"
14637
+ ) : null,
14638
+ renderedColumns.map((col, colIdx) => {
12156
14639
  const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
12157
- return /* @__PURE__ */ jsx42(
14640
+ const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
14641
+ const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
14642
+ return /* @__PURE__ */ jsxs32(
12158
14643
  "td",
12159
14644
  {
12160
14645
  "data-matrx-table-column-id": columnId(col),
12161
14646
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
14647
+ ...sheetCell ? {
14648
+ "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
14649
+ "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
14650
+ ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
14651
+ ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
14652
+ ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
14653
+ onMouseDown: sheetCell.onMouseDown,
14654
+ onMouseEnter: sheetCell.onMouseEnter
14655
+ } : {},
12162
14656
  className: cn(
12163
14657
  "py-1.5 align-middle lg:py-0.5",
14658
+ sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
14659
+ sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
14660
+ sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
14661
+ carriesFillHandle && "relative",
12164
14662
  col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
12165
14663
  // nowrap (NOT truncate — truncate clips the cell and
12166
14664
  // defeats w-max, killing the horizontal scroll).
@@ -12182,42 +14680,64 @@ function MatrxDataTableCore({
12182
14680
  style: {
12183
14681
  ...columnWidthVar(
12184
14682
  resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
12185
- effectiveColumnWidths[columnId(col)],
14683
+ windowedWidthFor(columnId(col), col.width),
12186
14684
  columnId(col),
12187
14685
  resizableColumns && col.resizable !== false
12188
14686
  ),
12189
14687
  ...frozenStyle(columnId(col))
12190
14688
  },
12191
- children: /* @__PURE__ */ jsxs32(
12192
- "div",
12193
- {
12194
- className: cn(
12195
- colIdx === 0 && hierarchy && "flex min-w-0 items-center"
12196
- ),
12197
- children: [
12198
- colIdx === 0 && hierarchy ? /* @__PURE__ */ jsx42(
12199
- HierarchyDragHandle,
12200
- {
12201
- id,
12202
- label: hierarchy.itemLabel?.(row) ?? "row",
12203
- disabled: !(hierarchy.canReparent?.(row) ?? true)
12204
- }
12205
- ) : null,
12206
- /* @__PURE__ */ jsx42(
12207
- "div",
12208
- {
12209
- "data-matrx-cell-content": true,
12210
- 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]",
12211
- children: renderRecordCell(row, col, index)
12212
- }
12213
- )
12214
- ]
12215
- }
12216
- )
14689
+ children: [
14690
+ /* @__PURE__ */ jsxs32(
14691
+ "div",
14692
+ {
14693
+ className: cn(
14694
+ colIdx === 0 && hierarchy && "flex min-w-0 items-center"
14695
+ ),
14696
+ children: [
14697
+ colIdx === 0 && hierarchy ? /* @__PURE__ */ jsx42(
14698
+ HierarchyDragHandle,
14699
+ {
14700
+ id,
14701
+ label: hierarchy.itemLabel?.(row) ?? "row",
14702
+ disabled: !(hierarchy.canReparent?.(row) ?? true)
14703
+ }
14704
+ ) : null,
14705
+ /* @__PURE__ */ jsx42(
14706
+ "div",
14707
+ {
14708
+ "data-matrx-cell-content": true,
14709
+ 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]",
14710
+ children: renderRecordCell(row, col, index)
14711
+ }
14712
+ )
14713
+ ]
14714
+ }
14715
+ ),
14716
+ carriesFillHandle ? /* @__PURE__ */ jsx42(
14717
+ "button",
14718
+ {
14719
+ type: "button",
14720
+ "data-matrx-table-fill-handle": true,
14721
+ "aria-label": "Drag to fill the cells below or beside this selection",
14722
+ onMouseDown: sheet.startFillDrag,
14723
+ className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
14724
+ }
14725
+ ) : null
14726
+ ]
12217
14727
  },
12218
14728
  columnId(col)
12219
14729
  );
12220
14730
  }),
14731
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14732
+ "td",
14733
+ {
14734
+ "aria-hidden": "true",
14735
+ "data-matrx-table-column-spacer": "end",
14736
+ className: "border-0 p-0",
14737
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14738
+ },
14739
+ "matrx-column-spacer-end"
14740
+ ) : null,
12221
14741
  showActionsCol && /* @__PURE__ */ jsx42(
12222
14742
  "td",
12223
14743
  {
@@ -12280,7 +14800,7 @@ function MatrxDataTableCore({
12280
14800
  children: /* @__PURE__ */ jsx42(
12281
14801
  "td",
12282
14802
  {
12283
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14803
+ colSpan: renderedColSpan,
12284
14804
  className: "p-4",
12285
14805
  children: expandedDetail.render(displayRow, controls)
12286
14806
  }
@@ -12306,7 +14826,7 @@ function MatrxDataTableCore({
12306
14826
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsx42("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ jsx42(
12307
14827
  "td",
12308
14828
  {
12309
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14829
+ colSpan: renderedColSpan,
12310
14830
  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",
12311
14831
  children: /* @__PURE__ */ jsxs32(
12312
14832
  "div",
@@ -12331,7 +14851,17 @@ function MatrxDataTableCore({
12331
14851
  ] }),
12332
14852
  footerRows.length > 0 ? /* @__PURE__ */ jsx42("tfoot", { className: "border-t-2 border-border bg-muted/50 font-semibold", children: footerRows.map((row, index) => /* @__PURE__ */ jsxs32("tr", { children: [
12333
14853
  selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2" }) : null,
12334
- visibleColumns.map((column) => /* @__PURE__ */ jsx42(
14854
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14855
+ "td",
14856
+ {
14857
+ "aria-hidden": "true",
14858
+ "data-matrx-table-column-spacer": "start",
14859
+ className: "border-0 p-0",
14860
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14861
+ },
14862
+ "matrx-column-spacer-start"
14863
+ ) : null,
14864
+ renderedColumns.map((column) => /* @__PURE__ */ jsx42(
12335
14865
  "td",
12336
14866
  {
12337
14867
  className: cn(
@@ -12343,7 +14873,7 @@ function MatrxDataTableCore({
12343
14873
  ),
12344
14874
  style: columnWidthVar(
12345
14875
  resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
12346
- effectiveColumnWidths[columnId(column)],
14876
+ windowedWidthFor(columnId(column), column.width),
12347
14877
  columnId(column),
12348
14878
  resizableColumns && column.resizable !== false
12349
14879
  ),
@@ -12351,6 +14881,16 @@ function MatrxDataTableCore({
12351
14881
  },
12352
14882
  columnId(column)
12353
14883
  )),
14884
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14885
+ "td",
14886
+ {
14887
+ "aria-hidden": "true",
14888
+ "data-matrx-table-column-spacer": "end",
14889
+ className: "border-0 p-0",
14890
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14891
+ },
14892
+ "matrx-column-spacer-end"
14893
+ ) : null,
12354
14894
  showActionsCol ? /* @__PURE__ */ jsx42("td", {}) : null
12355
14895
  ] }, `footer-${index}`)) }) : null
12356
14896
  ]
@@ -12514,8 +15054,8 @@ function MatrxDataTableCore({
12514
15054
  itemsPerPage: pageSize === 0 && allRowsAreLoaded ? 0 : effectivePageSize,
12515
15055
  currentPage: safePage,
12516
15056
  onPageChange: setPage,
12517
- onItemsPerPageChange: (n) => {
12518
- setPageSize(n);
15057
+ onItemsPerPageChange: (n2) => {
15058
+ setPageSize(n2);
12519
15059
  if (!controlledQuery) setPage(1);
12520
15060
  },
12521
15061
  pageSizeOptions: pageSizeOptions.filter((size) => size > 0),
@@ -12525,7 +15065,73 @@ function MatrxDataTableCore({
12525
15065
  compact: true,
12526
15066
  hideEntriesInfo: false
12527
15067
  }
12528
- ) }) : null
15068
+ ) }) : null,
15069
+ selection && selectionCount > 0 ? /* @__PURE__ */ jsxs32(
15070
+ "div",
15071
+ {
15072
+ "data-matrx-table-bulk-bar": true,
15073
+ 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",
15074
+ children: [
15075
+ /* @__PURE__ */ jsxs32("span", { className: "text-xs font-medium text-foreground", children: [
15076
+ formatCount3(selectionCount),
15077
+ " ",
15078
+ selectionNoun,
15079
+ selectionCount === 1 ? "" : "s",
15080
+ " selected"
15081
+ ] }),
15082
+ /* @__PURE__ */ jsxs32(
15083
+ Button,
15084
+ {
15085
+ type: "button",
15086
+ variant: "ghost",
15087
+ size: "sm",
15088
+ className: "h-7 gap-1 px-2 text-xs",
15089
+ onClick: () => {
15090
+ lastToggledIndex.current = null;
15091
+ selection.onSelectedIdsChange([]);
15092
+ },
15093
+ children: [
15094
+ /* @__PURE__ */ jsx42(X, { className: "h-3.5 w-3.5" }),
15095
+ "Clear"
15096
+ ]
15097
+ }
15098
+ ),
15099
+ /* @__PURE__ */ jsxs32("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
15100
+ effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ jsx42(
15101
+ CopyButtons,
15102
+ {
15103
+ size: "icon",
15104
+ "data-matrx-table-icon-action": true,
15105
+ sourceId: `${tableCopySourceNamespace}:selection`,
15106
+ ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
15107
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
15108
+ label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
15109
+ ...copy === void 0 ? {} : {
15110
+ human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
15111
+ json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
15112
+ },
15113
+ agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
15114
+ aiVariants: copySubsetVariantFor(
15115
+ () => selectedRows,
15116
+ "selected"
15117
+ ),
15118
+ export: {
15119
+ items: [],
15120
+ sheetRows: () => selectedRows.map(
15121
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
15122
+ ),
15123
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
15124
+ id: columnId(column),
15125
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
15126
+ }))
15127
+ }
15128
+ }
15129
+ ) : null,
15130
+ selection.actions?.(selectedRows, selection.selectedIds)
15131
+ ] })
15132
+ ]
15133
+ }
15134
+ ) : null
12529
15135
  ] }),
12530
15136
  /* @__PURE__ */ jsx42(
12531
15137
  ColumnManager,
@@ -12807,7 +15413,7 @@ function HierarchyDragIndicatorLayer({
12807
15413
  scrollRef,
12808
15414
  resolve: resolve2
12809
15415
  }) {
12810
- const [indicator, setIndicator] = useState21(
15416
+ const [indicator, setIndicator] = useState22(
12811
15417
  null
12812
15418
  );
12813
15419
  useDndMonitor2({
@@ -12948,6 +15554,71 @@ function normalizeColumnWidthsForColumns(widths, columns) {
12948
15554
  })
12949
15555
  );
12950
15556
  }
15557
+ function TableGroupHeaderRow({
15558
+ group,
15559
+ collapsed,
15560
+ columns,
15561
+ spacers,
15562
+ leadingCols,
15563
+ showActionsCol,
15564
+ rowNoun,
15565
+ onToggle
15566
+ }) {
15567
+ const count = group.rows.length;
15568
+ const summary = `${count.toLocaleString()} ${count === 1 ? rowNoun : `${rowNoun}s`}`;
15569
+ const firstId = columns[0] ? columnId(columns[0]) : null;
15570
+ return /* @__PURE__ */ jsxs32(
15571
+ "tr",
15572
+ {
15573
+ "data-matrx-table-group-row": group.key,
15574
+ "data-matrx-table-group-collapsed": collapsed ? "true" : "false",
15575
+ className: "border-y border-border bg-muted/40",
15576
+ children: [
15577
+ Array.from({ length: leadingCols }).map((_, i) => /* @__PURE__ */ jsx42("td", { className: "px-2 py-1.5" }, `group-lead-${i}`)),
15578
+ spacers && spacers.start > 0 ? /* @__PURE__ */ jsx42("td", { "aria-hidden": "true", className: "border-0 p-0", style: { width: `${spacers.start}px` } }) : null,
15579
+ columns.map((col) => {
15580
+ const id = columnId(col);
15581
+ const aggregate = group.aggregates[id];
15582
+ if (id === firstId) {
15583
+ return /* @__PURE__ */ jsx42("td", { className: cn("px-2 py-1.5 align-middle", col.mobileHidden && "max-sm:hidden"), children: /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-1.5", children: [
15584
+ /* @__PURE__ */ jsx42(
15585
+ "button",
15586
+ {
15587
+ type: "button",
15588
+ onClick: onToggle,
15589
+ "aria-expanded": !collapsed,
15590
+ "aria-label": `${collapsed ? "Show" : "Hide"} ${group.label}`,
15591
+ className: "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground",
15592
+ children: collapsed ? /* @__PURE__ */ jsx42(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx42(ChevronDown, { className: "h-4 w-4" })
15593
+ }
15594
+ ),
15595
+ /* @__PURE__ */ jsx42("span", { className: "truncate text-sm font-semibold text-foreground", children: group.label }),
15596
+ /* @__PURE__ */ jsx42("span", { className: "whitespace-nowrap text-xs text-muted-foreground", children: summary })
15597
+ ] }) }, id);
15598
+ }
15599
+ return /* @__PURE__ */ jsx42(
15600
+ "td",
15601
+ {
15602
+ className: cn(
15603
+ "px-2 py-1.5 align-middle text-xs font-medium text-muted-foreground",
15604
+ // A subtotal lines up with the numbers it is a subtotal OF.
15605
+ // Left-aligned under a right-aligned money column it reads as a
15606
+ // different column's value.
15607
+ alignForFormat(col) === "center" && "text-center",
15608
+ alignForFormat(col) === "right" && "text-right",
15609
+ col.mobileHidden && "max-sm:hidden"
15610
+ ),
15611
+ children: aggregate ? /* @__PURE__ */ jsx42("span", { "data-matrx-table-group-aggregate": id, className: "whitespace-nowrap", children: aggregate.label }) : null
15612
+ },
15613
+ id
15614
+ );
15615
+ }),
15616
+ spacers && spacers.end > 0 ? /* @__PURE__ */ jsx42("td", { "aria-hidden": "true", className: "border-0 p-0", style: { width: `${spacers.end}px` } }) : null,
15617
+ showActionsCol ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-1.5" }) : null
15618
+ ]
15619
+ }
15620
+ );
15621
+ }
12951
15622
  function columnWidthVar(width, resizedWidth, id, resizable) {
12952
15623
  if (width === void 0 && resizedWidth === void 0 && !resizable) return void 0;
12953
15624
  const inferredWidth = resizable && inferTableSearchRole(id) === "name" ? "12rem" : "auto";
@@ -12957,7 +15628,7 @@ function columnWidthVar(width, resizedWidth, id, resizable) {
12957
15628
  };
12958
15629
  }
12959
15630
  function DefaultJsonCell({ value }) {
12960
- const [open, setOpen] = useState21(false);
15631
+ const [open, setOpen] = useState22(false);
12961
15632
  const host = useMatrxDataTableHost();
12962
15633
  const Viewer = host.JsonViewer;
12963
15634
  const text = JSON.stringify(value, null, 2);
@@ -13040,7 +15711,7 @@ function resolveStringTitle(title, fallback) {
13040
15711
  }
13041
15712
 
13042
15713
  // src/data-table/SavedViewsControl.tsx
13043
- import { useId as useId3, useState as useState22 } from "react";
15714
+ import { useId as useId3, useState as useState23 } from "react";
13044
15715
 
13045
15716
  // src/dropdown-menu.tsx
13046
15717
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
@@ -13184,10 +15855,10 @@ function SavedViewsControl({
13184
15855
  onReload
13185
15856
  }) {
13186
15857
  const unsavedChangesId = useId3();
13187
- const [saveOpen, setSaveOpen] = useState22(false);
13188
- const [name, setName] = useState22("");
13189
- const [saving, setSaving] = useState22(false);
13190
- const [actionError, setActionError] = useState22(null);
15858
+ const [saveOpen, setSaveOpen] = useState23(false);
15859
+ const [name, setName] = useState23("");
15860
+ const [saving, setSaving] = useState23(false);
15861
+ const [actionError, setActionError] = useState23(null);
13191
15862
  const visibleError = actionError ?? error;
13192
15863
  const save = async () => {
13193
15864
  const next = name.trim();
@@ -13320,8 +15991,11 @@ export {
13320
15991
  CELL_TINT_CLASS,
13321
15992
  COLOR_RULE_OPS,
13322
15993
  COLOR_RULE_OP_LABELS,
15994
+ DEFAULT_OVERSCAN,
15995
+ DEFAULT_UNDO_DEPTH,
13323
15996
  DataRowInspector,
13324
15997
  DataRowWindow,
15998
+ EMPTY_GROUP_KEY,
13325
15999
  EMPTY_TABLE_STYLE,
13326
16000
  FORMAT_MISMATCH_CLASS,
13327
16001
  FormattedCell,
@@ -13332,6 +16006,7 @@ export {
13332
16006
  MatrxUuidCell,
13333
16007
  NO_FROZEN_COLUMNS,
13334
16008
  OPS_WITH_VALUE,
16009
+ PARTIAL_BADGE,
13335
16010
  ROW_TINT_CLASS,
13336
16011
  STYLE_COLORS,
13337
16012
  STYLE_COLOR_LABELS,
@@ -13341,22 +16016,62 @@ export {
13341
16016
  TABLE_STYLE_VERSION,
13342
16017
  TableTitleRow,
13343
16018
  TableViewTabs,
16019
+ UNKNOWN_TOTAL_BADGE,
13344
16020
  alignForFormat,
16021
+ allRange,
13345
16022
  applyStylePath,
16023
+ boundsContain,
13346
16024
  buildRowAgentInput,
13347
16025
  buildViewAgentInput,
13348
16026
  buildViewHuman,
16027
+ canRedo,
16028
+ canUndo,
16029
+ cellClipboardText,
16030
+ cellDomKey,
16031
+ cellsInRange,
16032
+ classifyGridKey,
16033
+ coerceGroupNumber,
13349
16034
  collectSelectOptions,
13350
16035
  colorForChoice,
16036
+ columnRange,
16037
+ computeVariableWindow,
16038
+ computeWindow,
16039
+ createUndoStack,
16040
+ describeCoverage,
16041
+ detectSeries,
16042
+ directClickKinds,
13351
16043
  downloadTableAsXlsx,
13352
16044
  evaluateRule,
16045
+ fillDown,
16046
+ fillRange,
16047
+ fillTargetFor,
13353
16048
  filterKindLabel,
16049
+ flattenGroups,
13354
16050
  formatColumnValue,
16051
+ formatGroupNumber,
16052
+ gridToTsv,
16053
+ groupKeyOf,
16054
+ groupRows,
16055
+ groupSummary,
13355
16056
  inferTableSearchRole,
16057
+ isBlankGroupValue,
16058
+ isDirectClickEditor,
16059
+ isGroupCollapsed,
16060
+ isSingleCell,
16061
+ isSingleCellRange,
13356
16062
  isStyleColor,
16063
+ moveSelection,
16064
+ parseClipboardGrid,
13357
16065
  parseTableStyle,
13358
16066
  parseTableViewSnapshot,
13359
16067
  pixelWidth,
16068
+ planPaste,
16069
+ popRedo,
16070
+ popUndo,
16071
+ pushUndo,
16072
+ rangeBounds,
16073
+ rangeRows,
16074
+ rangeSize,
13360
16075
  rankTableSearchRows,
13361
16076
  resolveCellColor,
13362
16077
  resolveColumnMarker,
@@ -13364,17 +16079,25 @@ export {
13364
16079
  resolveFrozenColumns,
13365
16080
  resolveRowColor,
13366
16081
  resolveTableSearchScopes,
16082
+ rowRange,
13367
16083
  rowsToCsvFromColumns,
13368
16084
  rowsToMarkdownTable,
13369
16085
  rowsToRecordsFromColumns,
16086
+ sameCell,
16087
+ scrollOffsetForIndex,
13370
16088
  shortUuid,
16089
+ storedValuesEqual,
13371
16090
  stylePath,
13372
16091
  tableStyleFromMetadata,
13373
16092
  tableStyleIsEmpty,
13374
16093
  tableUrlParamPrefix,
16094
+ tileIndex,
16095
+ toggleGroupCollapsed,
16096
+ undoEntryFor,
13375
16097
  useMatrxDataTableHost,
13376
16098
  useMatrxTableRowAlchemy,
13377
16099
  useScrollPagination,
16100
+ useSpreadsheetGrid,
13378
16101
  useTableUrlState
13379
16102
  };
13380
16103
  //# sourceMappingURL=index.js.map