react-glide-table 2.1.0 → 2.2.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.
package/dist/compound.cjs CHANGED
@@ -890,23 +890,49 @@ var useConvertTreeData = ({
890
890
  function getRowFieldValue(row, key) {
891
891
  return row[key];
892
892
  }
893
- function computeRowSpans(data, rowSpanKey) {
893
+ function normalizeRowSpanParent(value) {
894
+ if (!value) return [];
895
+ return typeof value === "string" ? [value] : [...value];
896
+ }
897
+ function toParentSpanList(parentSpans) {
898
+ if (!parentSpans?.length) return [];
899
+ const first = parentSpans[0];
900
+ if (!Array.isArray(first)) {
901
+ return [parentSpans];
902
+ }
903
+ return parentSpans;
904
+ }
905
+ function buildStartRowLookup(spans) {
906
+ const startRows = new Array(spans.length);
907
+ let origin = 0;
908
+ for (let i = 0; i < spans.length; i++) {
909
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
910
+ startRows[i] = origin;
911
+ }
912
+ return startRows;
913
+ }
914
+ function sharesParentGroup(parentStartRows, rowIndex) {
915
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
916
+ return parentStartRows.every(
917
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
918
+ );
919
+ }
920
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
894
921
  if (data.length === 0) return [];
922
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
895
923
  const result = [];
896
924
  for (let index = 0; index < data.length; index++) {
897
925
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
898
926
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
899
- if (index > 0 && currentValue === previousValue) {
927
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
900
928
  result.push({ rowSpan: 0, isFirstInGroup: false });
901
929
  continue;
902
930
  }
903
931
  let span = 1;
904
932
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
905
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
906
- span++;
907
- } else {
908
- break;
909
- }
933
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
934
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
935
+ span++;
910
936
  }
911
937
  result.push({ rowSpan: span, isFirstInGroup: true });
912
938
  }
@@ -928,10 +954,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
928
954
  }
929
955
  return { startRow: rowIndex, rowSpan: 1 };
930
956
  }
957
+ function findRowSpanColumn(spec, ref) {
958
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
959
+ }
931
960
  function buildColumnRowSpanMap(data, columnKeys) {
932
961
  const map = /* @__PURE__ */ new Map();
933
- for (const { columnId, rowSpanKey } of columnKeys) {
934
- map.set(columnId, computeRowSpans(data, rowSpanKey));
962
+ const visiting = /* @__PURE__ */ new Set();
963
+ const warnedCycles = /* @__PURE__ */ new Set();
964
+ const virtualParents = /* @__PURE__ */ new Map();
965
+ const spansForColumn = (columnId) => {
966
+ const cached = map.get(columnId);
967
+ if (cached !== void 0) return cached;
968
+ const column = columnKeys.find((item) => item.columnId === columnId);
969
+ if (!column) return void 0;
970
+ if (visiting.has(columnId)) {
971
+ if (!warnedCycles.has(columnId)) {
972
+ warnedCycles.add(columnId);
973
+ console.warn(
974
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
975
+ );
976
+ }
977
+ return void 0;
978
+ }
979
+ visiting.add(columnId);
980
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
981
+ visiting.delete(columnId);
982
+ const spans = computeRowSpans(
983
+ data,
984
+ column.rowSpanKey,
985
+ parentSpans.length > 0 ? parentSpans : void 0
986
+ );
987
+ map.set(columnId, spans);
988
+ return spans;
989
+ };
990
+ const spansForParentRef = (ref) => {
991
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
992
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
993
+ const cached = virtualParents.get(ref);
994
+ if (cached !== void 0) return cached;
995
+ const spans = computeRowSpans(data, ref);
996
+ virtualParents.set(ref, spans);
997
+ return spans;
998
+ };
999
+ for (const column of columnKeys) {
1000
+ spansForColumn(column.columnId);
935
1001
  }
936
1002
  return map;
937
1003
  }
@@ -947,7 +1013,8 @@ function collectRowSpanColumns(columns) {
947
1013
  if (!columnId || !columnDef.meta?.rowSpan) continue;
948
1014
  result.push({
949
1015
  columnId,
950
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
1016
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
1017
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
951
1018
  });
952
1019
  }
953
1020
  };
@@ -4409,6 +4476,7 @@ function buildColumnDef(props, sort, onSort) {
4409
4476
  align,
4410
4477
  rowSpan,
4411
4478
  rowSpanKey,
4479
+ rowSpanParent,
4412
4480
  editable,
4413
4481
  editType,
4414
4482
  editInputProps,
@@ -4443,6 +4511,7 @@ function buildColumnDef(props, sort, onSort) {
4443
4511
  align,
4444
4512
  rowSpan,
4445
4513
  rowSpanKey,
4514
+ rowSpanParent,
4446
4515
  editable,
4447
4516
  editType,
4448
4517
  editInputProps,
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
- import { l as DataTableProps, r as TableColumnProps, T as TableColumnGroupProps, s as TableProps } from './types-CMUGtH-c.cjs';
4
- export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, f as ColumnFreezeMeta, h as ColumnFreezeSide, i as DataTableClassNames, k as DataTableLabels, m as DataTableScrollSlotProps, n as DataTableSlots, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, p as SearchResultItem } from './types-CMUGtH-c.cjs';
3
+ import { l as DataTableProps, r as TableColumnProps, T as TableColumnGroupProps, s as TableProps } from './types-Iot4g4sq.cjs';
4
+ export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, f as ColumnFreezeMeta, h as ColumnFreezeSide, i as DataTableClassNames, k as DataTableLabels, m as DataTableScrollSlotProps, n as DataTableSlots, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, p as SearchResultItem } from './types-Iot4g4sq.cjs';
5
5
  export { ColumnDef, ColumnOrderState, ColumnResizeMode, ColumnSizingState, RowSelectionState } from '@tanstack/react-table';
6
6
 
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
- import { l as DataTableProps, r as TableColumnProps, T as TableColumnGroupProps, s as TableProps } from './types-CMUGtH-c.js';
4
- export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, f as ColumnFreezeMeta, h as ColumnFreezeSide, i as DataTableClassNames, k as DataTableLabels, m as DataTableScrollSlotProps, n as DataTableSlots, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, p as SearchResultItem } from './types-CMUGtH-c.js';
3
+ import { l as DataTableProps, r as TableColumnProps, T as TableColumnGroupProps, s as TableProps } from './types-Iot4g4sq.js';
4
+ export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, f as ColumnFreezeMeta, h as ColumnFreezeSide, i as DataTableClassNames, k as DataTableLabels, m as DataTableScrollSlotProps, n as DataTableSlots, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, p as SearchResultItem } from './types-Iot4g4sq.js';
5
5
  export { ColumnDef, ColumnOrderState, ColumnResizeMode, ColumnSizingState, RowSelectionState } from '@tanstack/react-table';
6
6
 
7
7
  /**
package/dist/compound.js CHANGED
@@ -862,23 +862,49 @@ var useConvertTreeData = ({
862
862
  function getRowFieldValue(row, key) {
863
863
  return row[key];
864
864
  }
865
- function computeRowSpans(data, rowSpanKey) {
865
+ function normalizeRowSpanParent(value) {
866
+ if (!value) return [];
867
+ return typeof value === "string" ? [value] : [...value];
868
+ }
869
+ function toParentSpanList(parentSpans) {
870
+ if (!parentSpans?.length) return [];
871
+ const first = parentSpans[0];
872
+ if (!Array.isArray(first)) {
873
+ return [parentSpans];
874
+ }
875
+ return parentSpans;
876
+ }
877
+ function buildStartRowLookup(spans) {
878
+ const startRows = new Array(spans.length);
879
+ let origin = 0;
880
+ for (let i = 0; i < spans.length; i++) {
881
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
882
+ startRows[i] = origin;
883
+ }
884
+ return startRows;
885
+ }
886
+ function sharesParentGroup(parentStartRows, rowIndex) {
887
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
888
+ return parentStartRows.every(
889
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
890
+ );
891
+ }
892
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
866
893
  if (data.length === 0) return [];
894
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
867
895
  const result = [];
868
896
  for (let index = 0; index < data.length; index++) {
869
897
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
870
898
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
871
- if (index > 0 && currentValue === previousValue) {
899
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
872
900
  result.push({ rowSpan: 0, isFirstInGroup: false });
873
901
  continue;
874
902
  }
875
903
  let span = 1;
876
904
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
877
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
878
- span++;
879
- } else {
880
- break;
881
- }
905
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
906
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
907
+ span++;
882
908
  }
883
909
  result.push({ rowSpan: span, isFirstInGroup: true });
884
910
  }
@@ -900,10 +926,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
900
926
  }
901
927
  return { startRow: rowIndex, rowSpan: 1 };
902
928
  }
929
+ function findRowSpanColumn(spec, ref) {
930
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
931
+ }
903
932
  function buildColumnRowSpanMap(data, columnKeys) {
904
933
  const map = /* @__PURE__ */ new Map();
905
- for (const { columnId, rowSpanKey } of columnKeys) {
906
- map.set(columnId, computeRowSpans(data, rowSpanKey));
934
+ const visiting = /* @__PURE__ */ new Set();
935
+ const warnedCycles = /* @__PURE__ */ new Set();
936
+ const virtualParents = /* @__PURE__ */ new Map();
937
+ const spansForColumn = (columnId) => {
938
+ const cached = map.get(columnId);
939
+ if (cached !== void 0) return cached;
940
+ const column = columnKeys.find((item) => item.columnId === columnId);
941
+ if (!column) return void 0;
942
+ if (visiting.has(columnId)) {
943
+ if (!warnedCycles.has(columnId)) {
944
+ warnedCycles.add(columnId);
945
+ console.warn(
946
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
947
+ );
948
+ }
949
+ return void 0;
950
+ }
951
+ visiting.add(columnId);
952
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
953
+ visiting.delete(columnId);
954
+ const spans = computeRowSpans(
955
+ data,
956
+ column.rowSpanKey,
957
+ parentSpans.length > 0 ? parentSpans : void 0
958
+ );
959
+ map.set(columnId, spans);
960
+ return spans;
961
+ };
962
+ const spansForParentRef = (ref) => {
963
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
964
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
965
+ const cached = virtualParents.get(ref);
966
+ if (cached !== void 0) return cached;
967
+ const spans = computeRowSpans(data, ref);
968
+ virtualParents.set(ref, spans);
969
+ return spans;
970
+ };
971
+ for (const column of columnKeys) {
972
+ spansForColumn(column.columnId);
907
973
  }
908
974
  return map;
909
975
  }
@@ -919,7 +985,8 @@ function collectRowSpanColumns(columns) {
919
985
  if (!columnId || !columnDef.meta?.rowSpan) continue;
920
986
  result.push({
921
987
  columnId,
922
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
988
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
989
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
923
990
  });
924
991
  }
925
992
  };
@@ -4404,6 +4471,7 @@ function buildColumnDef(props, sort, onSort) {
4404
4471
  align,
4405
4472
  rowSpan,
4406
4473
  rowSpanKey,
4474
+ rowSpanParent,
4407
4475
  editable,
4408
4476
  editType,
4409
4477
  editInputProps,
@@ -4438,6 +4506,7 @@ function buildColumnDef(props, sort, onSort) {
4438
4506
  align,
4439
4507
  rowSpan,
4440
4508
  rowSpanKey,
4509
+ rowSpanParent,
4441
4510
  editable,
4442
4511
  editType,
4443
4512
  editInputProps,
package/dist/core.cjs CHANGED
@@ -2296,23 +2296,49 @@ function applySelectionUpdater(mode, updater, previous) {
2296
2296
  function getRowFieldValue(row, key) {
2297
2297
  return row[key];
2298
2298
  }
2299
- function computeRowSpans(data, rowSpanKey) {
2299
+ function normalizeRowSpanParent(value) {
2300
+ if (!value) return [];
2301
+ return typeof value === "string" ? [value] : [...value];
2302
+ }
2303
+ function toParentSpanList(parentSpans) {
2304
+ if (!parentSpans?.length) return [];
2305
+ const first = parentSpans[0];
2306
+ if (!Array.isArray(first)) {
2307
+ return [parentSpans];
2308
+ }
2309
+ return parentSpans;
2310
+ }
2311
+ function buildStartRowLookup(spans) {
2312
+ const startRows = new Array(spans.length);
2313
+ let origin = 0;
2314
+ for (let i = 0; i < spans.length; i++) {
2315
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
2316
+ startRows[i] = origin;
2317
+ }
2318
+ return startRows;
2319
+ }
2320
+ function sharesParentGroup(parentStartRows, rowIndex) {
2321
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
2322
+ return parentStartRows.every(
2323
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
2324
+ );
2325
+ }
2326
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
2300
2327
  if (data.length === 0) return [];
2328
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
2301
2329
  const result = [];
2302
2330
  for (let index = 0; index < data.length; index++) {
2303
2331
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
2304
2332
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
2305
- if (index > 0 && currentValue === previousValue) {
2333
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
2306
2334
  result.push({ rowSpan: 0, isFirstInGroup: false });
2307
2335
  continue;
2308
2336
  }
2309
2337
  let span = 1;
2310
2338
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
2311
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
2312
- span++;
2313
- } else {
2314
- break;
2315
- }
2339
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
2340
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
2341
+ span++;
2316
2342
  }
2317
2343
  result.push({ rowSpan: span, isFirstInGroup: true });
2318
2344
  }
@@ -2334,10 +2360,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
2334
2360
  }
2335
2361
  return { startRow: rowIndex, rowSpan: 1 };
2336
2362
  }
2363
+ function findRowSpanColumn(spec, ref) {
2364
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
2365
+ }
2337
2366
  function buildColumnRowSpanMap(data, columnKeys) {
2338
2367
  const map = /* @__PURE__ */ new Map();
2339
- for (const { columnId, rowSpanKey } of columnKeys) {
2340
- map.set(columnId, computeRowSpans(data, rowSpanKey));
2368
+ const visiting = /* @__PURE__ */ new Set();
2369
+ const warnedCycles = /* @__PURE__ */ new Set();
2370
+ const virtualParents = /* @__PURE__ */ new Map();
2371
+ const spansForColumn = (columnId) => {
2372
+ const cached = map.get(columnId);
2373
+ if (cached !== void 0) return cached;
2374
+ const column = columnKeys.find((item) => item.columnId === columnId);
2375
+ if (!column) return void 0;
2376
+ if (visiting.has(columnId)) {
2377
+ if (!warnedCycles.has(columnId)) {
2378
+ warnedCycles.add(columnId);
2379
+ console.warn(
2380
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
2381
+ );
2382
+ }
2383
+ return void 0;
2384
+ }
2385
+ visiting.add(columnId);
2386
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
2387
+ visiting.delete(columnId);
2388
+ const spans = computeRowSpans(
2389
+ data,
2390
+ column.rowSpanKey,
2391
+ parentSpans.length > 0 ? parentSpans : void 0
2392
+ );
2393
+ map.set(columnId, spans);
2394
+ return spans;
2395
+ };
2396
+ const spansForParentRef = (ref) => {
2397
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
2398
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
2399
+ const cached = virtualParents.get(ref);
2400
+ if (cached !== void 0) return cached;
2401
+ const spans = computeRowSpans(data, ref);
2402
+ virtualParents.set(ref, spans);
2403
+ return spans;
2404
+ };
2405
+ for (const column of columnKeys) {
2406
+ spansForColumn(column.columnId);
2341
2407
  }
2342
2408
  return map;
2343
2409
  }
@@ -2353,7 +2419,8 @@ function collectRowSpanColumns(columns) {
2353
2419
  if (!columnId || !columnDef.meta?.rowSpan) continue;
2354
2420
  result.push({
2355
2421
  columnId,
2356
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
2422
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
2423
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
2357
2424
  });
2358
2425
  }
2359
2426
  };
package/dist/core.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { W as CellEditType, c as CellRenderer, C as CellKind, a as CellRenderContext, i as DataTableClassNames, R as RowSelectionMode, g as ColumnFreezeOffset, p as SearchResultItem, q as SearchStatus, l as DataTableProps, k as DataTableLabels, j as DataTableCopyActions, P as PasteMode, o as RowsPastePayload } from './types-CMUGtH-c.cjs';
2
- export { B as BuiltinCellKind, b as CellRenderFn, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, I as INLINE_SEARCH_MAX_RESULTS, S as SearchCorpusRow, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-CMUGtH-c.cjs';
1
+ import { W as CellEditType, c as CellRenderer, C as CellKind, a as CellRenderContext, i as DataTableClassNames, R as RowSelectionMode, g as ColumnFreezeOffset, p as SearchResultItem, q as SearchStatus, l as DataTableProps, k as DataTableLabels, j as DataTableCopyActions, P as PasteMode, o as RowsPastePayload } from './types-Iot4g4sq.cjs';
2
+ export { B as BuiltinCellKind, b as CellRenderFn, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, I as INLINE_SEARCH_MAX_RESULTS, S as SearchCorpusRow, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-Iot4g4sq.cjs';
3
3
  import { Row, ColumnDef, CellContext, Table, Cell, ColumnOrderState, Updater, RowSelectionState } from '@tanstack/react-table';
4
4
  export { ColumnDef, ColumnOrderState, ColumnResizeMode, ColumnSizingState, RowSelectionState } from '@tanstack/react-table';
5
5
  import { Virtualizer, VirtualItem } from '@tanstack/react-virtual';
@@ -115,6 +115,15 @@ type RowSpanInfo = {
115
115
  rowSpan: number;
116
116
  isFirstInGroup: boolean;
117
117
  };
118
+ type RowSpanColumnSpec = {
119
+ columnId: string;
120
+ rowSpanKey: string;
121
+ /**
122
+ * Column ids, rowSpanKeys, or row fields this merge cannot cross.
123
+ * Omit (or leave undefined) to merge independently of other rowSpan columns.
124
+ */
125
+ rowSpanParent?: readonly string[];
126
+ };
118
127
  type ColumnRowSpanMap = Map<string, RowSpanInfo[]>;
119
128
  /**
120
129
  * Returns the start row and rowSpan of the merged cell covering a given row.
@@ -126,15 +135,11 @@ declare function resolveRowSpanAt(rowSpans: RowSpanInfo[] | undefined, rowIndex:
126
135
  };
127
136
  /**
128
137
  * Computes merge info for every column that has rowSpan meta.
138
+ * Columns without `rowSpanParent` merge independently. Columns with a parent
139
+ * only merge inside that parent field / column's groups.
129
140
  */
130
- declare function buildColumnRowSpanMap<T extends Record<string, unknown>>(data: T[], columnKeys: Array<{
131
- columnId: string;
132
- rowSpanKey: string;
133
- }>): ColumnRowSpanMap;
134
- declare function collectRowSpanColumns<T extends Record<string, unknown>>(columns: ColumnDef<T, unknown>[]): Array<{
135
- columnId: string;
136
- rowSpanKey: string;
137
- }>;
141
+ declare function buildColumnRowSpanMap<T extends Record<string, unknown>>(data: T[], columnKeys: RowSpanColumnSpec[]): ColumnRowSpanMap;
142
+ declare function collectRowSpanColumns<T extends Record<string, unknown>>(columns: ColumnDef<T, unknown>[]): RowSpanColumnSpec[];
138
143
 
139
144
  type RowData = Record<string, unknown>;
140
145
  type DataTableRowContextValue = {
@@ -497,4 +502,4 @@ declare const useConvertTreeData: <T extends Record<string, unknown>>({ data, en
497
502
  declare function resolveRowSelection(mode: RowSelectionMode, controlledSelection: RowSelectionState | undefined, internalSelection: RowSelectionState): RowSelectionState;
498
503
  declare function applySelectionUpdater(mode: RowSelectionMode, updater: Updater<RowSelectionState>, previous: RowSelectionState): RowSelectionState;
499
504
 
500
- export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, type CellContextWithUpdate, CellKind, CellRenderContext, CellRenderer, type CellRendererRegistry, type CellSelectionBounds, type ColumnDropEdge, ColumnFreezeOffset, type ColumnRowSpanMap, type CopyRowEntry, type CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableCopyActions, DataTableLabels, DataTableProps, type DragState, type EditingCell, PasteMode, ResolvedTableCell, RowSelectionMode, type RowSpanInfo, RowsPastePayload, SearchResultItem, SearchStatus, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, type UseInlineSearchOptions, type UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard };
505
+ export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, type CellContextWithUpdate, CellKind, CellRenderContext, CellRenderer, type CellRendererRegistry, type CellSelectionBounds, type ColumnDropEdge, ColumnFreezeOffset, type ColumnRowSpanMap, type CopyRowEntry, type CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableCopyActions, DataTableLabels, DataTableProps, type DragState, type EditingCell, PasteMode, ResolvedTableCell, RowSelectionMode, type RowSpanColumnSpec, type RowSpanInfo, RowsPastePayload, SearchResultItem, SearchStatus, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, type UseInlineSearchOptions, type UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard };
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { W as CellEditType, c as CellRenderer, C as CellKind, a as CellRenderContext, i as DataTableClassNames, R as RowSelectionMode, g as ColumnFreezeOffset, p as SearchResultItem, q as SearchStatus, l as DataTableProps, k as DataTableLabels, j as DataTableCopyActions, P as PasteMode, o as RowsPastePayload } from './types-CMUGtH-c.js';
2
- export { B as BuiltinCellKind, b as CellRenderFn, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, I as INLINE_SEARCH_MAX_RESULTS, S as SearchCorpusRow, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-CMUGtH-c.js';
1
+ import { W as CellEditType, c as CellRenderer, C as CellKind, a as CellRenderContext, i as DataTableClassNames, R as RowSelectionMode, g as ColumnFreezeOffset, p as SearchResultItem, q as SearchStatus, l as DataTableProps, k as DataTableLabels, j as DataTableCopyActions, P as PasteMode, o as RowsPastePayload } from './types-Iot4g4sq.js';
2
+ export { B as BuiltinCellKind, b as CellRenderFn, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, I as INLINE_SEARCH_MAX_RESULTS, S as SearchCorpusRow, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-Iot4g4sq.js';
3
3
  import { Row, ColumnDef, CellContext, Table, Cell, ColumnOrderState, Updater, RowSelectionState } from '@tanstack/react-table';
4
4
  export { ColumnDef, ColumnOrderState, ColumnResizeMode, ColumnSizingState, RowSelectionState } from '@tanstack/react-table';
5
5
  import { Virtualizer, VirtualItem } from '@tanstack/react-virtual';
@@ -115,6 +115,15 @@ type RowSpanInfo = {
115
115
  rowSpan: number;
116
116
  isFirstInGroup: boolean;
117
117
  };
118
+ type RowSpanColumnSpec = {
119
+ columnId: string;
120
+ rowSpanKey: string;
121
+ /**
122
+ * Column ids, rowSpanKeys, or row fields this merge cannot cross.
123
+ * Omit (or leave undefined) to merge independently of other rowSpan columns.
124
+ */
125
+ rowSpanParent?: readonly string[];
126
+ };
118
127
  type ColumnRowSpanMap = Map<string, RowSpanInfo[]>;
119
128
  /**
120
129
  * Returns the start row and rowSpan of the merged cell covering a given row.
@@ -126,15 +135,11 @@ declare function resolveRowSpanAt(rowSpans: RowSpanInfo[] | undefined, rowIndex:
126
135
  };
127
136
  /**
128
137
  * Computes merge info for every column that has rowSpan meta.
138
+ * Columns without `rowSpanParent` merge independently. Columns with a parent
139
+ * only merge inside that parent field / column's groups.
129
140
  */
130
- declare function buildColumnRowSpanMap<T extends Record<string, unknown>>(data: T[], columnKeys: Array<{
131
- columnId: string;
132
- rowSpanKey: string;
133
- }>): ColumnRowSpanMap;
134
- declare function collectRowSpanColumns<T extends Record<string, unknown>>(columns: ColumnDef<T, unknown>[]): Array<{
135
- columnId: string;
136
- rowSpanKey: string;
137
- }>;
141
+ declare function buildColumnRowSpanMap<T extends Record<string, unknown>>(data: T[], columnKeys: RowSpanColumnSpec[]): ColumnRowSpanMap;
142
+ declare function collectRowSpanColumns<T extends Record<string, unknown>>(columns: ColumnDef<T, unknown>[]): RowSpanColumnSpec[];
138
143
 
139
144
  type RowData = Record<string, unknown>;
140
145
  type DataTableRowContextValue = {
@@ -497,4 +502,4 @@ declare const useConvertTreeData: <T extends Record<string, unknown>>({ data, en
497
502
  declare function resolveRowSelection(mode: RowSelectionMode, controlledSelection: RowSelectionState | undefined, internalSelection: RowSelectionState): RowSelectionState;
498
503
  declare function applySelectionUpdater(mode: RowSelectionMode, updater: Updater<RowSelectionState>, previous: RowSelectionState): RowSelectionState;
499
504
 
500
- export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, type CellContextWithUpdate, CellKind, CellRenderContext, CellRenderer, type CellRendererRegistry, type CellSelectionBounds, type ColumnDropEdge, ColumnFreezeOffset, type ColumnRowSpanMap, type CopyRowEntry, type CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableCopyActions, DataTableLabels, DataTableProps, type DragState, type EditingCell, PasteMode, ResolvedTableCell, RowSelectionMode, type RowSpanInfo, RowsPastePayload, SearchResultItem, SearchStatus, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, type UseInlineSearchOptions, type UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard };
505
+ export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, type CellContextWithUpdate, CellKind, CellRenderContext, CellRenderer, type CellRendererRegistry, type CellSelectionBounds, type ColumnDropEdge, ColumnFreezeOffset, type ColumnRowSpanMap, type CopyRowEntry, type CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableCopyActions, DataTableLabels, DataTableProps, type DragState, type EditingCell, PasteMode, ResolvedTableCell, RowSelectionMode, type RowSpanColumnSpec, type RowSpanInfo, RowsPastePayload, SearchResultItem, SearchStatus, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, type UseInlineSearchOptions, type UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard };
package/dist/core.js CHANGED
@@ -2210,23 +2210,49 @@ function applySelectionUpdater(mode, updater, previous) {
2210
2210
  function getRowFieldValue(row, key) {
2211
2211
  return row[key];
2212
2212
  }
2213
- function computeRowSpans(data, rowSpanKey) {
2213
+ function normalizeRowSpanParent(value) {
2214
+ if (!value) return [];
2215
+ return typeof value === "string" ? [value] : [...value];
2216
+ }
2217
+ function toParentSpanList(parentSpans) {
2218
+ if (!parentSpans?.length) return [];
2219
+ const first = parentSpans[0];
2220
+ if (!Array.isArray(first)) {
2221
+ return [parentSpans];
2222
+ }
2223
+ return parentSpans;
2224
+ }
2225
+ function buildStartRowLookup(spans) {
2226
+ const startRows = new Array(spans.length);
2227
+ let origin = 0;
2228
+ for (let i = 0; i < spans.length; i++) {
2229
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
2230
+ startRows[i] = origin;
2231
+ }
2232
+ return startRows;
2233
+ }
2234
+ function sharesParentGroup(parentStartRows, rowIndex) {
2235
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
2236
+ return parentStartRows.every(
2237
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
2238
+ );
2239
+ }
2240
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
2214
2241
  if (data.length === 0) return [];
2242
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
2215
2243
  const result = [];
2216
2244
  for (let index = 0; index < data.length; index++) {
2217
2245
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
2218
2246
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
2219
- if (index > 0 && currentValue === previousValue) {
2247
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
2220
2248
  result.push({ rowSpan: 0, isFirstInGroup: false });
2221
2249
  continue;
2222
2250
  }
2223
2251
  let span = 1;
2224
2252
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
2225
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
2226
- span++;
2227
- } else {
2228
- break;
2229
- }
2253
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
2254
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
2255
+ span++;
2230
2256
  }
2231
2257
  result.push({ rowSpan: span, isFirstInGroup: true });
2232
2258
  }
@@ -2248,10 +2274,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
2248
2274
  }
2249
2275
  return { startRow: rowIndex, rowSpan: 1 };
2250
2276
  }
2277
+ function findRowSpanColumn(spec, ref) {
2278
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
2279
+ }
2251
2280
  function buildColumnRowSpanMap(data, columnKeys) {
2252
2281
  const map = /* @__PURE__ */ new Map();
2253
- for (const { columnId, rowSpanKey } of columnKeys) {
2254
- map.set(columnId, computeRowSpans(data, rowSpanKey));
2282
+ const visiting = /* @__PURE__ */ new Set();
2283
+ const warnedCycles = /* @__PURE__ */ new Set();
2284
+ const virtualParents = /* @__PURE__ */ new Map();
2285
+ const spansForColumn = (columnId) => {
2286
+ const cached = map.get(columnId);
2287
+ if (cached !== void 0) return cached;
2288
+ const column = columnKeys.find((item) => item.columnId === columnId);
2289
+ if (!column) return void 0;
2290
+ if (visiting.has(columnId)) {
2291
+ if (!warnedCycles.has(columnId)) {
2292
+ warnedCycles.add(columnId);
2293
+ console.warn(
2294
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
2295
+ );
2296
+ }
2297
+ return void 0;
2298
+ }
2299
+ visiting.add(columnId);
2300
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
2301
+ visiting.delete(columnId);
2302
+ const spans = computeRowSpans(
2303
+ data,
2304
+ column.rowSpanKey,
2305
+ parentSpans.length > 0 ? parentSpans : void 0
2306
+ );
2307
+ map.set(columnId, spans);
2308
+ return spans;
2309
+ };
2310
+ const spansForParentRef = (ref) => {
2311
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
2312
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
2313
+ const cached = virtualParents.get(ref);
2314
+ if (cached !== void 0) return cached;
2315
+ const spans = computeRowSpans(data, ref);
2316
+ virtualParents.set(ref, spans);
2317
+ return spans;
2318
+ };
2319
+ for (const column of columnKeys) {
2320
+ spansForColumn(column.columnId);
2255
2321
  }
2256
2322
  return map;
2257
2323
  }
@@ -2267,7 +2333,8 @@ function collectRowSpanColumns(columns) {
2267
2333
  if (!columnId || !columnDef.meta?.rowSpan) continue;
2268
2334
  result.push({
2269
2335
  columnId,
2270
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
2336
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
2337
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
2271
2338
  });
2272
2339
  }
2273
2340
  };
package/dist/index.cjs CHANGED
@@ -2314,23 +2314,49 @@ function applySelectionUpdater(mode, updater, previous) {
2314
2314
  function getRowFieldValue(row, key) {
2315
2315
  return row[key];
2316
2316
  }
2317
- function computeRowSpans(data, rowSpanKey) {
2317
+ function normalizeRowSpanParent(value) {
2318
+ if (!value) return [];
2319
+ return typeof value === "string" ? [value] : [...value];
2320
+ }
2321
+ function toParentSpanList(parentSpans) {
2322
+ if (!parentSpans?.length) return [];
2323
+ const first = parentSpans[0];
2324
+ if (!Array.isArray(first)) {
2325
+ return [parentSpans];
2326
+ }
2327
+ return parentSpans;
2328
+ }
2329
+ function buildStartRowLookup(spans) {
2330
+ const startRows = new Array(spans.length);
2331
+ let origin = 0;
2332
+ for (let i = 0; i < spans.length; i++) {
2333
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
2334
+ startRows[i] = origin;
2335
+ }
2336
+ return startRows;
2337
+ }
2338
+ function sharesParentGroup(parentStartRows, rowIndex) {
2339
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
2340
+ return parentStartRows.every(
2341
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
2342
+ );
2343
+ }
2344
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
2318
2345
  if (data.length === 0) return [];
2346
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
2319
2347
  const result = [];
2320
2348
  for (let index = 0; index < data.length; index++) {
2321
2349
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
2322
2350
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
2323
- if (index > 0 && currentValue === previousValue) {
2351
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
2324
2352
  result.push({ rowSpan: 0, isFirstInGroup: false });
2325
2353
  continue;
2326
2354
  }
2327
2355
  let span = 1;
2328
2356
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
2329
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
2330
- span++;
2331
- } else {
2332
- break;
2333
- }
2357
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
2358
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
2359
+ span++;
2334
2360
  }
2335
2361
  result.push({ rowSpan: span, isFirstInGroup: true });
2336
2362
  }
@@ -2352,10 +2378,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
2352
2378
  }
2353
2379
  return { startRow: rowIndex, rowSpan: 1 };
2354
2380
  }
2381
+ function findRowSpanColumn(spec, ref) {
2382
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
2383
+ }
2355
2384
  function buildColumnRowSpanMap(data, columnKeys) {
2356
2385
  const map = /* @__PURE__ */ new Map();
2357
- for (const { columnId, rowSpanKey } of columnKeys) {
2358
- map.set(columnId, computeRowSpans(data, rowSpanKey));
2386
+ const visiting = /* @__PURE__ */ new Set();
2387
+ const warnedCycles = /* @__PURE__ */ new Set();
2388
+ const virtualParents = /* @__PURE__ */ new Map();
2389
+ const spansForColumn = (columnId) => {
2390
+ const cached = map.get(columnId);
2391
+ if (cached !== void 0) return cached;
2392
+ const column = columnKeys.find((item) => item.columnId === columnId);
2393
+ if (!column) return void 0;
2394
+ if (visiting.has(columnId)) {
2395
+ if (!warnedCycles.has(columnId)) {
2396
+ warnedCycles.add(columnId);
2397
+ console.warn(
2398
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
2399
+ );
2400
+ }
2401
+ return void 0;
2402
+ }
2403
+ visiting.add(columnId);
2404
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
2405
+ visiting.delete(columnId);
2406
+ const spans = computeRowSpans(
2407
+ data,
2408
+ column.rowSpanKey,
2409
+ parentSpans.length > 0 ? parentSpans : void 0
2410
+ );
2411
+ map.set(columnId, spans);
2412
+ return spans;
2413
+ };
2414
+ const spansForParentRef = (ref) => {
2415
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
2416
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
2417
+ const cached = virtualParents.get(ref);
2418
+ if (cached !== void 0) return cached;
2419
+ const spans = computeRowSpans(data, ref);
2420
+ virtualParents.set(ref, spans);
2421
+ return spans;
2422
+ };
2423
+ for (const column of columnKeys) {
2424
+ spansForColumn(column.columnId);
2359
2425
  }
2360
2426
  return map;
2361
2427
  }
@@ -2371,7 +2437,8 @@ function collectRowSpanColumns(columns) {
2371
2437
  if (!columnId || !columnDef.meta?.rowSpan) continue;
2372
2438
  result.push({
2373
2439
  columnId,
2374
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
2440
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
2441
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
2375
2442
  });
2376
2443
  }
2377
2444
  };
@@ -4524,6 +4591,7 @@ function buildColumnDef(props, sort, onSort) {
4524
4591
  align,
4525
4592
  rowSpan,
4526
4593
  rowSpanKey,
4594
+ rowSpanParent,
4527
4595
  editable,
4528
4596
  editType,
4529
4597
  editInputProps,
@@ -4558,6 +4626,7 @@ function buildColumnDef(props, sort, onSort) {
4558
4626
  align,
4559
4627
  rowSpan,
4560
4628
  rowSpanKey,
4629
+ rowSpanParent,
4561
4630
  editable,
4562
4631
  editType,
4563
4632
  editInputProps,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, g as ColumnFreezeOffset, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, i as DataTableClassNames, j as DataTableCopyActions, k as DataTableLabels, l as DataTableProps, m as DataTableScrollSlotProps, n as DataTableSlots, I as INLINE_SEARCH_MAX_RESULTS, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, S as SearchCorpusRow, p as SearchResultItem, q as SearchStatus, T as TableColumnGroupProps, r as TableColumnProps, s as TableProps, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-CMUGtH-c.cjs';
2
- export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, CellContextWithUpdate, CellRendererRegistry, CellSelectionBounds, ColumnDropEdge, ColumnRowSpanMap, CopyRowEntry, CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DragState, EditingCell, ResolvedTableCell, RowSpanInfo, TreeRow, UseConvertTreeDataParams, UseGlideTableOptions, UseGlideTableResult, UseInlineSearchOptions, UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard } from './core.cjs';
1
+ export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, g as ColumnFreezeOffset, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, i as DataTableClassNames, j as DataTableCopyActions, k as DataTableLabels, l as DataTableProps, m as DataTableScrollSlotProps, n as DataTableSlots, I as INLINE_SEARCH_MAX_RESULTS, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, S as SearchCorpusRow, p as SearchResultItem, q as SearchStatus, T as TableColumnGroupProps, r as TableColumnProps, s as TableProps, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-Iot4g4sq.cjs';
2
+ export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, CellContextWithUpdate, CellRendererRegistry, CellSelectionBounds, ColumnDropEdge, ColumnRowSpanMap, CopyRowEntry, CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DragState, EditingCell, ResolvedTableCell, RowSpanColumnSpec, RowSpanInfo, TreeRow, UseConvertTreeDataParams, UseGlideTableOptions, UseGlideTableResult, UseInlineSearchOptions, UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard } from './core.cjs';
3
3
  export { DataTable, Table, TableCompoundComponent, createTable } from './compound.cjs';
4
4
  export { ColumnDef, ColumnOrderState, ColumnResizeMode, ColumnSizingState, RowSelectionState } from '@tanstack/react-table';
5
5
  import 'react';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, g as ColumnFreezeOffset, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, i as DataTableClassNames, j as DataTableCopyActions, k as DataTableLabels, l as DataTableProps, m as DataTableScrollSlotProps, n as DataTableSlots, I as INLINE_SEARCH_MAX_RESULTS, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, S as SearchCorpusRow, p as SearchResultItem, q as SearchStatus, T as TableColumnGroupProps, r as TableColumnProps, s as TableProps, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-CMUGtH-c.js';
2
- export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, CellContextWithUpdate, CellRendererRegistry, CellSelectionBounds, ColumnDropEdge, ColumnRowSpanMap, CopyRowEntry, CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DragState, EditingCell, ResolvedTableCell, RowSpanInfo, TreeRow, UseConvertTreeDataParams, UseGlideTableOptions, UseGlideTableResult, UseInlineSearchOptions, UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard } from './core.js';
1
+ export { B as BuiltinCellKind, C as CellKind, a as CellRenderContext, b as CellRenderFn, c as CellRenderer, d as ColumnFreezeColumnInput, e as ColumnFreezeEdgeSide, f as ColumnFreezeMeta, g as ColumnFreezeOffset, h as ColumnFreezeSide, D as DEFAULT_DATA_TABLE_LABELS, i as DataTableClassNames, j as DataTableCopyActions, k as DataTableLabels, l as DataTableProps, m as DataTableScrollSlotProps, n as DataTableSlots, I as INLINE_SEARCH_MAX_RESULTS, P as PasteMode, R as RowSelectionMode, o as RowsPastePayload, S as SearchCorpusRow, p as SearchResultItem, q as SearchStatus, T as TableColumnGroupProps, r as TableColumnProps, s as TableProps, t as buildColumnFreezeOffsets, u as buildFlatSearchCorpus, v as buildSearchMatchKey, w as buildSearchMatchKeys, x as buildTreeSearchCorpus, y as cellValueToSearchText, z as collectAncestorKeysToExpand, A as collectSearchMatchesInRange, E as createSearchRegex, F as escapeSearchRegex, G as formatSearchResultLabel, H as getColumnFreezeEdgeAttr, J as getColumnFreezeStyle, K as mapSearchResultToVisibleItem, L as mapSearchResultsToVisibleKeys, M as nextSearchIndex, N as nextSearchStride, O as previousSearchIndex, Q as resolveColumnFreezeSide, U as resolveDataTableLabels, V as resolveHeaderFreezeOffset } from './types-Iot4g4sq.js';
2
+ export { BUILTIN_CELL_RENDERERS, CELL_SELECTION_EDGES_CLASS, CellContextWithUpdate, CellRendererRegistry, CellSelectionBounds, ColumnDropEdge, ColumnRowSpanMap, CopyRowEntry, CopySelectionMode, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DragState, EditingCell, ResolvedTableCell, RowSpanColumnSpec, RowSpanInfo, TreeRow, UseConvertTreeDataParams, UseGlideTableOptions, UseGlideTableResult, UseInlineSearchOptions, UseInlineSearchResult, applyCellEdit, applyFillData, applyLeafColumnOrder, applySelectionUpdater, buildColumnRowSpanMap, buildRowsPastePayload, canExpandRow, collectCopyRowEntries, collectCopyRows, collectFillChanges, collectLeafColumnIds, collectRowSpanColumns, commitCellValue, createCellRendererRegistry, flattenSubtreeRows, formatCellValue, formatDefaultCellValue, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getColumnSizeStyle, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, isEditablePasteTarget, measureMergedSpanRowHeights, moveColumnIds, parseCellEditValue, parseClipboardTSV, parseClipboardTSVWithDepths, resolveCellRenderer, resolveDropEdge, resolveLeafColumnOrder, resolvePasteColumnIds, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, serializeCopyRowsToTSV, serializeSelectionToTSV, toggleExpandedRowId, useCellEdit, useCellSelection, useColumnReorder, useConvertTreeData, useGlideTable, useInlineSearch, withCellUpdate, writeSelectionToClipboard } from './core.js';
3
3
  export { DataTable, Table, TableCompoundComponent, createTable } from './compound.js';
4
4
  export { ColumnDef, ColumnOrderState, ColumnResizeMode, ColumnSizingState, RowSelectionState } from '@tanstack/react-table';
5
5
  import 'react';
package/dist/index.js CHANGED
@@ -2225,23 +2225,49 @@ function applySelectionUpdater(mode, updater, previous) {
2225
2225
  function getRowFieldValue(row, key) {
2226
2226
  return row[key];
2227
2227
  }
2228
- function computeRowSpans(data, rowSpanKey) {
2228
+ function normalizeRowSpanParent(value) {
2229
+ if (!value) return [];
2230
+ return typeof value === "string" ? [value] : [...value];
2231
+ }
2232
+ function toParentSpanList(parentSpans) {
2233
+ if (!parentSpans?.length) return [];
2234
+ const first = parentSpans[0];
2235
+ if (!Array.isArray(first)) {
2236
+ return [parentSpans];
2237
+ }
2238
+ return parentSpans;
2239
+ }
2240
+ function buildStartRowLookup(spans) {
2241
+ const startRows = new Array(spans.length);
2242
+ let origin = 0;
2243
+ for (let i = 0; i < spans.length; i++) {
2244
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
2245
+ startRows[i] = origin;
2246
+ }
2247
+ return startRows;
2248
+ }
2249
+ function sharesParentGroup(parentStartRows, rowIndex) {
2250
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
2251
+ return parentStartRows.every(
2252
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
2253
+ );
2254
+ }
2255
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
2229
2256
  if (data.length === 0) return [];
2257
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
2230
2258
  const result = [];
2231
2259
  for (let index = 0; index < data.length; index++) {
2232
2260
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
2233
2261
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
2234
- if (index > 0 && currentValue === previousValue) {
2262
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
2235
2263
  result.push({ rowSpan: 0, isFirstInGroup: false });
2236
2264
  continue;
2237
2265
  }
2238
2266
  let span = 1;
2239
2267
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
2240
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
2241
- span++;
2242
- } else {
2243
- break;
2244
- }
2268
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
2269
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
2270
+ span++;
2245
2271
  }
2246
2272
  result.push({ rowSpan: span, isFirstInGroup: true });
2247
2273
  }
@@ -2263,10 +2289,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
2263
2289
  }
2264
2290
  return { startRow: rowIndex, rowSpan: 1 };
2265
2291
  }
2292
+ function findRowSpanColumn(spec, ref) {
2293
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
2294
+ }
2266
2295
  function buildColumnRowSpanMap(data, columnKeys) {
2267
2296
  const map = /* @__PURE__ */ new Map();
2268
- for (const { columnId, rowSpanKey } of columnKeys) {
2269
- map.set(columnId, computeRowSpans(data, rowSpanKey));
2297
+ const visiting = /* @__PURE__ */ new Set();
2298
+ const warnedCycles = /* @__PURE__ */ new Set();
2299
+ const virtualParents = /* @__PURE__ */ new Map();
2300
+ const spansForColumn = (columnId) => {
2301
+ const cached = map.get(columnId);
2302
+ if (cached !== void 0) return cached;
2303
+ const column = columnKeys.find((item) => item.columnId === columnId);
2304
+ if (!column) return void 0;
2305
+ if (visiting.has(columnId)) {
2306
+ if (!warnedCycles.has(columnId)) {
2307
+ warnedCycles.add(columnId);
2308
+ console.warn(
2309
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
2310
+ );
2311
+ }
2312
+ return void 0;
2313
+ }
2314
+ visiting.add(columnId);
2315
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
2316
+ visiting.delete(columnId);
2317
+ const spans = computeRowSpans(
2318
+ data,
2319
+ column.rowSpanKey,
2320
+ parentSpans.length > 0 ? parentSpans : void 0
2321
+ );
2322
+ map.set(columnId, spans);
2323
+ return spans;
2324
+ };
2325
+ const spansForParentRef = (ref) => {
2326
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
2327
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
2328
+ const cached = virtualParents.get(ref);
2329
+ if (cached !== void 0) return cached;
2330
+ const spans = computeRowSpans(data, ref);
2331
+ virtualParents.set(ref, spans);
2332
+ return spans;
2333
+ };
2334
+ for (const column of columnKeys) {
2335
+ spansForColumn(column.columnId);
2270
2336
  }
2271
2337
  return map;
2272
2338
  }
@@ -2282,7 +2348,8 @@ function collectRowSpanColumns(columns) {
2282
2348
  if (!columnId || !columnDef.meta?.rowSpan) continue;
2283
2349
  result.push({
2284
2350
  columnId,
2285
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
2351
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
2352
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
2286
2353
  });
2287
2354
  }
2288
2355
  };
@@ -4440,6 +4507,7 @@ function buildColumnDef(props, sort, onSort) {
4440
4507
  align,
4441
4508
  rowSpan,
4442
4509
  rowSpanKey,
4510
+ rowSpanParent,
4443
4511
  editable,
4444
4512
  editType,
4445
4513
  editInputProps,
@@ -4474,6 +4542,7 @@ function buildColumnDef(props, sort, onSort) {
4474
4542
  align,
4475
4543
  rowSpan,
4476
4544
  rowSpanKey,
4545
+ rowSpanParent,
4477
4546
  editable,
4478
4547
  editType,
4479
4548
  editInputProps,
@@ -180,10 +180,17 @@ type DataTableEditInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type
180
180
 
181
181
  declare module "@tanstack/react-table" {
182
182
  interface ColumnMeta<TData, TValue> {
183
- /** Whether this column participates in vertical row spanning */
183
+ /**
184
+ * Whether this column participates in vertical row spanning.
185
+ */
184
186
  rowSpan?: boolean;
185
187
  /** Merge key field. Falls back to the column id when omitted */
186
188
  rowSpanKey?: string;
189
+ /**
190
+ * Parent column id, rowSpanKey, or row field(s) that bound this merge.
191
+ * The column only groups consecutive rows that stay inside those parent groups.
192
+ */
193
+ rowSpanParent?: string | readonly string[];
187
194
  align?: "left" | "center" | "right";
188
195
  className?: string;
189
196
  headerClassName?: string;
@@ -559,8 +566,14 @@ type TableColumnProps<T extends Record<string, unknown>, K extends string = keyo
559
566
  */
560
567
  frozen?: ColumnFreezeMeta;
561
568
  align?: "left" | "center" | "right";
569
+ /** Vertical cell merge for consecutive identical `rowSpanKey` values. */
562
570
  rowSpan?: boolean;
563
571
  rowSpanKey?: string;
572
+ /**
573
+ * Parent field(s) this merge is nested under — a column `field`, `rowSpanKey`,
574
+ * or row data key. Omit to merge independently of other rowSpan columns.
575
+ */
576
+ rowSpanParent?: string | readonly string[];
564
577
  editable?: boolean;
565
578
  editType?: CellEditType;
566
579
  /** Inline editor input attribute overrides */
@@ -180,10 +180,17 @@ type DataTableEditInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type
180
180
 
181
181
  declare module "@tanstack/react-table" {
182
182
  interface ColumnMeta<TData, TValue> {
183
- /** Whether this column participates in vertical row spanning */
183
+ /**
184
+ * Whether this column participates in vertical row spanning.
185
+ */
184
186
  rowSpan?: boolean;
185
187
  /** Merge key field. Falls back to the column id when omitted */
186
188
  rowSpanKey?: string;
189
+ /**
190
+ * Parent column id, rowSpanKey, or row field(s) that bound this merge.
191
+ * The column only groups consecutive rows that stay inside those parent groups.
192
+ */
193
+ rowSpanParent?: string | readonly string[];
187
194
  align?: "left" | "center" | "right";
188
195
  className?: string;
189
196
  headerClassName?: string;
@@ -559,8 +566,14 @@ type TableColumnProps<T extends Record<string, unknown>, K extends string = keyo
559
566
  */
560
567
  frozen?: ColumnFreezeMeta;
561
568
  align?: "left" | "center" | "right";
569
+ /** Vertical cell merge for consecutive identical `rowSpanKey` values. */
562
570
  rowSpan?: boolean;
563
571
  rowSpanKey?: string;
572
+ /**
573
+ * Parent field(s) this merge is nested under — a column `field`, `rowSpanKey`,
574
+ * or row data key. Omit to merge independently of other rowSpan columns.
575
+ */
576
+ rowSpanParent?: string | readonly string[];
564
577
  editable?: boolean;
565
578
  editType?: CellEditType;
566
579
  /** Inline editor input attribute overrides */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-glide-table",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/zpxlffjrm/react-glide-table.git"