@ai-matrx/design-system 0.22.0 → 0.25.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 (93) hide show
  1. package/CHANGELOG.md +151 -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 +6 -2
  9. package/dist/data-table/copy-helpers.d.ts +6 -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/fill.cjs +299 -0
  17. package/dist/data-table/fill.cjs.map +1 -0
  18. package/dist/data-table/fill.d.cts +129 -0
  19. package/dist/data-table/fill.d.ts +129 -0
  20. package/dist/data-table/fill.js +277 -0
  21. package/dist/data-table/fill.js.map +1 -0
  22. package/dist/data-table/filter-engine.d.cts +6 -2
  23. package/dist/data-table/filter-engine.d.ts +6 -2
  24. package/dist/data-table/grid-clipboard.cjs +165 -0
  25. package/dist/data-table/grid-clipboard.cjs.map +1 -0
  26. package/dist/data-table/grid-clipboard.d.cts +85 -0
  27. package/dist/data-table/grid-clipboard.d.ts +85 -0
  28. package/dist/data-table/grid-clipboard.js +145 -0
  29. package/dist/data-table/grid-clipboard.js.map +1 -0
  30. package/dist/data-table/grid-selection.cjs +258 -0
  31. package/dist/data-table/grid-selection.cjs.map +1 -0
  32. package/dist/data-table/grid-selection.d.cts +166 -0
  33. package/dist/data-table/grid-selection.d.ts +166 -0
  34. package/dist/data-table/grid-selection.js +238 -0
  35. package/dist/data-table/grid-selection.js.map +1 -0
  36. package/dist/data-table/grouping.cjs +302 -0
  37. package/dist/data-table/grouping.cjs.map +1 -0
  38. package/dist/data-table/grouping.d.cts +119 -0
  39. package/dist/data-table/grouping.d.ts +119 -0
  40. package/dist/data-table/grouping.js +282 -0
  41. package/dist/data-table/grouping.js.map +1 -0
  42. package/dist/data-table/host.d.cts +6 -2
  43. package/dist/data-table/host.d.ts +6 -2
  44. package/dist/data-table/index.cjs +2433 -246
  45. package/dist/data-table/index.cjs.map +1 -1
  46. package/dist/data-table/index.d.cts +11 -3
  47. package/dist/data-table/index.d.ts +11 -3
  48. package/dist/data-table/index.js +2425 -238
  49. package/dist/data-table/index.js.map +1 -1
  50. package/dist/data-table/infer-filter.d.cts +6 -2
  51. package/dist/data-table/infer-filter.d.ts +6 -2
  52. package/dist/data-table/layered-filters.d.cts +6 -2
  53. package/dist/data-table/layered-filters.d.ts +6 -2
  54. package/dist/data-table/query-control.d.cts +6 -2
  55. package/dist/data-table/query-control.d.ts +6 -2
  56. package/dist/data-table/spreadsheet.cjs +1014 -0
  57. package/dist/data-table/spreadsheet.cjs.map +1 -0
  58. package/dist/data-table/spreadsheet.d.cts +118 -0
  59. package/dist/data-table/spreadsheet.d.ts +118 -0
  60. package/dist/data-table/spreadsheet.js +994 -0
  61. package/dist/data-table/spreadsheet.js.map +1 -0
  62. package/dist/data-table/types.cjs.map +1 -1
  63. package/dist/data-table/types.d.cts +6 -2
  64. package/dist/data-table/types.d.ts +6 -2
  65. package/dist/data-table/url-state.d.cts +6 -2
  66. package/dist/data-table/url-state.d.ts +6 -2
  67. package/dist/data-table/validation.cjs +229 -0
  68. package/dist/data-table/validation.cjs.map +1 -0
  69. package/dist/data-table/validation.d.cts +125 -0
  70. package/dist/data-table/validation.d.ts +125 -0
  71. package/dist/data-table/validation.js +209 -0
  72. package/dist/data-table/validation.js.map +1 -0
  73. package/dist/data-table/virtual-window.cjs +203 -0
  74. package/dist/data-table/virtual-window.cjs.map +1 -0
  75. package/dist/data-table/virtual-window.d.cts +84 -0
  76. package/dist/data-table/virtual-window.d.ts +84 -0
  77. package/dist/data-table/virtual-window.js +183 -0
  78. package/dist/data-table/virtual-window.js.map +1 -0
  79. package/dist/data-table/xlsx.d.cts +6 -2
  80. package/dist/data-table/xlsx.d.ts +6 -2
  81. package/dist/field-formats/index.d.cts +2 -2
  82. package/dist/field-formats/index.d.ts +2 -2
  83. package/dist/formulas/index.cjs +1041 -0
  84. package/dist/formulas/index.cjs.map +1 -0
  85. package/dist/formulas/index.d.cts +389 -0
  86. package/dist/formulas/index.d.ts +389 -0
  87. package/dist/formulas/index.js +1019 -0
  88. package/dist/formulas/index.js.map +1 -0
  89. package/dist/{types-BdbvlF9-.d.cts → types-CJWU1Gae.d.cts} +1 -1
  90. package/dist/{types-BdbvlF9-.d.ts → types-CJWU1Gae.d.ts} +1 -1
  91. package/dist/{types-Sn249eoy.d.cts → types-CmQrOz-T.d.cts} +154 -2
  92. package/dist/{types-D-flTpK0.d.ts → types-nlLCMiqd.d.ts} +154 -2
  93. package/package.json +109 -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 useRef14,
14
+ useState as useState22
15
15
  } from "react";
16
16
  import {
17
17
  DndContext,
@@ -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;
@@ -6771,6 +6771,1611 @@ function projectColumnDragOrder(orderedIds, preview2) {
6771
6771
  return order;
6772
6772
  }
6773
6773
 
6774
+ // src/data-table/grouping.ts
6775
+ var EMPTY_GROUP_KEY = "empty";
6776
+ var DEFAULT_EMPTY_LABEL = "(empty)";
6777
+ function isBlankGroupValue(value) {
6778
+ return value === null || value === void 0 || value === "";
6779
+ }
6780
+ function groupKeyOf(value) {
6781
+ if (isBlankGroupValue(value)) return EMPTY_GROUP_KEY;
6782
+ switch (typeof value) {
6783
+ case "string":
6784
+ return `s:${value}`;
6785
+ case "number":
6786
+ return `n:${String(value)}`;
6787
+ case "boolean":
6788
+ return `b:${value ? "true" : "false"}`;
6789
+ case "bigint":
6790
+ return `i:${String(value)}`;
6791
+ case "symbol":
6792
+ return `y:${String(value)}`;
6793
+ case "function":
6794
+ return `f:${safeString(value)}`;
6795
+ default:
6796
+ break;
6797
+ }
6798
+ if (value instanceof Date) {
6799
+ const time = value.getTime();
6800
+ return `d:${Number.isFinite(time) ? value.toISOString() : "invalid"}`;
6801
+ }
6802
+ return `o:${safeJson(value)}`;
6803
+ }
6804
+ function safeString(value) {
6805
+ try {
6806
+ return String(value);
6807
+ } catch {
6808
+ return "[unreadable]";
6809
+ }
6810
+ }
6811
+ function safeJson(value) {
6812
+ try {
6813
+ const json = JSON.stringify(value);
6814
+ return typeof json === "string" ? json : safeString(value);
6815
+ } catch {
6816
+ return safeString(value);
6817
+ }
6818
+ }
6819
+ var CURRENCY_AND_SPACE = /[\s,$£€¥]/g;
6820
+ function coerceGroupNumber(value) {
6821
+ if (isBlankGroupValue(value)) return null;
6822
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
6823
+ if (typeof value === "boolean") return value ? 1 : 0;
6824
+ if (typeof value === "bigint") {
6825
+ const asNumber = Number(value);
6826
+ return Number.isFinite(asNumber) ? asNumber : null;
6827
+ }
6828
+ if (typeof value !== "string") return null;
6829
+ const cleaned = value.replace(CURRENCY_AND_SPACE, "");
6830
+ if (cleaned === "" || cleaned === "-" || cleaned === "+") return null;
6831
+ const parsed = Number(cleaned);
6832
+ return Number.isFinite(parsed) ? parsed : null;
6833
+ }
6834
+ var NUMBER_FORMAT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
6835
+ function formatGroupNumber(value) {
6836
+ if (!Number.isFinite(value)) return "\u2014";
6837
+ try {
6838
+ return NUMBER_FORMAT.format(value);
6839
+ } catch {
6840
+ return String(value);
6841
+ }
6842
+ }
6843
+ function defaultLabelOf(value, emptyLabel) {
6844
+ if (isBlankGroupValue(value)) return emptyLabel;
6845
+ if (typeof value === "string") return value;
6846
+ if (typeof value === "number") return formatGroupNumber(value);
6847
+ if (typeof value === "boolean") return value ? "true" : "false";
6848
+ if (value instanceof Date) {
6849
+ const time = value.getTime();
6850
+ return Number.isFinite(time) ? value.toISOString() : "Invalid Date";
6851
+ }
6852
+ if (typeof value === "object") return safeJson(value);
6853
+ return safeString(value);
6854
+ }
6855
+ var AGGREGATE_NAMES = {
6856
+ count: { name: "Count", position: "before" },
6857
+ sum: { name: "Sum", position: "before" },
6858
+ average: { name: "Average", position: "before" },
6859
+ min: { name: "Min", position: "before" },
6860
+ max: { name: "Max", position: "before" },
6861
+ empty: { name: "empty", position: "after" },
6862
+ filled: { name: "filled", position: "after" },
6863
+ unique: { name: "unique", position: "after" },
6864
+ checked: { name: "checked", position: "after" },
6865
+ unchecked: { name: "unchecked", position: "after" },
6866
+ percentFilled: { name: "filled", position: "after" }
6867
+ };
6868
+ function aggregateLabel(kind, value, override) {
6869
+ const naming = AGGREGATE_NAMES[kind] ?? { name: kind, position: "before" };
6870
+ const name = override && override.trim() !== "" ? override : naming.name;
6871
+ const shown = value === null ? "\u2014" : kind === "percentFilled" ? `${formatGroupNumber(value)}%` : formatGroupNumber(value);
6872
+ return naming.position === "before" ? `${name} ${shown}` : `${shown} ${name}`;
6873
+ }
6874
+ function readValue(row, reader) {
6875
+ try {
6876
+ return reader(row);
6877
+ } catch {
6878
+ return null;
6879
+ }
6880
+ }
6881
+ function computeAggregate(rows, column) {
6882
+ const kind = column.spec.kind;
6883
+ const raw = rows.map((row) => readValue(row, column.readCell));
6884
+ const total = raw.length;
6885
+ const filled = raw.filter((value2) => !isBlankGroupValue(value2));
6886
+ const numbers = filled.map((value2) => coerceGroupNumber(value2)).filter((value2) => value2 !== null);
6887
+ let value;
6888
+ switch (kind) {
6889
+ case "count":
6890
+ value = total;
6891
+ break;
6892
+ case "sum":
6893
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n) => acc + n, 0);
6894
+ break;
6895
+ case "average":
6896
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n) => acc + n, 0) / numbers.length;
6897
+ break;
6898
+ case "min":
6899
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n) => n < acc ? n : acc);
6900
+ break;
6901
+ case "max":
6902
+ value = numbers.length === 0 ? null : numbers.reduce((acc, n) => n > acc ? n : acc);
6903
+ break;
6904
+ case "empty":
6905
+ value = total - filled.length;
6906
+ break;
6907
+ case "filled":
6908
+ value = filled.length;
6909
+ break;
6910
+ case "unique": {
6911
+ const seen = /* @__PURE__ */ new Set();
6912
+ for (const entry of filled) seen.add(groupKeyOf(entry));
6913
+ value = seen.size;
6914
+ break;
6915
+ }
6916
+ case "checked":
6917
+ value = filled.filter((entry) => entry === true).length;
6918
+ break;
6919
+ case "unchecked":
6920
+ value = filled.filter((entry) => entry === false).length;
6921
+ break;
6922
+ case "percentFilled":
6923
+ value = total === 0 ? null : filled.length / total * 100;
6924
+ break;
6925
+ default:
6926
+ value = null;
6927
+ break;
6928
+ }
6929
+ return { kind, value, label: aggregateLabel(kind, value, column.spec.label) };
6930
+ }
6931
+ function compareLabels(a, b) {
6932
+ if (a === b) return 0;
6933
+ return a < b ? -1 : 1;
6934
+ }
6935
+ function compareValues2(a, b) {
6936
+ const aNumber = typeof a.value === "number" ? a.value : null;
6937
+ const bNumber = typeof b.value === "number" ? b.value : null;
6938
+ if (aNumber !== null && bNumber !== null && aNumber !== bNumber) return aNumber < bNumber ? -1 : 1;
6939
+ if (typeof a.value === "boolean" && typeof b.value === "boolean" && a.value !== b.value) {
6940
+ return a.value ? 1 : -1;
6941
+ }
6942
+ return compareLabels(a.label, b.label);
6943
+ }
6944
+ function groupRows(options) {
6945
+ const rows = Array.isArray(options.rows) ? options.rows : [];
6946
+ const emptyLabel = options.emptyLabel ?? DEFAULT_EMPTY_LABEL;
6947
+ const order = options.order ?? "value-asc";
6948
+ const aggregates = options.aggregates ?? {};
6949
+ const labelFor = (value) => {
6950
+ if (!options.labelOf) return defaultLabelOf(value, emptyLabel);
6951
+ if (isBlankGroupValue(value)) {
6952
+ try {
6953
+ const custom = options.labelOf(value);
6954
+ return typeof custom === "string" && custom !== "" ? custom : emptyLabel;
6955
+ } catch {
6956
+ return emptyLabel;
6957
+ }
6958
+ }
6959
+ try {
6960
+ const custom = options.labelOf(value);
6961
+ return typeof custom === "string" ? custom : defaultLabelOf(value, emptyLabel);
6962
+ } catch {
6963
+ return defaultLabelOf(value, emptyLabel);
6964
+ }
6965
+ };
6966
+ const buckets = /* @__PURE__ */ new Map();
6967
+ for (const row of rows) {
6968
+ const raw = readValue(row, options.readCell);
6969
+ const blank = isBlankGroupValue(raw);
6970
+ const value = blank ? null : raw;
6971
+ const key = groupKeyOf(value);
6972
+ let bucket = buckets.get(key);
6973
+ if (!bucket) {
6974
+ bucket = { key, value, label: labelFor(value), rows: [], seen: buckets.size };
6975
+ buckets.set(key, bucket);
6976
+ }
6977
+ bucket.rows.push(row);
6978
+ }
6979
+ const ordered = [...buckets.values()].sort((a, b) => {
6980
+ const aBlank = a.key === EMPTY_GROUP_KEY;
6981
+ const bBlank = b.key === EMPTY_GROUP_KEY;
6982
+ if (aBlank !== bBlank) return aBlank ? 1 : -1;
6983
+ if (aBlank && bBlank) return 0;
6984
+ switch (order) {
6985
+ case "value-asc":
6986
+ return compareValues2(a, b) || a.seen - b.seen;
6987
+ case "value-desc":
6988
+ return -compareValues2(a, b) || a.seen - b.seen;
6989
+ case "count-desc":
6990
+ return b.rows.length - a.rows.length || a.seen - b.seen;
6991
+ case "first-seen":
6992
+ default:
6993
+ return a.seen - b.seen;
6994
+ }
6995
+ });
6996
+ const aggregateEntries = Object.entries(aggregates);
6997
+ return ordered.map((bucket, index) => {
6998
+ const results = {};
6999
+ for (const [columnId2, column] of aggregateEntries) {
7000
+ if (!column || typeof column.readCell !== "function" || !column.spec) continue;
7001
+ results[columnId2] = computeAggregate(bucket.rows, column);
7002
+ }
7003
+ return {
7004
+ key: bucket.key,
7005
+ value: bucket.value,
7006
+ label: bucket.label,
7007
+ rows: bucket.rows,
7008
+ aggregates: results,
7009
+ index
7010
+ };
7011
+ });
7012
+ }
7013
+ function toggleGroupCollapsed(collapsed, key) {
7014
+ const list = Array.isArray(collapsed) ? collapsed : [];
7015
+ return list.includes(key) ? list.filter((entry) => entry !== key) : [...list, key];
7016
+ }
7017
+ function isGroupCollapsed(collapsed, key) {
7018
+ return Array.isArray(collapsed) ? collapsed.includes(key) : false;
7019
+ }
7020
+ function flattenGroups(groups, collapsed) {
7021
+ const out = [];
7022
+ let dataIndex = 0;
7023
+ for (const group of Array.isArray(groups) ? groups : []) {
7024
+ out.push({ kind: "group", group });
7025
+ if (isGroupCollapsed(collapsed, group.key)) continue;
7026
+ for (const row of group.rows) {
7027
+ out.push({ kind: "row", group, row, dataIndex });
7028
+ dataIndex += 1;
7029
+ }
7030
+ }
7031
+ return out;
7032
+ }
7033
+ function groupSummary(group, rowNoun = "row") {
7034
+ const count = group?.rows?.length ?? 0;
7035
+ const noun = count === 1 ? rowNoun : `${rowNoun}s`;
7036
+ const parts = [`${formatGroupNumber(count)} ${noun}`];
7037
+ for (const result of Object.values(group?.aggregates ?? {})) {
7038
+ if (result?.label) parts.push(result.label);
7039
+ }
7040
+ return parts.join(" \xB7 ");
7041
+ }
7042
+
7043
+ // src/data-table/virtual-window.ts
7044
+ var DEFAULT_OVERSCAN = 6;
7045
+ function isNum(value) {
7046
+ return typeof value === "number" && Number.isFinite(value);
7047
+ }
7048
+ function toCount(value) {
7049
+ return isNum(value) && value > 0 ? Math.floor(value) : 0;
7050
+ }
7051
+ function toLength(value) {
7052
+ return isNum(value) && value > 0 ? value : 0;
7053
+ }
7054
+ function toOverscan(value) {
7055
+ if (!isNum(value)) return DEFAULT_OVERSCAN;
7056
+ return value > 0 ? Math.floor(value) : 0;
7057
+ }
7058
+ function toPinned(value, count) {
7059
+ if (!isNum(value) || value <= 0) return 0;
7060
+ return Math.min(Math.floor(value), count);
7061
+ }
7062
+ function assemble(args) {
7063
+ const { pinnedCount, start, end, padStart, body, total } = args;
7064
+ let padEnd = total - (padStart + body);
7065
+ if (!Number.isFinite(padEnd)) padEnd = 0;
7066
+ const sum = padStart + body + padEnd;
7067
+ if (sum !== total) padEnd = padEnd + (total - sum);
7068
+ const pinned = [];
7069
+ for (let i = 0; i < pinnedCount; i += 1) pinned.push(i);
7070
+ return { start, end, padStart, padEnd, total, pinned };
7071
+ }
7072
+ function emptyWindow(pinnedCount) {
7073
+ const pinned = [];
7074
+ for (let i = 0; i < pinnedCount; i += 1) pinned.push(i);
7075
+ return { start: pinnedCount, end: pinnedCount, padStart: 0, padEnd: 0, total: 0, pinned };
7076
+ }
7077
+ function band(firstVisible, lastVisible, scrollCount, overscan) {
7078
+ let first = firstVisible;
7079
+ if (first > scrollCount - 1) first = scrollCount - 1;
7080
+ if (first < 0) first = 0;
7081
+ let last = lastVisible;
7082
+ if (last < first + 1) last = first + 1;
7083
+ if (last > scrollCount) last = scrollCount;
7084
+ const start = Math.max(0, first - overscan);
7085
+ let end = Math.min(scrollCount, last + overscan);
7086
+ if (end < start) end = start;
7087
+ return { start, end };
7088
+ }
7089
+ function computeWindow(input) {
7090
+ const count = toCount(input?.count);
7091
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7092
+ if (count <= 0) return emptyWindow(0);
7093
+ const itemSize = toLength(input?.itemSize);
7094
+ const viewport = toLength(input?.viewport);
7095
+ const overscan = toOverscan(input?.overscan);
7096
+ const scrollCount = count - pinnedCount;
7097
+ if (scrollCount <= 0) return emptyWindow(pinnedCount);
7098
+ if (itemSize <= 0) {
7099
+ return assemble({ pinnedCount, start: pinnedCount, end: count, padStart: 0, body: 0, total: 0 });
7100
+ }
7101
+ const total = scrollCount * itemSize;
7102
+ const maxOffset = Math.max(0, total - viewport);
7103
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7104
+ const { start: relStart, end: relEnd } = band(
7105
+ Math.floor(offset / itemSize),
7106
+ Math.ceil((offset + viewport) / itemSize),
7107
+ scrollCount,
7108
+ overscan
7109
+ );
7110
+ return assemble({
7111
+ pinnedCount,
7112
+ start: pinnedCount + relStart,
7113
+ end: pinnedCount + relEnd,
7114
+ padStart: relStart * itemSize,
7115
+ body: (relEnd - relStart) * itemSize,
7116
+ total
7117
+ });
7118
+ }
7119
+ var PREFIX_CACHE = /* @__PURE__ */ new WeakMap();
7120
+ function prefixFor(sizeAt, count) {
7121
+ let byCount = PREFIX_CACHE.get(sizeAt);
7122
+ if (!byCount) {
7123
+ byCount = /* @__PURE__ */ new Map();
7124
+ PREFIX_CACHE.set(sizeAt, byCount);
7125
+ }
7126
+ const cached = byCount.get(count);
7127
+ if (cached) return cached;
7128
+ const prefix = new Float64Array(count + 1);
7129
+ let running = 0;
7130
+ for (let i = 0; i < count; i += 1) {
7131
+ let size = 0;
7132
+ try {
7133
+ size = sizeAt(i);
7134
+ } catch {
7135
+ size = 0;
7136
+ }
7137
+ running += isNum(size) && size > 0 ? size : 0;
7138
+ prefix[i + 1] = running;
7139
+ }
7140
+ byCount.set(count, prefix);
7141
+ return prefix;
7142
+ }
7143
+ function at(prefix, index) {
7144
+ return prefix[index] ?? 0;
7145
+ }
7146
+ function lastAtOrBefore(prefix, lo, hi, target) {
7147
+ let a = lo;
7148
+ let b = hi;
7149
+ while (a < b) {
7150
+ const mid = a + Math.ceil((b - a) / 2);
7151
+ if (at(prefix, mid) <= target) a = mid;
7152
+ else b = mid - 1;
7153
+ }
7154
+ return a;
7155
+ }
7156
+ function firstAtOrAfter(prefix, lo, hi, target) {
7157
+ let a = lo;
7158
+ let b = hi;
7159
+ while (a < b) {
7160
+ const mid = a + Math.floor((b - a) / 2);
7161
+ if (at(prefix, mid) >= target) b = mid;
7162
+ else a = mid + 1;
7163
+ }
7164
+ return a;
7165
+ }
7166
+ function computeVariableWindow(input) {
7167
+ const count = toCount(input?.count);
7168
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7169
+ if (count <= 0) return emptyWindow(0);
7170
+ const sizeAt = typeof input?.sizeAt === "function" ? input.sizeAt : () => 0;
7171
+ const viewport = toLength(input?.viewport);
7172
+ const overscan = toOverscan(input?.overscan);
7173
+ const scrollCount = count - pinnedCount;
7174
+ if (scrollCount <= 0) return emptyWindow(pinnedCount);
7175
+ const prefix = prefixFor(sizeAt, count);
7176
+ const pinnedLength = at(prefix, pinnedCount);
7177
+ const total = at(prefix, count) - pinnedLength;
7178
+ if (total <= 0) {
7179
+ return assemble({ pinnedCount, start: pinnedCount, end: count, padStart: 0, body: 0, total: total > 0 ? total : 0 });
7180
+ }
7181
+ const maxOffset = Math.max(0, total - viewport);
7182
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7183
+ const firstVisible = lastAtOrBefore(prefix, pinnedCount, count, pinnedLength + offset) - pinnedCount;
7184
+ const lastVisible = firstAtOrAfter(prefix, pinnedCount, count, pinnedLength + offset + viewport) - pinnedCount;
7185
+ const { start: relStart, end: relEnd } = band(firstVisible, lastVisible, scrollCount, overscan);
7186
+ const absStart = pinnedCount + relStart;
7187
+ const absEnd = pinnedCount + relEnd;
7188
+ return assemble({
7189
+ pinnedCount,
7190
+ start: absStart,
7191
+ end: absEnd,
7192
+ padStart: at(prefix, absStart) - pinnedLength,
7193
+ body: at(prefix, absEnd) - at(prefix, absStart),
7194
+ total
7195
+ });
7196
+ }
7197
+ function scrollOffsetForIndex(input) {
7198
+ const count = toCount(input?.count);
7199
+ if (count <= 0) return null;
7200
+ const pinnedCount = toPinned(input?.pinnedStart, count);
7201
+ const itemSize = toLength(input?.itemSize);
7202
+ const viewport = toLength(input?.viewport);
7203
+ const scrollCount = count - pinnedCount;
7204
+ if (scrollCount <= 0 || itemSize <= 0) return null;
7205
+ const rawIndex = isNum(input?.index) ? Math.floor(input.index) : 0;
7206
+ const index = Math.min(Math.max(rawIndex, 0), count - 1);
7207
+ if (index < pinnedCount) return null;
7208
+ const total = scrollCount * itemSize;
7209
+ const maxOffset = Math.max(0, total - viewport);
7210
+ const offset = Math.min(toLength(input?.offset), maxOffset);
7211
+ const itemStart = (index - pinnedCount) * itemSize;
7212
+ const itemEnd = itemStart + itemSize;
7213
+ if (itemStart >= offset && itemEnd <= offset + viewport) return null;
7214
+ const next = itemStart < offset ? itemStart : itemEnd - viewport;
7215
+ const clamped = Math.min(Math.max(next, 0), maxOffset);
7216
+ return clamped === offset ? null : clamped;
7217
+ }
7218
+
7219
+ // src/data-table/useSpreadsheetGrid.ts
7220
+ import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo6, useRef as useRef11, useState as useState16 } from "react";
7221
+
7222
+ // src/data-table/grid-selection.ts
7223
+ function rangeBounds(range, rowIds, fieldNames) {
7224
+ const ra = rowIds.indexOf(range.anchor.rowId);
7225
+ const rf = rowIds.indexOf(range.focus.rowId);
7226
+ const ca = fieldNames.indexOf(range.anchor.fieldName);
7227
+ const cf = fieldNames.indexOf(range.focus.fieldName);
7228
+ if (ra === -1 || rf === -1 || ca === -1 || cf === -1) return null;
7229
+ return {
7230
+ r0: Math.min(ra, rf),
7231
+ r1: Math.max(ra, rf),
7232
+ c0: Math.min(ca, cf),
7233
+ c1: Math.max(ca, cf)
7234
+ };
7235
+ }
7236
+ function rangeSize(bounds) {
7237
+ const rows = bounds.r1 - bounds.r0 + 1;
7238
+ const cols = bounds.c1 - bounds.c0 + 1;
7239
+ return { rows, cols, cells: rows * cols };
7240
+ }
7241
+ function boundsContain(bounds, rowIndex, colIndex) {
7242
+ return rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
7243
+ }
7244
+ function cellsInRange(range, rowIds, fieldNames) {
7245
+ const b = rangeBounds(range, rowIds, fieldNames);
7246
+ if (!b) return [];
7247
+ const out = [];
7248
+ for (let r = b.r0; r <= b.r1; r += 1) {
7249
+ const rowId = rowIds[r];
7250
+ if (rowId === void 0) continue;
7251
+ for (let c = b.c0; c <= b.c1; c += 1) {
7252
+ const fieldName = fieldNames[c];
7253
+ if (fieldName === void 0) continue;
7254
+ out.push({ rowId, fieldName });
7255
+ }
7256
+ }
7257
+ return out;
7258
+ }
7259
+ function rangeRows(range, rowIds, fieldNames) {
7260
+ const b = rangeBounds(range, rowIds, fieldNames);
7261
+ if (!b) return [];
7262
+ const rows = [];
7263
+ for (let r = b.r0; r <= b.r1; r += 1) {
7264
+ const rowId = rowIds[r];
7265
+ if (rowId === void 0) continue;
7266
+ const row = [];
7267
+ for (let c = b.c0; c <= b.c1; c += 1) {
7268
+ const fieldName = fieldNames[c];
7269
+ if (fieldName === void 0) continue;
7270
+ row.push({ rowId, fieldName });
7271
+ }
7272
+ rows.push(row);
7273
+ }
7274
+ return rows;
7275
+ }
7276
+ function isSingleCellRange(range) {
7277
+ return sameCell(range.anchor, range.focus);
7278
+ }
7279
+ function rowRange(rowId, fieldNames) {
7280
+ const firstField = fieldNames[0];
7281
+ const lastField = fieldNames[fieldNames.length - 1];
7282
+ if (firstField === void 0 || lastField === void 0) return null;
7283
+ return {
7284
+ anchor: { rowId, fieldName: firstField },
7285
+ focus: { rowId, fieldName: lastField }
7286
+ };
7287
+ }
7288
+ function columnRange(fieldName, rowIds) {
7289
+ const firstRow = rowIds[0];
7290
+ const lastRow = rowIds[rowIds.length - 1];
7291
+ if (firstRow === void 0 || lastRow === void 0) return null;
7292
+ return {
7293
+ anchor: { rowId: firstRow, fieldName },
7294
+ focus: { rowId: lastRow, fieldName }
7295
+ };
7296
+ }
7297
+ function allRange(rowIds, fieldNames) {
7298
+ const firstRow = rowIds[0];
7299
+ const lastRow = rowIds[rowIds.length - 1];
7300
+ const firstField = fieldNames[0];
7301
+ const lastField = fieldNames[fieldNames.length - 1];
7302
+ if (firstRow === void 0 || lastRow === void 0 || firstField === void 0 || lastField === void 0) {
7303
+ return null;
7304
+ }
7305
+ return {
7306
+ anchor: { rowId: firstRow, fieldName: firstField },
7307
+ focus: { rowId: lastRow, fieldName: lastField }
7308
+ };
7309
+ }
7310
+ function sameCell(a, b) {
7311
+ if (!a || !b) return a === b;
7312
+ return a.rowId === b.rowId && a.fieldName === b.fieldName;
7313
+ }
7314
+ function moveSelection(current, move, rowIds, fieldNames) {
7315
+ if (rowIds.length === 0 || fieldNames.length === 0) return null;
7316
+ const firstRowId = rowIds[0];
7317
+ const firstFieldName = fieldNames[0];
7318
+ if (firstRowId === void 0 || firstFieldName === void 0) return null;
7319
+ const first = { rowId: firstRowId, fieldName: firstFieldName };
7320
+ if (!current) return first;
7321
+ let r = rowIds.indexOf(current.rowId);
7322
+ let c = fieldNames.indexOf(current.fieldName);
7323
+ if (r === -1 || c === -1) return first;
7324
+ const lastRow = rowIds.length - 1;
7325
+ const lastCol = fieldNames.length - 1;
7326
+ const clamp = (n, max) => Math.min(Math.max(n, 0), max);
7327
+ switch (move) {
7328
+ case "up":
7329
+ r = clamp(r - 1, lastRow);
7330
+ break;
7331
+ case "down":
7332
+ r = clamp(r + 1, lastRow);
7333
+ break;
7334
+ case "left":
7335
+ c = clamp(c - 1, lastCol);
7336
+ break;
7337
+ case "right":
7338
+ c = clamp(c + 1, lastCol);
7339
+ break;
7340
+ case "rowStart":
7341
+ c = 0;
7342
+ break;
7343
+ case "rowEnd":
7344
+ c = lastCol;
7345
+ break;
7346
+ case "gridStart":
7347
+ r = 0;
7348
+ c = 0;
7349
+ break;
7350
+ case "gridEnd":
7351
+ r = lastRow;
7352
+ c = lastCol;
7353
+ break;
7354
+ case "nextCell":
7355
+ if (c < lastCol) c += 1;
7356
+ else if (r < lastRow) {
7357
+ r += 1;
7358
+ c = 0;
7359
+ }
7360
+ break;
7361
+ case "prevCell":
7362
+ if (c > 0) c -= 1;
7363
+ else if (r > 0) {
7364
+ r -= 1;
7365
+ c = lastCol;
7366
+ }
7367
+ break;
7368
+ }
7369
+ const rowId = rowIds[r];
7370
+ const fieldName = fieldNames[c];
7371
+ if (rowId === void 0 || fieldName === void 0) return first;
7372
+ return { rowId, fieldName };
7373
+ }
7374
+ function isTypingKey(e) {
7375
+ if (e.ctrlKey || e.metaKey || e.altKey) return false;
7376
+ return e.key.length === 1 && e.key !== " ";
7377
+ }
7378
+ function classifyGridKey(e) {
7379
+ const mod = e.ctrlKey || e.metaKey;
7380
+ const arrowMove = {
7381
+ ArrowUp: mod ? "gridStart" : "up",
7382
+ ArrowDown: mod ? "gridEnd" : "down",
7383
+ ArrowLeft: mod ? "rowStart" : "left",
7384
+ ArrowRight: mod ? "rowEnd" : "right",
7385
+ Home: mod ? "gridStart" : "rowStart",
7386
+ End: mod ? "gridEnd" : "rowEnd"
7387
+ };
7388
+ const arrow = arrowMove[e.key];
7389
+ if (arrow !== void 0) {
7390
+ return { kind: e.shiftKey ? "extend" : "move", move: arrow };
7391
+ }
7392
+ switch (e.key) {
7393
+ case "Tab":
7394
+ return { kind: "move", move: e.shiftKey ? "prevCell" : "nextCell" };
7395
+ case "Enter":
7396
+ case "F2":
7397
+ return { kind: "edit" };
7398
+ case " ":
7399
+ if (e.shiftKey) return { kind: "selectRow" };
7400
+ if (mod) return { kind: "selectColumn" };
7401
+ return { kind: "edit" };
7402
+ case "a":
7403
+ case "A":
7404
+ if (mod) return { kind: "selectAll" };
7405
+ break;
7406
+ case "d":
7407
+ case "D":
7408
+ if (mod) return { kind: "fillDown" };
7409
+ break;
7410
+ case "Escape":
7411
+ return { kind: "escape" };
7412
+ case "Delete":
7413
+ case "Backspace":
7414
+ return { kind: "clearCell" };
7415
+ case "c":
7416
+ case "C":
7417
+ if (mod) return { kind: "copy" };
7418
+ break;
7419
+ case "x":
7420
+ case "X":
7421
+ if (mod) return { kind: "cut" };
7422
+ break;
7423
+ case "v":
7424
+ case "V":
7425
+ if (mod) return { kind: "paste" };
7426
+ break;
7427
+ }
7428
+ if (isTypingKey(e)) return { kind: "editSeeded", seed: e.key };
7429
+ return null;
7430
+ }
7431
+ function directClickKinds() {
7432
+ return ["checkbox", "rating", "select", "multiselect"];
7433
+ }
7434
+ function isDirectClickEditor(editorKind) {
7435
+ return editorKind !== void 0 && directClickKinds().includes(editorKind);
7436
+ }
7437
+ function cellDomKey(address) {
7438
+ return `${address.rowId}::${address.fieldName}`;
7439
+ }
7440
+
7441
+ // src/data-table/grid-clipboard.ts
7442
+ function cellClipboardText(value) {
7443
+ if (value === null || value === void 0) return "";
7444
+ if (typeof value === "object") return JSON.stringify(value);
7445
+ return String(value);
7446
+ }
7447
+ function tsvCell(text) {
7448
+ return /[\t\r\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
7449
+ }
7450
+ function gridToTsv(block) {
7451
+ return block.map((row) => row.map((cell) => tsvCell(cellClipboardText(cell))).join(" ")).join("\n");
7452
+ }
7453
+ function parseClipboardGrid(text) {
7454
+ const rows = [];
7455
+ let row = [];
7456
+ let cell = "";
7457
+ let i = 0;
7458
+ const n = text.length;
7459
+ while (i < n) {
7460
+ const ch = text[i];
7461
+ if (cell === "" && ch === '"') {
7462
+ let j = i + 1;
7463
+ let out = "";
7464
+ let closed = false;
7465
+ while (j < n) {
7466
+ const c = text[j];
7467
+ if (c === '"') {
7468
+ if (text[j + 1] === '"') {
7469
+ out += '"';
7470
+ j += 2;
7471
+ continue;
7472
+ }
7473
+ closed = true;
7474
+ j += 1;
7475
+ break;
7476
+ }
7477
+ out += c;
7478
+ j += 1;
7479
+ }
7480
+ if (closed && (j >= n || text[j] === " " || text[j] === "\n" || text[j] === "\r")) {
7481
+ cell = out;
7482
+ i = j;
7483
+ if (i >= n) break;
7484
+ const d = text[i];
7485
+ if (d === " ") {
7486
+ row.push(cell);
7487
+ cell = "";
7488
+ i += 1;
7489
+ continue;
7490
+ }
7491
+ row.push(cell);
7492
+ rows.push(row);
7493
+ row = [];
7494
+ cell = "";
7495
+ i += d === "\r" && text[i + 1] === "\n" ? 2 : 1;
7496
+ continue;
7497
+ }
7498
+ cell += ch;
7499
+ i += 1;
7500
+ continue;
7501
+ }
7502
+ if (ch === " ") {
7503
+ row.push(cell);
7504
+ cell = "";
7505
+ i += 1;
7506
+ continue;
7507
+ }
7508
+ if (ch === "\n" || ch === "\r") {
7509
+ row.push(cell);
7510
+ rows.push(row);
7511
+ row = [];
7512
+ cell = "";
7513
+ i += ch === "\r" && text[i + 1] === "\n" ? 2 : 1;
7514
+ continue;
7515
+ }
7516
+ cell += ch;
7517
+ i += 1;
7518
+ }
7519
+ const endedOnBreak = n > 0 && (text[n - 1] === "\n" || text[n - 1] === "\r");
7520
+ if (!endedOnBreak) {
7521
+ row.push(cell);
7522
+ rows.push(row);
7523
+ } else if (row.length > 0) {
7524
+ row.push(cell);
7525
+ rows.push(row);
7526
+ }
7527
+ return rows.length === 0 ? [[""]] : rows;
7528
+ }
7529
+ function planPaste(anchor, block, rowIds, fieldNames) {
7530
+ const r0 = rowIds.indexOf(anchor.rowId);
7531
+ const c0 = fieldNames.indexOf(anchor.fieldName);
7532
+ if (r0 === -1 || c0 === -1) return null;
7533
+ const width = block.reduce((w, row) => Math.max(w, row.length), 0);
7534
+ const colsAvailable = fieldNames.length - c0;
7535
+ const colsUsed = Math.min(width, colsAvailable);
7536
+ const targetFields = fieldNames.slice(c0, c0 + colsUsed);
7537
+ const cells = [];
7538
+ const overflowRows = [];
7539
+ block.forEach((blockRow, dr) => {
7540
+ const rowId = rowIds[r0 + dr];
7541
+ if (rowId === void 0) {
7542
+ overflowRows.push(targetFields.map((_, dc) => blockRow[dc] ?? ""));
7543
+ return;
7544
+ }
7545
+ targetFields.forEach((fieldName, dc) => {
7546
+ if (dc >= blockRow.length) return;
7547
+ const raw = blockRow[dc];
7548
+ if (raw === void 0) return;
7549
+ cells.push({ rowId, fieldName, raw });
7550
+ });
7551
+ });
7552
+ return {
7553
+ cells,
7554
+ overflowRows,
7555
+ clippedColumns: Math.max(0, width - colsAvailable),
7556
+ fieldNames: targetFields
7557
+ };
7558
+ }
7559
+ function isSingleCell(block) {
7560
+ return block.length === 1 && block[0]?.length === 1;
7561
+ }
7562
+ function storedValuesEqual(a, b) {
7563
+ if (a === b) return true;
7564
+ if (a === null || b === null || a === void 0 || b === void 0) {
7565
+ return (a ?? null) === (b ?? null);
7566
+ }
7567
+ if (typeof a === "object" || typeof b === "object") {
7568
+ try {
7569
+ return JSON.stringify(a) === JSON.stringify(b);
7570
+ } catch {
7571
+ return false;
7572
+ }
7573
+ }
7574
+ return false;
7575
+ }
7576
+
7577
+ // src/data-table/fill.ts
7578
+ var NO_SERIES = { kind: "none", step: 0 };
7579
+ var MS_PER_DAY = 864e5;
7580
+ function plural(count, one, many) {
7581
+ return `${count} ${count === 1 ? one : many}`;
7582
+ }
7583
+ function isValidRect(rect) {
7584
+ 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;
7585
+ }
7586
+ function asBounds(rect) {
7587
+ return { r0: rect.top, r1: rect.bottom, c0: rect.left, c1: rect.right };
7588
+ }
7589
+ function directionOf(source, target) {
7590
+ if (!isValidRect(source) || !isValidRect(target)) return null;
7591
+ if (target.top === source.bottom + 1) return "down";
7592
+ if (target.bottom === source.top - 1) return "up";
7593
+ if (target.left === source.right + 1) return "right";
7594
+ if (target.right === source.left - 1) return "left";
7595
+ return null;
7596
+ }
7597
+ function tileIndex(i, a, b) {
7598
+ const n = b - a + 1;
7599
+ if (n <= 0) return a;
7600
+ if (i >= a && i <= b) return i;
7601
+ if (i > b) return a + (i - a) % n;
7602
+ return b - (a - 1 - i) % n;
7603
+ }
7604
+ function isFiniteNumber(v) {
7605
+ return typeof v === "number" && Number.isFinite(v);
7606
+ }
7607
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
7608
+ var DATE_ISH = /^\d{4}-\d{2}-\d{2}([T ]|$)/;
7609
+ function dateMs(v) {
7610
+ if (v instanceof Date) {
7611
+ const t = v.getTime();
7612
+ return Number.isFinite(t) ? t : null;
7613
+ }
7614
+ if (typeof v === "string" && DATE_ISH.test(v)) {
7615
+ const t = Date.parse(v);
7616
+ return Number.isFinite(t) ? t : null;
7617
+ }
7618
+ return null;
7619
+ }
7620
+ function detectSeries(values) {
7621
+ if (!Array.isArray(values) || values.length < 2) return NO_SERIES;
7622
+ if (values.every(isFiniteNumber)) {
7623
+ const nums = values;
7624
+ const first = nums[0];
7625
+ const second = nums[1];
7626
+ if (first === void 0 || second === void 0) return NO_SERIES;
7627
+ const step = second - first;
7628
+ for (let i = 1; i < nums.length; i += 1) {
7629
+ const prev = nums[i - 1];
7630
+ const cur = nums[i];
7631
+ if (prev === void 0 || cur === void 0) return NO_SERIES;
7632
+ if (cur - prev !== step) return NO_SERIES;
7633
+ }
7634
+ return { kind: "number", step };
7635
+ }
7636
+ const stamps = values.map(dateMs);
7637
+ if (stamps.every((t) => t !== null)) {
7638
+ const first = stamps[0];
7639
+ const second = stamps[1];
7640
+ if (first === void 0 || second === void 0) return NO_SERIES;
7641
+ const deltaMs = second - first;
7642
+ if (deltaMs % MS_PER_DAY !== 0) return NO_SERIES;
7643
+ for (let i = 1; i < stamps.length; i += 1) {
7644
+ const prev = stamps[i - 1];
7645
+ const cur = stamps[i];
7646
+ if (prev === void 0 || cur === void 0) return NO_SERIES;
7647
+ if (cur - prev !== deltaMs) return NO_SERIES;
7648
+ }
7649
+ return { kind: "date", step: deltaMs / MS_PER_DAY };
7650
+ }
7651
+ return NO_SERIES;
7652
+ }
7653
+ function formatDateLike(ms, sample) {
7654
+ const iso = new Date(ms).toISOString();
7655
+ if (typeof sample === "string" && DATE_ONLY.test(sample)) return iso.slice(0, 10);
7656
+ return iso;
7657
+ }
7658
+ function extrapolate(line, series, k) {
7659
+ const last = line[line.length - 1];
7660
+ if (series.kind === "number" && isFiniteNumber(last)) {
7661
+ return last + k * series.step;
7662
+ }
7663
+ if (series.kind === "date") {
7664
+ const base = dateMs(last);
7665
+ if (base === null) return jsonable(last);
7666
+ return formatDateLike(base + k * series.step * MS_PER_DAY, last);
7667
+ }
7668
+ return jsonable(last);
7669
+ }
7670
+ function jsonable(value) {
7671
+ if (value instanceof Date) {
7672
+ const t = value.getTime();
7673
+ return Number.isFinite(t) ? value.toISOString() : null;
7674
+ }
7675
+ if (value === void 0) return null;
7676
+ return value;
7677
+ }
7678
+ function emptyResult(summary) {
7679
+ return { patches: [], skipped: [], summary };
7680
+ }
7681
+ function fillRange(request) {
7682
+ const { source, target, columnIds, rowIds } = request;
7683
+ const mode = request.mode ?? "series";
7684
+ const isWritable = request.isColumnWritable ?? (() => true);
7685
+ const direction = directionOf(source, target);
7686
+ if (!direction) {
7687
+ return emptyResult(
7688
+ "Filled 0 cells because the fill area does not touch the selection."
7689
+ );
7690
+ }
7691
+ if (rowIds.length === 0 || columnIds.length === 0) {
7692
+ return emptyResult("Filled 0 cells because the table is empty.");
7693
+ }
7694
+ const read = (r, c) => {
7695
+ try {
7696
+ return { ok: true, value: request.valueAt(r, c) };
7697
+ } catch {
7698
+ return { ok: false, value: void 0 };
7699
+ }
7700
+ };
7701
+ const vertical = direction === "down" || direction === "up";
7702
+ const forward = direction === "down" || direction === "right";
7703
+ const fillStart = vertical ? source.top : source.left;
7704
+ const fillEnd = vertical ? source.bottom : source.right;
7705
+ const crossStart = vertical ? source.left : source.top;
7706
+ const crossEnd = vertical ? source.right : source.bottom;
7707
+ const maxFill = (vertical ? rowIds.length : columnIds.length) - 1;
7708
+ const maxCross = (vertical ? columnIds.length : rowIds.length) - 1;
7709
+ const rawFillFrom = vertical ? target.top : target.left;
7710
+ const rawFillTo = vertical ? target.bottom : target.right;
7711
+ const rawCrossFrom = vertical ? target.left : target.top;
7712
+ const rawCrossTo = vertical ? target.right : target.bottom;
7713
+ const fillFrom = Math.max(rawFillFrom, 0);
7714
+ const fillTo = Math.min(rawFillTo, maxFill);
7715
+ const crossFrom = Math.max(rawCrossFrom, 0);
7716
+ const crossTo = Math.min(rawCrossTo, maxCross);
7717
+ const requested = Math.max(rawFillTo - rawFillFrom + 1, 0) * Math.max(rawCrossTo - rawCrossFrom + 1, 0);
7718
+ const inside = Math.max(fillTo - fillFrom + 1, 0) * Math.max(crossTo - crossFrom + 1, 0);
7719
+ const outside = Math.max(requested - inside, 0);
7720
+ const sourceBounds = asBounds(source);
7721
+ const patches = [];
7722
+ const skipped = [];
7723
+ const touchedColumns = /* @__PURE__ */ new Set();
7724
+ const lines = /* @__PURE__ */ new Map();
7725
+ const lineFor = (crossIndex) => {
7726
+ const srcCross = tileIndex(crossIndex, crossStart, crossEnd);
7727
+ const cached = lines.get(srcCross);
7728
+ if (cached) return cached;
7729
+ const values = [];
7730
+ const reads = [];
7731
+ for (let f = fillStart; f <= fillEnd; f += 1) {
7732
+ const r = vertical ? f : srcCross;
7733
+ const c = vertical ? srcCross : f;
7734
+ const got = read(r, c);
7735
+ values.push(got.value);
7736
+ reads.push(got.ok);
7737
+ }
7738
+ const series = mode === "series" ? detectSeries(values) : NO_SERIES;
7739
+ const line = { values, reads, series };
7740
+ lines.set(srcCross, line);
7741
+ return line;
7742
+ };
7743
+ for (let f = fillFrom; f <= fillTo; f += 1) {
7744
+ for (let x = crossFrom; x <= crossTo; x += 1) {
7745
+ const rowIndex = vertical ? f : x;
7746
+ const colIndex = vertical ? x : f;
7747
+ const rowId = rowIds[rowIndex];
7748
+ const columnId2 = columnIds[colIndex];
7749
+ if (rowId === void 0 || columnId2 === void 0) continue;
7750
+ if (boundsContain(sourceBounds, rowIndex, colIndex)) continue;
7751
+ if (!isWritable(columnId2)) {
7752
+ skipped.push({
7753
+ rowId,
7754
+ columnId: columnId2,
7755
+ reason: `${columnId2} cannot be filled because that column is read-only.`
7756
+ });
7757
+ continue;
7758
+ }
7759
+ const line = lineFor(x);
7760
+ if (line.series.kind !== "none") {
7761
+ const k = forward ? f - fillEnd : fillStart - f;
7762
+ patches.push({ rowId, columnId: columnId2, value: jsonable(extrapolate(line.values, line.series, k)) });
7763
+ touchedColumns.add(columnId2);
7764
+ continue;
7765
+ }
7766
+ const srcFill = tileIndex(f, fillStart, fillEnd);
7767
+ const patternIndex = srcFill - fillStart;
7768
+ if (line.reads[patternIndex] !== true) {
7769
+ skipped.push({
7770
+ rowId,
7771
+ columnId: columnId2,
7772
+ reason: `The value to copy into ${columnId2} could not be read, so that cell was left alone.`
7773
+ });
7774
+ continue;
7775
+ }
7776
+ patches.push({ rowId, columnId: columnId2, value: jsonable(line.values[patternIndex]) });
7777
+ touchedColumns.add(columnId2);
7778
+ }
7779
+ }
7780
+ return { patches, skipped, summary: summarize(direction, patches, skipped, touchedColumns, outside) };
7781
+ }
7782
+ function summarize(direction, patches, skipped, touchedColumns, outside) {
7783
+ const parts = [];
7784
+ if (patches.length === 0) {
7785
+ parts.push("Filled 0 cells because there was nothing to fill.");
7786
+ } else if (direction === "down" || direction === "up") {
7787
+ const word = direction === "down" ? "down" : "up";
7788
+ parts.push(
7789
+ `Filled ${plural(patches.length, "cell", "cells")} ${word} ${plural(touchedColumns.size, "column", "columns")}.`
7790
+ );
7791
+ } else {
7792
+ const word = direction === "right" ? "right" : "left";
7793
+ parts.push(
7794
+ `Filled ${plural(patches.length, "cell", "cells")} ${word} across ${plural(touchedColumns.size, "column", "columns")}.`
7795
+ );
7796
+ }
7797
+ if (skipped.length > 0) {
7798
+ parts.push(`Skipped ${plural(skipped.length, "cell", "cells")} that could not be filled.`);
7799
+ }
7800
+ if (outside > 0) {
7801
+ parts.push(`Ignored ${plural(outside, "cell", "cells")} outside the table.`);
7802
+ }
7803
+ return parts.join(" ");
7804
+ }
7805
+ function fillDown(request) {
7806
+ const { range, ...rest } = request;
7807
+ if (!isValidRect(range)) {
7808
+ return emptyResult("Filled 0 cells because the selection is not a rectangle.");
7809
+ }
7810
+ if (range.top === range.bottom) {
7811
+ return emptyResult("Filled 0 cells because the selection is a single row.");
7812
+ }
7813
+ return fillRange({
7814
+ ...rest,
7815
+ source: { top: range.top, left: range.left, bottom: range.top, right: range.right },
7816
+ target: { top: range.top + 1, left: range.left, bottom: range.bottom, right: range.right }
7817
+ });
7818
+ }
7819
+ function fillTargetFor(source, toRow, toColumn) {
7820
+ if (!isValidRect(source)) return null;
7821
+ if (!Number.isInteger(toRow) || !Number.isInteger(toColumn)) return null;
7822
+ const below = toRow - source.bottom;
7823
+ const above = source.top - toRow;
7824
+ const rightOf = toColumn - source.right;
7825
+ const leftOf = source.left - toColumn;
7826
+ const vertical = Math.max(below, above, 0);
7827
+ const horizontal = Math.max(rightOf, leftOf, 0);
7828
+ if (vertical === 0 && horizontal === 0) return null;
7829
+ if (vertical >= horizontal) {
7830
+ if (below > 0) {
7831
+ return { top: source.bottom + 1, bottom: toRow, left: source.left, right: source.right };
7832
+ }
7833
+ return { top: toRow, bottom: source.top - 1, left: source.left, right: source.right };
7834
+ }
7835
+ if (rightOf > 0) {
7836
+ return { top: source.top, bottom: source.bottom, left: source.right + 1, right: toColumn };
7837
+ }
7838
+ return { top: source.top, bottom: source.bottom, left: toColumn, right: source.left - 1 };
7839
+ }
7840
+
7841
+ // src/data-table/cell-undo.ts
7842
+ var DEFAULT_UNDO_DEPTH = 100;
7843
+ function createUndoStack(depth = DEFAULT_UNDO_DEPTH) {
7844
+ const bounded = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 0;
7845
+ return { past: [], future: [], depth: bounded };
7846
+ }
7847
+ function pushUndo(stack, entry) {
7848
+ if (stack.depth <= 0) {
7849
+ return { past: [], future: [], depth: stack.depth };
7850
+ }
7851
+ const past = [...stack.past, entry];
7852
+ while (past.length > stack.depth) past.shift();
7853
+ return { past, future: [], depth: stack.depth };
7854
+ }
7855
+ function popUndo(stack) {
7856
+ const entry = stack.past[stack.past.length - 1];
7857
+ if (entry === void 0) return { stack, entry: null };
7858
+ return {
7859
+ stack: {
7860
+ past: stack.past.slice(0, -1),
7861
+ future: [...stack.future, entry],
7862
+ depth: stack.depth
7863
+ },
7864
+ entry
7865
+ };
7866
+ }
7867
+ function popRedo(stack) {
7868
+ const entry = stack.future[stack.future.length - 1];
7869
+ if (entry === void 0) return { stack, entry: null };
7870
+ const past = [...stack.past, entry];
7871
+ while (past.length > stack.depth) past.shift();
7872
+ return {
7873
+ stack: { past, future: stack.future.slice(0, -1), depth: stack.depth },
7874
+ entry
7875
+ };
7876
+ }
7877
+ function keyOf(patch) {
7878
+ return JSON.stringify([patch.rowId, patch.columnId]);
7879
+ }
7880
+ function undoEntryFor(before, after, label) {
7881
+ const priors = /* @__PURE__ */ new Map();
7882
+ for (const patch of before) priors.set(keyOf(patch), patch);
7883
+ const undo = after.map((patch) => {
7884
+ const prior = priors.get(keyOf(patch));
7885
+ return {
7886
+ rowId: patch.rowId,
7887
+ columnId: patch.columnId,
7888
+ value: prior ? prior.value : null
7889
+ };
7890
+ });
7891
+ return {
7892
+ label,
7893
+ undo,
7894
+ redo: after.map((patch) => ({
7895
+ rowId: patch.rowId,
7896
+ columnId: patch.columnId,
7897
+ value: patch.value
7898
+ }))
7899
+ };
7900
+ }
7901
+ function canUndo(stack) {
7902
+ return stack.depth > 0 && stack.past.length > 0;
7903
+ }
7904
+ function canRedo(stack) {
7905
+ return stack.depth > 0 && stack.future.length > 0;
7906
+ }
7907
+
7908
+ // src/data-table/useSpreadsheetGrid.ts
7909
+ function targetsAnEditor(target) {
7910
+ const element = target instanceof Element ? target : null;
7911
+ if (!element) return false;
7912
+ return element.closest("input, textarea, select, [contenteditable='true']") !== null;
7913
+ }
7914
+ function useSpreadsheetGrid(options) {
7915
+ const [range, setRange] = useState16(null);
7916
+ const [fillTarget, setFillTarget] = useState16(null);
7917
+ const [stack, setStack] = useState16(() => createUndoStack(options?.undoDepth ?? 100));
7918
+ const draggingRef = useRef11(null);
7919
+ const optionsRef = useRef11(options);
7920
+ optionsRef.current = options;
7921
+ const rowIds = options?.rowIds ?? [];
7922
+ const columnIds = useMemo6(
7923
+ () => options ? options.columnIds.filter((id) => id !== "") : [],
7924
+ [options]
7925
+ );
7926
+ useEffect11(() => {
7927
+ if (!range) return;
7928
+ const rowSet = new Set(rowIds);
7929
+ const colSet = new Set(columnIds);
7930
+ const alive = rowSet.has(range.anchor.rowId) && rowSet.has(range.focus.rowId) && colSet.has(range.anchor.fieldName) && colSet.has(range.focus.fieldName);
7931
+ if (!alive) setRange(null);
7932
+ }, [range, rowIds, columnIds]);
7933
+ const bounds = useMemo6(
7934
+ () => range ? rangeBounds(range, rowIds, columnIds) : null,
7935
+ [range, rowIds, columnIds]
7936
+ );
7937
+ const rowIndexById = useMemo6(() => {
7938
+ const map = /* @__PURE__ */ new Map();
7939
+ for (let i = 0; i < rowIds.length; i += 1) map.set(rowIds[i], i);
7940
+ return map;
7941
+ }, [rowIds]);
7942
+ const columnIndexById = useMemo6(() => {
7943
+ const map = /* @__PURE__ */ new Map();
7944
+ for (let i = 0; i < columnIds.length; i += 1) map.set(columnIds[i], i);
7945
+ return map;
7946
+ }, [columnIds]);
7947
+ const applyPatches = useCallback7(
7948
+ async (patches, intent, label) => {
7949
+ const current = optionsRef.current;
7950
+ if (!current || patches.length === 0) return;
7951
+ const depth = current.undoDepth ?? 100;
7952
+ if (depth > 0 && intent !== "undo" && intent !== "redo") {
7953
+ const before = patches.map((patch) => ({
7954
+ rowId: patch.rowId,
7955
+ columnId: patch.columnId,
7956
+ value: current.valueAt(patch.rowId, patch.columnId) ?? null
7957
+ }));
7958
+ setStack((previous) => pushUndo(previous, undoEntryFor(before, patches, label)));
7959
+ }
7960
+ await current.onPatch(patches, intent);
7961
+ },
7962
+ []
7963
+ );
7964
+ const readValueAt = useCallback7((rowIndex, columnIndex) => {
7965
+ const current = optionsRef.current;
7966
+ const rowId = current?.rowIds[rowIndex];
7967
+ const columnId2 = current?.columnIds[columnIndex];
7968
+ if (!current || rowId === void 0 || columnId2 === void 0) return null;
7969
+ return current.valueAt(rowId, columnId2);
7970
+ }, []);
7971
+ const fillRequestBase = useCallback7(() => {
7972
+ const current = optionsRef.current;
7973
+ if (!current) return null;
7974
+ return {
7975
+ valueAt: readValueAt,
7976
+ columnIds: [...current.columnIds],
7977
+ rowIds: [...current.rowIds],
7978
+ isColumnWritable: current.isColumnWritable
7979
+ };
7980
+ }, [readValueAt]);
7981
+ const announceSkips = useCallback7(
7982
+ (skipped, summary) => {
7983
+ const current = optionsRef.current;
7984
+ if (!current?.notify) return;
7985
+ if (skipped.length === 0) {
7986
+ current.notify(summary);
7987
+ return;
7988
+ }
7989
+ const first = skipped[0];
7990
+ const reason = current.columnRefusal?.(first.columnId) ?? first.reason;
7991
+ const rest = skipped.length > 1 ? ` ${skipped.length - 1} more were left alone.` : "";
7992
+ current.notify(`${summary} ${reason}${rest}`);
7993
+ },
7994
+ []
7995
+ );
7996
+ const doFillDown = useCallback7(async () => {
7997
+ const base = fillRequestBase();
7998
+ if (!base || !bounds) return;
7999
+ const result = fillDown({
8000
+ ...base,
8001
+ range: { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 }
8002
+ });
8003
+ await applyPatches(result.patches, "fill-down", "Filled down");
8004
+ announceSkips(result.skipped, result.summary);
8005
+ }, [fillRequestBase, bounds, applyPatches, announceSkips]);
8006
+ const doClear = useCallback7(async () => {
8007
+ const current = optionsRef.current;
8008
+ if (!current || !bounds) return;
8009
+ const patches = [];
8010
+ const skipped = [];
8011
+ for (let r = bounds.r0; r <= bounds.r1; r += 1) {
8012
+ for (let c = bounds.c0; c <= bounds.c1; c += 1) {
8013
+ const rowId = current.rowIds[r];
8014
+ const columnId2 = current.columnIds[c];
8015
+ if (rowId === void 0 || columnId2 === void 0) continue;
8016
+ if (!current.isColumnWritable(columnId2)) {
8017
+ skipped.push({
8018
+ columnId: columnId2,
8019
+ reason: current.columnRefusal?.(columnId2) ?? `${columnId2} cannot be cleared.`
8020
+ });
8021
+ continue;
8022
+ }
8023
+ patches.push({ rowId, columnId: columnId2, value: null });
8024
+ }
8025
+ }
8026
+ await applyPatches(patches, "clear", "Cleared");
8027
+ announceSkips(skipped, `Cleared ${patches.length} cells.`);
8028
+ }, [bounds, applyPatches, announceSkips]);
8029
+ const doUndo = useCallback7(async () => {
8030
+ const taken = popUndo(stack);
8031
+ if (!taken.entry) return;
8032
+ setStack(taken.stack);
8033
+ await applyPatches(taken.entry.undo, "undo", taken.entry.label);
8034
+ optionsRef.current?.notify?.(taken.entry.label);
8035
+ }, [stack, applyPatches]);
8036
+ const doRedo = useCallback7(async () => {
8037
+ const taken = popRedo(stack);
8038
+ if (!taken.entry) return;
8039
+ setStack(taken.stack);
8040
+ await applyPatches(taken.entry.redo, "redo", taken.entry.label);
8041
+ }, [stack, applyPatches]);
8042
+ const move = useCallback7(
8043
+ (direction, extend) => {
8044
+ setRange((previous) => {
8045
+ const from = previous ? previous.focus : null;
8046
+ const next = moveSelection(from, direction, rowIds, columnIds);
8047
+ if (!next) return previous;
8048
+ if (extend && previous) return { anchor: previous.anchor, focus: next };
8049
+ return { anchor: next, focus: next };
8050
+ });
8051
+ },
8052
+ [rowIds, columnIds]
8053
+ );
8054
+ const onKeyDown = useCallback7(
8055
+ (event) => {
8056
+ const current = optionsRef.current;
8057
+ if (!current) return;
8058
+ if (targetsAnEditor(event.target)) return;
8059
+ const accel = event.metaKey || event.ctrlKey;
8060
+ if (accel && (event.key === "z" || event.key === "Z")) {
8061
+ event.preventDefault();
8062
+ if (event.shiftKey) void doRedo();
8063
+ else void doUndo();
8064
+ return;
8065
+ }
8066
+ if (accel && (event.key === "y" || event.key === "Y")) {
8067
+ event.preventDefault();
8068
+ void doRedo();
8069
+ return;
8070
+ }
8071
+ const action = classifyGridKey(event);
8072
+ if (!action) return;
8073
+ switch (action.kind) {
8074
+ case "move":
8075
+ event.preventDefault();
8076
+ move(action.move, false);
8077
+ return;
8078
+ case "extend":
8079
+ event.preventDefault();
8080
+ move(action.move, true);
8081
+ return;
8082
+ case "selectAll": {
8083
+ event.preventDefault();
8084
+ const all = allRange(rowIds, columnIds);
8085
+ if (all) setRange(all);
8086
+ return;
8087
+ }
8088
+ case "selectRow": {
8089
+ event.preventDefault();
8090
+ const focused = range?.focus;
8091
+ const next = focused ? rowRange(focused.rowId, columnIds) : null;
8092
+ if (next) setRange(next);
8093
+ return;
8094
+ }
8095
+ case "selectColumn": {
8096
+ event.preventDefault();
8097
+ const focused = range?.focus;
8098
+ const next = focused ? columnRange(focused.fieldName, rowIds) : null;
8099
+ if (next) setRange(next);
8100
+ return;
8101
+ }
8102
+ case "clearCell":
8103
+ event.preventDefault();
8104
+ void doClear();
8105
+ return;
8106
+ case "fillDown":
8107
+ event.preventDefault();
8108
+ void doFillDown();
8109
+ return;
8110
+ case "escape":
8111
+ setRange(null);
8112
+ return;
8113
+ default:
8114
+ return;
8115
+ }
8116
+ },
8117
+ [move, range, rowIds, columnIds, doClear, doFillDown, doUndo, doRedo]
8118
+ );
8119
+ const selectionAsTsv = useCallback7(() => {
8120
+ const current = optionsRef.current;
8121
+ if (!current || !range) return "";
8122
+ const grid = rangeRows(range, rowIds, columnIds).map(
8123
+ (row) => row.map((address) => current.valueAt(address.rowId, address.fieldName))
8124
+ );
8125
+ return gridToTsv(grid);
8126
+ }, [range, rowIds, columnIds]);
8127
+ const onCopy = useCallback7(
8128
+ (event) => {
8129
+ if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;
8130
+ const text = selectionAsTsv();
8131
+ if (!text) return;
8132
+ event.preventDefault();
8133
+ event.clipboardData?.setData("text/plain", text);
8134
+ },
8135
+ [range, selectionAsTsv]
8136
+ );
8137
+ const onCut = useCallback7(
8138
+ (event) => {
8139
+ if (!optionsRef.current || !range || targetsAnEditor(event.target)) return;
8140
+ const text = selectionAsTsv();
8141
+ if (!text) return;
8142
+ event.preventDefault();
8143
+ event.clipboardData?.setData("text/plain", text);
8144
+ void doClear();
8145
+ },
8146
+ [range, selectionAsTsv, doClear]
8147
+ );
8148
+ const onPaste = useCallback7(
8149
+ (event) => {
8150
+ const current = optionsRef.current;
8151
+ if (!current || current.paste === false || !range || targetsAnEditor(event.target)) return;
8152
+ const text = event.clipboardData?.getData("text/plain") ?? "";
8153
+ if (!text) return;
8154
+ event.preventDefault();
8155
+ const block = parseClipboardGrid(text);
8156
+ const start = range.focus;
8157
+ const startRow = rowIds.indexOf(start.rowId);
8158
+ const startCol = columnIds.indexOf(start.fieldName);
8159
+ if (startRow === -1 || startCol === -1) return;
8160
+ const patches = [];
8161
+ const skipped = [];
8162
+ let outside = 0;
8163
+ for (let r = 0; r < block.length; r += 1) {
8164
+ const line = block[r] ?? [];
8165
+ for (let c = 0; c < line.length; c += 1) {
8166
+ const rowId = rowIds[startRow + r];
8167
+ const columnId2 = columnIds[startCol + c];
8168
+ if (rowId === void 0 || columnId2 === void 0) {
8169
+ outside += 1;
8170
+ continue;
8171
+ }
8172
+ if (!current.isColumnWritable(columnId2)) {
8173
+ skipped.push({
8174
+ columnId: columnId2,
8175
+ reason: current.columnRefusal?.(columnId2) ?? `${columnId2} cannot take a pasted value.`
8176
+ });
8177
+ continue;
8178
+ }
8179
+ patches.push({ rowId, columnId: columnId2, value: line[c] ?? null });
8180
+ }
8181
+ }
8182
+ void applyPatches(patches, "paste", "Pasted");
8183
+ const tail = outside > 0 ? ` ${outside} landed past the edge of the table and were ignored.` : "";
8184
+ announceSkips(skipped, `Pasted ${patches.length} cells.${tail}`);
8185
+ },
8186
+ [range, rowIds, columnIds, applyPatches, announceSkips]
8187
+ );
8188
+ const cellProps = useCallback7(
8189
+ (rowId, columnId2) => {
8190
+ const address = { rowId, fieldName: columnId2 };
8191
+ const rowIndex = rowIndexById.get(rowId) ?? -1;
8192
+ const colIndex = columnIndexById.get(columnId2) ?? -1;
8193
+ const selected = bounds !== null && rowIndex >= bounds.r0 && rowIndex <= bounds.r1 && colIndex >= bounds.c0 && colIndex <= bounds.c1;
8194
+ const active = range !== null && sameCell(range.focus, address);
8195
+ const inFillPreview = fillTarget !== null && rowIndex >= fillTarget.top && rowIndex <= fillTarget.bottom && colIndex >= fillTarget.left && colIndex <= fillTarget.right;
8196
+ return {
8197
+ "data-matrx-cell-row": rowId,
8198
+ "data-matrx-cell-col": columnId2,
8199
+ ...selected ? { "data-matrx-cell-selected": "true" } : {},
8200
+ ...active ? { "data-matrx-cell-active": "true" } : {},
8201
+ ...inFillPreview ? { "data-matrx-cell-fill-preview": "true" } : {},
8202
+ onMouseDown: (event) => {
8203
+ if (event.button !== 0) return;
8204
+ event.preventDefault();
8205
+ setRange(
8206
+ (previous) => event.shiftKey && previous ? { anchor: previous.anchor, focus: address } : { anchor: address, focus: address }
8207
+ );
8208
+ draggingRef.current = "select";
8209
+ },
8210
+ onMouseEnter: () => {
8211
+ if (draggingRef.current === "select") {
8212
+ setRange((previous) => previous ? { anchor: previous.anchor, focus: address } : previous);
8213
+ return;
8214
+ }
8215
+ if (draggingRef.current === "fill" && bounds) {
8216
+ setFillTarget(
8217
+ fillTargetFor(
8218
+ { top: bounds.r0, left: bounds.c0, bottom: bounds.r1, right: bounds.c1 },
8219
+ rowIndex,
8220
+ colIndex
8221
+ )
8222
+ );
8223
+ }
8224
+ }
8225
+ };
8226
+ },
8227
+ [rowIndexById, columnIndexById, bounds, range, fillTarget]
8228
+ );
8229
+ const isFillHandleCell = useCallback7(
8230
+ (rowId, columnId2) => {
8231
+ const current = optionsRef.current;
8232
+ if (!current || current.fillHandle === false || !bounds) return false;
8233
+ return rowIds[bounds.r1] === rowId && columnIds[bounds.c1] === columnId2;
8234
+ },
8235
+ [bounds, rowIds, columnIds]
8236
+ );
8237
+ const startFillDrag = useCallback7(
8238
+ (event) => {
8239
+ event.preventDefault();
8240
+ event.stopPropagation();
8241
+ draggingRef.current = "fill";
8242
+ },
8243
+ []
8244
+ );
8245
+ const fillTargetRef = useRef11(null);
8246
+ fillTargetRef.current = fillTarget;
8247
+ const boundsRef = useRef11(null);
8248
+ boundsRef.current = bounds;
8249
+ useEffect11(() => {
8250
+ if (!options) return;
8251
+ const commit = () => {
8252
+ const wasDragging = draggingRef.current;
8253
+ draggingRef.current = null;
8254
+ if (wasDragging !== "fill") return;
8255
+ const target = fillTargetRef.current;
8256
+ const source = boundsRef.current;
8257
+ setFillTarget(null);
8258
+ if (!target || !source) return;
8259
+ const base = fillRequestBase();
8260
+ if (!base) return;
8261
+ const result = fillRange({
8262
+ ...base,
8263
+ source: { top: source.r0, left: source.c0, bottom: source.r1, right: source.c1 },
8264
+ target
8265
+ });
8266
+ void applyPatches(result.patches, "fill", "Filled");
8267
+ announceSkips(result.skipped, result.summary);
8268
+ };
8269
+ window.addEventListener("mouseup", commit);
8270
+ return () => window.removeEventListener("mouseup", commit);
8271
+ }, [options, fillRequestBase, applyPatches, announceSkips]);
8272
+ return {
8273
+ range,
8274
+ bounds,
8275
+ enabled: options !== null,
8276
+ containerProps: {
8277
+ tabIndex: options ? 0 : -1,
8278
+ onKeyDown,
8279
+ onCopy,
8280
+ onCut,
8281
+ onPaste
8282
+ },
8283
+ cellProps,
8284
+ isFillHandleCell,
8285
+ startFillDrag,
8286
+ canUndo: stack.past.length > 0,
8287
+ canRedo: stack.future.length > 0
8288
+ };
8289
+ }
8290
+
8291
+ // src/data-table/coverage.ts
8292
+ var PARTIAL_BADGE = "Partial";
8293
+ var UNKNOWN_TOTAL_BADGE = "Unknown total";
8294
+ var NUMBERS = new Intl.NumberFormat("en-US");
8295
+ function formatNumber(value) {
8296
+ try {
8297
+ return NUMBERS.format(value);
8298
+ } catch {
8299
+ return String(value);
8300
+ }
8301
+ }
8302
+ function pluralize(noun) {
8303
+ const word = noun.trim();
8304
+ if (!word) return "rows";
8305
+ const lower = word.toLowerCase();
8306
+ if (/[^aeiou]y$/.test(lower)) return `${word.slice(0, -1)}ies`;
8307
+ if (/(s|x|z|ch|sh)$/.test(lower)) return `${word}es`;
8308
+ return `${word}s`;
8309
+ }
8310
+ function quantity(value, noun) {
8311
+ return `${formatNumber(value)} ${value === 1 ? noun.trim() || "row" : pluralize(noun)}`;
8312
+ }
8313
+ function toWhole(value) {
8314
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
8315
+ }
8316
+ function toOptionalWhole(value) {
8317
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
8318
+ return Math.floor(value);
8319
+ }
8320
+ function describeCoverage(input) {
8321
+ const safe = input && typeof input === "object" ? input : { loaded: 0, answeredBy: "client" };
8322
+ const loaded = toWhole(safe.loaded);
8323
+ const matched = toOptionalWhole(safe.matched);
8324
+ const total = toOptionalWhole(safe.total);
8325
+ const cap = toOptionalWhole(safe.cap);
8326
+ const answeredBy = safe.answeredBy === "source" ? "source" : "client";
8327
+ const noun = typeof safe.noun === "string" && safe.noun.trim() ? safe.noun.trim() : "row";
8328
+ const reference = matched ?? total;
8329
+ const capReached = cap !== void 0 && cap > 0 && loaded >= cap;
8330
+ if (reference !== void 0 && loaded >= reference) {
8331
+ return {
8332
+ scope: "all",
8333
+ complete: true,
8334
+ count: { value: matched ?? reference, exact: true },
8335
+ notice: "",
8336
+ badge: ""
8337
+ };
8338
+ }
8339
+ if (answeredBy === "source" && matched !== void 0 && !capReached) {
8340
+ return { scope: "all", complete: true, count: { value: matched, exact: true }, notice: "", badge: "" };
8341
+ }
8342
+ if (capReached && cap !== void 0) {
8343
+ const rest = reference !== void 0 ? `${quantity(reference, noun)} in this table; the rest were not looked at.` : "Anything past that was not looked at.";
8344
+ return {
8345
+ scope: "capped",
8346
+ complete: false,
8347
+ count: { value: loaded, exact: false },
8348
+ notice: `This filter searched the first ${quantity(cap, noun)} only. ${rest}`,
8349
+ badge: PARTIAL_BADGE
8350
+ };
8351
+ }
8352
+ if (answeredBy === "client" && reference !== void 0) {
8353
+ return {
8354
+ scope: "loaded",
8355
+ complete: false,
8356
+ count: { value: loaded, exact: false },
8357
+ notice: `Filtering the ${quantity(loaded, noun)} loaded so far, not all ${quantity(reference, noun)} in this table.`,
8358
+ badge: PARTIAL_BADGE
8359
+ };
8360
+ }
8361
+ if (answeredBy === "source" && total !== void 0) {
8362
+ return {
8363
+ scope: "all",
8364
+ complete: false,
8365
+ count: { value: loaded, exact: false },
8366
+ notice: `Showing the ${quantity(loaded, noun)} loaded so far out of ${quantity(total, noun)} in this table.`,
8367
+ badge: PARTIAL_BADGE
8368
+ };
8369
+ }
8370
+ return {
8371
+ scope: answeredBy === "source" ? "all" : "loaded",
8372
+ complete: false,
8373
+ count: { value: loaded, exact: false },
8374
+ notice: `Showing the ${quantity(loaded, noun)} loaded so far. The total number of ${pluralize(noun)} in this table is not known.`,
8375
+ badge: UNKNOWN_TOTAL_BADGE
8376
+ };
8377
+ }
8378
+
6774
8379
  // src/data-table/ranked-search.ts
6775
8380
  function inferTableSearchRole(field) {
6776
8381
  const key = field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/[\s-]+/g, "_");
@@ -6831,7 +8436,7 @@ function rankTableSearchRows(rows, columns, query, options = {}, extraText) {
6831
8436
  }
6832
8437
 
6833
8438
  // src/data-table/ColumnManager.tsx
6834
- import { useEffect as useEffect11, useMemo as useMemo6, useState as useState16 } from "react";
8439
+ import { useEffect as useEffect12, useMemo as useMemo7, useState as useState17 } from "react";
6835
8440
  import { useDndMonitor, useDraggable as useDraggable2, useDroppable } from "@dnd-kit/core";
6836
8441
  import { jsx as jsx33, jsxs as jsxs23 } from "react/jsx-runtime";
6837
8442
  function ColumnManager({
@@ -6842,15 +8447,15 @@ function ColumnManager({
6842
8447
  hidden,
6843
8448
  onApply
6844
8449
  }) {
6845
- const byId = useMemo6(
8450
+ const byId = useMemo7(
6846
8451
  () => new Map(columns.map((column) => [columnId(column), column])),
6847
8452
  [columns]
6848
8453
  );
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(() => {
8454
+ const [draftOrder, setDraftOrder] = useState17(order);
8455
+ const [draftHidden, setDraftHidden] = useState17(() => new Set(hidden));
8456
+ const [draggedColumnId, setDraggedColumnId] = useState17(null);
8457
+ const [dropTarget, setDropTarget] = useState17(null);
8458
+ useEffect12(() => {
6854
8459
  if (!open) return;
6855
8460
  setDraftOrder(order);
6856
8461
  setDraftHidden(new Set(hidden));
@@ -7381,9 +8986,9 @@ function parseTableViewSnapshot(raw) {
7381
8986
 
7382
8987
  // src/data-table/MatrxUuidCell.tsx
7383
8988
  import {
7384
- useEffect as useEffect12,
7385
- useRef as useRef11,
7386
- useState as useState17
8989
+ useEffect as useEffect13,
8990
+ useRef as useRef12,
8991
+ useState as useState18
7387
8992
  } from "react";
7388
8993
  import { jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
7389
8994
  var SHORT_LEN = 8;
@@ -7424,11 +9029,11 @@ function MatrxUuidCell({
7424
9029
  }) {
7425
9030
  const { resolveEntityDoors, ResourcePeek } = useMatrxDataTableHost();
7426
9031
  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(
9032
+ const [copied, setCopied] = useState18(false);
9033
+ const [opening, setOpening] = useState18(false);
9034
+ const [peekOpen, setPeekOpen] = useState18(false);
9035
+ const timer = useRef12(null);
9036
+ useEffect13(
7432
9037
  () => () => {
7433
9038
  if (timer.current) clearTimeout(timer.current);
7434
9039
  },
@@ -7723,7 +9328,7 @@ function DataRowInspector({
7723
9328
  }
7724
9329
 
7725
9330
  // src/data-table/DataRowWindow.tsx
7726
- import { useEffect as useEffect13, useState as useState18 } from "react";
9331
+ import { useEffect as useEffect14, useState as useState19 } from "react";
7727
9332
  import { jsx as jsx36, jsxs as jsxs26 } from "react/jsx-runtime";
7728
9333
  function DataRowWindow({
7729
9334
  isOpen,
@@ -7744,8 +9349,8 @@ function DataRowWindow({
7744
9349
  const hasEdit = editContent != null;
7745
9350
  const hasTabs = hasEdit && children == null;
7746
9351
  const initialTab = defaultTab ?? (hasEdit ? "edit" : "view");
7747
- const [tab, setTab] = useState18(initialTab);
7748
- useEffect13(() => {
9352
+ const [tab, setTab] = useState19(initialTab);
9353
+ useEffect14(() => {
7749
9354
  if (!isOpen) return;
7750
9355
  setTab(defaultTab ?? (hasEdit ? "edit" : "view"));
7751
9356
  }, [isOpen, windowId, hasEdit, defaultTab]);
@@ -8628,7 +10233,7 @@ function DirtySavePill({
8628
10233
  }
8629
10234
 
8630
10235
  // src/data-table/EditableTableCell.tsx
8631
- import { useEffect as useEffect14, useRef as useRef12, useState as useState19 } from "react";
10236
+ import { useEffect as useEffect15, useRef as useRef13, useState as useState20 } from "react";
8632
10237
  import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
8633
10238
  function CellEditShell({
8634
10239
  href,
@@ -8678,8 +10283,8 @@ function EditableTableCell({
8678
10283
  editTrigger = "click",
8679
10284
  editRequest
8680
10285
  }) {
8681
- const [open, setOpen] = useState19(false);
8682
- useEffect14(() => {
10286
+ const [open, setOpen] = useState20(false);
10287
+ useEffect15(() => {
8683
10288
  if (editRequest !== void 0) setOpen(true);
8684
10289
  }, [editRequest]);
8685
10290
  const usePencil = Boolean(href) || editTrigger === "pencil";
@@ -8772,10 +10377,10 @@ function InlineStringEditor({
8772
10377
  usePencil,
8773
10378
  editRequest
8774
10379
  }) {
8775
- const [editing, setEditing] = useState19(false);
8776
- const [draft, setDraft] = useState19(value);
8777
- const handledEditRequest = useRef12(void 0);
8778
- useEffect14(() => {
10380
+ const [editing, setEditing] = useState20(false);
10381
+ const [draft, setDraft] = useState20(value);
10382
+ const handledEditRequest = useRef13(void 0);
10383
+ useEffect15(() => {
8779
10384
  if (editRequest === void 0 || handledEditRequest.current === editRequest) return;
8780
10385
  handledEditRequest.current = editRequest;
8781
10386
  setDraft(value);
@@ -8931,8 +10536,8 @@ function TagsEditor({
8931
10536
  onCommit,
8932
10537
  onCancel
8933
10538
  }) {
8934
- const [tags, setTags] = useState19(value);
8935
- const [draft, setDraft] = useState19("");
10539
+ const [tags, setTags] = useState20(value);
10540
+ const [draft, setDraft] = useState20("");
8936
10541
  const add = (raw) => {
8937
10542
  const next = raw.trim();
8938
10543
  if (!next || tags.includes(next)) return;
@@ -9029,7 +10634,7 @@ function NumberEditor({
9029
10634
  onCommit,
9030
10635
  onCancel
9031
10636
  }) {
9032
- const [draft, setDraft] = useState19(String(value));
10637
+ const [draft, setDraft] = useState20(String(value));
9033
10638
  return /* @__PURE__ */ jsxs29("div", { className: "flex flex-col gap-2", children: [
9034
10639
  /* @__PURE__ */ jsx39(
9035
10640
  Input,
@@ -9159,7 +10764,7 @@ function resetToolbarFacets(facets) {
9159
10764
  }
9160
10765
 
9161
10766
  // src/data-table/LayeredFilterBuilder.tsx
9162
- import { useState as useState20 } from "react";
10767
+ import { useState as useState21 } from "react";
9163
10768
  import { Fragment as Fragment5, jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
9164
10769
  function moveRule(rules, from, to) {
9165
10770
  if (from === to || to < 0 || to >= rules.length) return [...rules];
@@ -9176,8 +10781,8 @@ function LayeredFilterBuilder({
9176
10781
  maxRules = 20,
9177
10782
  label = "Advanced filters"
9178
10783
  }) {
9179
- const [open, setOpen] = useState20(false);
9180
- const [draggedIndex, setDraggedIndex] = useState20(null);
10784
+ const [open, setOpen] = useState21(false);
10785
+ const [draggedIndex, setDraggedIndex] = useState21(null);
9181
10786
  if (fields.length === 0) return null;
9182
10787
  const updateRule = (index, patch) => {
9183
10788
  onChange(
@@ -9721,7 +11326,7 @@ function MatrxDataTableWithUrlState({
9721
11326
  ...urlState.history === void 0 ? {} : { history: urlState.history },
9722
11327
  ...urlState.textHistory === void 0 ? {} : { textHistory: urlState.textHistory }
9723
11328
  });
9724
- useEffect15(() => {
11329
+ useEffect16(() => {
9725
11330
  if (activeUrlTableIds.has(urlState.id)) {
9726
11331
  throw new Error(
9727
11332
  `Duplicate MatrxDataTable urlState.id "${urlState.id}" on one page. Give every table a unique stable id.`
@@ -9812,6 +11417,11 @@ function opensRowHref(event) {
9812
11417
  function TableContextPassthrough({ children }) {
9813
11418
  return /* @__PURE__ */ jsx42(Fragment7, { children });
9814
11419
  }
11420
+ function sameWindow(a, b) {
11421
+ if (a === null || b === null) return a === b;
11422
+ 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;
11423
+ }
11424
+ var NO_GROUPING_VALUE = "__matrx_no_grouping__";
9815
11425
  function MatrxDataTableCore({
9816
11426
  data: suppliedData,
9817
11427
  columns,
@@ -9868,11 +11478,16 @@ function MatrxDataTableCore({
9868
11478
  mobileCardsBreakpoint = "sm",
9869
11479
  getRowHref,
9870
11480
  onRowOpen,
9871
- density
11481
+ density,
11482
+ grouping,
11483
+ virtualize,
11484
+ coverage,
11485
+ spreadsheet,
11486
+ rowHeight: explicitRowHeight
9872
11487
  }) {
9873
11488
  const appendQuery = query?.mode === "controlled-append" ? query : null;
9874
11489
  const appendQueryKey = appendQuery?.pagination.queryKey ?? null;
9875
- const committedAppendQueryKey = useRef13(appendQueryKey);
11490
+ const committedAppendQueryKey = useRef14(appendQueryKey);
9876
11491
  const appendSourceChanged = appendQuery !== null && committedAppendQueryKey.current !== appendQueryKey;
9877
11492
  const data = (appendQuery ? appendQuery.pagination.rows : suppliedData) ?? [];
9878
11493
  const isLoading = appendQuery ? appendQuery.pagination.loading : suppliedLoading;
@@ -9884,10 +11499,10 @@ function MatrxDataTableCore({
9884
11499
  const Link = host.Link ?? DefaultTableLink;
9885
11500
  const SidePanelSurface = host.SidePanelSurface ?? DefaultTableSidePanel;
9886
11501
  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 });
11502
+ const [savedViewError, setSavedViewError] = useState22(null);
11503
+ const [toolbarActionError, setToolbarActionError] = useState22(null);
11504
+ const [toolbarActionPending, setToolbarActionPending] = useState22({ refresh: false, add: false });
11505
+ const toolbarActionPendingRef = useRef14({ refresh: false, add: false });
9891
11506
  const CHECKBOX_TAP_AREA = "h-4 w-4 touch-manipulation";
9892
11507
  const mobileCardsClass = mobileCardsBreakpoint === "lg" ? "lg:hidden" : "sm:hidden";
9893
11508
  const tableWithCardsClass = mobileCardsBreakpoint === "lg" ? "max-lg:hidden" : "max-sm:hidden";
@@ -9900,7 +11515,7 @@ function MatrxDataTableCore({
9900
11515
  const sourceOwnsIntelligentSearch = sourceProcessing?.intelligentSearch === "source";
9901
11516
  const sourceOwnsSearch = remoteQuery?.mode === "controlled" || appendQuery !== null && sourceProcessing === void 0 || sourceProcessing?.search === "source" || sourceOwnsIntelligentSearch;
9902
11517
  const intelligentSearchEnabled = toolbar?.intelligentSearch !== void 0 && (sourceOwnsIntelligentSearch || remoteQuery === null && !sourceOwnsSearch);
9903
- const emitControlledQueryChange = useCallback7(
11518
+ const emitControlledQueryChange = useCallback8(
9904
11519
  (patch, options) => {
9905
11520
  if (!controlledQuery) return;
9906
11521
  controlledQuery.onStateChange(
@@ -9909,8 +11524,8 @@ function MatrxDataTableCore({
9909
11524
  },
9910
11525
  [controlledQuery]
9911
11526
  );
9912
- const [internalSearch, setInternalSearch] = useState21("");
9913
- const [draggedRowId, setDraggedRowId] = useState21(null);
11527
+ const [internalSearch, setInternalSearch] = useState22("");
11528
+ const [draggedRowId, setDraggedRowId] = useState22(null);
9914
11529
  const hierarchySensors = useSensors(
9915
11530
  useSensor(PointerSensor, {
9916
11531
  activationConstraint: { distance: COLUMN_REORDER_ACTIVATION_DISTANCE }
@@ -9926,7 +11541,7 @@ function MatrxDataTableCore({
9926
11541
  }
9927
11542
  toolbar?.onSearchChange?.(v);
9928
11543
  };
9929
- const [internalSearchMatchMode, setInternalSearchMatchMode] = useState21(
11544
+ const [internalSearchMatchMode, setInternalSearchMatchMode] = useState22(
9930
11545
  toolbar?.searchMatch?.defaultMode ?? "contains"
9931
11546
  );
9932
11547
  const searchMatchMode = controlledQuery ? controlledQuery.state.searchMatchMode ?? toolbar?.searchMatch?.defaultMode ?? "contains" : internalSearchMatchMode;
@@ -9937,7 +11552,7 @@ function MatrxDataTableCore({
9937
11552
  }
9938
11553
  setInternalSearchMatchMode(mode);
9939
11554
  };
9940
- const [internalSearchScope, setInternalSearchScope] = useState21("all");
11555
+ const [internalSearchScope, setInternalSearchScope] = useState22("all");
9941
11556
  const searchScope = controlledQuery ? controlledQuery.state.searchScope ?? "all" : internalSearchScope;
9942
11557
  const setSearchScope = (scope) => {
9943
11558
  if (controlledQuery) {
@@ -9946,7 +11561,7 @@ function MatrxDataTableCore({
9946
11561
  }
9947
11562
  setInternalSearchScope(scope);
9948
11563
  };
9949
- const [internalAnyOf, setInternalAnyOf] = useState21("");
11564
+ const [internalAnyOf, setInternalAnyOf] = useState22("");
9950
11565
  const toolbarAnyOfControlled = toolbar?.anyOf?.value !== void 0;
9951
11566
  const anyOfValue = controlledQuery ? controlledQuery.state.anyOf : toolbarAnyOfControlled ? toolbar?.anyOf?.value ?? "" : internalAnyOf;
9952
11567
  const setAnyOfValue = (v) => {
@@ -9957,7 +11572,7 @@ function MatrxDataTableCore({
9957
11572
  }
9958
11573
  toolbar?.anyOf?.onChange?.(v);
9959
11574
  };
9960
- const [internalLayeredFilters, setInternalLayeredFilters] = useState21([]);
11575
+ const [internalLayeredFilters, setInternalLayeredFilters] = useState22([]);
9961
11576
  const layeredFilters = controlledQuery ? controlledQuery.state.layeredFilters ?? [] : internalLayeredFilters;
9962
11577
  const setLayeredFilters = (next) => {
9963
11578
  if (controlledQuery) {
@@ -9966,7 +11581,7 @@ function MatrxDataTableCore({
9966
11581
  }
9967
11582
  setInternalLayeredFilters(next);
9968
11583
  };
9969
- const [internalColumnFilters, setInternalColumnFilters] = useState21({});
11584
+ const [internalColumnFilters, setInternalColumnFilters] = useState22({});
9970
11585
  const columnFilters = controlledQuery ? controlledQuery.state.columnFilters : internalColumnFilters;
9971
11586
  const setColumnFilter = (id, next) => {
9972
11587
  const nextFilters = { ...columnFilters, [id]: next };
@@ -9979,7 +11594,7 @@ function MatrxDataTableCore({
9979
11594
  }
9980
11595
  setInternalColumnFilters(nextFilters);
9981
11596
  };
9982
- const [internalSort, setInternalSort] = useState21(defaultSort);
11597
+ const [internalSort, setInternalSort] = useState22(defaultSort);
9983
11598
  const sort = controlledQuery ? controlledQuery.state.sort : internalSort;
9984
11599
  const setSort = (next) => {
9985
11600
  if (controlledQuery) {
@@ -9988,7 +11603,7 @@ function MatrxDataTableCore({
9988
11603
  }
9989
11604
  setInternalSort(next);
9990
11605
  };
9991
- const [internalPage, setInternalPage] = useState21(1);
11606
+ const [internalPage, setInternalPage] = useState22(1);
9992
11607
  const page = controlledQuery ? controlledQuery.state.page : internalPage;
9993
11608
  const setPage = (next) => {
9994
11609
  if (controlledQuery) {
@@ -9997,7 +11612,7 @@ function MatrxDataTableCore({
9997
11612
  }
9998
11613
  setInternalPage(next);
9999
11614
  };
10000
- const [internalPageSize, setInternalPageSize] = useState21(
11615
+ const [internalPageSize, setInternalPageSize] = useState22(
10001
11616
  defaultPageSize
10002
11617
  );
10003
11618
  const pageSize = controlledQuery ? controlledQuery.state.pageSize : internalPageSize;
@@ -10008,28 +11623,28 @@ function MatrxDataTableCore({
10008
11623
  }
10009
11624
  setInternalPageSize(next);
10010
11625
  };
10011
- const [uncontrolledSelectedId, setUncontrolledSelectedId] = useState21(null);
11626
+ const [uncontrolledSelectedId, setUncontrolledSelectedId] = useState22(null);
10012
11627
  const rawSelectedId = controlledSelectedId !== void 0 ? controlledSelectedId : uncontrolledSelectedId;
10013
11628
  const selectedId = appendSourceChanged ? null : rawSelectedId;
10014
11629
  const setSelectedId = (id) => {
10015
11630
  if (controlledSelectedId === void 0) setUncontrolledSelectedId(id);
10016
11631
  onSelectedIdChange?.(id);
10017
11632
  };
10018
- const [uncontrolledWindowRowId, setUncontrolledWindowRowId] = useState21(null);
11633
+ const [uncontrolledWindowRowId, setUncontrolledWindowRowId] = useState22(null);
10019
11634
  const rawWindowRowId = controlledWindowRowId !== void 0 ? controlledWindowRowId : uncontrolledWindowRowId;
10020
11635
  const windowRowId = appendSourceChanged ? null : rawWindowRowId;
10021
11636
  const setWindowRowId = (id) => {
10022
11637
  if (controlledWindowRowId === void 0) setUncontrolledWindowRowId(id);
10023
11638
  onWindowRowIdChange?.(id);
10024
11639
  };
10025
- const [windowRowSnapshot, setWindowRowSnapshot] = useState21(null);
11640
+ const [windowRowSnapshot, setWindowRowSnapshot] = useState22(null);
10026
11641
  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);
11642
+ const [editsByScope, setEditsByScope] = useState22({});
11643
+ const editRevisionRef = useRef14({});
11644
+ const [savingByScope, setSavingByScope] = useState22({});
11645
+ const savingScopesRef = useRef14(/* @__PURE__ */ new Set());
11646
+ const [editRequest, setEditRequest] = useState22(null);
11647
+ const [confirmForeignDraftDiscard, setConfirmForeignDraftDiscard] = useState22(false);
10033
11648
  const edits = editsByScope[editScopeKey]?.values ?? {};
10034
11649
  const saving = (savingByScope[editScopeKey] ?? 0) > 0;
10035
11650
  const setEdits = (next) => {
@@ -10096,7 +11711,7 @@ function MatrxDataTableCore({
10096
11711
  rows: Object.entries(draft.values).map(([rowId, columns2]) => ({ rowId, columns: columns2 }))
10097
11712
  }));
10098
11713
  const selection = appendSourceChanged && selectionConfig ? { ...selectionConfig, selectedIds: [] } : selectionConfig;
10099
- useEffect15(() => {
11714
+ useEffect16(() => {
10100
11715
  const previous = committedAppendQueryKey.current;
10101
11716
  committedAppendQueryKey.current = appendQueryKey;
10102
11717
  if (previous === null || appendQueryKey === null || previous === appendQueryKey) {
@@ -10114,13 +11729,13 @@ function MatrxDataTableCore({
10114
11729
  onWindowRowIdChange,
10115
11730
  selectionConfig
10116
11731
  ]);
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);
11732
+ const scrollRef = useRef14(null);
11733
+ const tableRef = useRef14(null);
11734
+ const tableHeaderRef = useRef14(null);
11735
+ const cardsScrollRef = useRef14(null);
11736
+ const [documentStickyHeader, setDocumentStickyHeader] = useState22(false);
11737
+ const documentStickyHeaderRef = useRef14(false);
11738
+ const previousAppendKeyForScrollReset = useRef14(appendQueryKey);
10124
11739
  useLayoutEffect2(() => {
10125
11740
  const previous = previousAppendKeyForScrollReset.current;
10126
11741
  previousAppendKeyForScrollReset.current = appendQueryKey;
@@ -10146,13 +11761,13 @@ function MatrxDataTableCore({
10146
11761
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.thresholdPx !== void 0 ? { thresholdPx: appendScrollPolicy.thresholdPx } : {},
10147
11762
  ...appendScrollPolicy?.mode === "scroll" && appendScrollPolicy.intentTimeoutMs !== void 0 ? { intentTimeoutMs: appendScrollPolicy.intentTimeoutMs } : {}
10148
11763
  });
10149
- const rootRef = useRef13(null);
11764
+ const rootRef = useRef14(null);
10150
11765
  useClippedContentGuard(rootRef, {
10151
11766
  label: typeof copy === "object" && copy.listLabel ? `Table: ${copy.listLabel}` : "MatrxDataTable"
10152
11767
  });
10153
- const [scrollHintRight, setScrollHintRight] = useState21(false);
10154
- const [scrollHintLeft, setScrollHintLeft] = useState21(false);
10155
- const updateScrollHint = useCallback7(() => {
11768
+ const [scrollHintRight, setScrollHintRight] = useState22(false);
11769
+ const [scrollHintLeft, setScrollHintLeft] = useState22(false);
11770
+ const updateScrollHint = useCallback8(() => {
10156
11771
  const el = scrollRef.current;
10157
11772
  if (!el) return;
10158
11773
  setScrollHintRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 2);
@@ -10166,7 +11781,7 @@ function MatrxDataTableCore({
10166
11781
  behavior: "smooth"
10167
11782
  });
10168
11783
  };
10169
- useEffect15(() => {
11784
+ useEffect16(() => {
10170
11785
  updateScrollHint();
10171
11786
  window.addEventListener("resize", updateScrollHint);
10172
11787
  const el = scrollRef.current;
@@ -10240,30 +11855,30 @@ function MatrxDataTableCore({
10240
11855
  window.removeEventListener("resize", requestUpdate);
10241
11856
  };
10242
11857
  }, [stickyHeader, data, columns]);
10243
- const [columnOrder, setColumnOrder] = useState21(() => columnState?.order ?? columns.map(columnId));
10244
- const [hiddenColumnIds, setHiddenColumnIds] = useState21(
11858
+ const [columnOrder, setColumnOrder] = useState22(() => columnState?.order ?? columns.map(columnId));
11859
+ const [hiddenColumnIds, setHiddenColumnIds] = useState22(
10245
11860
  () => new Set(columnState?.hidden ?? columns.filter((column) => column.hidden).map(columnId))
10246
11861
  );
10247
- const [columnWidths, setColumnWidths] = useState21(
11862
+ const [columnWidths, setColumnWidths] = useState22(
10248
11863
  () => normalizeColumnWidthsForColumns(columnState?.widths, columns)
10249
11864
  );
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(() => {
11865
+ const [fixedTableGeometry, setFixedTableGeometry] = useState22(null);
11866
+ const knownColumnIds = useRef14(new Set(columns.map(columnId)));
11867
+ const draggedColumnId = useRef14(null);
11868
+ const columnDragActive = useRef14(false);
11869
+ const columnDragRaf = useRef14(null);
11870
+ const columnDragScrollDirection = useRef14(0);
11871
+ const [columnDragPreview, setColumnDragPreview] = useState22(null);
11872
+ const stopColumnDragScroll = useCallback8(() => {
10258
11873
  if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
10259
11874
  columnDragRaf.current = null;
10260
11875
  columnDragScrollDirection.current = 0;
10261
11876
  }, []);
10262
- useEffect15(() => () => {
11877
+ useEffect16(() => () => {
10263
11878
  columnDragActive.current = false;
10264
11879
  stopColumnDragScroll();
10265
11880
  }, [stopColumnDragScroll]);
10266
- const startColumnDragScroll = useCallback7(() => {
11881
+ const startColumnDragScroll = useCallback8(() => {
10267
11882
  if (!columnDragActive.current || columnDragRaf.current !== null) return;
10268
11883
  const scrollStep = () => {
10269
11884
  const scroll = scrollRef.current;
@@ -10290,25 +11905,25 @@ function MatrxDataTableCore({
10290
11905
  };
10291
11906
  columnDragRaf.current = requestAnimationFrame(scrollStep);
10292
11907
  }, []);
10293
- const commitColumnState = useCallback7((order, hidden) => {
11908
+ const commitColumnState = useCallback8((order, hidden) => {
10294
11909
  if (columnState) columnState.onChange({ order, hidden: [...hidden] });
10295
11910
  else {
10296
11911
  setColumnOrder(order);
10297
11912
  setHiddenColumnIds(hidden);
10298
11913
  }
10299
11914
  }, [columnState]);
10300
- const [internalColumnManagerOpen, setInternalColumnManagerOpen] = useState21(false);
11915
+ const [internalColumnManagerOpen, setInternalColumnManagerOpen] = useState22(false);
10301
11916
  const columnManagerOpen = controlledColumnManager?.open ?? internalColumnManagerOpen;
10302
11917
  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(
11918
+ const columnIds = useMemo8(() => columns.map(columnId), [columns]);
11919
+ 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]);
11920
+ const effectiveHiddenColumnIds = useMemo8(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
11921
+ const effectiveColumnWidths = useMemo8(
10307
11922
  () => normalizeColumnWidthsForColumns(columnState?.widths ?? columnWidths, columns),
10308
11923
  [columns, columnState?.widths, columnWidths]
10309
11924
  );
10310
- const resizeGestureSnapshotRef = useRef13(null);
10311
- const measureVisibleColumnWidths = useCallback7(() => {
11925
+ const resizeGestureSnapshotRef = useRef14(null);
11926
+ const measureVisibleColumnWidths = useCallback8(() => {
10312
11927
  const measured = {};
10313
11928
  const headers = tableRef.current ? Array.from(tableRef.current.querySelectorAll(
10314
11929
  "thead th[data-matrx-table-column-id]"
@@ -10322,7 +11937,7 @@ function MatrxDataTableCore({
10322
11937
  }
10323
11938
  return normalizeColumnWidthsForColumns(measured, columns);
10324
11939
  }, [columns]);
10325
- const measureFixedTableGeometry = useCallback7(() => {
11940
+ const measureFixedTableGeometry = useCallback8(() => {
10326
11941
  const table = tableRef.current;
10327
11942
  const headers = table ? Array.from(table.querySelectorAll("thead th")) : [];
10328
11943
  const widthFor = (selector) => {
@@ -10357,16 +11972,16 @@ function MatrxDataTableCore({
10357
11972
  ...actionsWidth === void 0 ? {} : { actionsWidth }
10358
11973
  };
10359
11974
  }, [resolvedDensity]);
10360
- const captureResizeGestureSnapshot = useCallback7(() => {
11975
+ const captureResizeGestureSnapshot = useCallback8(() => {
10361
11976
  resizeGestureSnapshotRef.current = {
10362
11977
  widths: measureVisibleColumnWidths(),
10363
11978
  geometry: measureFixedTableGeometry()
10364
11979
  };
10365
11980
  }, [measureFixedTableGeometry, measureVisibleColumnWidths]);
10366
- const clearResizeGestureSnapshot = useCallback7(() => {
11981
+ const clearResizeGestureSnapshot = useCallback8(() => {
10367
11982
  resizeGestureSnapshotRef.current = null;
10368
11983
  }, []);
10369
- const commitColumnWidths = useCallback7((next, captureInitialWidths = false) => {
11984
+ const commitColumnWidths = useCallback8((next, captureInitialWidths = false) => {
10370
11985
  const gestureSnapshot = captureInitialWidths ? resizeGestureSnapshotRef.current : null;
10371
11986
  resizeGestureSnapshotRef.current = null;
10372
11987
  const normalized = normalizeColumnWidthsForColumns(
@@ -10385,7 +12000,7 @@ function MatrxDataTableCore({
10385
12000
  }
10386
12001
  setColumnWidths(normalized);
10387
12002
  }, [columnIds, columnState, columns, measureFixedTableGeometry, measureVisibleColumnWidths]);
10388
- useEffect15(() => {
12003
+ useEffect16(() => {
10389
12004
  const ids = columns.map(columnId);
10390
12005
  if (columnState) return;
10391
12006
  const previousIds = knownColumnIds.current;
@@ -10409,12 +12024,12 @@ function MatrxDataTableCore({
10409
12024
  tableRef.current?.style.removeProperty(columnResizeCssVar(id));
10410
12025
  }
10411
12026
  });
10412
- const previewColumnOrder = useMemo7(
12027
+ const previewColumnOrder = useMemo8(
10413
12028
  () => projectColumnDragOrder(effectiveColumnOrder, columnDragPreview),
10414
12029
  [columnDragPreview, effectiveColumnOrder]
10415
12030
  );
10416
12031
  useColumnReorderAnimation(tableRef, previewColumnOrder.join("\0"), columnDragPreview !== null);
10417
- const visibleColumns = useMemo7(() => {
12032
+ const visibleColumns = useMemo8(() => {
10418
12033
  const byId = new Map(columns.map((column) => [columnId(column), column]));
10419
12034
  return previewColumnOrder.map((id) => byId.get(id)).filter(
10420
12035
  (column) => column !== void 0 && !effectiveHiddenColumnIds.has(columnId(column))
@@ -10450,7 +12065,7 @@ function MatrxDataTableCore({
10450
12065
  setFixedTableGeometry(geometry);
10451
12066
  }, [resolvedDensity, measureFixedTableGeometry, usesMeasuredFixedLayout]);
10452
12067
  const fixedLayoutActive = usesMeasuredFixedLayout && fixedTableGeometry !== null;
10453
- const frozenColumns = useMemo7(() => {
12068
+ const frozenColumns = useMemo8(() => {
10454
12069
  if (!visibleColumns.some((column) => column.frozen)) return NO_FROZEN_COLUMNS;
10455
12070
  const ids = visibleColumns.map(columnId);
10456
12071
  const byId = new Map(visibleColumns.map((column) => [columnId(column), column]));
@@ -10492,7 +12107,7 @@ function MatrxDataTableCore({
10492
12107
  );
10493
12108
  return color ? CELL_TINT_CLASS[color] : void 0;
10494
12109
  };
10495
- const declaredVisibleCopyColumns = useMemo7(
12110
+ const declaredVisibleCopyColumns = useMemo8(
10496
12111
  () => visibleColumns.filter((column) => column.accessorFn !== void 0 || column.accessorKey !== void 0),
10497
12112
  [visibleColumns]
10498
12113
  );
@@ -10500,7 +12115,7 @@ function MatrxDataTableCore({
10500
12115
  const tableContextMenuInstanceId = useId2();
10501
12116
  const tableDndContextId = useId2();
10502
12117
  const defaultCopyTitle = toolbar?.title || "Table";
10503
- const defaultCopy = useMemo7(() => ({
12118
+ const defaultCopy = useMemo8(() => ({
10504
12119
  label: defaultCopyTitle,
10505
12120
  listLabel: defaultCopyTitle,
10506
12121
  location: defaultCopyTitle,
@@ -10535,7 +12150,7 @@ function MatrxDataTableCore({
10535
12150
  serializer: (shaped) => buildViewAgentInput(effectiveCopy, shaped, data, { scope: "custom" })
10536
12151
  }))
10537
12152
  ] : [];
10538
- const filterMeta = useMemo7(() => {
12153
+ const filterMeta = useMemo8(() => {
10539
12154
  const meta = /* @__PURE__ */ new Map();
10540
12155
  for (const col of visibleColumns) {
10541
12156
  const id = columnId(col);
@@ -10543,14 +12158,14 @@ function MatrxDataTableCore({
10543
12158
  }
10544
12159
  return meta;
10545
12160
  }, [visibleColumns, data, remoteQuery]);
10546
- const intelligentSearchScopes = useMemo7(
12161
+ const intelligentSearchScopes = useMemo8(
10547
12162
  () => resolveTableSearchScopes(data, columns, {
10548
12163
  ...toolbar?.intelligentSearch?.roles === void 0 ? {} : { roles: toolbar.intelligentSearch.roles },
10549
12164
  ...toolbar?.intelligentSearch?.scopes === void 0 ? {} : { scopes: toolbar.intelligentSearch.scopes }
10550
12165
  }),
10551
12166
  [columns, data, toolbar?.intelligentSearch?.roles, toolbar?.intelligentSearch?.scopes]
10552
12167
  );
10553
- const processed = useMemo7(() => {
12168
+ const processed = useMemo8(() => {
10554
12169
  if (remoteQuery && !appendUsesLocalProcessing) return data;
10555
12170
  if (processLocalRows) {
10556
12171
  return processLocalRows(data, {
@@ -10613,8 +12228,8 @@ function MatrxDataTableCore({
10613
12228
  intelligentSearchEnabled,
10614
12229
  searchScope
10615
12230
  ]);
10616
- const lastNotifiedViewRows = useRef13(null);
10617
- useEffect15(() => {
12231
+ const lastNotifiedViewRows = useRef14(null);
12232
+ useEffect16(() => {
10618
12233
  if (!onViewChange) {
10619
12234
  lastNotifiedViewRows.current = null;
10620
12235
  return;
@@ -10623,7 +12238,7 @@ function MatrxDataTableCore({
10623
12238
  lastNotifiedViewRows.current = processed;
10624
12239
  if (changed) onViewChange(processed);
10625
12240
  }, [processed, onViewChange]);
10626
- useEffect15(() => {
12241
+ useEffect16(() => {
10627
12242
  if (controlledQuery) return;
10628
12243
  setInternalPage(1);
10629
12244
  }, [
@@ -10667,7 +12282,7 @@ function MatrxDataTableCore({
10667
12282
  numberedPageHasMore: !hidePagination && !isLoading && !isFetching && processed.length > 0 && safePage < pageCount && (remoteQuery?.mode === "controlled" || !remoteQuery && localPagination?.mode === "numbered"),
10668
12283
  onPageAdvance: setPage
10669
12284
  });
10670
- const paginated = useMemo7(() => {
12285
+ const paginated = useMemo8(() => {
10671
12286
  if (remoteQuery) return processed;
10672
12287
  if (hidePagination) return processed;
10673
12288
  if (localProgressiveEnabled) return [...progressiveRows.rows];
@@ -10675,15 +12290,268 @@ function MatrxDataTableCore({
10675
12290
  const start = (safePage - 1) * effectivePageSize;
10676
12291
  return processed.slice(start, start + effectivePageSize);
10677
12292
  }, [processed, safePage, effectivePageSize, pageSize, remoteQuery, hidePagination, localProgressiveEnabled, progressiveRows.rows]);
10678
- useEffect15(() => {
12293
+ const groupingColumnId = grouping?.columnId ?? null;
12294
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState22([]);
12295
+ const collapsedGroups = grouping?.collapsed ?? uncontrolledCollapsed;
12296
+ const setCollapsedGroups = useCallback8(
12297
+ (next) => {
12298
+ if (grouping?.onCollapsedChange) grouping.onCollapsedChange(next);
12299
+ else setUncontrolledCollapsed(next);
12300
+ },
12301
+ [grouping]
12302
+ );
12303
+ const groups = useMemo8(() => {
12304
+ if (!grouping || !groupingColumnId) return null;
12305
+ const groupColumn = columns.find((col) => columnId(col) === groupingColumnId);
12306
+ if (!groupColumn) return null;
12307
+ const aggregateColumns = {};
12308
+ for (const [aggColumnId, spec] of Object.entries(grouping.aggregates ?? {})) {
12309
+ const col = columns.find((c) => columnId(c) === aggColumnId);
12310
+ if (!col) continue;
12311
+ aggregateColumns[aggColumnId] = { spec, readCell: (row) => getCellValue(row, col) };
12312
+ }
12313
+ return groupRows({
12314
+ rows: paginated,
12315
+ columnId: groupingColumnId,
12316
+ readCell: (row) => grouping.readCell ? grouping.readCell(row, groupingColumnId) : getCellValue(row, groupColumn),
12317
+ aggregates: aggregateColumns,
12318
+ ...grouping.order ? { order: grouping.order } : {},
12319
+ ...grouping.emptyLabel ? { emptyLabel: grouping.emptyLabel } : {}
12320
+ });
12321
+ }, [grouping, groupingColumnId, columns, paginated]);
12322
+ const groupableColumns = useMemo8(() => {
12323
+ const allowed = grouping?.groupableColumnIds;
12324
+ return visibleColumns.filter((col) => {
12325
+ const id = columnId(col);
12326
+ if (allowed) return allowed.includes(id);
12327
+ return col.accessorKey !== void 0 || col.accessorFn !== void 0;
12328
+ });
12329
+ }, [visibleColumns, grouping?.groupableColumnIds]);
12330
+ const groupedRenderRows = useMemo8(
12331
+ () => groups ? flattenGroups(groups, collapsedGroups) : null,
12332
+ [groups, collapsedGroups]
12333
+ );
12334
+ const virtualizeConfig = virtualize === void 0 || virtualize === false ? null : virtualize === true ? {} : virtualize;
12335
+ const virtualRowHeight = explicitRowHeight ?? virtualizeConfig?.rowHeight ?? (resolvedDensity === "condensed" ? 32 : resolvedDensity === "spacious" ? 52 : 40);
12336
+ const virtualThreshold = virtualizeConfig?.threshold ?? 150;
12337
+ const bodyEntryCount = groupedRenderRows ? groupedRenderRows.length : paginated.length;
12338
+ const virtualizeActive = virtualizeConfig !== null && virtualizeConfig.enabled !== false && bodyEntryCount > virtualThreshold && !hierarchy && expandedDetail === void 0;
12339
+ const columnWindowWidth = virtualizeConfig?.columnWidth ?? 160;
12340
+ const frozenRunLength = useMemo8(() => {
12341
+ let run = 0;
12342
+ for (const col of visibleColumns) {
12343
+ if (frozenColumns.offsets[columnId(col)] === void 0) break;
12344
+ run += 1;
12345
+ }
12346
+ return run;
12347
+ }, [visibleColumns, frozenColumns]);
12348
+ const columnVirtualizeActive = virtualizeConfig !== null && virtualizeConfig.enabled !== false && virtualizeConfig.columns === true && visibleColumns.length - frozenRunLength > 30 && !hierarchy && mobileCards === void 0;
12349
+ const [rowWindow, setRowWindow] = useState22(null);
12350
+ const [columnWindow, setColumnWindow] = useState22(null);
12351
+ const windowInputs = useRef14({
12352
+ virtualizeActive: false,
12353
+ bodyEntryCount: 0,
12354
+ virtualRowHeight: 40,
12355
+ overscan: 8,
12356
+ columnVirtualizeActive: false,
12357
+ columnCount: 0,
12358
+ columnWindowWidth: 160,
12359
+ frozenRunLength: 0
12360
+ });
12361
+ windowInputs.current = {
12362
+ virtualizeActive,
12363
+ bodyEntryCount,
12364
+ virtualRowHeight,
12365
+ overscan: virtualizeConfig?.overscan ?? 8,
12366
+ columnVirtualizeActive,
12367
+ columnCount: visibleColumns.length,
12368
+ columnWindowWidth,
12369
+ frozenRunLength
12370
+ };
12371
+ const recomputeWindows = useCallback8(() => {
12372
+ const el = scrollRef.current;
12373
+ const input = windowInputs.current;
12374
+ const nextRow = input.virtualizeActive ? computeWindow({
12375
+ count: input.bodyEntryCount,
12376
+ itemSize: input.virtualRowHeight,
12377
+ viewport: el?.clientHeight || 600,
12378
+ offset: el?.scrollTop ?? 0,
12379
+ overscan: input.overscan
12380
+ }) : null;
12381
+ const nextColumn = input.columnVirtualizeActive ? computeWindow({
12382
+ count: input.columnCount,
12383
+ itemSize: input.columnWindowWidth,
12384
+ viewport: el?.clientWidth || 1200,
12385
+ offset: el?.scrollLeft ?? 0,
12386
+ overscan: Math.max(2, Math.round(input.overscan / 2)),
12387
+ pinnedStart: input.frozenRunLength
12388
+ }) : null;
12389
+ setRowWindow((prev) => sameWindow(prev, nextRow) ? prev : nextRow);
12390
+ setColumnWindow((prev) => sameWindow(prev, nextColumn) ? prev : nextColumn);
12391
+ }, []);
12392
+ const effectiveRowWindow = useMemo8(() => {
12393
+ if (!virtualizeActive) return null;
12394
+ if (rowWindow && rowWindow.end <= bodyEntryCount) return rowWindow;
12395
+ return computeWindow({
12396
+ count: bodyEntryCount,
12397
+ itemSize: virtualRowHeight,
12398
+ viewport: scrollRef.current?.clientHeight || 600,
12399
+ offset: scrollRef.current?.scrollTop ?? 0,
12400
+ overscan: virtualizeConfig?.overscan ?? 8
12401
+ });
12402
+ }, [virtualizeActive, rowWindow, bodyEntryCount, virtualRowHeight, virtualizeConfig?.overscan]);
12403
+ const effectiveColumnWindow = useMemo8(() => {
12404
+ if (!columnVirtualizeActive) return null;
12405
+ if (columnWindow && columnWindow.end <= visibleColumns.length) return columnWindow;
12406
+ return computeWindow({
12407
+ count: visibleColumns.length,
12408
+ itemSize: columnWindowWidth,
12409
+ viewport: scrollRef.current?.clientWidth || 1200,
12410
+ offset: scrollRef.current?.scrollLeft ?? 0,
12411
+ overscan: Math.max(2, Math.round((virtualizeConfig?.overscan ?? 8) / 2)),
12412
+ pinnedStart: frozenRunLength
12413
+ });
12414
+ }, [
12415
+ columnVirtualizeActive,
12416
+ columnWindow,
12417
+ visibleColumns.length,
12418
+ columnWindowWidth,
12419
+ virtualizeConfig?.overscan,
12420
+ frozenRunLength
12421
+ ]);
12422
+ const metricsWanted = virtualizeActive || columnVirtualizeActive;
12423
+ useEffect16(() => {
12424
+ recomputeWindows();
12425
+ }, [
12426
+ recomputeWindows,
12427
+ virtualizeActive,
12428
+ bodyEntryCount,
12429
+ virtualRowHeight,
12430
+ columnVirtualizeActive,
12431
+ visibleColumns.length,
12432
+ columnWindowWidth,
12433
+ frozenRunLength
12434
+ ]);
12435
+ useEffect16(() => {
12436
+ if (!metricsWanted) return;
12437
+ const el = scrollRef.current;
12438
+ if (!el) return;
12439
+ let frame = 0;
12440
+ const onScroll = () => {
12441
+ if (frame) return;
12442
+ frame = requestAnimationFrame(() => {
12443
+ frame = 0;
12444
+ recomputeWindows();
12445
+ });
12446
+ };
12447
+ el.addEventListener("scroll", onScroll, { passive: true });
12448
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(onScroll);
12449
+ observer?.observe(el);
12450
+ return () => {
12451
+ if (frame) cancelAnimationFrame(frame);
12452
+ el.removeEventListener("scroll", onScroll);
12453
+ observer?.disconnect();
12454
+ };
12455
+ }, [metricsWanted, recomputeWindows]);
12456
+ const renderedColumns = useMemo8(
12457
+ () => effectiveColumnWindow ? [
12458
+ ...visibleColumns.slice(0, frozenRunLength),
12459
+ ...visibleColumns.slice(effectiveColumnWindow.start, effectiveColumnWindow.end)
12460
+ ] : visibleColumns,
12461
+ [effectiveColumnWindow, visibleColumns, frozenRunLength]
12462
+ );
12463
+ const columnSpacers = effectiveColumnWindow ? { start: effectiveColumnWindow.padStart, end: effectiveColumnWindow.padEnd } : null;
12464
+ const windowedWidthFor = useCallback8(
12465
+ (id, declared) => {
12466
+ const resized = effectiveColumnWidths[id];
12467
+ if (!effectiveColumnWindow) return resized;
12468
+ if (frozenColumns.offsets[id] !== void 0) return resized;
12469
+ return resized ?? (typeof declared === "number" ? declared : columnWindowWidth);
12470
+ },
12471
+ [effectiveColumnWindow, effectiveColumnWidths, frozenColumns, columnWindowWidth]
12472
+ );
12473
+ const coverageResult = useMemo8(() => {
12474
+ if (!coverage) return null;
12475
+ return describeCoverage({
12476
+ loaded: processed.length,
12477
+ ...coverage.matched === void 0 ? {} : { matched: coverage.matched },
12478
+ ...coverage.total === void 0 ? {} : { total: coverage.total },
12479
+ ...coverage.cap === void 0 ? {} : { cap: coverage.cap },
12480
+ answeredBy: coverage.answeredBy ?? "client",
12481
+ ...coverage.noun === void 0 ? {} : { noun: coverage.noun }
12482
+ });
12483
+ }, [coverage, processed.length]);
12484
+ const bodyEntries = useMemo8(() => {
12485
+ const from = effectiveRowWindow ? effectiveRowWindow.start : 0;
12486
+ const to = effectiveRowWindow ? effectiveRowWindow.end : void 0;
12487
+ const windowed = groupedRenderRows ? groupedRenderRows.slice(from, to).map(
12488
+ (entry) => entry.kind === "group" ? {
12489
+ kind: "group",
12490
+ group: entry.group,
12491
+ collapsed: isGroupCollapsed(collapsedGroups, entry.group.key)
12492
+ } : { kind: "row", row: entry.row, index: entry.dataIndex ?? 0 }
12493
+ ) : paginated.slice(from, to).map((row, offset) => ({ kind: "row", row, index: from + offset }));
12494
+ if (!effectiveRowWindow) return windowed;
12495
+ if (effectiveRowWindow.padStart > 0)
12496
+ windowed.unshift({ kind: "spacer", height: effectiveRowWindow.padStart, place: "start" });
12497
+ if (effectiveRowWindow.padEnd > 0)
12498
+ windowed.push({ kind: "spacer", height: effectiveRowWindow.padEnd, place: "end" });
12499
+ return windowed;
12500
+ }, [groupedRenderRows, collapsedGroups, paginated, effectiveRowWindow]);
12501
+ const getRowIdRef = useRef14(getRowId);
12502
+ getRowIdRef.current = getRowId;
12503
+ const spreadsheetRowIds = useMemo8(
12504
+ () => paginated.map((row) => getRowIdRef.current(row)),
12505
+ [paginated]
12506
+ );
12507
+ const spreadsheetColumnIds = useMemo8(
12508
+ () => visibleColumns.map((col) => columnId(col)),
12509
+ [visibleColumns]
12510
+ );
12511
+ const spreadsheetRowById = useMemo8(() => {
12512
+ const map = /* @__PURE__ */ new Map();
12513
+ for (const row of paginated) map.set(getRowIdRef.current(row), row);
12514
+ return map;
12515
+ }, [paginated]);
12516
+ const spreadsheetColumnById = useMemo8(() => {
12517
+ const map = /* @__PURE__ */ new Map();
12518
+ for (const col of visibleColumns) map.set(columnId(col), col);
12519
+ return map;
12520
+ }, [visibleColumns]);
12521
+ const spreadsheetEnabled = Boolean(spreadsheet && spreadsheet.enabled !== false);
12522
+ const sheet = useSpreadsheetGrid(
12523
+ spreadsheetEnabled && spreadsheet ? {
12524
+ rowIds: spreadsheetRowIds,
12525
+ columnIds: spreadsheetColumnIds,
12526
+ valueAt: (rowId, colId) => {
12527
+ const row = spreadsheetRowById.get(rowId);
12528
+ const col = spreadsheetColumnById.get(colId);
12529
+ if (row === void 0 || col === void 0) return null;
12530
+ if (spreadsheet.readCell) return spreadsheet.readCell(row, colId);
12531
+ return getCellValue(applyRowEdits(row, edits[rowId]), col);
12532
+ },
12533
+ isColumnWritable: (colId) => {
12534
+ if (spreadsheet.writableColumnIds) return spreadsheet.writableColumnIds.includes(colId);
12535
+ const col = spreadsheetColumnById.get(colId);
12536
+ return Boolean(col && col.editable);
12537
+ },
12538
+ ...spreadsheet.columnRefusal ? { columnRefusal: spreadsheet.columnRefusal } : {},
12539
+ onPatch: spreadsheet.onPatch,
12540
+ ...spreadsheet.undoDepth === void 0 ? {} : { undoDepth: spreadsheet.undoDepth },
12541
+ ...spreadsheet.fillHandle === void 0 ? {} : { fillHandle: spreadsheet.fillHandle },
12542
+ ...spreadsheet.paste === void 0 ? {} : { paste: spreadsheet.paste },
12543
+ notify: spreadsheet.notify ?? ((message) => toast.success(message))
12544
+ } : null
12545
+ );
12546
+ useEffect16(() => {
10679
12547
  if (!controlledQuery || controlledQuery.state.page === safePage) return;
10680
12548
  emitControlledQueryChange({ page: safePage });
10681
12549
  }, [controlledQuery, emitControlledQueryChange, safePage]);
10682
- const selectedRow = useMemo7(() => {
12550
+ const selectedRow = useMemo8(() => {
10683
12551
  if (!selectedId) return null;
10684
12552
  return data.find((r) => getRowId(r) === selectedId) ?? null;
10685
12553
  }, [data, selectedId, getRowId]);
10686
- const windowRow = useMemo7(() => {
12554
+ const windowRow = useMemo8(() => {
10687
12555
  if (!windowRowId) return null;
10688
12556
  return data.find((r) => getRowId(r) === windowRowId) ?? (windowRowSnapshot && windowRowSnapshot.queryKey === appendQueryKey && getRowId(windowRowSnapshot.row) === windowRowId ? windowRowSnapshot.row : null);
10689
12557
  }, [data, windowRowId, windowRowSnapshot, getRowId, appendQueryKey]);
@@ -10718,7 +12586,7 @@ function MatrxDataTableCore({
10718
12586
  return def !== void 0 && f.value !== def;
10719
12587
  });
10720
12588
  const hasActiveFilters = activeFilterCount > 0 || Boolean(searchValue.trim()) || intelligentSearchEnabled && searchScope !== "all" || Boolean(anyOfValue.trim()) || completeLayeredFilterRules(layeredFilters).length > 0 || facetActive;
10721
- const changeCount = useMemo7(() => {
12589
+ const changeCount = useMemo8(() => {
10722
12590
  let n = 0;
10723
12591
  for (const fields of Object.values(edits)) {
10724
12592
  n += Object.keys(fields).length;
@@ -10874,7 +12742,7 @@ function MatrxDataTableCore({
10874
12742
  };
10875
12743
  const hierarchyRows = hierarchy?.rows ?? data;
10876
12744
  const hierarchyGetParentId = hierarchy?.getParentId;
10877
- const hierarchyIndex = useMemo7(() => {
12745
+ const hierarchyIndex = useMemo8(() => {
10878
12746
  if (!hierarchyGetParentId) return null;
10879
12747
  const byId = /* @__PURE__ */ new Map();
10880
12748
  const parentById = /* @__PURE__ */ new Map();
@@ -10906,7 +12774,7 @@ function MatrxDataTableCore({
10906
12774
  return { byId, parentById, depthById };
10907
12775
  }, [hierarchyGetParentId, hierarchyRows, getRowId]);
10908
12776
  const draggedRow = draggedRowId && hierarchyIndex ? hierarchyIndex.byId.get(draggedRowId) : void 0;
10909
- const hierarchyBlockedIds = useMemo7(() => {
12777
+ const hierarchyBlockedIds = useMemo8(() => {
10910
12778
  if (!draggedRowId || !hierarchyIndex) return null;
10911
12779
  const childrenById = /* @__PURE__ */ new Map();
10912
12780
  for (const [id, parentId] of hierarchyIndex.parentById) {
@@ -11089,7 +12957,7 @@ function MatrxDataTableCore({
11089
12957
  setEdits({});
11090
12958
  edit?.onCancel?.();
11091
12959
  };
11092
- useEffect15(() => {
12960
+ useEffect16(() => {
11093
12961
  if (!host.rowContextRegistry || !contextMenu && !host.createDefaultRowContext) return;
11094
12962
  return host.rowContextRegistry.register(tableContextMenuInstanceId, (rowId) => {
11095
12963
  const row = data.find((candidate) => getRowId(candidate) === rowId);
@@ -11101,7 +12969,7 @@ function MatrxDataTableCore({
11101
12969
  });
11102
12970
  }, [buildRecordControls, contextMenu, data, getRowId, host.createDefaultRowContext, host.rowContextRegistry, tableContextMenuInstanceId]);
11103
12971
  const showActionsCol = windowEnabled || Boolean(rowActions) || showInlineRowCopy;
11104
- const selectedIdSet = useMemo7(
12972
+ const selectedIdSet = useMemo8(
11105
12973
  () => new Set(selection?.selectedIds ?? []),
11106
12974
  [selection?.selectedIds]
11107
12975
  );
@@ -11113,11 +12981,11 @@ function MatrxDataTableCore({
11113
12981
  );
11114
12982
  const allOnPageSelected = selectableRows.length > 0 && selectedOnPage.length === selectableRows.length;
11115
12983
  const someOnPageSelected = selectedOnPage.length > 0 && !allOnPageSelected;
11116
- const selectedRows = useMemo7(
12984
+ const selectedRows = useMemo8(
11117
12985
  () => data.filter((row) => selectedIdSet.has(getRowId(row))),
11118
12986
  [data, selectedIdSet, getRowId]
11119
12987
  );
11120
- const lastToggledIndex = useRef13(null);
12988
+ const lastToggledIndex = useRef14(null);
11121
12989
  const setSelectedIds = (next) => selection?.onSelectedIdsChange([...next]);
11122
12990
  const toggleRowSelected = (index, shiftKey) => {
11123
12991
  if (!selection) return;
@@ -11154,6 +13022,7 @@ function MatrxDataTableCore({
11154
13022
  const selectionNoun = selection?.noun ?? "row";
11155
13023
  const selectionCount = selection?.selectedIds.length ?? 0;
11156
13024
  const leadingCols = selection ? 1 : 0;
13025
+ const renderedColSpan = renderedColumns.length + leadingCols + (showActionsCol ? 1 : 0) + (columnSpacers ? (columnSpacers.start > 0 ? 1 : 0) + (columnSpacers.end > 0 ? 1 : 0) : 0);
11157
13026
  const renderedFacets = controlledQuery ? toolbar?.facets?.map(
11158
13027
  (facet) => facet.type === "button-group" ? {
11159
13028
  ...facet,
@@ -11187,7 +13056,7 @@ function MatrxDataTableCore({
11187
13056
  }
11188
13057
  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
13058
  };
11190
- const currentViewSnapshot = useMemo7(
13059
+ const currentViewSnapshot = useMemo8(
11191
13060
  () => ({
11192
13061
  __kind: "matrx-table-view",
11193
13062
  version: 1,
@@ -11222,7 +13091,7 @@ function MatrxDataTableCore({
11222
13091
  sort
11223
13092
  ]
11224
13093
  );
11225
- const declaredDefaultViewSnapshot = useMemo7(
13094
+ const declaredDefaultViewSnapshot = useMemo8(
11226
13095
  () => defaultViewSnapshot ?? {
11227
13096
  __kind: "matrx-table-view",
11228
13097
  version: 1,
@@ -11241,7 +13110,7 @@ function MatrxDataTableCore({
11241
13110
  },
11242
13111
  [columns, defaultPageSize, defaultSort, defaultViewSnapshot]
11243
13112
  );
11244
- const applySavedView = useCallback7(
13113
+ const applySavedView = useCallback8(
11245
13114
  (rawSnapshot) => {
11246
13115
  const snapshot = parseTableViewSnapshot(rawSnapshot);
11247
13116
  if (!snapshot) {
@@ -11593,6 +13462,28 @@ function MatrxDataTableCore({
11593
13462
  }
11594
13463
  }
11595
13464
  ) : null,
13465
+ grouping?.onColumnIdChange ? /* @__PURE__ */ jsxs32(
13466
+ Select,
13467
+ {
13468
+ value: groupingColumnId ?? NO_GROUPING_VALUE,
13469
+ onValueChange: (value) => grouping.onColumnIdChange?.(value === NO_GROUPING_VALUE ? null : value),
13470
+ children: [
13471
+ /* @__PURE__ */ jsx42(
13472
+ SelectTrigger,
13473
+ {
13474
+ "data-matrx-table-group-by": true,
13475
+ "aria-label": "Group rows by a column",
13476
+ className: "h-8 w-auto min-w-[9rem] gap-1 text-xs",
13477
+ children: /* @__PURE__ */ jsx42(SelectValue, { placeholder: "No groups" })
13478
+ }
13479
+ ),
13480
+ /* @__PURE__ */ jsxs32(SelectContent, { children: [
13481
+ /* @__PURE__ */ jsx42(SelectItem, { value: NO_GROUPING_VALUE, children: "No groups" }),
13482
+ groupableColumns.map((col) => /* @__PURE__ */ jsx42(SelectItem, { value: columnId(col), children: `Group by ${col.label ?? (typeof col.header === "string" ? col.header : columnId(col))}` }, columnId(col)))
13483
+ ] })
13484
+ ]
13485
+ }
13486
+ ) : null,
11596
13487
  columns.length > 0 ? /* @__PURE__ */ jsx42(
11597
13488
  TableToolbarAction,
11598
13489
  {
@@ -11660,65 +13551,6 @@ function MatrxDataTableCore({
11660
13551
  ]
11661
13552
  }
11662
13553
  ),
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
13554
  /* @__PURE__ */ jsxs32("div", { "data-matrx-table-body-region": true, className: "flex min-h-0 flex-1 flex-col gap-2", children: [
11723
13555
  /* @__PURE__ */ jsxs32("div", { className: "relative min-h-0 flex-1", children: [
11724
13556
  mobileCards ? /* @__PURE__ */ jsxs32(
@@ -11826,12 +13658,33 @@ function MatrxDataTableCore({
11826
13658
  ]
11827
13659
  }
11828
13660
  ) : null,
13661
+ coverageResult && coverageResult.notice ? /* @__PURE__ */ jsxs32(
13662
+ "div",
13663
+ {
13664
+ "data-matrx-table-coverage": true,
13665
+ "data-matrx-table-coverage-scope": coverageResult.scope,
13666
+ role: "status",
13667
+ 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",
13668
+ children: [
13669
+ /* @__PURE__ */ jsx42("span", { className: "font-semibold", children: coverageResult.badge }),
13670
+ /* @__PURE__ */ jsx42("span", { children: coverageResult.notice })
13671
+ ]
13672
+ }
13673
+ ) : null,
11829
13674
  /* @__PURE__ */ jsxs32(
11830
13675
  "div",
11831
13676
  {
11832
13677
  ref: scrollRef,
11833
13678
  "data-matrx-table-scroll": true,
11834
13679
  onScroll: updateScrollHint,
13680
+ ...spreadsheetEnabled ? {
13681
+ tabIndex: sheet.containerProps.tabIndex,
13682
+ onKeyDown: sheet.containerProps.onKeyDown,
13683
+ onCopy: sheet.containerProps.onCopy,
13684
+ onCut: sheet.containerProps.onCut,
13685
+ onPaste: sheet.containerProps.onPaste,
13686
+ "data-matrx-table-spreadsheet": "true"
13687
+ } : {},
11835
13688
  "aria-busy": isLoading || isFetching,
11836
13689
  style: { containerType: "inline-size" },
11837
13690
  className: cn(
@@ -11889,7 +13742,14 @@ function MatrxDataTableCore({
11889
13742
  ref: tableHeaderRef,
11890
13743
  "data-matrx-table-sticky-header": stickyHeader ? "true" : void 0,
11891
13744
  className: cn(
11892
- "z-10 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
13745
+ // 🚨 ABOVE THE FROZEN COLUMN, NOT UNDER IT. A frozen body
13746
+ // cell is `sm:z-20` so it can slide under the columns to
13747
+ // its right; the header was `z-10`, so the frozen column's
13748
+ // cells scrolled straight OVER the header they belong to
13749
+ // and the top data row printed through the column names.
13750
+ // Only a table long enough to scroll shows it, which is why
13751
+ // it survived until a hundred-thousand-row screenshot.
13752
+ "z-30 border-b border-border bg-muted/90 shadow-[0_1px_0_0_var(--border)] backdrop-blur-sm",
11893
13753
  stickyHeader && (documentStickyHeader ? "relative translate-y-[var(--matrx-document-sticky-offset)]" : "sticky top-0")
11894
13754
  ),
11895
13755
  children: /* @__PURE__ */ jsxs32("tr", { children: [
@@ -11922,7 +13782,17 @@ function MatrxDataTableCore({
11922
13782
  )
11923
13783
  }
11924
13784
  ) : null,
11925
- visibleColumns.map((col) => {
13785
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
13786
+ "th",
13787
+ {
13788
+ "aria-hidden": "true",
13789
+ "data-matrx-table-column-spacer": "start",
13790
+ className: "border-0 p-0",
13791
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
13792
+ },
13793
+ "matrx-column-spacer-start"
13794
+ ) : null,
13795
+ renderedColumns.map((col) => {
11926
13796
  const id = columnId(col);
11927
13797
  const meta = filterMeta.get(id);
11928
13798
  const isSorted = sort?.id === id;
@@ -11965,7 +13835,7 @@ function MatrxDataTableCore({
11965
13835
  frozenEdgeId === id && FROZEN_EDGE_CLASS
11966
13836
  ),
11967
13837
  style: {
11968
- ...columnWidthVar(width, effectiveColumnWidths[id], id, columnResizable),
13838
+ ...columnWidthVar(width, windowedWidthFor(id, width), id, columnResizable),
11969
13839
  ...frozenStyle(id)
11970
13840
  },
11971
13841
  children: [
@@ -12029,6 +13899,16 @@ function MatrxDataTableCore({
12029
13899
  id
12030
13900
  );
12031
13901
  }),
13902
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
13903
+ "th",
13904
+ {
13905
+ "aria-hidden": "true",
13906
+ "data-matrx-table-column-spacer": "end",
13907
+ className: "border-0 p-0",
13908
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
13909
+ },
13910
+ "matrx-column-spacer-end"
13911
+ ) : null,
12032
13912
  showActionsCol && /* @__PURE__ */ jsx42(
12033
13913
  "th",
12034
13914
  {
@@ -12044,7 +13924,17 @@ function MatrxDataTableCore({
12044
13924
  /* @__PURE__ */ jsxs32("tbody", { children: [
12045
13925
  isLoading ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs32("tr", { className: "border-b border-border/60", children: [
12046
13926
  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(
13927
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
13928
+ "td",
13929
+ {
13930
+ "aria-hidden": "true",
13931
+ "data-matrx-table-column-spacer": "start",
13932
+ className: "border-0 p-0",
13933
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
13934
+ },
13935
+ "matrx-column-spacer-start"
13936
+ ) : null,
13937
+ renderedColumns.map((col) => /* @__PURE__ */ jsx42(
12048
13938
  "td",
12049
13939
  {
12050
13940
  className: cn(
@@ -12056,11 +13946,21 @@ function MatrxDataTableCore({
12056
13946
  },
12057
13947
  columnId(col)
12058
13948
  )),
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" }) })
13949
+ showActionsCol && /* @__PURE__ */ jsx42("td", { className: "text-right align-middle", style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth), children: /* @__PURE__ */ jsx42(Skeleton, { className: "ml-auto h-5 w-10" }) }),
13950
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
13951
+ "td",
13952
+ {
13953
+ "aria-hidden": "true",
13954
+ "data-matrx-table-column-spacer": "end",
13955
+ className: "border-0 p-0",
13956
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
13957
+ },
13958
+ "matrx-column-spacer-end"
13959
+ ) : null
12060
13960
  ] }, `sk-${i}`)) : paginated.length === 0 ? /* @__PURE__ */ jsx42("tr", { children: /* @__PURE__ */ jsx42(
12061
13961
  "td",
12062
13962
  {
12063
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
13963
+ colSpan: renderedColSpan,
12064
13964
  className: "px-0 py-12 text-center",
12065
13965
  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
13966
  renderedEmptyState?.icon,
@@ -12069,7 +13969,43 @@ function MatrxDataTableCore({
12069
13969
  renderedEmptyState?.action
12070
13970
  ] })
12071
13971
  }
12072
- ) }) : paginated.map((row, index) => {
13972
+ ) }) : bodyEntries.map((entry) => {
13973
+ if (entry.kind === "spacer") {
13974
+ return /* @__PURE__ */ jsx42(
13975
+ "tr",
13976
+ {
13977
+ "aria-hidden": "true",
13978
+ "data-matrx-table-virtual-spacer": entry.place,
13979
+ style: { height: `${entry.height}px` },
13980
+ children: /* @__PURE__ */ jsx42(
13981
+ "td",
13982
+ {
13983
+ className: "border-0 p-0",
13984
+ colSpan: renderedColSpan
13985
+ }
13986
+ )
13987
+ },
13988
+ `matrx-virtual-spacer-${entry.place}`
13989
+ );
13990
+ }
13991
+ if (entry.kind === "group") {
13992
+ return /* @__PURE__ */ jsx42(
13993
+ TableGroupHeaderRow,
13994
+ {
13995
+ group: entry.group,
13996
+ collapsed: entry.collapsed,
13997
+ columns: renderedColumns,
13998
+ spacers: columnSpacers,
13999
+ leadingCols,
14000
+ showActionsCol,
14001
+ rowNoun: grouping?.rowNoun ?? "row",
14002
+ onToggle: () => setCollapsedGroups(toggleGroupCollapsed(collapsedGroups, entry.group.key))
14003
+ },
14004
+ `matrx-group-${entry.group.key}`
14005
+ );
14006
+ }
14007
+ const row = entry.row;
14008
+ const index = entry.index;
12073
14009
  const id = getRowId(row);
12074
14010
  const isSelected = selectedId === id;
12075
14011
  const rowEdits = edits[id];
@@ -12080,6 +14016,7 @@ function MatrxDataTableCore({
12080
14016
  "tr",
12081
14017
  {
12082
14018
  "data-row-id": id,
14019
+ style: explicitRowHeight !== void 0 ? { height: `${explicitRowHeight}px` } : virtualizeActive ? { height: `${virtualRowHeight}px` } : void 0,
12083
14020
  "data-state": isSelected ? "selected" : void 0,
12084
14021
  onClick: (e) => {
12085
14022
  if (rowEventTargetsInteractiveDescendant(e.target)) return;
@@ -12152,15 +14089,40 @@ function MatrxDataTableCore({
12152
14089
  ) : null
12153
14090
  }
12154
14091
  ) : null,
12155
- visibleColumns.map((col, colIdx) => {
14092
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14093
+ "td",
14094
+ {
14095
+ "aria-hidden": "true",
14096
+ "data-matrx-table-column-spacer": "start",
14097
+ className: "border-0 p-0",
14098
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14099
+ },
14100
+ "matrx-column-spacer-start"
14101
+ ) : null,
14102
+ renderedColumns.map((col, colIdx) => {
12156
14103
  const isColumnDragShadow = columnDragPreview?.draggedId === columnId(col) && columnDragPreview.targetId !== null;
12157
- return /* @__PURE__ */ jsx42(
14104
+ const sheetCell = spreadsheetEnabled ? sheet.cellProps(id, columnId(col)) : null;
14105
+ const carriesFillHandle = sheetCell !== null && sheet.isFillHandleCell(id, columnId(col));
14106
+ return /* @__PURE__ */ jsxs32(
12158
14107
  "td",
12159
14108
  {
12160
14109
  "data-matrx-table-column-id": columnId(col),
12161
14110
  "data-matrx-column-drag-shadow": isColumnDragShadow ? "true" : void 0,
14111
+ ...sheetCell ? {
14112
+ "data-matrx-cell-row": sheetCell["data-matrx-cell-row"],
14113
+ "data-matrx-cell-col": sheetCell["data-matrx-cell-col"],
14114
+ ...sheetCell["data-matrx-cell-selected"] ? { "data-matrx-cell-selected": "true" } : {},
14115
+ ...sheetCell["data-matrx-cell-active"] ? { "data-matrx-cell-active": "true" } : {},
14116
+ ...sheetCell["data-matrx-cell-fill-preview"] ? { "data-matrx-cell-fill-preview": "true" } : {},
14117
+ onMouseDown: sheetCell.onMouseDown,
14118
+ onMouseEnter: sheetCell.onMouseEnter
14119
+ } : {},
12162
14120
  className: cn(
12163
14121
  "py-1.5 align-middle lg:py-0.5",
14122
+ sheetCell?.["data-matrx-cell-selected"] && "bg-primary/10",
14123
+ sheetCell?.["data-matrx-cell-fill-preview"] && "ring-1 ring-inset ring-primary/40",
14124
+ sheetCell?.["data-matrx-cell-active"] && "relative z-10 ring-2 ring-inset ring-primary",
14125
+ carriesFillHandle && "relative",
12164
14126
  col.compact || resolveColumnMarker(col) !== void 0 ? "px-1" : "px-2",
12165
14127
  // nowrap (NOT truncate — truncate clips the cell and
12166
14128
  // defeats w-max, killing the horizontal scroll).
@@ -12182,42 +14144,64 @@ function MatrxDataTableCore({
12182
14144
  style: {
12183
14145
  ...columnWidthVar(
12184
14146
  resolveColumnMarker(col) && col.width === void 0 ? 40 : col.width,
12185
- effectiveColumnWidths[columnId(col)],
14147
+ windowedWidthFor(columnId(col), col.width),
12186
14148
  columnId(col),
12187
14149
  resizableColumns && col.resizable !== false
12188
14150
  ),
12189
14151
  ...frozenStyle(columnId(col))
12190
14152
  },
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
- )
14153
+ children: [
14154
+ /* @__PURE__ */ jsxs32(
14155
+ "div",
14156
+ {
14157
+ className: cn(
14158
+ colIdx === 0 && hierarchy && "flex min-w-0 items-center"
14159
+ ),
14160
+ children: [
14161
+ colIdx === 0 && hierarchy ? /* @__PURE__ */ jsx42(
14162
+ HierarchyDragHandle,
14163
+ {
14164
+ id,
14165
+ label: hierarchy.itemLabel?.(row) ?? "row",
14166
+ disabled: !(hierarchy.canReparent?.(row) ?? true)
14167
+ }
14168
+ ) : null,
14169
+ /* @__PURE__ */ jsx42(
14170
+ "div",
14171
+ {
14172
+ "data-matrx-cell-content": true,
14173
+ 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]",
14174
+ children: renderRecordCell(row, col, index)
14175
+ }
14176
+ )
14177
+ ]
14178
+ }
14179
+ ),
14180
+ carriesFillHandle ? /* @__PURE__ */ jsx42(
14181
+ "button",
14182
+ {
14183
+ type: "button",
14184
+ "data-matrx-table-fill-handle": true,
14185
+ "aria-label": "Drag to fill the cells below or beside this selection",
14186
+ onMouseDown: sheet.startFillDrag,
14187
+ className: "absolute -bottom-[3px] -right-[3px] z-20 h-2 w-2 cursor-crosshair rounded-[1px] border border-background bg-primary"
14188
+ }
14189
+ ) : null
14190
+ ]
12217
14191
  },
12218
14192
  columnId(col)
12219
14193
  );
12220
14194
  }),
14195
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14196
+ "td",
14197
+ {
14198
+ "aria-hidden": "true",
14199
+ "data-matrx-table-column-spacer": "end",
14200
+ className: "border-0 p-0",
14201
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14202
+ },
14203
+ "matrx-column-spacer-end"
14204
+ ) : null,
12221
14205
  showActionsCol && /* @__PURE__ */ jsx42(
12222
14206
  "td",
12223
14207
  {
@@ -12280,7 +14264,7 @@ function MatrxDataTableCore({
12280
14264
  children: /* @__PURE__ */ jsx42(
12281
14265
  "td",
12282
14266
  {
12283
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14267
+ colSpan: renderedColSpan,
12284
14268
  className: "p-4",
12285
14269
  children: expandedDetail.render(displayRow, controls)
12286
14270
  }
@@ -12306,7 +14290,7 @@ function MatrxDataTableCore({
12306
14290
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsx42("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ jsx42(
12307
14291
  "td",
12308
14292
  {
12309
- colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
14293
+ colSpan: renderedColSpan,
12310
14294
  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
14295
  children: /* @__PURE__ */ jsxs32(
12312
14296
  "div",
@@ -12331,7 +14315,17 @@ function MatrxDataTableCore({
12331
14315
  ] }),
12332
14316
  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
14317
  selection ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-2" }) : null,
12334
- visibleColumns.map((column) => /* @__PURE__ */ jsx42(
14318
+ columnSpacers && columnSpacers.start > 0 ? /* @__PURE__ */ jsx42(
14319
+ "td",
14320
+ {
14321
+ "aria-hidden": "true",
14322
+ "data-matrx-table-column-spacer": "start",
14323
+ className: "border-0 p-0",
14324
+ style: { width: `${columnSpacers.start}px`, minWidth: `${columnSpacers.start}px` }
14325
+ },
14326
+ "matrx-column-spacer-start"
14327
+ ) : null,
14328
+ renderedColumns.map((column) => /* @__PURE__ */ jsx42(
12335
14329
  "td",
12336
14330
  {
12337
14331
  className: cn(
@@ -12343,7 +14337,7 @@ function MatrxDataTableCore({
12343
14337
  ),
12344
14338
  style: columnWidthVar(
12345
14339
  resolveColumnMarker(column) && column.width === void 0 ? 40 : column.width,
12346
- effectiveColumnWidths[columnId(column)],
14340
+ windowedWidthFor(columnId(column), column.width),
12347
14341
  columnId(column),
12348
14342
  resizableColumns && column.resizable !== false
12349
14343
  ),
@@ -12351,6 +14345,16 @@ function MatrxDataTableCore({
12351
14345
  },
12352
14346
  columnId(column)
12353
14347
  )),
14348
+ columnSpacers && columnSpacers.end > 0 ? /* @__PURE__ */ jsx42(
14349
+ "td",
14350
+ {
14351
+ "aria-hidden": "true",
14352
+ "data-matrx-table-column-spacer": "end",
14353
+ className: "border-0 p-0",
14354
+ style: { width: `${columnSpacers.end}px`, minWidth: `${columnSpacers.end}px` }
14355
+ },
14356
+ "matrx-column-spacer-end"
14357
+ ) : null,
12354
14358
  showActionsCol ? /* @__PURE__ */ jsx42("td", {}) : null
12355
14359
  ] }, `footer-${index}`)) }) : null
12356
14360
  ]
@@ -12525,7 +14529,73 @@ function MatrxDataTableCore({
12525
14529
  compact: true,
12526
14530
  hideEntriesInfo: false
12527
14531
  }
12528
- ) }) : null
14532
+ ) }) : null,
14533
+ selection && selectionCount > 0 ? /* @__PURE__ */ jsxs32(
14534
+ "div",
14535
+ {
14536
+ "data-matrx-table-bulk-bar": true,
14537
+ 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",
14538
+ children: [
14539
+ /* @__PURE__ */ jsxs32("span", { className: "text-xs font-medium text-foreground", children: [
14540
+ formatCount3(selectionCount),
14541
+ " ",
14542
+ selectionNoun,
14543
+ selectionCount === 1 ? "" : "s",
14544
+ " selected"
14545
+ ] }),
14546
+ /* @__PURE__ */ jsxs32(
14547
+ Button,
14548
+ {
14549
+ type: "button",
14550
+ variant: "ghost",
14551
+ size: "sm",
14552
+ className: "h-7 gap-1 px-2 text-xs",
14553
+ onClick: () => {
14554
+ lastToggledIndex.current = null;
14555
+ selection.onSelectedIdsChange([]);
14556
+ },
14557
+ children: [
14558
+ /* @__PURE__ */ jsx42(X, { className: "h-3.5 w-3.5" }),
14559
+ "Clear"
14560
+ ]
14561
+ }
14562
+ ),
14563
+ /* @__PURE__ */ jsxs32("div", { className: "ml-auto flex flex-wrap items-center gap-1.5", children: [
14564
+ effectiveCopy && selectedRows.length > 0 ? /* @__PURE__ */ jsx42(
14565
+ CopyButtons,
14566
+ {
14567
+ size: "icon",
14568
+ "data-matrx-table-icon-action": true,
14569
+ sourceId: `${tableCopySourceNamespace}:selection`,
14570
+ ...effectiveCopy.references ? { references: effectiveCopy.references(selectedRows, data) } : {},
14571
+ ...effectiveCopy.triggerVariant ? { triggerVariant: effectiveCopy.triggerVariant } : {},
14572
+ label: `${selectedRows.length} selected ${selectionNoun}${selectedRows.length === 1 ? "" : "s"}`,
14573
+ ...copy === void 0 ? {} : {
14574
+ human: () => buildViewHuman(effectiveCopy, selectedRows, effectiveCopyColumns),
14575
+ json: () => selectedRows.map((row) => effectiveCopy.agentRow ? effectiveCopy.agentRow(row) : row)
14576
+ },
14577
+ agent: () => copy === void 0 ? buildDefaultTableViewAgentInput(effectiveCopy, selectedRows, data, declaredVisibleCopyColumns, { scope: "selected" }) : buildViewAgentInput(effectiveCopy, selectedRows, data, { scope: "selected" }),
14578
+ aiVariants: copySubsetVariantFor(
14579
+ () => selectedRows,
14580
+ "selected"
14581
+ ),
14582
+ export: {
14583
+ items: [],
14584
+ sheetRows: () => selectedRows.map(
14585
+ (row) => projectDefaultTableCopyRow(row, declaredVisibleCopyColumns)
14586
+ ),
14587
+ sheetColumns: () => declaredVisibleCopyColumns.map((column) => ({
14588
+ id: columnId(column),
14589
+ label: column.label ?? (typeof column.header === "string" ? column.header : columnId(column))
14590
+ }))
14591
+ }
14592
+ }
14593
+ ) : null,
14594
+ selection.actions?.(selectedRows, selection.selectedIds)
14595
+ ] })
14596
+ ]
14597
+ }
14598
+ ) : null
12529
14599
  ] }),
12530
14600
  /* @__PURE__ */ jsx42(
12531
14601
  ColumnManager,
@@ -12807,7 +14877,7 @@ function HierarchyDragIndicatorLayer({
12807
14877
  scrollRef,
12808
14878
  resolve: resolve2
12809
14879
  }) {
12810
- const [indicator, setIndicator] = useState21(
14880
+ const [indicator, setIndicator] = useState22(
12811
14881
  null
12812
14882
  );
12813
14883
  useDndMonitor2({
@@ -12948,6 +15018,71 @@ function normalizeColumnWidthsForColumns(widths, columns) {
12948
15018
  })
12949
15019
  );
12950
15020
  }
15021
+ function TableGroupHeaderRow({
15022
+ group,
15023
+ collapsed,
15024
+ columns,
15025
+ spacers,
15026
+ leadingCols,
15027
+ showActionsCol,
15028
+ rowNoun,
15029
+ onToggle
15030
+ }) {
15031
+ const count = group.rows.length;
15032
+ const summary = `${count.toLocaleString()} ${count === 1 ? rowNoun : `${rowNoun}s`}`;
15033
+ const firstId = columns[0] ? columnId(columns[0]) : null;
15034
+ return /* @__PURE__ */ jsxs32(
15035
+ "tr",
15036
+ {
15037
+ "data-matrx-table-group-row": group.key,
15038
+ "data-matrx-table-group-collapsed": collapsed ? "true" : "false",
15039
+ className: "border-y border-border bg-muted/40",
15040
+ children: [
15041
+ Array.from({ length: leadingCols }).map((_, i) => /* @__PURE__ */ jsx42("td", { className: "px-2 py-1.5" }, `group-lead-${i}`)),
15042
+ spacers && spacers.start > 0 ? /* @__PURE__ */ jsx42("td", { "aria-hidden": "true", className: "border-0 p-0", style: { width: `${spacers.start}px` } }) : null,
15043
+ columns.map((col) => {
15044
+ const id = columnId(col);
15045
+ const aggregate = group.aggregates[id];
15046
+ if (id === firstId) {
15047
+ 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: [
15048
+ /* @__PURE__ */ jsx42(
15049
+ "button",
15050
+ {
15051
+ type: "button",
15052
+ onClick: onToggle,
15053
+ "aria-expanded": !collapsed,
15054
+ "aria-label": `${collapsed ? "Show" : "Hide"} ${group.label}`,
15055
+ className: "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground",
15056
+ children: collapsed ? /* @__PURE__ */ jsx42(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx42(ChevronDown, { className: "h-4 w-4" })
15057
+ }
15058
+ ),
15059
+ /* @__PURE__ */ jsx42("span", { className: "truncate text-sm font-semibold text-foreground", children: group.label }),
15060
+ /* @__PURE__ */ jsx42("span", { className: "whitespace-nowrap text-xs text-muted-foreground", children: summary })
15061
+ ] }) }, id);
15062
+ }
15063
+ return /* @__PURE__ */ jsx42(
15064
+ "td",
15065
+ {
15066
+ className: cn(
15067
+ "px-2 py-1.5 align-middle text-xs font-medium text-muted-foreground",
15068
+ // A subtotal lines up with the numbers it is a subtotal OF.
15069
+ // Left-aligned under a right-aligned money column it reads as a
15070
+ // different column's value.
15071
+ alignForFormat(col) === "center" && "text-center",
15072
+ alignForFormat(col) === "right" && "text-right",
15073
+ col.mobileHidden && "max-sm:hidden"
15074
+ ),
15075
+ children: aggregate ? /* @__PURE__ */ jsx42("span", { "data-matrx-table-group-aggregate": id, className: "whitespace-nowrap", children: aggregate.label }) : null
15076
+ },
15077
+ id
15078
+ );
15079
+ }),
15080
+ spacers && spacers.end > 0 ? /* @__PURE__ */ jsx42("td", { "aria-hidden": "true", className: "border-0 p-0", style: { width: `${spacers.end}px` } }) : null,
15081
+ showActionsCol ? /* @__PURE__ */ jsx42("td", { className: "px-2 py-1.5" }) : null
15082
+ ]
15083
+ }
15084
+ );
15085
+ }
12951
15086
  function columnWidthVar(width, resizedWidth, id, resizable) {
12952
15087
  if (width === void 0 && resizedWidth === void 0 && !resizable) return void 0;
12953
15088
  const inferredWidth = resizable && inferTableSearchRole(id) === "name" ? "12rem" : "auto";
@@ -12957,7 +15092,7 @@ function columnWidthVar(width, resizedWidth, id, resizable) {
12957
15092
  };
12958
15093
  }
12959
15094
  function DefaultJsonCell({ value }) {
12960
- const [open, setOpen] = useState21(false);
15095
+ const [open, setOpen] = useState22(false);
12961
15096
  const host = useMatrxDataTableHost();
12962
15097
  const Viewer = host.JsonViewer;
12963
15098
  const text = JSON.stringify(value, null, 2);
@@ -13040,7 +15175,7 @@ function resolveStringTitle(title, fallback) {
13040
15175
  }
13041
15176
 
13042
15177
  // src/data-table/SavedViewsControl.tsx
13043
- import { useId as useId3, useState as useState22 } from "react";
15178
+ import { useId as useId3, useState as useState23 } from "react";
13044
15179
 
13045
15180
  // src/dropdown-menu.tsx
13046
15181
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
@@ -13184,10 +15319,10 @@ function SavedViewsControl({
13184
15319
  onReload
13185
15320
  }) {
13186
15321
  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);
15322
+ const [saveOpen, setSaveOpen] = useState23(false);
15323
+ const [name, setName] = useState23("");
15324
+ const [saving, setSaving] = useState23(false);
15325
+ const [actionError, setActionError] = useState23(null);
13191
15326
  const visibleError = actionError ?? error;
13192
15327
  const save = async () => {
13193
15328
  const next = name.trim();
@@ -13320,8 +15455,11 @@ export {
13320
15455
  CELL_TINT_CLASS,
13321
15456
  COLOR_RULE_OPS,
13322
15457
  COLOR_RULE_OP_LABELS,
15458
+ DEFAULT_OVERSCAN,
15459
+ DEFAULT_UNDO_DEPTH,
13323
15460
  DataRowInspector,
13324
15461
  DataRowWindow,
15462
+ EMPTY_GROUP_KEY,
13325
15463
  EMPTY_TABLE_STYLE,
13326
15464
  FORMAT_MISMATCH_CLASS,
13327
15465
  FormattedCell,
@@ -13332,6 +15470,7 @@ export {
13332
15470
  MatrxUuidCell,
13333
15471
  NO_FROZEN_COLUMNS,
13334
15472
  OPS_WITH_VALUE,
15473
+ PARTIAL_BADGE,
13335
15474
  ROW_TINT_CLASS,
13336
15475
  STYLE_COLORS,
13337
15476
  STYLE_COLOR_LABELS,
@@ -13341,22 +15480,62 @@ export {
13341
15480
  TABLE_STYLE_VERSION,
13342
15481
  TableTitleRow,
13343
15482
  TableViewTabs,
15483
+ UNKNOWN_TOTAL_BADGE,
13344
15484
  alignForFormat,
15485
+ allRange,
13345
15486
  applyStylePath,
15487
+ boundsContain,
13346
15488
  buildRowAgentInput,
13347
15489
  buildViewAgentInput,
13348
15490
  buildViewHuman,
15491
+ canRedo,
15492
+ canUndo,
15493
+ cellClipboardText,
15494
+ cellDomKey,
15495
+ cellsInRange,
15496
+ classifyGridKey,
15497
+ coerceGroupNumber,
13349
15498
  collectSelectOptions,
13350
15499
  colorForChoice,
15500
+ columnRange,
15501
+ computeVariableWindow,
15502
+ computeWindow,
15503
+ createUndoStack,
15504
+ describeCoverage,
15505
+ detectSeries,
15506
+ directClickKinds,
13351
15507
  downloadTableAsXlsx,
13352
15508
  evaluateRule,
15509
+ fillDown,
15510
+ fillRange,
15511
+ fillTargetFor,
13353
15512
  filterKindLabel,
15513
+ flattenGroups,
13354
15514
  formatColumnValue,
15515
+ formatGroupNumber,
15516
+ gridToTsv,
15517
+ groupKeyOf,
15518
+ groupRows,
15519
+ groupSummary,
13355
15520
  inferTableSearchRole,
15521
+ isBlankGroupValue,
15522
+ isDirectClickEditor,
15523
+ isGroupCollapsed,
15524
+ isSingleCell,
15525
+ isSingleCellRange,
13356
15526
  isStyleColor,
15527
+ moveSelection,
15528
+ parseClipboardGrid,
13357
15529
  parseTableStyle,
13358
15530
  parseTableViewSnapshot,
13359
15531
  pixelWidth,
15532
+ planPaste,
15533
+ popRedo,
15534
+ popUndo,
15535
+ pushUndo,
15536
+ rangeBounds,
15537
+ rangeRows,
15538
+ rangeSize,
13360
15539
  rankTableSearchRows,
13361
15540
  resolveCellColor,
13362
15541
  resolveColumnMarker,
@@ -13364,17 +15543,25 @@ export {
13364
15543
  resolveFrozenColumns,
13365
15544
  resolveRowColor,
13366
15545
  resolveTableSearchScopes,
15546
+ rowRange,
13367
15547
  rowsToCsvFromColumns,
13368
15548
  rowsToMarkdownTable,
13369
15549
  rowsToRecordsFromColumns,
15550
+ sameCell,
15551
+ scrollOffsetForIndex,
13370
15552
  shortUuid,
15553
+ storedValuesEqual,
13371
15554
  stylePath,
13372
15555
  tableStyleFromMetadata,
13373
15556
  tableStyleIsEmpty,
13374
15557
  tableUrlParamPrefix,
15558
+ tileIndex,
15559
+ toggleGroupCollapsed,
15560
+ undoEntryFor,
13375
15561
  useMatrxDataTableHost,
13376
15562
  useMatrxTableRowAlchemy,
13377
15563
  useScrollPagination,
15564
+ useSpreadsheetGrid,
13378
15565
  useTableUrlState
13379
15566
  };
13380
15567
  //# sourceMappingURL=index.js.map