react-glide-table 1.1.3 → 1.1.5

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/core.cjs CHANGED
@@ -40,10 +40,12 @@ __export(core_exports, {
40
40
  hasCellSelectionEdges: () => hasCellSelectionEdges,
41
41
  isCellInSelection: () => isCellInSelection,
42
42
  isColumnEditable: () => isColumnEditable,
43
+ measureMergedSpanRowHeights: () => measureMergedSpanRowHeights,
43
44
  parseCellEditValue: () => parseCellEditValue,
44
45
  resolveDataTableLabels: () => resolveDataTableLabels,
45
46
  resolveRowSelection: () => resolveRowSelection,
46
47
  resolveRowSpanAt: () => resolveRowSpanAt,
48
+ rowRangeToHeightRatios: () => rowRangeToHeightRatios,
47
49
  toggleExpandedRowId: () => toggleExpandedRowId,
48
50
  useCellEdit: () => useCellEdit,
49
51
  useCellSelection: () => useCellSelection,
@@ -230,10 +232,36 @@ function getCellSelectionBounds(start, end) {
230
232
  endCol: Math.max(start.col, end.col)
231
233
  };
232
234
  }
235
+ function measureMergedSpanRowHeights(rowIndex, rowSpan, cellElement) {
236
+ if (rowSpan <= 1) return void 0;
237
+ const tbody = cellElement?.closest("tbody") ?? (typeof document !== "undefined" ? document.querySelector("tbody.data-table-body") : null);
238
+ if (!tbody) return void 0;
239
+ const rows = tbody.querySelectorAll(":scope > tr");
240
+ if (rows.length < rowIndex + rowSpan) return void 0;
241
+ const heights = [];
242
+ for (let i = 0; i < rowSpan; i++) {
243
+ const row = rows[rowIndex + i];
244
+ const height = row?.getBoundingClientRect().height ?? 0;
245
+ if (height <= 0) return void 0;
246
+ heights.push(height);
247
+ }
248
+ return heights;
249
+ }
233
250
  function getRowIndexInMergedCell(clientY, cellElement, rowIndex, rowSpan) {
234
251
  if (rowSpan <= 1) return rowIndex;
235
252
  const rect = cellElement.getBoundingClientRect();
236
253
  const relativeY = clientY - rect.top;
254
+ const heights = measureMergedSpanRowHeights(rowIndex, rowSpan, cellElement);
255
+ if (heights && heights.length === rowSpan) {
256
+ let accrued = 0;
257
+ for (let i = 0; i < rowSpan; i++) {
258
+ accrued += heights[i];
259
+ if (relativeY < accrued) {
260
+ return rowIndex + i;
261
+ }
262
+ }
263
+ return rowIndex + rowSpan - 1;
264
+ }
237
265
  const rowHeight = rect.height / rowSpan;
238
266
  const offset = Math.min(
239
267
  Math.max(Math.floor(relativeY / rowHeight), 0),
@@ -255,7 +283,35 @@ function getActiveSelectionBounds(dragState, selectionBounds) {
255
283
  var SELECTION_EDGE_WIDTH_PX = 2;
256
284
  var SELECTION_EDGE_COLOR = "var(--color-brand-primary)";
257
285
  var CELL_SELECTION_EDGES_CLASS = "cell-selection-edges";
258
- function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallySelectedAt) {
286
+ function rowRangeToHeightRatios(rowIndex, span, fromRow, toRowExclusive, rowHeights) {
287
+ const clampedFrom = Math.max(fromRow, rowIndex);
288
+ const clampedTo = Math.min(toRowExclusive, rowIndex + span);
289
+ if (clampedTo <= clampedFrom) {
290
+ return { offsetRatio: 0, lengthRatio: 0 };
291
+ }
292
+ if (!rowHeights || rowHeights.length !== span) {
293
+ return {
294
+ offsetRatio: (clampedFrom - rowIndex) / span,
295
+ lengthRatio: (clampedTo - clampedFrom) / span
296
+ };
297
+ }
298
+ const total = rowHeights.reduce((sum, height) => sum + height, 0) || 1;
299
+ let offsetPx = 0;
300
+ for (let i = 0; i < clampedFrom - rowIndex; i++) {
301
+ offsetPx += rowHeights[i] ?? 0;
302
+ }
303
+ let lengthPx = 0;
304
+ for (let i = clampedFrom - rowIndex; i < clampedTo - rowIndex; i++) {
305
+ lengthPx += rowHeights[i] ?? 0;
306
+ }
307
+ return {
308
+ offsetRatio: offsetPx / total,
309
+ lengthRatio: lengthPx / total,
310
+ offsetPx,
311
+ lengthPx
312
+ };
313
+ }
314
+ function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallySelectedAt, rowHeights) {
259
315
  const cellEndRow = rowIndex + rowSpan - 1;
260
316
  const span = cellEndRow - rowIndex + 1;
261
317
  if (span <= 1) return [];
@@ -274,20 +330,30 @@ function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallyS
274
330
  continue;
275
331
  }
276
332
  if (runStart !== null) {
277
- edges.push({
278
- side,
279
- offsetRatio: (runStart - rowIndex) / span,
280
- heightRatio: (row - runStart) / span
281
- });
333
+ const ratios = rowRangeToHeightRatios(
334
+ rowIndex,
335
+ span,
336
+ runStart,
337
+ row,
338
+ rowHeights
339
+ );
340
+ if (ratios.lengthRatio > 0) {
341
+ edges.push({ side, ...ratios });
342
+ }
282
343
  runStart = null;
283
344
  }
284
345
  }
285
346
  if (runStart !== null) {
286
- edges.push({
287
- side,
288
- offsetRatio: (runStart - rowIndex) / span,
289
- heightRatio: (toRowExclusive - runStart) / span
290
- });
347
+ const ratios = rowRangeToHeightRatios(
348
+ rowIndex,
349
+ span,
350
+ runStart,
351
+ toRowExclusive,
352
+ rowHeights
353
+ );
354
+ if (ratios.lengthRatio > 0) {
355
+ edges.push({ side, ...ratios });
356
+ }
291
357
  }
292
358
  };
293
359
  const collectSide = (side, neighborCol) => {
@@ -316,14 +382,17 @@ function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallyS
316
382
  }
317
383
  return edges;
318
384
  }
319
- function buildPartialVerticalGradient(edge) {
385
+ function buildPartialEdgeGradient(edge) {
386
+ const usePx = edge.offsetPx != null && edge.lengthPx != null;
320
387
  const startPct = edge.offsetRatio * 100;
321
- const endPct = (edge.offsetRatio + edge.heightRatio) * 100;
322
- const overlapPx = SELECTION_EDGE_WIDTH_PX + 1;
323
- const isTopProtrusion = edge.offsetRatio === 0 && edge.heightRatio < 1;
324
- const isBottomProtrusion = edge.offsetRatio > 0;
325
- const startStop = isBottomProtrusion ? `calc(${startPct}% - ${overlapPx}px)` : `${startPct}%`;
326
- const endStop = isTopProtrusion ? `calc(${endPct}% + ${overlapPx}px)` : `${endPct}%`;
388
+ const endPct = (edge.offsetRatio + edge.lengthRatio) * 100;
389
+ const startPx = edge.offsetPx ?? 0;
390
+ const endPx = (edge.offsetPx ?? 0) + (edge.lengthPx ?? 0);
391
+ const overlapPx = SELECTION_EDGE_WIDTH_PX;
392
+ const isTopProtrusion = (usePx ? startPx === 0 : edge.offsetRatio === 0) && edge.lengthRatio < 1;
393
+ const isBottomProtrusion = usePx ? startPx > 0 : edge.offsetRatio > 0;
394
+ const startStop = usePx ? isBottomProtrusion ? `${Math.max(0, startPx - overlapPx)}px` : `${startPx}px` : isBottomProtrusion ? `calc(${startPct}% - ${overlapPx}px)` : `${startPct}%`;
395
+ const endStop = usePx ? isTopProtrusion ? `${endPx + overlapPx}px` : `${endPx}px` : isTopProtrusion ? `calc(${endPct}% + ${overlapPx}px)` : `${endPct}%`;
327
396
  const xPos = edge.side === "left" ? "0" : "100%";
328
397
  const layers = [
329
398
  {
@@ -333,7 +402,7 @@ function buildPartialVerticalGradient(edge) {
333
402
  }
334
403
  ];
335
404
  if (isTopProtrusion || isBottomProtrusion) {
336
- const capTop = isTopProtrusion ? `calc(${endPct}% - ${SELECTION_EDGE_WIDTH_PX}px)` : `calc(${startPct}% - ${SELECTION_EDGE_WIDTH_PX}px)`;
405
+ const capTop = usePx ? isTopProtrusion ? `${Math.max(0, endPx - SELECTION_EDGE_WIDTH_PX)}px` : `${Math.max(0, startPx - SELECTION_EDGE_WIDTH_PX)}px` : isTopProtrusion ? `calc(${endPct}% - ${SELECTION_EDGE_WIDTH_PX}px)` : `calc(${startPct}% - ${SELECTION_EDGE_WIDTH_PX}px)`;
337
406
  layers.push({
338
407
  image: `linear-gradient(${SELECTION_EDGE_COLOR}, ${SELECTION_EDGE_COLOR})`,
339
408
  size: `${SELECTION_EDGE_WIDTH_PX}px ${SELECTION_EDGE_WIDTH_PX}px`,
@@ -342,7 +411,7 @@ function buildPartialVerticalGradient(edge) {
342
411
  }
343
412
  return layers;
344
413
  }
345
- function getCellSelectionEdgeStyle(rowIndex, colIndex, bounds, rowSpan = 1, isVisuallySelectedAt) {
414
+ function getCellSelectionEdgeStyle(rowIndex, colIndex, bounds, rowSpan = 1, isVisuallySelectedAt, rowHeights) {
346
415
  if (!isCellInSelection(rowIndex, colIndex, bounds, rowSpan) || !bounds)
347
416
  return void 0;
348
417
  const cellEndRow = rowIndex + rowSpan - 1;
@@ -351,39 +420,47 @@ function getCellSelectionEdgeStyle(rowIndex, colIndex, bounds, rowSpan = 1, isVi
351
420
  const isLeftEdge = colIndex === bounds.startCol;
352
421
  const isRightEdge = colIndex === bounds.endCol;
353
422
  const selectionContinuesBelow = cellEndRow < bounds.endRow;
354
- const shadows = [];
355
- if (isTopEdge) {
356
- shadows.push(
357
- `inset 0 ${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
358
- );
359
- }
360
- if (isBottomEdge) {
361
- shadows.push(
362
- `inset 0 -${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
363
- );
364
- }
365
- if (isLeftEdge) {
366
- shadows.push(
367
- `inset ${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
368
- );
369
- }
370
- if (isRightEdge) {
371
- shadows.push(
372
- `inset -${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
373
- );
374
- }
375
423
  const stepEdges = getMergedCellStepEdges(
376
424
  rowIndex,
377
425
  colIndex,
378
426
  bounds,
379
427
  rowSpan,
380
- isVisuallySelectedAt
428
+ isVisuallySelectedAt,
429
+ rowHeights
381
430
  );
431
+ const shadows = [];
432
+ const hasFullPerimeter = isTopEdge && isBottomEdge && isLeftEdge && isRightEdge && stepEdges.length === 0;
433
+ if (hasFullPerimeter) {
434
+ shadows.push(
435
+ `inset 0 0 0 ${SELECTION_EDGE_WIDTH_PX}px ${SELECTION_EDGE_COLOR}`
436
+ );
437
+ } else {
438
+ if (isTopEdge) {
439
+ shadows.push(
440
+ `inset 0 ${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
441
+ );
442
+ }
443
+ if (isBottomEdge) {
444
+ shadows.push(
445
+ `inset 0 -${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
446
+ );
447
+ }
448
+ if (isLeftEdge) {
449
+ shadows.push(
450
+ `inset ${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
451
+ );
452
+ }
453
+ if (isRightEdge) {
454
+ shadows.push(
455
+ `inset -${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
456
+ );
457
+ }
458
+ }
382
459
  const gradients = [];
383
460
  const sizes = [];
384
461
  const positions = [];
385
462
  for (const edge of stepEdges) {
386
- for (const partial of buildPartialVerticalGradient(edge)) {
463
+ for (const partial of buildPartialEdgeGradient(edge)) {
387
464
  gradients.push(partial.image);
388
465
  sizes.push(partial.size);
389
466
  positions.push(partial.position);
@@ -1186,10 +1263,12 @@ function useGlideTable(options) {
1186
1263
  hasCellSelectionEdges,
1187
1264
  isCellInSelection,
1188
1265
  isColumnEditable,
1266
+ measureMergedSpanRowHeights,
1189
1267
  parseCellEditValue,
1190
1268
  resolveDataTableLabels,
1191
1269
  resolveRowSelection,
1192
1270
  resolveRowSpanAt,
1271
+ rowRangeToHeightRatios,
1193
1272
  toggleExpandedRowId,
1194
1273
  useCellEdit,
1195
1274
  useCellSelection,
package/dist/core.d.cts CHANGED
@@ -55,29 +55,50 @@ type DragState = {
55
55
  fillAnchor: CellPosition | null;
56
56
  fillEnd: CellPosition | null;
57
57
  };
58
+ /**
59
+ * Reads per-row heights for a merged cell span from the table body.
60
+ * Expand + merge yields unequal row heights (e.g. parent 25px / child 22px);
61
+ * step borders must use these instead of equal-ratio splits.
62
+ */
63
+ declare function measureMergedSpanRowHeights(rowIndex: number, rowSpan: number, cellElement?: HTMLElement | null): number[] | undefined;
58
64
  declare function getRowIndexInMergedCell(clientY: number, cellElement: HTMLElement, rowIndex: number, rowSpan: number): number;
59
65
  declare function isCellInSelection(rowIndex: number, colIndex: number, bounds: CellSelectionBounds | null, rowSpan?: number): boolean;
60
66
  declare const CELL_SELECTION_EDGES_CLASS = "cell-selection-edges";
61
67
  type CellSelectionEdgeStyle = {
62
68
  /** Full-side inset border applied via ::after */
63
69
  ["--selection-edge-shadows"]?: string;
64
- /** Partial vertical border for merged-cell step (stair) segments */
70
+ /** Partial borders for merged-cell step (stair) segments */
65
71
  ["--selection-edge-gradients"]?: string;
66
72
  ["--selection-edge-sizes"]?: string;
67
73
  ["--selection-edge-positions"]?: string;
68
74
  /** Hide internal horizontal grid lines inside the selection with background color */
69
75
  borderBottomColor?: string;
70
76
  };
77
+ /**
78
+ * Maps a logical row range inside a merged cell to height ratios / pixels.
79
+ * When `rowHeights` is omitted, falls back to equal row splits.
80
+ */
81
+ declare function rowRangeToHeightRatios(rowIndex: number, span: number, fromRow: number, toRowExclusive: number, rowHeights?: number[]): {
82
+ offsetRatio: number;
83
+ lengthRatio: number;
84
+ offsetPx?: number;
85
+ lengthPx?: number;
86
+ };
71
87
  /**
72
88
  * Draws the selection border with ::after (extended -1px at the bottom) plus
73
89
  * inset box-shadow / partial gradients so it does not break on the cell's
74
90
  * border-bottom.
75
91
  *
92
+ * Merged cells that overlap the selection are filled as a whole, so top/bottom
93
+ * edges follow the cell's visual box when it contains the selection start/end.
94
+ * Step (stair) segments use height-weighted ratios when expand makes rows unequal.
95
+ *
76
96
  * @param isVisuallySelectedAt Whether a logical cell is visually selected
77
97
  * (including adjacent merged cells). Used to suppress internal step borders
78
98
  * when multiple columns have rowSpan.
99
+ * @param rowHeights Per-row heights inside this merged span.
79
100
  */
80
- declare function getCellSelectionEdgeStyle(rowIndex: number, colIndex: number, bounds: CellSelectionBounds | null, rowSpan?: number, isVisuallySelectedAt?: (row: number, col: number) => boolean): CellSelectionEdgeStyle | undefined;
101
+ declare function getCellSelectionEdgeStyle(rowIndex: number, colIndex: number, bounds: CellSelectionBounds | null, rowSpan?: number, isVisuallySelectedAt?: (row: number, col: number) => boolean, rowHeights?: number[]): CellSelectionEdgeStyle | undefined;
81
102
  declare function hasCellSelectionEdges(style: CellSelectionEdgeStyle | undefined): boolean;
82
103
 
83
104
  type RowSpanInfo = {
@@ -253,4 +274,4 @@ declare const useConvertTreeData: <T extends Record<string, unknown>>({ data, en
253
274
  declare function resolveRowSelection(mode: RowSelectionMode, controlledSelection: RowSelectionState | undefined, internalSelection: RowSelectionState): RowSelectionState;
254
275
  declare function applySelectionUpdater(mode: RowSelectionMode, updater: Updater<RowSelectionState>, previous: RowSelectionState): RowSelectionState;
255
276
 
256
- export { CELL_SELECTION_EDGES_CLASS, type CellSelectionBounds, type ColumnRowSpanMap, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableLabels, DataTableProps, type DragState, type EditingCell, RowSelectionMode, type RowSpanInfo, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, applyCellEdit, applyFillData, applySelectionUpdater, buildColumnRowSpanMap, canExpandRow, collectFillChanges, collectRowSpanColumns, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, parseCellEditValue, resolveRowSelection, resolveRowSpanAt, toggleExpandedRowId, useCellEdit, useCellSelection, useConvertTreeData, useGlideTable };
277
+ export { CELL_SELECTION_EDGES_CLASS, type CellSelectionBounds, type ColumnRowSpanMap, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableLabels, DataTableProps, type DragState, type EditingCell, RowSelectionMode, type RowSpanInfo, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, applyCellEdit, applyFillData, applySelectionUpdater, buildColumnRowSpanMap, canExpandRow, collectFillChanges, collectRowSpanColumns, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, measureMergedSpanRowHeights, parseCellEditValue, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, toggleExpandedRowId, useCellEdit, useCellSelection, useConvertTreeData, useGlideTable };
package/dist/core.d.ts CHANGED
@@ -55,29 +55,50 @@ type DragState = {
55
55
  fillAnchor: CellPosition | null;
56
56
  fillEnd: CellPosition | null;
57
57
  };
58
+ /**
59
+ * Reads per-row heights for a merged cell span from the table body.
60
+ * Expand + merge yields unequal row heights (e.g. parent 25px / child 22px);
61
+ * step borders must use these instead of equal-ratio splits.
62
+ */
63
+ declare function measureMergedSpanRowHeights(rowIndex: number, rowSpan: number, cellElement?: HTMLElement | null): number[] | undefined;
58
64
  declare function getRowIndexInMergedCell(clientY: number, cellElement: HTMLElement, rowIndex: number, rowSpan: number): number;
59
65
  declare function isCellInSelection(rowIndex: number, colIndex: number, bounds: CellSelectionBounds | null, rowSpan?: number): boolean;
60
66
  declare const CELL_SELECTION_EDGES_CLASS = "cell-selection-edges";
61
67
  type CellSelectionEdgeStyle = {
62
68
  /** Full-side inset border applied via ::after */
63
69
  ["--selection-edge-shadows"]?: string;
64
- /** Partial vertical border for merged-cell step (stair) segments */
70
+ /** Partial borders for merged-cell step (stair) segments */
65
71
  ["--selection-edge-gradients"]?: string;
66
72
  ["--selection-edge-sizes"]?: string;
67
73
  ["--selection-edge-positions"]?: string;
68
74
  /** Hide internal horizontal grid lines inside the selection with background color */
69
75
  borderBottomColor?: string;
70
76
  };
77
+ /**
78
+ * Maps a logical row range inside a merged cell to height ratios / pixels.
79
+ * When `rowHeights` is omitted, falls back to equal row splits.
80
+ */
81
+ declare function rowRangeToHeightRatios(rowIndex: number, span: number, fromRow: number, toRowExclusive: number, rowHeights?: number[]): {
82
+ offsetRatio: number;
83
+ lengthRatio: number;
84
+ offsetPx?: number;
85
+ lengthPx?: number;
86
+ };
71
87
  /**
72
88
  * Draws the selection border with ::after (extended -1px at the bottom) plus
73
89
  * inset box-shadow / partial gradients so it does not break on the cell's
74
90
  * border-bottom.
75
91
  *
92
+ * Merged cells that overlap the selection are filled as a whole, so top/bottom
93
+ * edges follow the cell's visual box when it contains the selection start/end.
94
+ * Step (stair) segments use height-weighted ratios when expand makes rows unequal.
95
+ *
76
96
  * @param isVisuallySelectedAt Whether a logical cell is visually selected
77
97
  * (including adjacent merged cells). Used to suppress internal step borders
78
98
  * when multiple columns have rowSpan.
99
+ * @param rowHeights Per-row heights inside this merged span.
79
100
  */
80
- declare function getCellSelectionEdgeStyle(rowIndex: number, colIndex: number, bounds: CellSelectionBounds | null, rowSpan?: number, isVisuallySelectedAt?: (row: number, col: number) => boolean): CellSelectionEdgeStyle | undefined;
101
+ declare function getCellSelectionEdgeStyle(rowIndex: number, colIndex: number, bounds: CellSelectionBounds | null, rowSpan?: number, isVisuallySelectedAt?: (row: number, col: number) => boolean, rowHeights?: number[]): CellSelectionEdgeStyle | undefined;
81
102
  declare function hasCellSelectionEdges(style: CellSelectionEdgeStyle | undefined): boolean;
82
103
 
83
104
  type RowSpanInfo = {
@@ -253,4 +274,4 @@ declare const useConvertTreeData: <T extends Record<string, unknown>>({ data, en
253
274
  declare function resolveRowSelection(mode: RowSelectionMode, controlledSelection: RowSelectionState | undefined, internalSelection: RowSelectionState): RowSelectionState;
254
275
  declare function applySelectionUpdater(mode: RowSelectionMode, updater: Updater<RowSelectionState>, previous: RowSelectionState): RowSelectionState;
255
276
 
256
- export { CELL_SELECTION_EDGES_CLASS, type CellSelectionBounds, type ColumnRowSpanMap, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableLabels, DataTableProps, type DragState, type EditingCell, RowSelectionMode, type RowSpanInfo, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, applyCellEdit, applyFillData, applySelectionUpdater, buildColumnRowSpanMap, canExpandRow, collectFillChanges, collectRowSpanColumns, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, parseCellEditValue, resolveRowSelection, resolveRowSpanAt, toggleExpandedRowId, useCellEdit, useCellSelection, useConvertTreeData, useGlideTable };
277
+ export { CELL_SELECTION_EDGES_CLASS, type CellSelectionBounds, type ColumnRowSpanMap, DEFAULT_TREE_CHILDREN_FIELD, DEFAULT_TREE_ID_FIELD, DEFAULT_TREE_PARENT_ID_FIELD, DEFAULT_TREE_QTY_FIELD, DataTableLabels, DataTableProps, type DragState, type EditingCell, RowSelectionMode, type RowSpanInfo, type TreeRow, type UseConvertTreeDataParams, type UseGlideTableOptions, type UseGlideTableResult, applyCellEdit, applyFillData, applySelectionUpdater, buildColumnRowSpanMap, canExpandRow, collectFillChanges, collectRowSpanColumns, getCellEditDraftValue, getCellSelectionEdgeStyle, getColumnEditType, getRowIndexInMergedCell, hasCellSelectionEdges, isCellInSelection, isColumnEditable, measureMergedSpanRowHeights, parseCellEditValue, resolveRowSelection, resolveRowSpanAt, rowRangeToHeightRatios, toggleExpandedRowId, useCellEdit, useCellSelection, useConvertTreeData, useGlideTable };
package/dist/core.js CHANGED
@@ -187,10 +187,36 @@ function getCellSelectionBounds(start, end) {
187
187
  endCol: Math.max(start.col, end.col)
188
188
  };
189
189
  }
190
+ function measureMergedSpanRowHeights(rowIndex, rowSpan, cellElement) {
191
+ if (rowSpan <= 1) return void 0;
192
+ const tbody = cellElement?.closest("tbody") ?? (typeof document !== "undefined" ? document.querySelector("tbody.data-table-body") : null);
193
+ if (!tbody) return void 0;
194
+ const rows = tbody.querySelectorAll(":scope > tr");
195
+ if (rows.length < rowIndex + rowSpan) return void 0;
196
+ const heights = [];
197
+ for (let i = 0; i < rowSpan; i++) {
198
+ const row = rows[rowIndex + i];
199
+ const height = row?.getBoundingClientRect().height ?? 0;
200
+ if (height <= 0) return void 0;
201
+ heights.push(height);
202
+ }
203
+ return heights;
204
+ }
190
205
  function getRowIndexInMergedCell(clientY, cellElement, rowIndex, rowSpan) {
191
206
  if (rowSpan <= 1) return rowIndex;
192
207
  const rect = cellElement.getBoundingClientRect();
193
208
  const relativeY = clientY - rect.top;
209
+ const heights = measureMergedSpanRowHeights(rowIndex, rowSpan, cellElement);
210
+ if (heights && heights.length === rowSpan) {
211
+ let accrued = 0;
212
+ for (let i = 0; i < rowSpan; i++) {
213
+ accrued += heights[i];
214
+ if (relativeY < accrued) {
215
+ return rowIndex + i;
216
+ }
217
+ }
218
+ return rowIndex + rowSpan - 1;
219
+ }
194
220
  const rowHeight = rect.height / rowSpan;
195
221
  const offset = Math.min(
196
222
  Math.max(Math.floor(relativeY / rowHeight), 0),
@@ -212,7 +238,35 @@ function getActiveSelectionBounds(dragState, selectionBounds) {
212
238
  var SELECTION_EDGE_WIDTH_PX = 2;
213
239
  var SELECTION_EDGE_COLOR = "var(--color-brand-primary)";
214
240
  var CELL_SELECTION_EDGES_CLASS = "cell-selection-edges";
215
- function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallySelectedAt) {
241
+ function rowRangeToHeightRatios(rowIndex, span, fromRow, toRowExclusive, rowHeights) {
242
+ const clampedFrom = Math.max(fromRow, rowIndex);
243
+ const clampedTo = Math.min(toRowExclusive, rowIndex + span);
244
+ if (clampedTo <= clampedFrom) {
245
+ return { offsetRatio: 0, lengthRatio: 0 };
246
+ }
247
+ if (!rowHeights || rowHeights.length !== span) {
248
+ return {
249
+ offsetRatio: (clampedFrom - rowIndex) / span,
250
+ lengthRatio: (clampedTo - clampedFrom) / span
251
+ };
252
+ }
253
+ const total = rowHeights.reduce((sum, height) => sum + height, 0) || 1;
254
+ let offsetPx = 0;
255
+ for (let i = 0; i < clampedFrom - rowIndex; i++) {
256
+ offsetPx += rowHeights[i] ?? 0;
257
+ }
258
+ let lengthPx = 0;
259
+ for (let i = clampedFrom - rowIndex; i < clampedTo - rowIndex; i++) {
260
+ lengthPx += rowHeights[i] ?? 0;
261
+ }
262
+ return {
263
+ offsetRatio: offsetPx / total,
264
+ lengthRatio: lengthPx / total,
265
+ offsetPx,
266
+ lengthPx
267
+ };
268
+ }
269
+ function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallySelectedAt, rowHeights) {
216
270
  const cellEndRow = rowIndex + rowSpan - 1;
217
271
  const span = cellEndRow - rowIndex + 1;
218
272
  if (span <= 1) return [];
@@ -231,20 +285,30 @@ function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallyS
231
285
  continue;
232
286
  }
233
287
  if (runStart !== null) {
234
- edges.push({
235
- side,
236
- offsetRatio: (runStart - rowIndex) / span,
237
- heightRatio: (row - runStart) / span
238
- });
288
+ const ratios = rowRangeToHeightRatios(
289
+ rowIndex,
290
+ span,
291
+ runStart,
292
+ row,
293
+ rowHeights
294
+ );
295
+ if (ratios.lengthRatio > 0) {
296
+ edges.push({ side, ...ratios });
297
+ }
239
298
  runStart = null;
240
299
  }
241
300
  }
242
301
  if (runStart !== null) {
243
- edges.push({
244
- side,
245
- offsetRatio: (runStart - rowIndex) / span,
246
- heightRatio: (toRowExclusive - runStart) / span
247
- });
302
+ const ratios = rowRangeToHeightRatios(
303
+ rowIndex,
304
+ span,
305
+ runStart,
306
+ toRowExclusive,
307
+ rowHeights
308
+ );
309
+ if (ratios.lengthRatio > 0) {
310
+ edges.push({ side, ...ratios });
311
+ }
248
312
  }
249
313
  };
250
314
  const collectSide = (side, neighborCol) => {
@@ -273,14 +337,17 @@ function getMergedCellStepEdges(rowIndex, colIndex, bounds, rowSpan, isVisuallyS
273
337
  }
274
338
  return edges;
275
339
  }
276
- function buildPartialVerticalGradient(edge) {
340
+ function buildPartialEdgeGradient(edge) {
341
+ const usePx = edge.offsetPx != null && edge.lengthPx != null;
277
342
  const startPct = edge.offsetRatio * 100;
278
- const endPct = (edge.offsetRatio + edge.heightRatio) * 100;
279
- const overlapPx = SELECTION_EDGE_WIDTH_PX + 1;
280
- const isTopProtrusion = edge.offsetRatio === 0 && edge.heightRatio < 1;
281
- const isBottomProtrusion = edge.offsetRatio > 0;
282
- const startStop = isBottomProtrusion ? `calc(${startPct}% - ${overlapPx}px)` : `${startPct}%`;
283
- const endStop = isTopProtrusion ? `calc(${endPct}% + ${overlapPx}px)` : `${endPct}%`;
343
+ const endPct = (edge.offsetRatio + edge.lengthRatio) * 100;
344
+ const startPx = edge.offsetPx ?? 0;
345
+ const endPx = (edge.offsetPx ?? 0) + (edge.lengthPx ?? 0);
346
+ const overlapPx = SELECTION_EDGE_WIDTH_PX;
347
+ const isTopProtrusion = (usePx ? startPx === 0 : edge.offsetRatio === 0) && edge.lengthRatio < 1;
348
+ const isBottomProtrusion = usePx ? startPx > 0 : edge.offsetRatio > 0;
349
+ const startStop = usePx ? isBottomProtrusion ? `${Math.max(0, startPx - overlapPx)}px` : `${startPx}px` : isBottomProtrusion ? `calc(${startPct}% - ${overlapPx}px)` : `${startPct}%`;
350
+ const endStop = usePx ? isTopProtrusion ? `${endPx + overlapPx}px` : `${endPx}px` : isTopProtrusion ? `calc(${endPct}% + ${overlapPx}px)` : `${endPct}%`;
284
351
  const xPos = edge.side === "left" ? "0" : "100%";
285
352
  const layers = [
286
353
  {
@@ -290,7 +357,7 @@ function buildPartialVerticalGradient(edge) {
290
357
  }
291
358
  ];
292
359
  if (isTopProtrusion || isBottomProtrusion) {
293
- const capTop = isTopProtrusion ? `calc(${endPct}% - ${SELECTION_EDGE_WIDTH_PX}px)` : `calc(${startPct}% - ${SELECTION_EDGE_WIDTH_PX}px)`;
360
+ const capTop = usePx ? isTopProtrusion ? `${Math.max(0, endPx - SELECTION_EDGE_WIDTH_PX)}px` : `${Math.max(0, startPx - SELECTION_EDGE_WIDTH_PX)}px` : isTopProtrusion ? `calc(${endPct}% - ${SELECTION_EDGE_WIDTH_PX}px)` : `calc(${startPct}% - ${SELECTION_EDGE_WIDTH_PX}px)`;
294
361
  layers.push({
295
362
  image: `linear-gradient(${SELECTION_EDGE_COLOR}, ${SELECTION_EDGE_COLOR})`,
296
363
  size: `${SELECTION_EDGE_WIDTH_PX}px ${SELECTION_EDGE_WIDTH_PX}px`,
@@ -299,7 +366,7 @@ function buildPartialVerticalGradient(edge) {
299
366
  }
300
367
  return layers;
301
368
  }
302
- function getCellSelectionEdgeStyle(rowIndex, colIndex, bounds, rowSpan = 1, isVisuallySelectedAt) {
369
+ function getCellSelectionEdgeStyle(rowIndex, colIndex, bounds, rowSpan = 1, isVisuallySelectedAt, rowHeights) {
303
370
  if (!isCellInSelection(rowIndex, colIndex, bounds, rowSpan) || !bounds)
304
371
  return void 0;
305
372
  const cellEndRow = rowIndex + rowSpan - 1;
@@ -308,39 +375,47 @@ function getCellSelectionEdgeStyle(rowIndex, colIndex, bounds, rowSpan = 1, isVi
308
375
  const isLeftEdge = colIndex === bounds.startCol;
309
376
  const isRightEdge = colIndex === bounds.endCol;
310
377
  const selectionContinuesBelow = cellEndRow < bounds.endRow;
311
- const shadows = [];
312
- if (isTopEdge) {
313
- shadows.push(
314
- `inset 0 ${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
315
- );
316
- }
317
- if (isBottomEdge) {
318
- shadows.push(
319
- `inset 0 -${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
320
- );
321
- }
322
- if (isLeftEdge) {
323
- shadows.push(
324
- `inset ${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
325
- );
326
- }
327
- if (isRightEdge) {
328
- shadows.push(
329
- `inset -${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
330
- );
331
- }
332
378
  const stepEdges = getMergedCellStepEdges(
333
379
  rowIndex,
334
380
  colIndex,
335
381
  bounds,
336
382
  rowSpan,
337
- isVisuallySelectedAt
383
+ isVisuallySelectedAt,
384
+ rowHeights
338
385
  );
386
+ const shadows = [];
387
+ const hasFullPerimeter = isTopEdge && isBottomEdge && isLeftEdge && isRightEdge && stepEdges.length === 0;
388
+ if (hasFullPerimeter) {
389
+ shadows.push(
390
+ `inset 0 0 0 ${SELECTION_EDGE_WIDTH_PX}px ${SELECTION_EDGE_COLOR}`
391
+ );
392
+ } else {
393
+ if (isTopEdge) {
394
+ shadows.push(
395
+ `inset 0 ${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
396
+ );
397
+ }
398
+ if (isBottomEdge) {
399
+ shadows.push(
400
+ `inset 0 -${SELECTION_EDGE_WIDTH_PX}px 0 0 ${SELECTION_EDGE_COLOR}`
401
+ );
402
+ }
403
+ if (isLeftEdge) {
404
+ shadows.push(
405
+ `inset ${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
406
+ );
407
+ }
408
+ if (isRightEdge) {
409
+ shadows.push(
410
+ `inset -${SELECTION_EDGE_WIDTH_PX}px 0 0 0 ${SELECTION_EDGE_COLOR}`
411
+ );
412
+ }
413
+ }
339
414
  const gradients = [];
340
415
  const sizes = [];
341
416
  const positions = [];
342
417
  for (const edge of stepEdges) {
343
- for (const partial of buildPartialVerticalGradient(edge)) {
418
+ for (const partial of buildPartialEdgeGradient(edge)) {
344
419
  gradients.push(partial.image);
345
420
  sizes.push(partial.size);
346
421
  positions.push(partial.position);
@@ -1142,10 +1217,12 @@ export {
1142
1217
  hasCellSelectionEdges,
1143
1218
  isCellInSelection,
1144
1219
  isColumnEditable,
1220
+ measureMergedSpanRowHeights,
1145
1221
  parseCellEditValue,
1146
1222
  resolveDataTableLabels,
1147
1223
  resolveRowSelection,
1148
1224
  resolveRowSpanAt,
1225
+ rowRangeToHeightRatios,
1149
1226
  toggleExpandedRowId,
1150
1227
  useCellEdit,
1151
1228
  useCellSelection,