@lensui/lens-table 0.1.3 → 0.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/index.js CHANGED
@@ -27,6 +27,7 @@ var ZH_CN_GRID_LABELS = {
27
27
  sortDesc: "\u5F53\u524D\uFF1A\u964D\u5E8F",
28
28
  sortBoth: "\u5347\u5E8F / \u964D\u5E8F",
29
29
  dragColumn: "\u62D6\u52A8\u5217\u6392\u5E8F",
30
+ revealSelectedCell: "\u5B9A\u4F4D\u5230\u9009\u4E2D\u5355\u5143\u683C",
30
31
  previousYear: "\u4E0A\u4E00\u5E74",
31
32
  nextYear: "\u4E0B\u4E00\u5E74",
32
33
  previousMonth: "\u4E0A\u4E2A\u6708",
@@ -95,6 +96,7 @@ var EN_US_GRID_LABELS = {
95
96
  sortDesc: "Current: descending",
96
97
  sortBoth: "Ascending / descending",
97
98
  dragColumn: "Drag column to reorder",
99
+ revealSelectedCell: "Reveal selected cell",
98
100
  previousYear: "Previous year",
99
101
  nextYear: "Next year",
100
102
  previousMonth: "Previous month",
@@ -303,7 +305,6 @@ function LoadingSpinnerIcon() {
303
305
  // src/components/editors/ChoiceEditor.tsx
304
306
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
305
307
  function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
306
- const editorStyle = typeof style.width === "number" ? { ...style, width: Math.max(0, style.width - 1) } : style;
307
308
  const collection = createListCollection({
308
309
  items: options,
309
310
  itemToValue: (item) => item.value,
@@ -313,7 +314,7 @@ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCan
313
314
  Select.Root,
314
315
  {
315
316
  className: "rvg-choice-editor",
316
- style: editorStyle,
317
+ style,
317
318
  collection,
318
319
  value: value ? [value] : [],
319
320
  defaultOpen: true,
@@ -863,6 +864,8 @@ function ellipsizeText(context, text, maxWidth) {
863
864
  }
864
865
  function paintGrid(options) {
865
866
  const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, headerHeight, fixedHeader, verticalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview } = options;
867
+ const headerLeafTop = options.headerLeafTop ?? 0;
868
+ const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
866
869
  const colors = { ...COLORS, ...options.colors };
867
870
  const bodyTop = options.bodyTop ?? headerHeight;
868
871
  const getCellSpan = (rowIndex, columnIndex) => options.cellSpans.get(getCellCoordKey(rowIndex, columnIndex));
@@ -879,9 +882,17 @@ function paintGrid(options) {
879
882
  ctx.fillRect(0, 0, width, height);
880
883
  ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
881
884
  ctx.textBaseline = "middle";
882
- const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? Math.max(value, metrics[index].right) : value, 0);
885
+ const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? value + metrics[index].width : value, 0);
883
886
  const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
884
887
  const rightFixedLeft = width - rightFixedWidth;
888
+ const rightFixedBoundaryInset = rightFixedWidth > 0 && !verticalBorderless ? 1 : 0;
889
+ const leftOffsets = /* @__PURE__ */ new Map();
890
+ let leftOffset = 0;
891
+ for (let index = 0; index < columns.length; index += 1) {
892
+ if (columns[index].fixed !== "left") continue;
893
+ leftOffsets.set(index, leftOffset);
894
+ leftOffset += metrics[index].width;
895
+ }
885
896
  const rightOffsets = /* @__PURE__ */ new Map();
886
897
  let rightOffset = 0;
887
898
  for (let index = columns.length - 1; index >= 0; index -= 1) {
@@ -889,8 +900,14 @@ function paintGrid(options) {
889
900
  rightOffsets.set(index, rightOffset);
890
901
  rightOffset += metrics[index].width;
891
902
  }
903
+ const scrollableLefts = /* @__PURE__ */ new Map();
904
+ let scrollableLeft = leftFixedWidth;
905
+ for (let index = 0; index < columns.length; index += 1) {
906
+ scrollableLefts.set(index, scrollableLeft);
907
+ if (columns[index].fixed === void 0) scrollableLeft += metrics[index].width;
908
+ }
892
909
  const getColumnX = (index) => {
893
- return columns[index].fixed === "left" ? metrics[index].left : columns[index].fixed === "right" ? width - (rightOffsets.get(index) ?? 0) - metrics[index].width : metrics[index].left - scrollLeft;
910
+ return columns[index].fixed === "left" ? leftOffsets.get(index) ?? 0 : columns[index].fixed === "right" ? width - (rightOffsets.get(index) ?? 0) - metrics[index].width : (scrollableLefts.get(index) ?? leftFixedWidth) - scrollLeft;
894
911
  };
895
912
  const paintBodyColumns = (layer) => {
896
913
  const rowStart = Math.max(0, range.rowStart - Math.max(0, options.maxRowSpan - 1));
@@ -1040,7 +1057,7 @@ function paintGrid(options) {
1040
1057
  if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1041
1058
  ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1042
1059
  const column = columns[columnIndex];
1043
- if (!column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1060
+ if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1044
1061
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1045
1062
  const titleWidth = ctx.measureText(column.title).width;
1046
1063
  const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
@@ -1057,11 +1074,11 @@ function paintGrid(options) {
1057
1074
  const textX = titleAlign === "right" ? x + contentWidth - padding : titleAlign === "center" ? x + centeredTextX : x + padding;
1058
1075
  ctx.save();
1059
1076
  ctx.beginPath();
1060
- ctx.rect(x + 2, headerY + 1, Math.max(0, clipWidth - 4), headerHeight - 2);
1077
+ ctx.rect(x + 2, headerY + headerLeafTop + 1, Math.max(0, clipWidth - 4), headerLeafHeight - 2);
1061
1078
  ctx.clip();
1062
1079
  ctx.fillStyle = colors.muted;
1063
1080
  ctx.textAlign = titleAlign === "right" ? "right" : titleAlign === "center" ? "center" : "left";
1064
- ctx.fillText(title, textX, headerY + headerHeight / 2);
1081
+ ctx.fillText(title, textX, headerY + headerLeafTop + headerLeafHeight / 2);
1065
1082
  ctx.restore();
1066
1083
  }
1067
1084
  }
@@ -1069,22 +1086,6 @@ function paintGrid(options) {
1069
1086
  paintHeaderColumns("scroll");
1070
1087
  paintHeaderColumns("left");
1071
1088
  paintHeaderColumns("right");
1072
- if (leftFixedWidth > 0 && scrollLeft > 0) {
1073
- const shadowWidth = 10;
1074
- const shadow = ctx.createLinearGradient(leftFixedWidth, 0, leftFixedWidth + shadowWidth, 0);
1075
- shadow.addColorStop(0, "rgba(5, 5, 5, 0.06)");
1076
- shadow.addColorStop(1, "rgba(5, 5, 5, 0)");
1077
- ctx.fillStyle = shadow;
1078
- ctx.fillRect(leftFixedWidth, 0, shadowWidth, height);
1079
- }
1080
- if (rightFixedWidth > 0 && scrollLeft < (metrics[metrics.length - 1]?.right ?? 0) - width) {
1081
- const shadowWidth = 10;
1082
- const shadow = ctx.createLinearGradient(rightFixedLeft - shadowWidth, 0, rightFixedLeft, 0);
1083
- shadow.addColorStop(0, "rgba(5, 5, 5, 0)");
1084
- shadow.addColorStop(1, "rgba(5, 5, 5, 0.06)");
1085
- ctx.fillStyle = shadow;
1086
- ctx.fillRect(rightFixedLeft - shadowWidth, 0, shadowWidth, height);
1087
- }
1088
1089
  if (rightFixedWidth > 0 && !verticalBorderless) {
1089
1090
  ctx.fillStyle = colors.grid;
1090
1091
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
@@ -1108,22 +1109,22 @@ function paintGrid(options) {
1108
1109
  const markerBottom = Math.min(bottom, height);
1109
1110
  const startX = getColumnX(columnStart);
1110
1111
  const endX = getColumnX(columnEnd) + metrics[columnEnd].width;
1111
- const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth - 1, startX);
1112
+ const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth, startX);
1112
1113
  const visibleRight = columns[columnEnd].fixed === "right" ? Math.min(width, endX) : Math.min(rightFixedLeft, endX);
1113
1114
  ctx.fillStyle = colors.selection;
1114
1115
  if (markerBottom > markerTop) {
1115
1116
  if (columns[columnStart].fixed === void 0 && startX < leftFixedWidth) {
1116
- ctx.fillRect(Math.max(0, leftFixedWidth - 1), markerTop, 2, markerBottom - markerTop);
1117
+ ctx.fillRect(Math.max(0, leftFixedWidth), markerTop, 2, markerBottom - markerTop);
1117
1118
  }
1118
1119
  if (columns[columnEnd].fixed === void 0 && endX > rightFixedLeft) {
1119
- ctx.fillRect(Math.max(0, rightFixedLeft - 1), markerTop, 2, markerBottom - markerTop);
1120
+ ctx.fillRect(Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset), markerTop, 2, markerBottom - markerTop);
1120
1121
  }
1121
1122
  }
1122
1123
  if (visibleRight > visibleLeft) {
1123
1124
  if (top < clipTop) ctx.fillRect(visibleLeft, clipTop, visibleRight - visibleLeft, 2);
1124
1125
  if (bottom > height) ctx.fillRect(visibleLeft, height - 2, visibleRight - visibleLeft, 2);
1125
1126
  } else if (top < clipTop || bottom > height) {
1126
- const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth - 1) : Math.max(0, rightFixedLeft - 1);
1127
+ const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth) : Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset);
1127
1128
  ctx.fillRect(markerX, top < clipTop ? clipTop : height - 2, 2, 2);
1128
1129
  }
1129
1130
  }
@@ -1145,15 +1146,15 @@ function paintGrid(options) {
1145
1146
  const visibleRight = x + selectionWidth;
1146
1147
  const visibleBottom = y + selectionHeight;
1147
1148
  const selectionTop = fixedHeader ? bodyTop : 0;
1148
- const selectionClipTop = fixedHeader ? Math.max(0, selectionTop - 1) : selectionTop;
1149
+ const selectionClipTop = selectionTop;
1149
1150
  const hiddenAboveHeader = visibleBottom <= selectionTop;
1150
1151
  const hiddenBelowViewport = y >= height;
1151
1152
  const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
1152
1153
  const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
1153
- const leftMarkerX = Math.max(0, leftFixedWidth - 1);
1154
+ const leftMarkerX = Math.max(0, leftFixedWidth);
1154
1155
  if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
1155
1156
  const markerSize = 2;
1156
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1157
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1157
1158
  const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
1158
1159
  ctx.fillStyle = colors.selection;
1159
1160
  ctx.fillRect(markerX, markerY, markerSize, markerSize);
@@ -1161,7 +1162,7 @@ function paintGrid(options) {
1161
1162
  }
1162
1163
  ctx.save();
1163
1164
  ctx.beginPath();
1164
- const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth - 1);
1165
+ const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth);
1165
1166
  const clipRight = Math.min(width, selectionAreaRight + 1);
1166
1167
  ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
1167
1168
  ctx.clip();
@@ -1179,14 +1180,14 @@ function paintGrid(options) {
1179
1180
  if (hiddenBehindLeft || hiddenBehindRight) {
1180
1181
  const markerTop = Math.max(y - borderWidth, selectionTop);
1181
1182
  const markerBottom = Math.min(y + selectionHeight + 1, height);
1182
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1183
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1183
1184
  ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
1184
1185
  }
1185
1186
  if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
1186
1187
  ctx.restore();
1187
1188
  return;
1188
1189
  }
1189
- const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft - 1);
1190
+ const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft);
1190
1191
  const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
1191
1192
  const borderTop = Math.max(y - borderWidth, selectionClipTop);
1192
1193
  const borderBottom = Math.min(y + selectionHeight - 1, height - borderWidth);
@@ -1236,6 +1237,45 @@ var getRowNumberColumnWidth = (rowCount) => {
1236
1237
  const digits = String(Math.max(1, rowCount)).length;
1237
1238
  return digits <= 4 ? 44 : 44 + (digits - 4) * 8;
1238
1239
  };
1240
+ function getColumnDepth(column) {
1241
+ if (!column.children?.length) return 1;
1242
+ return 1 + Math.max(...column.children.map(getColumnDepth));
1243
+ }
1244
+ function flattenDataColumns(columns, inheritedFixed, insideGroup = false) {
1245
+ return columns.flatMap((column) => {
1246
+ if (!column.children?.length) {
1247
+ return insideGroup ? [{ ...column, fixed: inheritedFixed }] : [column];
1248
+ }
1249
+ return flattenDataColumns(column.children, column.fixed ?? inheritedFixed, true);
1250
+ });
1251
+ }
1252
+ function buildHeaderCells(sourceColumns, utilityColumnCount, depth) {
1253
+ const cells = [];
1254
+ let leafIndex = utilityColumnCount;
1255
+ const walk = (column, level, inheritedFixed, insideGroup, rootIndex, parentKey) => {
1256
+ const fixedColumn = insideGroup ? { ...column, fixed: inheritedFixed } : column;
1257
+ if (!fixedColumn.children?.length) {
1258
+ const startIndex2 = leafIndex;
1259
+ leafIndex += 1;
1260
+ cells.push({ key: fixedColumn.key, column: fixedColumn, level: depth - 1, startIndex: startIndex2, endIndex: startIndex2, leaf: true, parentKey, rootIndex });
1261
+ return { startIndex: startIndex2, endIndex: startIndex2 };
1262
+ }
1263
+ const startIndex = leafIndex;
1264
+ fixedColumn.children.forEach((child) => walk(child, level + 1, fixedColumn.fixed, true, rootIndex, fixedColumn.key));
1265
+ const endIndex = leafIndex - 1;
1266
+ cells.push({ key: fixedColumn.key, column: fixedColumn, level, startIndex, endIndex, leaf: false, parentKey, rootIndex });
1267
+ return { startIndex, endIndex };
1268
+ };
1269
+ sourceColumns.forEach((column, index) => walk(column, 0, void 0, false, index));
1270
+ return cells;
1271
+ }
1272
+ function hasNestedFixedColumns(columns, insideGroup = false) {
1273
+ return columns.some((column) => insideGroup && column.fixed !== void 0 || (column.children?.length ? hasNestedFixedColumns(column.children, true) : false));
1274
+ }
1275
+ function isProductionRuntime() {
1276
+ const env = globalThis.process?.env?.NODE_ENV;
1277
+ return env === "production";
1278
+ }
1239
1279
  var createPendingInsertId = () => `table-inserted-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
1240
1280
  var waitForPaint = () => new Promise((resolve) => {
1241
1281
  requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
@@ -1256,6 +1296,25 @@ var createEmptyCellSpanLookup = () => ({
1256
1296
  covered: /* @__PURE__ */ new Map(),
1257
1297
  maxRowSpan: 1
1258
1298
  });
1299
+ var summaryFunctionIds = /* @__PURE__ */ new WeakMap();
1300
+ var nextSummaryFunctionId = 1;
1301
+ function getSummaryFunctionId(fn) {
1302
+ if (!fn) return "";
1303
+ const existing = summaryFunctionIds.get(fn);
1304
+ if (existing !== void 0) return existing;
1305
+ const next = nextSummaryFunctionId;
1306
+ nextSummaryFunctionId += 1;
1307
+ summaryFunctionIds.set(fn, next);
1308
+ return next;
1309
+ }
1310
+ function getSummarySignature(columns) {
1311
+ return columns.filter((column) => Boolean(column.summary)).map((column) => [
1312
+ column.key,
1313
+ String(column.dataIndex ?? ""),
1314
+ typeof column.summary === "function" ? getSummaryFunctionId(column.summary) : "sum",
1315
+ getSummaryFunctionId(column.formatter)
1316
+ ].join("")).join("");
1317
+ }
1259
1318
  async function calculateSummaryValues(columns, rows) {
1260
1319
  const values = /* @__PURE__ */ new Map();
1261
1320
  for (const column of columns) {
@@ -1350,10 +1409,24 @@ function Table({
1350
1409
  }) {
1351
1410
  const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
1352
1411
  const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
1412
+ const headerDepth = useMemo(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
1353
1413
  const { language, labels } = useMemo(() => resolveGridLocale(locale), [locale]);
1354
1414
  const customHeaderContentRefs = useRef2(/* @__PURE__ */ new Map());
1355
- const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState4(0);
1356
- const headerHeight = Math.max(baseHeaderHeight, measuredHeaderHeight);
1415
+ const customHeaderMeasurementsRef = useRef2(/* @__PURE__ */ new Map());
1416
+ const [measuredHeaderRowHeights, setMeasuredHeaderRowHeights] = useState4([]);
1417
+ const headerRowHeights = useMemo(() => Array.from({ length: headerDepth }, (_, level) => Math.max(baseHeaderHeight, measuredHeaderRowHeights[level] ?? 0)), [baseHeaderHeight, headerDepth, measuredHeaderRowHeights]);
1418
+ const headerRowOffsets = useMemo(() => {
1419
+ const offsets = [];
1420
+ let top = 0;
1421
+ headerRowHeights.forEach((height2) => {
1422
+ offsets.push(top);
1423
+ top += height2;
1424
+ });
1425
+ return offsets;
1426
+ }, [headerRowHeights]);
1427
+ const headerHeight = headerRowHeights.reduce((total, height2) => total + height2, 0);
1428
+ const headerLeafTop = headerRowOffsets[headerDepth - 1] ?? 0;
1429
+ const headerLeafHeight = headerRowHeights[headerDepth - 1] ?? baseHeaderHeight;
1357
1430
  const hasVerticalBorders = !(borderless ?? verticalBorderless ?? false);
1358
1431
  const hasStripedRows = Boolean(striped);
1359
1432
  const stripedColor = typeof striped === "string" ? striped : void 0;
@@ -1373,6 +1446,13 @@ function Table({
1373
1446
  const summaryEmptyValue = typeof summary === "object" ? summary.emptyValue ?? "" : "";
1374
1447
  const contextMenuEnabled = contextMenuConfig !== false;
1375
1448
  const customContextMenuConfig = typeof contextMenuConfig === "object" ? contextMenuConfig : void 0;
1449
+ const [resizedColumnWidths, setResizedColumnWidths] = useState4({});
1450
+ const leafSourceColumns = useMemo(() => flattenDataColumns(sourceColumns), [sourceColumns]);
1451
+ useEffect2(() => {
1452
+ if (!isProductionRuntime() && hasNestedFixedColumns(sourceColumns)) {
1453
+ console.warn("[Table] Multi-level headers only support fixed columns on top-level columns. Nested fixed values are ignored.");
1454
+ }
1455
+ }, [sourceColumns]);
1376
1456
  const columns = useMemo(() => {
1377
1457
  const utilityColumns = [];
1378
1458
  if (rowDraggable) {
@@ -1384,9 +1464,24 @@ function Table({
1384
1464
  if (rowNumber) {
1385
1465
  utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: "left", rowNumber: true });
1386
1466
  }
1387
- return [...utilityColumns, ...sourceColumns];
1388
- }, [rowDraggable, rowNumber, rowSelection, sourceColumns, sourceRows.length]);
1467
+ const dataColumns = leafSourceColumns.map((column) => {
1468
+ const resizedWidth = resizedColumnWidths[column.key];
1469
+ return resizedWidth === void 0 ? column : { ...column, width: resizedWidth };
1470
+ });
1471
+ return [...utilityColumns, ...dataColumns];
1472
+ }, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber, rowSelection, sourceRows.length]);
1389
1473
  const utilityColumnCount = (rowDraggable ? 1 : 0) + (rowSelection ? 1 : 0) + (rowNumber ? 1 : 0);
1474
+ const headerCells = useMemo(() => buildHeaderCells(sourceColumns, utilityColumnCount, headerDepth), [headerDepth, sourceColumns, utilityColumnCount]);
1475
+ const activeCustomHeaderLevels = useMemo(() => {
1476
+ const levels = /* @__PURE__ */ new Map();
1477
+ columns.forEach((column) => {
1478
+ if (column.renderHeader) levels.set(column.key, headerDepth - 1);
1479
+ });
1480
+ headerCells.forEach((cell) => {
1481
+ if (cell.column.renderHeader) levels.set(`group:${cell.key}:${cell.level}`, cell.level);
1482
+ });
1483
+ return levels;
1484
+ }, [columns, headerCells, headerDepth]);
1390
1485
  const canvasRef = useRef2(null);
1391
1486
  const scrollerRef = useRef2(null);
1392
1487
  const frameRef = useRef2(null);
@@ -1404,6 +1499,15 @@ function Table({
1404
1499
  const dragGuideRef = useRef2(null);
1405
1500
  const rowDragGuideRef = useRef2(null);
1406
1501
  const selectionFocusRef = useRef2(null);
1502
+ const selectionEdgeTargetRef = useRef2(null);
1503
+ const verticalScrollbarRef = useRef2(null);
1504
+ const verticalScrollbarThumbRef = useRef2(null);
1505
+ const verticalScrollbarDragRef = useRef2(null);
1506
+ const horizontalScrollbarRef = useRef2(null);
1507
+ const horizontalScrollbarThumbRef = useRef2(null);
1508
+ const horizontalScrollbarDragRef = useRef2(null);
1509
+ const verticalScrollbarVisibilityTimerRef = useRef2(null);
1510
+ const horizontalScrollbarVisibilityTimerRef = useRef2(null);
1407
1511
  const rowByKeyRef = useRef2(/* @__PURE__ */ new Map());
1408
1512
  const rowIndexByKeyRef = useRef2(/* @__PURE__ */ new Map());
1409
1513
  const headerIconsRef = useRef2(null);
@@ -1456,24 +1560,57 @@ function Table({
1456
1560
  rangeDragRef.current = null;
1457
1561
  setSelectionRange(null);
1458
1562
  }, [rangeSelection]);
1563
+ useEffect2(() => {
1564
+ setResizedColumnWidths((current) => {
1565
+ const sourceColumnByKey = new Map(leafSourceColumns.map((column) => [column.key, column]));
1566
+ let changed = false;
1567
+ const next = {};
1568
+ Object.entries(current).forEach(([key, value]) => {
1569
+ const sourceColumn = sourceColumnByKey.get(key);
1570
+ if (!sourceColumn) {
1571
+ changed = true;
1572
+ return;
1573
+ }
1574
+ if (sourceColumn.width !== void 0 && sourceColumn.width !== value) {
1575
+ changed = true;
1576
+ return;
1577
+ }
1578
+ next[key] = value;
1579
+ });
1580
+ return changed ? next : current;
1581
+ });
1582
+ }, [leafSourceColumns]);
1459
1583
  useEffect2(() => () => {
1460
1584
  if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
1461
1585
  if (cellTooltipTimerRef.current !== null) clearTimeout(cellTooltipTimerRef.current);
1462
1586
  }, []);
1463
1587
  const measureCustomHeaderHeight = useCallback2(() => {
1464
- let nextHeight = 0;
1588
+ const nextHeights = Array.from({ length: headerDepth }, () => baseHeaderHeight);
1465
1589
  customHeaderContentRefs.current.forEach((node) => {
1466
- nextHeight = Math.max(nextHeight, Math.ceil(node.scrollHeight) + 16);
1590
+ const headerCell = node.closest(".rvg-header-title");
1591
+ const key = headerCell?.dataset.measureKey;
1592
+ const level = key ? activeCustomHeaderLevels.get(key) : void 0;
1593
+ if (!key || level === void 0 || level < 0 || level >= nextHeights.length) return;
1594
+ customHeaderMeasurementsRef.current.set(key, { level, height: Math.ceil(node.scrollHeight) + 16 });
1467
1595
  });
1468
- setMeasuredHeaderHeight((current) => current === nextHeight ? current : nextHeight);
1469
- }, []);
1596
+ customHeaderMeasurementsRef.current.forEach((measurement, key) => {
1597
+ if (!activeCustomHeaderLevels.has(key)) return;
1598
+ nextHeights[measurement.level] = Math.max(nextHeights[measurement.level], measurement.height);
1599
+ });
1600
+ setMeasuredHeaderRowHeights((current) => {
1601
+ const changed = nextHeights.length !== current.length || nextHeights.some((height2, index) => height2 !== current[index]);
1602
+ return changed ? nextHeights : current;
1603
+ });
1604
+ }, [activeCustomHeaderLevels, baseHeaderHeight, headerDepth]);
1470
1605
  useLayoutEffect2(() => {
1471
- const activeCustomHeaderKeys = new Set(columns.filter((column) => column.renderHeader).map((column) => column.key));
1472
1606
  customHeaderContentRefs.current.forEach((_, key) => {
1473
- if (!activeCustomHeaderKeys.has(key)) customHeaderContentRefs.current.delete(key);
1607
+ if (!activeCustomHeaderLevels.has(key)) customHeaderContentRefs.current.delete(key);
1608
+ });
1609
+ customHeaderMeasurementsRef.current.forEach((_, key) => {
1610
+ if (!activeCustomHeaderLevels.has(key)) customHeaderMeasurementsRef.current.delete(key);
1474
1611
  });
1475
- if (customHeaderContentRefs.current.size === 0) {
1476
- setMeasuredHeaderHeight(0);
1612
+ if (activeCustomHeaderLevels.size === 0) {
1613
+ setMeasuredHeaderRowHeights([]);
1477
1614
  return;
1478
1615
  }
1479
1616
  measureCustomHeaderHeight();
@@ -1481,7 +1618,7 @@ function Table({
1481
1618
  const observer = new ResizeObserver(measureCustomHeaderHeight);
1482
1619
  customHeaderContentRefs.current.forEach((node) => observer.observe(node));
1483
1620
  return () => observer.disconnect();
1484
- }, [columns, measureCustomHeaderHeight, viewport.width]);
1621
+ }, [activeCustomHeaderLevels, measureCustomHeaderHeight, viewport.width]);
1485
1622
  const [filterEditor, setFilterEditor] = useState4(null);
1486
1623
  const [contextMenu, setContextMenu] = useState4(null);
1487
1624
  const [insertCounts, setInsertCounts] = useState4({ before: 1, after: 1 });
@@ -1491,6 +1628,7 @@ function Table({
1491
1628
  const [deleteBusy, setDeleteBusy] = useState4(false);
1492
1629
  const [toast, setToast] = useState4(null);
1493
1630
  const [resizeGuideX, setResizeGuideX] = useState4(null);
1631
+ const [resizeGuideTop, setResizeGuideTop] = useState4(0);
1494
1632
  const [hoveredHeaderAction, setHoveredHeaderAction] = useState4(null);
1495
1633
  const [visibleHeaderTooltip, setVisibleHeaderTooltip] = useState4(null);
1496
1634
  const [hoveredCellTooltip, setHoveredCellTooltip] = useState4(null);
@@ -1575,7 +1713,17 @@ function Table({
1575
1713
  const metrics = useMemo(() => buildColumnMetrics(columns, { columnDraggable, viewportWidth: viewport.width }), [columnDraggable, columns, viewport.width]);
1576
1714
  const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
1577
1715
  const contentHeight = rows.length * rowHeight;
1578
- const fixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "left" ? Math.max(width2, metrics[index].right) : width2, 0), [columns, metrics]);
1716
+ const fixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "left" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
1717
+ const leftFixedOffsets = useMemo(() => {
1718
+ const offsets = /* @__PURE__ */ new Map();
1719
+ let offset = 0;
1720
+ for (let index = 0; index < columns.length; index += 1) {
1721
+ if (columns[index].fixed !== "left") continue;
1722
+ offsets.set(index, offset);
1723
+ offset += metrics[index].width;
1724
+ }
1725
+ return offsets;
1726
+ }, [columns, metrics]);
1579
1727
  const rightFixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "right" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
1580
1728
  const rightFixedOffsets = useMemo(() => {
1581
1729
  const offsets = /* @__PURE__ */ new Map();
@@ -1587,9 +1735,20 @@ function Table({
1587
1735
  }
1588
1736
  return offsets;
1589
1737
  }, [columns, metrics]);
1738
+ const scrollableColumnLefts = useMemo(() => {
1739
+ const lefts = [];
1740
+ let width2 = fixedWidth;
1741
+ for (let index = 0; index < columns.length; index += 1) {
1742
+ lefts[index] = width2;
1743
+ if (columns[index].fixed === void 0) width2 += metrics[index].width;
1744
+ }
1745
+ return lefts;
1746
+ }, [columns, fixedWidth, metrics]);
1747
+ const getScrollableColumnLeft = useCallback2((columnIndex) => scrollableColumnLefts[columnIndex] ?? fixedWidth, [fixedWidth, scrollableColumnLefts]);
1590
1748
  const hasRowSelectionColumn = useMemo(() => columns.some((column) => column.rowSelection), [columns]);
1591
1749
  const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
1592
- const hasSummaryRow = useMemo(() => rows.length > 0 && summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, rows.length, summaryEnabled]);
1750
+ const summarySignature = useMemo(() => getSummarySignature(columns), [columns]);
1751
+ const hasSummaryRow = rows.length > 0 && summaryEnabled && summarySignature.length > 0;
1593
1752
  const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
1594
1753
  const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
1595
1754
  const bodyTop = headerHeight + topSummaryHeight;
@@ -1597,22 +1756,22 @@ function Table({
1597
1756
  const realContentHeight = bodyTop + bodyContentHeight + bottomSummaryHeight + (contentWidth > viewport.width ? horizontalScrollbarHeight : 0);
1598
1757
  const effectiveHeight = autoHeight ? Math.max(bodyTop + bottomSummaryHeight, Math.min(resolvedHeight, realContentHeight)) : resolvedHeight;
1599
1758
  const renderHeight = Math.max(0, effectiveHeight - bottomSummaryHeight);
1600
- const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1601
- const [summaryState, setSummaryState] = useState4(() => ({ columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1602
- const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.columns !== columns || summaryState.rows !== rows);
1603
- const summaryValues = hasSummaryRow && summaryState.columns === columns && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1759
+ const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight + topSummaryHeight : 0));
1760
+ const [summaryState, setSummaryState] = useState4(() => ({ signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1761
+ const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.signature !== summarySignature || summaryState.rows !== rows);
1762
+ const summaryValues = hasSummaryRow && summaryState.signature === summarySignature && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1604
1763
  useEffect2(() => {
1605
1764
  let cancelled = false;
1606
1765
  if (!hasSummaryRow) {
1607
- setSummaryState((current) => current.columns === null && current.rows === null && current.values.size === 0 && !current.pending ? current : { columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false });
1766
+ setSummaryState((current) => current.signature === "" && current.rows === null && current.values.size === 0 && !current.pending ? current : { signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false });
1608
1767
  return () => {
1609
1768
  cancelled = true;
1610
1769
  };
1611
1770
  }
1612
1771
  setSummaryState((current) => ({
1613
- columns,
1772
+ signature: summarySignature,
1614
1773
  rows,
1615
- values: current.columns === columns && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1774
+ values: current.signature === summarySignature && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1616
1775
  pending: true
1617
1776
  }));
1618
1777
  if (loading) {
@@ -1625,13 +1784,13 @@ function Table({
1625
1784
  if (cancelled) return;
1626
1785
  const values = await calculateSummaryValues(columns, rows);
1627
1786
  if (cancelled) return;
1628
- setSummaryState({ columns, rows, values, pending: false });
1787
+ setSummaryState({ signature: summarySignature, rows, values, pending: false });
1629
1788
  };
1630
1789
  void run();
1631
1790
  return () => {
1632
1791
  cancelled = true;
1633
1792
  };
1634
- }, [columns, hasSummaryRow, loading, rows]);
1793
+ }, [hasSummaryRow, loading, rows, summarySignature]);
1635
1794
  const domRange = useMemo(() => {
1636
1795
  if (!isVirtualized) return { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
1637
1796
  const currentScroll2 = scrollRef.current;
@@ -1773,11 +1932,163 @@ function Table({
1773
1932
  const scroll = scrollRef.current;
1774
1933
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
1775
1934
  const range = isVirtualized ? getViewportRange(scroll.left, rowScrollTop, viewport.width, bodyViewportHeight, rows.length, rowHeight, metrics, virtualOverscan) : { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
1776
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1777
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1935
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1936
+ }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1778
1937
  const scheduleDraw = useCallback2(() => {
1779
1938
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
1780
1939
  }, [draw]);
1940
+ const drawImmediately = useCallback2(() => {
1941
+ if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
1942
+ frameRef.current = null;
1943
+ draw();
1944
+ }, [draw]);
1945
+ const getSelectionHorizontalVisibility = useCallback2((scrollLeft) => {
1946
+ if (!selection || selectionRange) return "visible";
1947
+ const column = columns[selection.columnIndex];
1948
+ if (!column || column.fixed !== void 0) return "visible";
1949
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1950
+ const left = getScrollableColumnLeft(selection.columnIndex) - scrollLeft;
1951
+ const right = left + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1952
+ if (right <= fixedWidth) return "hidden-left";
1953
+ if (left >= viewport.width - rightFixedWidth) return "hidden-right";
1954
+ return "visible";
1955
+ }, [columns, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, rightFixedWidth, selection, selectionRange, viewport.width]);
1956
+ const getSelectionEdgeIndicator = useCallback2((scrollLeft, scrollTop) => {
1957
+ if (!selection || selectionRange) return null;
1958
+ const row = rows[selection.rowIndex];
1959
+ const metric = metrics[selection.columnIndex];
1960
+ const column = columns[selection.columnIndex];
1961
+ if (!row || !metric || !column || getRowKey(row, selection.rowIndex) !== selection.rowKey) return null;
1962
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1963
+ const rawLeft = column.fixed === "left" ? leftFixedOffsets.get(selection.columnIndex) ?? 0 : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : getScrollableColumnLeft(selection.columnIndex) - scrollLeft;
1964
+ const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1965
+ const rawTop = bodyTop + selection.rowIndex * rowHeight - scrollTop;
1966
+ const rawBottom = rawTop + rowHeight * (span?.rowSpan ?? 1);
1967
+ const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
1968
+ const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
1969
+ const verticalStart = fixedHeader ? bodyTop : 0;
1970
+ const hiddenLeft = column.fixed === void 0 && rawRight <= horizontalStart;
1971
+ const hiddenRight = column.fixed === void 0 && rawLeft >= horizontalEnd;
1972
+ const hiddenTop = rawBottom <= verticalStart;
1973
+ const hiddenBottom = rawTop >= renderHeight;
1974
+ if (!hiddenLeft && !hiddenRight && !hiddenTop && !hiddenBottom) return null;
1975
+ const borderWidth = 2;
1976
+ const cornerLength = 10;
1977
+ const hitSlop = 6;
1978
+ const targetSize = borderWidth + hitSlop * 2;
1979
+ const rightBoundaryInset = rightFixedWidth > 0 && hasVerticalBorders ? 1 : 0;
1980
+ const markerX = hiddenLeft ? horizontalStart : horizontalEnd - borderWidth - rightBoundaryInset;
1981
+ const markerY = hiddenTop ? verticalStart : renderHeight - borderWidth;
1982
+ if (hiddenLeft || hiddenRight) {
1983
+ let markerTop = Math.max(rawTop - borderWidth, verticalStart);
1984
+ let markerBottom = Math.min(rawBottom + 1, renderHeight);
1985
+ const topClip = Math.max(0, verticalStart - (rawTop - borderWidth));
1986
+ const bottomClip = Math.max(0, rawBottom + 1 - renderHeight);
1987
+ if (topClip > 0 || bottomClip > 0) {
1988
+ const foldAtTop = topClip > 0;
1989
+ const fold = Math.min(cornerLength, foldAtTop ? topClip : bottomClip);
1990
+ if (fold > borderWidth) {
1991
+ const cornerWidth = fold;
1992
+ const cornerHeight = Math.max(cornerLength, markerBottom - markerTop);
1993
+ const cornerOnLeft = hiddenLeft;
1994
+ const shapeLeft = cornerOnLeft ? markerX : markerX + borderWidth - cornerWidth;
1995
+ const shapeTop = foldAtTop ? verticalStart : renderHeight - cornerHeight;
1996
+ return {
1997
+ side: `${foldAtTop ? "top" : "bottom"}-${cornerOnLeft ? "left" : "right"}`,
1998
+ orientation: "corner",
1999
+ style: {
2000
+ left: shapeLeft - hitSlop,
2001
+ top: shapeTop - hitSlop,
2002
+ width: cornerWidth + hitSlop * 2,
2003
+ height: cornerHeight + hitSlop * 2
2004
+ },
2005
+ corner: { width: cornerWidth, height: cornerHeight }
2006
+ };
2007
+ }
2008
+ }
2009
+ if (markerBottom <= markerTop) return null;
2010
+ return {
2011
+ side: hiddenLeft ? "left" : "right",
2012
+ orientation: "vertical",
2013
+ viewportEdge: hiddenLeft && horizontalStart === 0 ? "left" : hiddenRight && horizontalEnd === viewport.width ? "right" : void 0,
2014
+ style: {
2015
+ left: markerX - hitSlop,
2016
+ top: markerTop,
2017
+ width: targetSize,
2018
+ height: markerBottom - markerTop
2019
+ }
2020
+ };
2021
+ }
2022
+ const horizontalMarkerEnd = horizontalEnd - rightBoundaryInset;
2023
+ let markerLeft = Math.max(rawLeft - borderWidth, horizontalStart);
2024
+ let markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2025
+ const leftClip = Math.max(0, horizontalStart - (rawLeft - borderWidth));
2026
+ const rightClip = Math.max(0, rawRight + 1 - horizontalMarkerEnd);
2027
+ if (leftClip > 0 || rightClip > 0) {
2028
+ const foldAtLeft = leftClip > 0;
2029
+ const fold = Math.min(cornerLength, foldAtLeft ? leftClip : rightClip);
2030
+ if (fold > borderWidth) {
2031
+ const cornerWidth = Math.max(cornerLength, markerRight - markerLeft);
2032
+ const cornerHeight = fold;
2033
+ const shapeLeft = foldAtLeft ? horizontalStart : horizontalMarkerEnd - cornerWidth;
2034
+ const shapeTop = hiddenTop ? verticalStart : renderHeight - cornerHeight;
2035
+ return {
2036
+ side: `${hiddenTop ? "top" : "bottom"}-${foldAtLeft ? "left" : "right"}`,
2037
+ orientation: "corner",
2038
+ style: {
2039
+ left: shapeLeft - hitSlop,
2040
+ top: shapeTop - hitSlop,
2041
+ width: cornerWidth + hitSlop * 2,
2042
+ height: cornerHeight + hitSlop * 2
2043
+ },
2044
+ corner: { width: cornerWidth, height: cornerHeight }
2045
+ };
2046
+ }
2047
+ }
2048
+ if (markerRight <= markerLeft) return null;
2049
+ return {
2050
+ side: hiddenTop ? "top" : "bottom",
2051
+ orientation: "horizontal",
2052
+ viewportEdge: hiddenTop && verticalStart === 0 ? "top" : hiddenBottom ? "bottom" : void 0,
2053
+ style: {
2054
+ left: markerLeft,
2055
+ top: markerY - hitSlop,
2056
+ width: markerRight - markerLeft,
2057
+ height: targetSize
2058
+ }
2059
+ };
2060
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, hasVerticalBorders, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2061
+ const syncSelectionEdgeTarget = useCallback2((scrollLeft, scrollTop) => {
2062
+ const target = selectionEdgeTargetRef.current;
2063
+ if (!target) return;
2064
+ const indicator = getSelectionEdgeIndicator(scrollLeft, scrollTop);
2065
+ if (!indicator) {
2066
+ target.style.visibility = "hidden";
2067
+ return;
2068
+ }
2069
+ const orientationChanged = !target.classList.contains(`is-${indicator.orientation}`);
2070
+ target.className = `rvg-selection-edge-target is-${indicator.orientation} is-${indicator.side}${indicator.viewportEdge ? ` is-viewport-${indicator.viewportEdge}` : ""}${orientationChanged ? " is-shape-syncing" : ""}`;
2071
+ target.style.left = `${Number(indicator.style.left)}px`;
2072
+ target.style.top = `${Number(indicator.style.top)}px`;
2073
+ target.style.width = `${Number(indicator.style.width)}px`;
2074
+ target.style.height = `${Number(indicator.style.height)}px`;
2075
+ if (indicator.corner) {
2076
+ target.style.setProperty("--rvg-corner-width", `${indicator.corner.width}px`);
2077
+ target.style.setProperty("--rvg-corner-height", `${indicator.corner.height}px`);
2078
+ target.style.setProperty("--rvg-corner-hover-width-extension", "4px");
2079
+ target.style.setProperty("--rvg-corner-hover-height-extension", "4px");
2080
+ } else {
2081
+ target.style.removeProperty("--rvg-corner-width");
2082
+ target.style.removeProperty("--rvg-corner-height");
2083
+ target.style.removeProperty("--rvg-corner-hover-width-extension");
2084
+ target.style.removeProperty("--rvg-corner-hover-height-extension");
2085
+ }
2086
+ target.style.visibility = "visible";
2087
+ if (orientationChanged) {
2088
+ void target.offsetWidth;
2089
+ target.classList.remove("is-shape-syncing");
2090
+ }
2091
+ }, [getSelectionEdgeIndicator]);
1781
2092
  useLayoutEffect2(() => {
1782
2093
  const element = scrollerRef.current;
1783
2094
  if (!element) return;
@@ -1811,6 +2122,8 @@ function Table({
1811
2122
  if (sortDebounceRef.current !== null) clearTimeout(sortDebounceRef.current);
1812
2123
  if (textFrameRef.current !== null) cancelAnimationFrame(textFrameRef.current);
1813
2124
  if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
2125
+ if (verticalScrollbarVisibilityTimerRef.current !== null) clearTimeout(verticalScrollbarVisibilityTimerRef.current);
2126
+ if (horizontalScrollbarVisibilityTimerRef.current !== null) clearTimeout(horizontalScrollbarVisibilityTimerRef.current);
1814
2127
  }, []);
1815
2128
  useEffect2(() => {
1816
2129
  const scroller = scrollerRef.current;
@@ -1862,10 +2175,218 @@ function Table({
1862
2175
  scroller.addEventListener("scroll", lockScroll);
1863
2176
  return () => scroller.removeEventListener("scroll", lockScroll);
1864
2177
  }, [insertBusy]);
2178
+ const showCustomScrollbar = useCallback2((axis, persist = false) => {
2179
+ const showVertical = axis === "vertical" || axis === "both";
2180
+ const showHorizontal = axis === "horizontal" || axis === "both";
2181
+ if (showVertical) {
2182
+ verticalScrollbarRef.current?.classList.add("is-active");
2183
+ if (verticalScrollbarVisibilityTimerRef.current !== null) clearTimeout(verticalScrollbarVisibilityTimerRef.current);
2184
+ if (!persist) {
2185
+ verticalScrollbarVisibilityTimerRef.current = setTimeout(() => {
2186
+ verticalScrollbarRef.current?.classList.remove("is-active");
2187
+ verticalScrollbarVisibilityTimerRef.current = null;
2188
+ }, 900);
2189
+ }
2190
+ }
2191
+ if (showHorizontal) {
2192
+ horizontalScrollbarRef.current?.classList.add("is-active");
2193
+ if (horizontalScrollbarVisibilityTimerRef.current !== null) clearTimeout(horizontalScrollbarVisibilityTimerRef.current);
2194
+ if (!persist) {
2195
+ horizontalScrollbarVisibilityTimerRef.current = setTimeout(() => {
2196
+ horizontalScrollbarRef.current?.classList.remove("is-active");
2197
+ horizontalScrollbarVisibilityTimerRef.current = null;
2198
+ }, 900);
2199
+ }
2200
+ }
2201
+ }, []);
2202
+ const keepCustomScrollbarVisible = useCallback2((axis) => {
2203
+ showCustomScrollbar(axis, true);
2204
+ }, [showCustomScrollbar]);
2205
+ const releaseCustomScrollbarVisible = useCallback2((axis) => {
2206
+ if (axis === "vertical" && verticalScrollbarDragRef.current) return;
2207
+ if (axis === "horizontal" && horizontalScrollbarDragRef.current) return;
2208
+ showCustomScrollbar(axis);
2209
+ }, [showCustomScrollbar]);
2210
+ const updateCustomScrollbars = useCallback2(() => {
2211
+ const scroller = scrollerRef.current;
2212
+ const verticalTrack = verticalScrollbarRef.current;
2213
+ const verticalThumb = verticalScrollbarThumbRef.current;
2214
+ const horizontalTrack = horizontalScrollbarRef.current;
2215
+ const horizontalThumb = horizontalScrollbarThumbRef.current;
2216
+ if (!scroller || !verticalTrack || !verticalThumb || !horizontalTrack || !horizontalThumb) return;
2217
+ const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
2218
+ if (maxScrollTop <= 1 || renderHeight <= 0) {
2219
+ verticalTrack.style.display = "none";
2220
+ } else {
2221
+ const top = fixedHeader ? bodyTop : 0;
2222
+ const bottom = bottomSummaryHeight;
2223
+ const trackHeight = Math.max(0, effectiveHeight - top - bottom);
2224
+ if (trackHeight <= 0) {
2225
+ verticalTrack.style.display = "none";
2226
+ } else {
2227
+ const thumbHeight = Math.max(28, Math.round(trackHeight * (scroller.clientHeight / scroller.scrollHeight)));
2228
+ const maxThumbTop = Math.max(0, trackHeight - thumbHeight);
2229
+ const thumbTop = maxScrollTop > 0 ? Math.round(scroller.scrollTop / maxScrollTop * maxThumbTop) : 0;
2230
+ verticalTrack.style.display = "block";
2231
+ verticalTrack.style.top = `${top}px`;
2232
+ verticalTrack.style.bottom = `${bottom}px`;
2233
+ verticalThumb.style.height = `${Math.min(trackHeight, thumbHeight)}px`;
2234
+ verticalThumb.style.transform = `translateY(${thumbTop}px)`;
2235
+ }
2236
+ }
2237
+ const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;
2238
+ if (maxScrollLeft <= 1 || viewport.width <= 0) {
2239
+ horizontalTrack.style.display = "none";
2240
+ return;
2241
+ }
2242
+ const trackLeft = fixedWidth;
2243
+ const trackRight = rightFixedWidth;
2244
+ const trackWidth = Math.max(0, viewport.width - trackLeft - trackRight);
2245
+ if (trackWidth <= 0) {
2246
+ horizontalTrack.style.display = "none";
2247
+ return;
2248
+ }
2249
+ const thumbWidth = Math.max(36, Math.round(trackWidth * (scroller.clientWidth / scroller.scrollWidth)));
2250
+ const maxThumbLeft = Math.max(0, trackWidth - thumbWidth);
2251
+ const thumbLeft = maxScrollLeft > 0 ? Math.round(scroller.scrollLeft / maxScrollLeft * maxThumbLeft) : 0;
2252
+ horizontalTrack.style.display = "block";
2253
+ horizontalTrack.style.left = `${trackLeft}px`;
2254
+ horizontalTrack.style.right = `${trackRight}px`;
2255
+ horizontalTrack.style.bottom = `${bottomSummaryHeight + 2}px`;
2256
+ horizontalThumb.style.width = `${Math.min(trackWidth, thumbWidth)}px`;
2257
+ horizontalThumb.style.transform = `translateX(${thumbLeft}px)`;
2258
+ }, [bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, renderHeight, rightFixedWidth, viewport.width]);
2259
+ useLayoutEffect2(() => {
2260
+ updateCustomScrollbars();
2261
+ }, [contentHeight, contentWidth, updateCustomScrollbars, viewport.height, viewport.width]);
2262
+ const handleVerticalScrollbarPointerDown = useCallback2((event) => {
2263
+ const scroller = scrollerRef.current;
2264
+ const track = verticalScrollbarRef.current;
2265
+ const thumb = verticalScrollbarThumbRef.current;
2266
+ if (!scroller || !track || !thumb || event.button !== 0) return;
2267
+ event.preventDefault();
2268
+ event.currentTarget.setPointerCapture(event.pointerId);
2269
+ const trackRect = track.getBoundingClientRect();
2270
+ const thumbRect = thumb.getBoundingClientRect();
2271
+ const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
2272
+ const trackHeight = trackRect.height;
2273
+ const thumbHeight = thumbRect.height;
2274
+ showCustomScrollbar("vertical", true);
2275
+ if (event.target === thumb) {
2276
+ verticalScrollbarDragRef.current = {
2277
+ pointerId: event.pointerId,
2278
+ startY: event.clientY,
2279
+ startTop: thumbRect.top - trackRect.top,
2280
+ trackHeight,
2281
+ thumbHeight,
2282
+ maxScrollTop
2283
+ };
2284
+ return;
2285
+ }
2286
+ const nextThumbTop = Math.max(0, Math.min(trackHeight - thumbHeight, event.clientY - trackRect.top - thumbHeight / 2));
2287
+ const maxThumbTop = Math.max(1, trackHeight - thumbHeight);
2288
+ scroller.scrollTop = nextThumbTop / maxThumbTop * maxScrollTop;
2289
+ updateCustomScrollbars();
2290
+ verticalScrollbarDragRef.current = {
2291
+ pointerId: event.pointerId,
2292
+ startY: event.clientY,
2293
+ startTop: nextThumbTop,
2294
+ trackHeight,
2295
+ thumbHeight,
2296
+ maxScrollTop
2297
+ };
2298
+ }, [showCustomScrollbar, updateCustomScrollbars]);
2299
+ const handleVerticalScrollbarPointerMove = useCallback2((event) => {
2300
+ const drag = verticalScrollbarDragRef.current;
2301
+ const scroller = scrollerRef.current;
2302
+ if (!drag || !scroller || drag.pointerId !== event.pointerId) return;
2303
+ event.preventDefault();
2304
+ const maxThumbTop = Math.max(1, drag.trackHeight - drag.thumbHeight);
2305
+ const nextThumbTop = Math.max(0, Math.min(maxThumbTop, drag.startTop + event.clientY - drag.startY));
2306
+ scroller.scrollTop = nextThumbTop / maxThumbTop * drag.maxScrollTop;
2307
+ updateCustomScrollbars();
2308
+ }, [updateCustomScrollbars]);
2309
+ const handleVerticalScrollbarPointerUp = useCallback2((event) => {
2310
+ const drag = verticalScrollbarDragRef.current;
2311
+ if (!drag || drag.pointerId !== event.pointerId) return;
2312
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2313
+ verticalScrollbarDragRef.current = null;
2314
+ showCustomScrollbar("vertical");
2315
+ }, [showCustomScrollbar]);
2316
+ const handleHorizontalScrollbarPointerDown = useCallback2((event) => {
2317
+ const scroller = scrollerRef.current;
2318
+ const track = horizontalScrollbarRef.current;
2319
+ const thumb = horizontalScrollbarThumbRef.current;
2320
+ if (!scroller || !track || !thumb || event.button !== 0) return;
2321
+ event.preventDefault();
2322
+ event.currentTarget.setPointerCapture(event.pointerId);
2323
+ const trackRect = track.getBoundingClientRect();
2324
+ const thumbRect = thumb.getBoundingClientRect();
2325
+ const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;
2326
+ const trackWidth = trackRect.width;
2327
+ const thumbWidth = thumbRect.width;
2328
+ showCustomScrollbar("horizontal", true);
2329
+ if (event.target === thumb) {
2330
+ horizontalScrollbarDragRef.current = {
2331
+ pointerId: event.pointerId,
2332
+ startX: event.clientX,
2333
+ startLeft: thumbRect.left - trackRect.left,
2334
+ trackWidth,
2335
+ thumbWidth,
2336
+ maxScrollLeft
2337
+ };
2338
+ return;
2339
+ }
2340
+ const nextThumbLeft = Math.max(0, Math.min(trackWidth - thumbWidth, event.clientX - trackRect.left - thumbWidth / 2));
2341
+ const maxThumbLeft = Math.max(1, trackWidth - thumbWidth);
2342
+ const previousScrollLeft = scroller.scrollLeft;
2343
+ const nextScrollLeft = nextThumbLeft / maxThumbLeft * maxScrollLeft;
2344
+ const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
2345
+ scroller.scrollLeft = nextScrollLeft;
2346
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2347
+ syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
2348
+ if (visibilityChanged) drawImmediately();
2349
+ updateCustomScrollbars();
2350
+ horizontalScrollbarDragRef.current = {
2351
+ pointerId: event.pointerId,
2352
+ startX: event.clientX,
2353
+ startLeft: nextThumbLeft,
2354
+ trackWidth,
2355
+ thumbWidth,
2356
+ maxScrollLeft
2357
+ };
2358
+ }, [drawImmediately, getSelectionHorizontalVisibility, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars]);
2359
+ const handleHorizontalScrollbarPointerMove = useCallback2((event) => {
2360
+ const drag = horizontalScrollbarDragRef.current;
2361
+ const scroller = scrollerRef.current;
2362
+ if (!drag || !scroller || drag.pointerId !== event.pointerId) return;
2363
+ event.preventDefault();
2364
+ const maxThumbLeft = Math.max(1, drag.trackWidth - drag.thumbWidth);
2365
+ const nextThumbLeft = Math.max(0, Math.min(maxThumbLeft, drag.startLeft + event.clientX - drag.startX));
2366
+ const previousScrollLeft = scroller.scrollLeft;
2367
+ const nextScrollLeft = nextThumbLeft / maxThumbLeft * drag.maxScrollLeft;
2368
+ const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
2369
+ scroller.scrollLeft = nextScrollLeft;
2370
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2371
+ syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
2372
+ if (visibilityChanged) drawImmediately();
2373
+ updateCustomScrollbars();
2374
+ }, [drawImmediately, getSelectionHorizontalVisibility, syncSelectionEdgeTarget, updateCustomScrollbars]);
2375
+ const handleHorizontalScrollbarPointerUp = useCallback2((event) => {
2376
+ const drag = horizontalScrollbarDragRef.current;
2377
+ if (!drag || drag.pointerId !== event.pointerId) return;
2378
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2379
+ horizontalScrollbarDragRef.current = null;
2380
+ showCustomScrollbar("horizontal");
2381
+ }, [showCustomScrollbar]);
1865
2382
  const handleScroll = useCallback2(() => {
1866
2383
  const element = scrollerRef.current;
1867
2384
  if (!element) return;
2385
+ const previousScroll = scrollRef.current;
2386
+ const scrollDeltaX = element.scrollLeft - previousScroll.left;
2387
+ const scrollDeltaY = element.scrollTop - previousScroll.top;
1868
2388
  scrollRef.current = { left: element.scrollLeft, top: element.scrollTop };
2389
+ syncSelectionEdgeTarget(element.scrollLeft, element.scrollTop);
1869
2390
  const focus = selectionFocusRef.current;
1870
2391
  if (focus && selection && !selectionRange) {
1871
2392
  const row = rows[selection.rowIndex];
@@ -1875,7 +2396,7 @@ function Table({
1875
2396
  if (!validSelection) {
1876
2397
  focus.style.display = "none";
1877
2398
  } else {
1878
- const rawLeft = column.fixed === "left" ? metric.left : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : metric.left - element.scrollLeft;
2399
+ const rawLeft = column.fixed === "left" ? leftFixedOffsets.get(selection.columnIndex) ?? 0 : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : getScrollableColumnLeft(selection.columnIndex) - element.scrollLeft;
1879
2400
  const rawTop = bodyTop + selection.rowIndex * rowHeight - element.scrollTop;
1880
2401
  const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1881
2402
  const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
@@ -1887,9 +2408,9 @@ function Table({
1887
2408
  focus.style.display = "none";
1888
2409
  } else {
1889
2410
  const borderWidth = 2;
1890
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
2411
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
1891
2412
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
1892
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
2413
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
1893
2414
  const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
1894
2415
  focus.style.display = "block";
1895
2416
  focus.style.left = `${left}px`;
@@ -1908,6 +2429,10 @@ function Table({
1908
2429
  if (scrollingTextRef.current) scrollingTextRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
1909
2430
  if (scrollingHeaderIconsRef.current) scrollingHeaderIconsRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
1910
2431
  if (headerIconsRef.current) headerIconsRef.current.style.transform = `translateY(${fixedHeader ? 0 : -element.scrollTop}px)`;
2432
+ updateCustomScrollbars();
2433
+ if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {
2434
+ showCustomScrollbar(scrollDeltaX !== 0 && scrollDeltaY !== 0 ? "both" : scrollDeltaX !== 0 ? "horizontal" : "vertical");
2435
+ }
1911
2436
  if (hoveredHeaderActionRef.current) {
1912
2437
  hoveredHeaderActionRef.current = null;
1913
2438
  setHoveredHeaderAction(null);
@@ -1915,7 +2440,7 @@ function Table({
1915
2440
  setHoveredCellTooltip(null);
1916
2441
  scheduleDraw();
1917
2442
  setEditing(null);
1918
- }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
2443
+ }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars, viewport.width]);
1919
2444
  const locateColumn = useCallback2((clientX) => {
1920
2445
  const canvas = canvasRef.current;
1921
2446
  if (!canvas) return -1;
@@ -1923,7 +2448,11 @@ function Table({
1923
2448
  const localX = clientX - rect.left;
1924
2449
  let columnIndex;
1925
2450
  if (localX < fixedWidth) {
1926
- columnIndex = hitTestColumn(metrics, localX);
2451
+ columnIndex = columns.findIndex((column, index) => {
2452
+ if (column.fixed !== "left") return false;
2453
+ const left = leftFixedOffsets.get(index) ?? 0;
2454
+ return localX >= left && localX < left + metrics[index].width;
2455
+ });
1927
2456
  } else if (rightFixedWidth > 0 && localX >= viewport.width - rightFixedWidth) {
1928
2457
  columnIndex = columns.findIndex((column, index) => {
1929
2458
  if (column.fixed !== "right") return false;
@@ -1931,41 +2460,33 @@ function Table({
1931
2460
  return localX >= left && localX < left + metrics[index].width;
1932
2461
  });
1933
2462
  } else {
1934
- columnIndex = hitTestColumn(metrics, localX + scrollRef.current.left);
2463
+ const scrollX = localX + scrollRef.current.left;
2464
+ columnIndex = columns.findIndex((column, index) => {
2465
+ if (column.fixed !== void 0) return false;
2466
+ const left = getScrollableColumnLeft(index);
2467
+ return scrollX >= left && scrollX < left + metrics[index].width;
2468
+ });
1935
2469
  }
1936
2470
  return columnIndex;
1937
- }, [columns, fixedWidth, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
2471
+ }, [columns, fixedWidth, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
1938
2472
  const getDisplayedColumnLeft = useCallback2((columnIndex) => {
1939
2473
  const metric = metrics[columnIndex];
1940
- if (columns[columnIndex].fixed === "left") return metric.left;
2474
+ if (columns[columnIndex].fixed === "left") return leftFixedOffsets.get(columnIndex) ?? 0;
1941
2475
  if (columns[columnIndex].fixed === "right") return viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metric.width;
1942
- return metric.left - scrollRef.current.left;
1943
- }, [columns, metrics, rightFixedOffsets, viewport.width]);
1944
- const locateResizeHandle = useCallback2((clientX) => {
1945
- if (!columnResizable || !canvasRef.current) return -1;
1946
- const localX = clientX - canvasRef.current.getBoundingClientRect().left;
1947
- let closest = -1;
1948
- let distance = 6;
1949
- for (let index = 0; index < columns.length; index += 1) {
1950
- if (columns[index].rowSelection || columns[index].rowDragHandle || columns[index].rowNumber) continue;
1951
- const nextDistance = Math.abs(localX - (getDisplayedColumnLeft(index) + metrics[index].width));
1952
- if (nextDistance < distance) {
1953
- distance = nextDistance;
1954
- closest = index;
1955
- }
1956
- }
1957
- return closest;
1958
- }, [columnResizable, columns, getDisplayedColumnLeft, metrics]);
1959
- const findResizeNeighbor = useCallback2((columnIndex) => {
1960
- const resizedColumn = columns[columnIndex];
1961
- for (let index = columnIndex + 1; index < columns.length; index += 1) {
1962
- const column = columns[index];
1963
- if (column.rowSelection || column.rowDragHandle || column.rowNumber) continue;
1964
- if (resizedColumn?.fixed && column.fixed !== resizedColumn.fixed) return -1;
1965
- return index;
2476
+ return getScrollableColumnLeft(columnIndex) - scrollRef.current.left;
2477
+ }, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
2478
+ const getHeaderLevelAtY = useCallback2((localY, headerY) => {
2479
+ const y = localY - headerY;
2480
+ for (let level = 0; level < headerRowHeights.length; level += 1) {
2481
+ const top = headerRowOffsets[level] ?? 0;
2482
+ if (y >= top && y < top + headerRowHeights[level]) return level;
1966
2483
  }
1967
- return -1;
1968
- }, [columns]);
2484
+ return headerDepth - 1;
2485
+ }, [headerDepth, headerRowHeights, headerRowOffsets]);
2486
+ const resizeColumn = useCallback2((columnKey, width2) => {
2487
+ setResizedColumnWidths((current) => current[columnKey] === width2 ? current : { ...current, [columnKey]: width2 });
2488
+ onColumnResize?.(columnKey, width2);
2489
+ }, [onColumnResize]);
1969
2490
  const locateHeaderAction = useCallback2((clientX, columnIndex) => {
1970
2491
  if (columnIndex < 0 || !canvasRef.current) return null;
1971
2492
  const column = columns[columnIndex];
@@ -2000,11 +2521,97 @@ function Table({
2000
2521
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2001
2522
  return localX >= columnRight - 16 && localX < columnRight - 5;
2002
2523
  }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2003
- const setDragGuide = useCallback2((x) => {
2524
+ const getHeaderCellLeft = useCallback2((cell) => {
2525
+ const startMetric = metrics[cell.startIndex];
2526
+ const fixedSide = columns[cell.startIndex]?.fixed;
2527
+ if (!startMetric) return 0;
2528
+ return fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex) - scrollRef.current.left;
2529
+ }, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
2530
+ const getHeaderCellWidth = useCallback2((cell) => cell.leaf ? metrics[cell.startIndex]?.width ?? 0 : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1), [getCellDisplayWidth, metrics]);
2531
+ const getHeaderCellEdgeFromMetrics = useCallback2((startIndex, endIndex, edge, nextMetrics) => {
2532
+ const fixedSide = columns[startIndex]?.fixed;
2533
+ const width2 = nextMetrics.slice(startIndex, endIndex + 1).reduce((total, metric) => total + metric.width, 0);
2534
+ let left;
2535
+ if (fixedSide === "left") {
2536
+ left = 0;
2537
+ for (let index = 0; index < startIndex; index += 1) {
2538
+ if (columns[index].fixed === "left") left += nextMetrics[index]?.width ?? 0;
2539
+ }
2540
+ } else if (fixedSide === "right") {
2541
+ let rightOffset = 0;
2542
+ for (let index = columns.length - 1; index > startIndex; index -= 1) {
2543
+ if (columns[index].fixed === "right") rightOffset += nextMetrics[index]?.width ?? 0;
2544
+ }
2545
+ left = viewport.width - rightOffset - width2;
2546
+ } else {
2547
+ left = 0;
2548
+ for (let index = 0; index < startIndex; index += 1) {
2549
+ if (columns[index].fixed === void 0) left += nextMetrics[index]?.width ?? 0;
2550
+ }
2551
+ left += fixedWidth - scrollRef.current.left;
2552
+ }
2553
+ return left + (edge === "right" ? width2 : 0);
2554
+ }, [columns, fixedWidth, viewport.width]);
2555
+ const locateHeaderResizeHit = useCallback2((clientX, clientY) => {
2556
+ if (!columnResizable || !canvasRef.current) return null;
2557
+ const rect = canvasRef.current.getBoundingClientRect();
2558
+ const localX = clientX - rect.left;
2559
+ const localY = clientY - rect.top;
2560
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2561
+ if (localY < headerY || localY >= headerY + headerHeight) return null;
2562
+ const level = getHeaderLevelAtY(localY, headerY);
2563
+ let closest = null;
2564
+ let distance = 6;
2565
+ let priority = -1;
2566
+ for (const cell of headerCells) {
2567
+ if (cell.level !== level) continue;
2568
+ if (columns[cell.startIndex]?.rowSelection || columns[cell.startIndex]?.rowDragHandle || columns[cell.startIndex]?.rowNumber) continue;
2569
+ const fixedSide = columns[cell.startIndex]?.fixed;
2570
+ const edge = fixedSide === "right" && cell.leaf ? "left" : "right";
2571
+ const edgeX = getHeaderCellLeft(cell) + (edge === "right" ? getHeaderCellWidth(cell) : 0);
2572
+ const nextDistance = Math.abs(localX - edgeX);
2573
+ const nextPriority = fixedSide === "right" ? 2 : fixedSide === "left" ? 1 : 0;
2574
+ if (nextDistance < distance || nextDistance === distance && nextPriority > priority) {
2575
+ distance = nextDistance;
2576
+ priority = nextPriority;
2577
+ closest = { cell, edge };
2578
+ }
2579
+ }
2580
+ return closest;
2581
+ }, [columnResizable, columns, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, getHeaderLevelAtY, headerCells, headerHeight]);
2582
+ const locateHeaderResizeCell = useCallback2((clientX, clientY) => locateHeaderResizeHit(clientX, clientY)?.cell ?? null, [locateHeaderResizeHit]);
2583
+ const locateHeaderCellDragHandle = useCallback2((clientX, cell) => {
2584
+ if (!columnDraggable || !canvasRef.current) return false;
2585
+ if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
2586
+ const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2587
+ const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
2588
+ return localX >= right - 16 && localX < right - 5;
2589
+ }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
2590
+ const locateHeaderCell = useCallback2((clientX, clientY) => {
2591
+ const canvas = canvasRef.current;
2592
+ if (!canvas) return null;
2593
+ const rect = canvas.getBoundingClientRect();
2594
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2595
+ const localX = clientX - rect.left;
2596
+ const localY = clientY - rect.top;
2597
+ if (localY < headerY || localY >= headerY + headerHeight) return null;
2598
+ const level = getHeaderLevelAtY(localY, headerY);
2599
+ for (const cell of headerCells) {
2600
+ if (cell.level !== level) continue;
2601
+ const left = getHeaderCellLeft(cell);
2602
+ const width2 = getHeaderCellWidth(cell);
2603
+ if (localX >= left && localX < left + width2) return cell;
2604
+ }
2605
+ return null;
2606
+ }, [fixedHeader, getHeaderCellLeft, getHeaderCellWidth, getHeaderLevelAtY, headerCells, headerHeight]);
2607
+ const setDragGuide = useCallback2((x, top = 0) => {
2004
2608
  const guide = dragGuideRef.current;
2005
2609
  if (!guide) return;
2006
2610
  guide.style.display = x === null ? "none" : "block";
2007
- if (x !== null) guide.style.transform = `translateX(${Math.round(x)}px)`;
2611
+ if (x !== null) {
2612
+ guide.style.top = `${top}px`;
2613
+ guide.style.transform = `translateX(${Math.round(x)}px)`;
2614
+ }
2008
2615
  }, []);
2009
2616
  const setRowDragGuide = useCallback2((y) => {
2010
2617
  const guide = rowDragGuideRef.current;
@@ -2022,6 +2629,15 @@ function Table({
2022
2629
  useEffect2(() => {
2023
2630
  const canvas = canvasRef.current;
2024
2631
  if (!canvas || !columnDraggable && !rowDraggable) return;
2632
+ const getColumnDropIndex = (sourceIndex, targetIndex, edge) => {
2633
+ if (edge === "right") return targetIndex;
2634
+ const destinationIndex = targetIndex - (sourceIndex < targetIndex ? 1 : 0);
2635
+ return Math.max(0, Math.min(columns.length - 1, destinationIndex));
2636
+ };
2637
+ const getColumnDropPlacement = (sourceIndex, targetIndex, edge) => {
2638
+ if (edge !== "right") return "before";
2639
+ return targetIndex < sourceIndex ? "before" : "after";
2640
+ };
2025
2641
  const getHeaderColumn = (clientX, clientY) => {
2026
2642
  const rect = canvas.getBoundingClientRect();
2027
2643
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
@@ -2029,22 +2645,35 @@ function Table({
2029
2645
  if (localY < headerY || localY >= headerY + headerHeight) return -1;
2030
2646
  return locateColumn(clientX);
2031
2647
  };
2648
+ const getHeaderDragCell = (clientX, clientY) => {
2649
+ const cell = locateHeaderCell(clientX, clientY);
2650
+ if (!cell) return null;
2651
+ return locateHeaderCellDragHandle(clientX, cell) && !locateHeaderResizeCell(clientX, clientY) ? cell : null;
2652
+ };
2032
2653
  const cleanupDrag = draggable({
2033
2654
  element: canvas,
2034
2655
  canDrag: ({ input }) => {
2035
- const index = getHeaderColumn(input.clientX, input.clientY);
2036
- if (columnDraggable && index >= 0) return locateHeaderDragHandle(input.clientX, index) && locateResizeHandle(input.clientX) < 0;
2656
+ const headerDragCell = columnDraggable ? getHeaderDragCell(input.clientX, input.clientY) : null;
2657
+ if (headerDragCell) return true;
2037
2658
  const columnIndex = locateColumn(input.clientX);
2038
2659
  return rowDraggable && columnIndex >= 0 && Boolean(columns[columnIndex].rowDragHandle) && locateRow(input.clientY) >= 0;
2039
2660
  },
2040
2661
  getInitialData: ({ input }) => {
2041
- const headerColumn = getHeaderColumn(input.clientX, input.clientY);
2042
- if (headerColumn < 0) {
2662
+ const headerDragCell = columnDraggable ? getHeaderDragCell(input.clientX, input.clientY) : null;
2663
+ if (!headerDragCell) {
2043
2664
  return { type: "table-row", sourceIndex: locateRow(input.clientY) };
2044
2665
  }
2045
- const sourceIndex = headerColumn;
2666
+ const sourceIndex = headerDragCell.startIndex;
2046
2667
  columnDragPreviewRef.current = { sourceIndex, targetIndex: sourceIndex };
2047
- return { type: "table-column", sourceIndex };
2668
+ return {
2669
+ type: "table-column",
2670
+ sourceIndex,
2671
+ sourceKey: headerDragCell.key,
2672
+ parentKey: headerDragCell.parentKey,
2673
+ headerLevel: headerDragCell.level,
2674
+ headerKind: headerDragCell.leaf ? "column" : "group",
2675
+ fixed: columns[headerDragCell.startIndex]?.fixed
2676
+ };
2048
2677
  },
2049
2678
  onGenerateDragPreview: ({ nativeSetDragImage, source }) => {
2050
2679
  const sourceIndex = Number(source.data.sourceIndex);
@@ -2147,31 +2776,35 @@ function Table({
2147
2776
  return;
2148
2777
  }
2149
2778
  const targetIndex = Number(target?.targetIndex);
2150
- if (!Number.isInteger(targetIndex) || columns[targetIndex]?.fixed !== columns[sourceIndex]?.fixed) {
2779
+ if (!Number.isInteger(targetIndex) || target?.headerKind !== source.data.headerKind || target?.parentKey !== source.data.parentKey || target?.fixed !== source.data.fixed) {
2151
2780
  setDragGuide(null);
2152
2781
  return;
2153
2782
  }
2154
2783
  const edge = target?.columnEdge;
2155
- let destinationIndex = targetIndex + (edge === "right" ? 1 : 0);
2156
- if (sourceIndex < destinationIndex) destinationIndex -= 1;
2157
- destinationIndex = Math.max(0, Math.min(columns.length - 1, destinationIndex));
2784
+ const destinationIndex = getColumnDropIndex(sourceIndex, targetIndex, edge);
2158
2785
  columnDragPreviewRef.current = { sourceIndex, targetIndex: destinationIndex };
2159
- const targetLeft = getDisplayedColumnLeft(targetIndex);
2160
- setDragGuide(edge === "right" ? targetLeft + metrics[targetIndex].width : targetLeft);
2786
+ const targetLeft = Number(target?.targetLeft);
2787
+ const targetWidth = Number(target?.targetWidth);
2788
+ if (!Number.isFinite(targetLeft) || !Number.isFinite(targetWidth)) {
2789
+ setDragGuide(null);
2790
+ return;
2791
+ }
2792
+ const guideTop = headerRowOffsets[Number(source.data.headerLevel)] ?? 0;
2793
+ setDragGuide(edge === "right" ? targetLeft + targetWidth : targetLeft, guideTop);
2161
2794
  },
2162
2795
  onDrop: ({ source, location }) => {
2163
2796
  const target = location.current.dropTargets[0]?.data;
2164
2797
  const sourceIndex = Number(source.data.sourceIndex);
2165
2798
  if (source.data.type === "table-row") {
2166
- const rawTargetIndex = Number(target?.targetIndex);
2167
- let targetIndex2 = rawTargetIndex + (target?.rowEdge === "bottom" ? 1 : 0);
2799
+ const rawTargetIndex2 = Number(target?.targetIndex);
2800
+ let targetIndex2 = rawTargetIndex2 + (target?.rowEdge === "bottom" ? 1 : 0);
2168
2801
  if (sourceIndex < targetIndex2) targetIndex2 -= 1;
2169
2802
  targetIndex2 = Math.max(0, Math.min(rows.length - 1, targetIndex2));
2170
2803
  scrollerRef.current?.classList.add("rvg-row-drop-settling");
2171
2804
  setRowDragPreview(null);
2172
2805
  setRowDragGuide(null);
2173
2806
  canvas.style.cursor = "default";
2174
- if (Number.isInteger(rawTargetIndex) && sourceIndex !== targetIndex2) onRowOrderChange?.(sourceIndex, targetIndex2);
2807
+ if (Number.isInteger(rawTargetIndex2) && sourceIndex !== targetIndex2) onRowOrderChange?.(sourceIndex, targetIndex2);
2175
2808
  requestAnimationFrame(() => {
2176
2809
  requestAnimationFrame(() => scrollerRef.current?.classList.remove("rvg-row-drop-settling"));
2177
2810
  });
@@ -2180,12 +2813,23 @@ function Table({
2180
2813
  }, 0);
2181
2814
  return;
2182
2815
  }
2183
- const targetIndex = columnDragPreviewRef.current?.targetIndex ?? Number(target?.targetIndex);
2816
+ const rawTargetIndex = Number(target?.targetIndex);
2817
+ const targetIndex = columnDragPreviewRef.current?.targetIndex ?? (Number.isInteger(rawTargetIndex) ? getColumnDropIndex(sourceIndex, rawTargetIndex, target?.columnEdge) : rawTargetIndex);
2184
2818
  canvas.style.cursor = "default";
2185
2819
  setDragGuide(null);
2186
2820
  columnDragPreviewRef.current = null;
2187
- if (Number.isInteger(targetIndex) && sourceIndex !== targetIndex && columns[targetIndex]?.fixed === columns[sourceIndex]?.fixed) {
2188
- onColumnOrderChange?.(sourceIndex - utilityColumnCount, targetIndex - utilityColumnCount);
2821
+ if (Number.isInteger(rawTargetIndex) && Number.isInteger(targetIndex) && sourceIndex !== targetIndex && target?.headerKind === source.data.headerKind && target?.parentKey === source.data.parentKey && target?.fixed === source.data.fixed) {
2822
+ onColumnOrderChange?.(
2823
+ sourceIndex - utilityColumnCount,
2824
+ targetIndex - utilityColumnCount,
2825
+ {
2826
+ type: source.data.headerKind === "group" ? "group" : "column",
2827
+ sourceKey: String(source.data.sourceKey),
2828
+ targetKey: String(target.targetKey),
2829
+ parentKey: typeof source.data.parentKey === "string" ? source.data.parentKey : void 0,
2830
+ placement: getColumnDropPlacement(sourceIndex, rawTargetIndex, target.columnEdge)
2831
+ }
2832
+ );
2189
2833
  }
2190
2834
  window.setTimeout(() => {
2191
2835
  suppressClickRef.current = false;
@@ -2203,24 +2847,38 @@ function Table({
2203
2847
  const rowEdge = input.clientY - canvas.getBoundingClientRect().top < rowTop + rowHeight / 2 ? "top" : "bottom";
2204
2848
  return attachClosestEdge({ targetIndex: targetIndex2, rowEdge }, { element, input, allowedEdges: ["top", "bottom"] });
2205
2849
  }
2206
- let targetIndex = getHeaderColumn(input.clientX, input.clientY);
2207
- if (targetIndex < 0) return { targetIndex: -1 };
2850
+ const targetCell = locateHeaderCell(input.clientX, input.clientY);
2851
+ if (!targetCell) return { targetIndex: -1 };
2852
+ let targetIndex = targetCell.startIndex;
2853
+ const targetFixed = columns[targetCell.startIndex]?.fixed;
2854
+ const headerKind = targetCell.leaf ? "column" : "group";
2855
+ if (source.data.headerKind !== headerKind || source.data.parentKey !== targetCell.parentKey || source.data.fixed !== targetFixed) return { targetIndex: -1 };
2208
2856
  if (columns[targetIndex].rowSelection || columns[targetIndex].rowDragHandle || columns[targetIndex].rowNumber) {
2209
2857
  const fixedSide = columns[Number(source.data.sourceIndex)]?.fixed;
2210
2858
  targetIndex = columns.findIndex((column, index) => index > targetIndex && column.fixed === fixedSide && !column.rowSelection && !column.rowDragHandle && !column.rowNumber);
2211
2859
  if (targetIndex < 0) return { targetIndex: -1 };
2212
2860
  return { targetIndex, columnEdge: "left" };
2213
2861
  }
2214
- const left = getDisplayedColumnLeft(targetIndex);
2215
- const columnEdge = input.clientX - canvas.getBoundingClientRect().left < left + metrics[targetIndex].width / 2 ? "left" : "right";
2216
- return attachClosestEdge({ targetIndex, columnEdge }, { element, input, allowedEdges: ["left", "right"] });
2862
+ const left = getHeaderCellLeft(targetCell);
2863
+ const targetWidth = targetCell.leaf ? metrics[targetIndex].width : getHeaderCellWidth(targetCell);
2864
+ const columnEdge = input.clientX - canvas.getBoundingClientRect().left < left + targetWidth / 2 ? "left" : "right";
2865
+ return attachClosestEdge({
2866
+ targetIndex,
2867
+ targetKey: targetCell.key,
2868
+ parentKey: targetCell.parentKey,
2869
+ headerKind,
2870
+ fixed: targetFixed,
2871
+ targetLeft: left,
2872
+ targetWidth,
2873
+ columnEdge
2874
+ }, { element, input, allowedEdges: ["left", "right"] });
2217
2875
  }
2218
2876
  });
2219
2877
  return () => {
2220
2878
  cleanupDrag();
2221
2879
  cleanupDrop();
2222
2880
  };
2223
- }, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getRowKey, headerHeight, locateColumn, locateHeaderDragHandle, locateResizeHandle, locateRow, metrics, onColumnOrderChange, onRowOrderChange, rowDraggable, rowHeight, rows, selectedRowKeySet, setDragGuide, setRowDragGuide, utilityColumnCount]);
2881
+ }, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerHeight, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, metrics, onColumnOrderChange, onRowOrderChange, rowDraggable, rowHeight, rows, selectedRowKeySet, setDragGuide, setRowDragGuide, utilityColumnCount]);
2224
2882
  const locateCell = useCallback2((clientX, clientY) => {
2225
2883
  const canvas = canvasRef.current;
2226
2884
  if (!canvas) return null;
@@ -2327,8 +2985,9 @@ function Table({
2327
2985
  const horizontalFocusGap = 0;
2328
2986
  const verticalFocusGap = 0;
2329
2987
  if (column.fixed === void 0) {
2330
- if (metric.left - nextLeft < fixedWidth) nextLeft = Math.max(0, metric.left - fixedWidth - horizontalFocusGap);
2331
- if (metric.left + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = metric.left + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2988
+ const cellLeft = getScrollableColumnLeft(cell.columnIndex);
2989
+ if (cellLeft - nextLeft < fixedWidth) nextLeft = Math.max(0, cellLeft - fixedWidth - horizontalFocusGap);
2990
+ if (cellLeft + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2332
2991
  }
2333
2992
  const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
2334
2993
  const rowBottom = rowTop + rowHeight * rowSpan;
@@ -2341,7 +3000,7 @@ function Table({
2341
3000
  scroller.scrollTop = nextTop;
2342
3001
  scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2343
3002
  scheduleDraw();
2344
- }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
3003
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
2345
3004
  const beginEdit = useCallback2((cell) => {
2346
3005
  const column = columns[cell.columnIndex];
2347
3006
  if (!column?.editable || column.dataIndex === void 0) return;
@@ -2362,8 +3021,9 @@ function Table({
2362
3021
  const horizontalFocusGap = 0;
2363
3022
  const verticalFocusGap = 0;
2364
3023
  if (column.fixed === void 0) {
2365
- if (metric.left - nextLeft < fixedWidth) nextLeft = Math.max(0, metric.left - fixedWidth - horizontalFocusGap);
2366
- if (metric.left + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = metric.left + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
3024
+ const cellLeft = getScrollableColumnLeft(cell.columnIndex);
3025
+ if (cellLeft - nextLeft < fixedWidth) nextLeft = Math.max(0, cellLeft - fixedWidth - horizontalFocusGap);
3026
+ if (cellLeft + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2367
3027
  }
2368
3028
  const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
2369
3029
  const rowBottom = rowTop + rowHeight * rowSpan;
@@ -2385,7 +3045,7 @@ function Table({
2385
3045
  return;
2386
3046
  }
2387
3047
  setEditing(cell);
2388
- }, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
3048
+ }, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
2389
3049
  const getCellEditKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
2390
3050
  const markCellEdited = (cell, previousValue, nextValue) => {
2391
3051
  const key = getCellEditKey(cell);
@@ -2516,75 +3176,113 @@ function Table({
2516
3176
  const visibleRowHeight = fixedHeader ? renderHeight - bodyTop : renderHeight;
2517
3177
  if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
2518
3178
  if (rowBottom > scroller.scrollTop + visibleRowHeight) scroller.scrollTop = rowBottom - visibleRowHeight;
2519
- if (columns[columnIndex].fixed === void 0 && metric.left < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, metric.left - fixedWidth);
2520
- if (columns[columnIndex].fixed === void 0 && metric.left + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = metric.left + cellWidth - viewport.width + rightFixedWidth;
3179
+ if (columns[columnIndex].fixed === void 0) {
3180
+ const cellLeft = getScrollableColumnLeft(columnIndex);
3181
+ if (cellLeft < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, cellLeft - fixedWidth);
3182
+ if (cellLeft + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth;
3183
+ }
2521
3184
  }
2522
- }, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
3185
+ }, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2523
3186
  const editorStyle = useMemo(() => {
2524
3187
  if (!editing) return void 0;
2525
3188
  const metric = metrics[editing.columnIndex];
2526
- const fixedSide = columns[editing.columnIndex].fixed;
3189
+ const column = columns[editing.columnIndex];
3190
+ if (!metric || !column) return void 0;
3191
+ const fixedSide = column.fixed;
2527
3192
  const span = getCellSpan(editing.rowIndex, editing.columnIndex);
2528
3193
  const cellWidth = getCellDisplayWidth(editing.columnIndex, span?.colSpan ?? 1);
2529
3194
  const cellHeight = rowHeight * (span?.rowSpan ?? 1);
2530
- const cellLeft = fixedSide === "left" ? metric.left : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(editing.columnIndex) ?? 0) - metric.width : metric.left - scrollRef.current.left;
3195
+ const cellLeft = fixedSide === "left" ? leftFixedOffsets.get(editing.columnIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(editing.columnIndex) ?? 0) - metric.width : getScrollableColumnLeft(editing.columnIndex) - scrollRef.current.left;
3196
+ const cellTop = bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top;
3197
+ const horizontalStart = fixedSide === void 0 ? fixedWidth : 0;
3198
+ const horizontalEnd = fixedSide === void 0 ? viewport.width - rightFixedWidth : viewport.width;
3199
+ const verticalStart = fixedHeader ? bodyTop : 0;
3200
+ const visibleLeft = Math.max(cellLeft, horizontalStart);
3201
+ const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
3202
+ const visibleTop = Math.max(cellTop, verticalStart);
3203
+ const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
2531
3204
  return {
2532
- left: (fixedSide ? cellLeft : Math.max(cellLeft, fixedWidth)) + 1,
2533
- top: Math.max(fixedHeader ? bodyTop : 0, bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top) + 1,
2534
- width: cellWidth - 2,
2535
- height: cellHeight - 2
3205
+ left: visibleLeft,
3206
+ top: visibleTop,
3207
+ width: Math.max(0, visibleRight - visibleLeft),
3208
+ height: Math.max(0, visibleBottom - visibleTop)
2536
3209
  };
2537
- }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedOffsets, rowHeight, viewport.width]);
3210
+ }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
2538
3211
  const handleColumnPointerDown = useCallback2((event) => {
2539
3212
  const rect = event.currentTarget.getBoundingClientRect();
2540
3213
  const localY = event.clientY - rect.top;
2541
3214
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2542
3215
  if (localY < headerY || localY >= headerY + headerHeight) return;
2543
- const resizeIndex = locateResizeHandle(event.clientX);
2544
- if (resizeIndex >= 0) {
3216
+ const resizeHit = locateHeaderResizeHit(event.clientX, event.clientY);
3217
+ if (resizeHit) {
3218
+ const { cell: resizeCell, edge: resizeEdge } = resizeHit;
2545
3219
  event.preventDefault();
2546
3220
  event.currentTarget.setPointerCapture(event.pointerId);
2547
- const neighborIndex = findResizeNeighbor(resizeIndex);
2548
- const startGuideX = getDisplayedColumnLeft(resizeIndex) + metrics[resizeIndex].width;
3221
+ const resizeIndex = resizeCell.endIndex;
3222
+ const resizeIndices = resizeCell.leaf ? [resizeIndex] : Array.from({ length: resizeCell.endIndex - resizeCell.startIndex + 1 }, (_, index) => resizeCell.startIndex + index);
3223
+ const startWidths = resizeIndices.map((index) => metrics[index].width);
3224
+ const startGuideX = getHeaderCellLeft(resizeCell) + (resizeEdge === "right" ? getHeaderCellWidth(resizeCell) : 0);
2549
3225
  columnDragRef.current = {
2550
3226
  type: "resize",
2551
3227
  columnIndex: resizeIndex,
2552
3228
  startX: event.clientX,
2553
- startWidth: metrics[resizeIndex].width,
2554
- startGuideX,
2555
- neighborIndex,
2556
- startNeighborWidth: neighborIndex >= 0 ? metrics[neighborIndex].width : 0
3229
+ startWidth: startWidths.reduce((total, width2) => total + width2, 0),
3230
+ guideTop: headerRowOffsets[resizeCell.level] ?? 0,
3231
+ resizeEdge,
3232
+ resizeStartIndex: resizeCell.startIndex,
3233
+ resizeEndIndex: resizeCell.endIndex,
3234
+ resizeIndices,
3235
+ startWidths
2557
3236
  };
2558
3237
  suppressClickRef.current = true;
3238
+ setResizeGuideTop(headerRowOffsets[resizeCell.level] ?? 0);
2559
3239
  setResizeGuideX(startGuideX);
2560
3240
  return;
2561
3241
  }
2562
- }, [findResizeNeighbor, fixedHeader, getDisplayedColumnLeft, headerHeight, locateResizeHandle, metrics]);
3242
+ }, [fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerHeight, headerRowOffsets, locateHeaderResizeHit, metrics]);
2563
3243
  const handleColumnPointerMove = useCallback2((event) => {
2564
3244
  const drag = columnDragRef.current;
2565
3245
  if (!drag) return;
2566
3246
  const column = columns[drag.columnIndex];
2567
3247
  const minWidth = getMinimumColumnWidth(column, columnDraggable);
2568
- let delta = Math.round(event.clientX - drag.startX);
2569
- if (drag.neighborIndex >= 0) {
2570
- const neighbor = columns[drag.neighborIndex];
2571
- const minNeighborWidth = getMinimumColumnWidth(neighbor, columnDraggable);
2572
- delta = Math.max(minWidth - drag.startWidth, Math.min(drag.startNeighborWidth - minNeighborWidth, delta));
2573
- onColumnResize?.(column.key, drag.startWidth + delta);
2574
- onColumnResize?.(neighbor.key, drag.startNeighborWidth - delta);
2575
- setResizeGuideX(drag.startGuideX + delta);
3248
+ const pointerDelta = Math.round(event.clientX - drag.startX);
3249
+ const delta = drag.resizeEdge === "left" ? -pointerDelta : pointerDelta;
3250
+ const setGuideFromWidths = (nextWidths) => {
3251
+ const widthByIndex = new Map(drag.resizeIndices.map((columnIndex, index) => [columnIndex, nextWidths[index]]));
3252
+ const nextColumns = columns.map((column2, index) => widthByIndex.has(index) ? { ...column2, width: widthByIndex.get(index) } : column2);
3253
+ const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth: viewport.width });
3254
+ setResizeGuideX(getHeaderCellEdgeFromMetrics(drag.resizeStartIndex, drag.resizeEndIndex, drag.resizeEdge, nextMetrics));
3255
+ };
3256
+ if (drag.resizeIndices.length > 1) {
3257
+ const minWidths = drag.resizeIndices.map((index) => getMinimumColumnWidth(columns[index], columnDraggable));
3258
+ const minTotalWidth = minWidths.reduce((total, width3) => total + width3, 0);
3259
+ const targetTotalWidth = Math.max(minTotalWidth, drag.startWidth + delta);
3260
+ const ratio = drag.startWidth > 0 ? targetTotalWidth / drag.startWidth : 1;
3261
+ const nextWidths = drag.startWidths.map((width3, index) => Math.max(minWidths[index], Math.round(width3 * ratio)));
3262
+ let remainder = targetTotalWidth - nextWidths.reduce((total, width3) => total + width3, 0);
3263
+ for (let index = nextWidths.length - 1; index >= 0 && remainder !== 0; index -= 1) {
3264
+ const next = nextWidths[index] + (remainder > 0 ? 1 : -1);
3265
+ if (next < minWidths[index]) continue;
3266
+ nextWidths[index] = next;
3267
+ remainder += remainder > 0 ? -1 : 1;
3268
+ }
3269
+ drag.resizeIndices.forEach((columnIndex, index) => {
3270
+ resizeColumn(columns[columnIndex].key, nextWidths[index]);
3271
+ });
3272
+ setGuideFromWidths(nextWidths);
2576
3273
  return;
2577
3274
  }
2578
3275
  const width2 = Math.max(minWidth, Math.round(drag.startWidth + delta));
2579
- onColumnResize?.(column.key, width2);
2580
- setResizeGuideX(drag.startGuideX + width2 - drag.startWidth);
2581
- }, [columnDraggable, columns, onColumnResize]);
3276
+ resizeColumn(column.key, width2);
3277
+ setGuideFromWidths([width2]);
3278
+ }, [columnDraggable, columns, getHeaderCellEdgeFromMetrics, resizeColumn, viewport.width]);
2582
3279
  const handleColumnPointerUp = useCallback2((event) => {
2583
3280
  const drag = columnDragRef.current;
2584
3281
  if (!drag) return;
2585
3282
  if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2586
3283
  columnDragRef.current = null;
2587
3284
  setResizeGuideX(null);
3285
+ setResizeGuideTop(0);
2588
3286
  }, []);
2589
3287
  const queueSortStateChange = useCallback2((next) => {
2590
3288
  setDisplaySortState(next);
@@ -2597,7 +3295,7 @@ function Table({
2597
3295
  const renderHeaderIcons = (columnIndex, left) => {
2598
3296
  const column = columns[columnIndex];
2599
3297
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2600
- const top = (headerHeight - 14) / 2;
3298
+ const top = headerLeafTop + (headerLeafHeight - 14) / 2;
2601
3299
  const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2602
3300
  let right = left + metrics[columnIndex].width - (visibleActions.drag ? 18 : 4);
2603
3301
  const icons = [];
@@ -2623,44 +3321,87 @@ function Table({
2623
3321
  }
2624
3322
  return icons;
2625
3323
  };
2626
- const headerTooltip = (() => {
2627
- if (!visibleHeaderTooltip) return null;
2628
- const { columnIndex, action } = visibleHeaderTooltip;
2629
- const column = columns[columnIndex];
3324
+ const createHeaderTooltip = useCallback2((action, columnIndex, cell) => {
3325
+ const column = cell?.column ?? columns[columnIndex];
2630
3326
  if (!column) return null;
2631
- const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2632
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2633
- let anchorX = columnRight - (visibleActions.drag ? 18 : 4);
2634
- let anchorLeft = anchorX;
2635
- let label;
3327
+ const cellLeft = cell ? getHeaderCellLeft(cell) : getDisplayedColumnLeft(columnIndex);
3328
+ const cellWidth = cell ? getHeaderCellWidth(cell) : metrics[columnIndex]?.width ?? 0;
3329
+ const level = cell?.level ?? headerDepth - 1;
3330
+ const rowTop = (fixedHeader ? 0 : -scrollPosition.top) + (headerRowOffsets[level] ?? 0);
3331
+ const rowHeight2 = headerRowHeights[level] ?? baseHeaderHeight;
3332
+ let anchorX = cellLeft + cellWidth / 2;
3333
+ let anchorLeft = cellLeft;
3334
+ let label = column.title;
2636
3335
  if (action === "title") {
2637
- const titleWidth = measureHeaderTitleWidth(columnIndex);
2638
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18;
2639
- const contentWidth2 = Math.max(0, metrics[columnIndex].width - actionWidth);
2640
- const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
2641
- const columnLeft = getDisplayedColumnLeft(columnIndex);
2642
- anchorX = column.align === "right" ? columnLeft + contentWidth2 - 10 : column.align === "center" ? columnLeft + metrics[columnIndex].width / 2 + textWidth / 2 : columnLeft + 10 + textWidth;
2643
- anchorLeft = column.align === "right" ? anchorX - textWidth : column.align === "center" ? columnLeft + metrics[columnIndex].width / 2 - textWidth / 2 : columnLeft + 10;
2644
- label = column.title;
3336
+ const canvas = canvasRef.current;
3337
+ const context = canvas?.getContext("2d");
3338
+ if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3339
+ const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3340
+ const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3341
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable ? 18 : 0;
3342
+ const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3343
+ const maxTextWidth = Math.max(0, contentWidth2 - 20);
3344
+ const textWidth = Math.min(titleWidth, maxTextWidth);
3345
+ if (column.align === "right") {
3346
+ anchorX = cellLeft + contentWidth2 - 10;
3347
+ anchorLeft = anchorX - textWidth;
3348
+ } else if (column.align === "center") {
3349
+ anchorX = cellLeft + contentWidth2 / 2 + textWidth / 2;
3350
+ anchorLeft = cellLeft + contentWidth2 / 2 - textWidth / 2;
3351
+ } else {
3352
+ anchorLeft = cellLeft + 10;
3353
+ anchorX = anchorLeft + textWidth;
3354
+ }
2645
3355
  } else if (action === "drag") {
2646
- anchorX = columnRight;
3356
+ anchorX = cellLeft + cellWidth;
2647
3357
  anchorLeft = anchorX;
2648
3358
  label = labels.dragColumn;
2649
3359
  } else if (action === "sort") {
2650
3360
  const direction = displaySortState?.columnKey === column.key ? displaySortState.direction : null;
2651
3361
  label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
2652
- } else {
2653
- if (visibleActions.sort) anchorX -= 18;
3362
+ anchorX = cellLeft + cellWidth - 22;
3363
+ anchorLeft = anchorX;
3364
+ } else if (action === "filter") {
2654
3365
  const value = filterValues[column.key];
2655
3366
  label = value ? labels.filterWithValue(value) : labels.filter;
3367
+ anchorX = cellLeft + cellWidth - 40;
3368
+ anchorLeft = anchorX;
2656
3369
  }
2657
3370
  const placement = anchorX + 328 > viewport.width ? "left" : "right";
2658
3371
  return {
3372
+ key: `${action}:${cell ? `cell:${cell.key}:${cell.level}` : `column:${column.key}`}:${Math.round(anchorX)}:${Math.round(rowTop)}`,
3373
+ columnIndex,
3374
+ action,
2659
3375
  left: placement === "right" ? anchorX + 8 : Math.max(8, anchorLeft - 8),
2660
- top: (fixedHeader ? 0 : -scrollPosition.top) + headerHeight / 2,
3376
+ top: rowTop + rowHeight2 / 2,
2661
3377
  label,
2662
3378
  placement
2663
3379
  };
3380
+ }, [baseHeaderHeight, columnDraggable, columns, displaySortState, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, viewport.width]);
3381
+ const isPointerOnHeaderTitle = useCallback2((clientX, clientY, cell) => {
3382
+ const canvas = canvasRef.current;
3383
+ if (!canvas) return false;
3384
+ const rect = canvas.getBoundingClientRect();
3385
+ const localX = clientX - rect.left;
3386
+ const localY = clientY - rect.top;
3387
+ const column = cell.column;
3388
+ const cellLeft = getHeaderCellLeft(cell);
3389
+ const cellWidth = getHeaderCellWidth(cell);
3390
+ const rowTop = (fixedHeader ? 0 : -scrollRef.current.top) + (headerRowOffsets[cell.level] ?? 0);
3391
+ const context = canvas.getContext("2d");
3392
+ if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3393
+ const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3394
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3395
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable ? 18 : 0;
3396
+ const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3397
+ const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
3398
+ const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
3399
+ const textTop = rowTop + 8;
3400
+ return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3401
+ }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
3402
+ const headerTooltip = (() => {
3403
+ if (!visibleHeaderTooltip) return null;
3404
+ return visibleHeaderTooltip;
2664
3405
  })();
2665
3406
  const getRangeBounds = () => selectionRange ? {
2666
3407
  rowStart: Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex),
@@ -2719,7 +3460,7 @@ function Table({
2719
3460
  }, 1800);
2720
3461
  };
2721
3462
  const showHeaderTooltipAfterDelay = (next) => {
2722
- const nextKey = next ? `${next.columnIndex}:${next.action}` : "";
3463
+ const nextKey = next ? next.key : "";
2723
3464
  if (nextKey && pendingHeaderTooltipKeyRef.current === nextKey) return;
2724
3465
  pendingHeaderTooltipKeyRef.current = nextKey;
2725
3466
  if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
@@ -2967,12 +3708,14 @@ function Table({
2967
3708
  const renderHeaderTitle = (columnIndex, left) => {
2968
3709
  const column = columns[columnIndex];
2969
3710
  const currentScrollTop = scrollRef.current.top;
3711
+ const headerTop = fixedHeader ? 0 : -currentScrollTop;
3712
+ const leafTop = headerTop + headerLeafTop;
2970
3713
  if (column.rowNumber) {
2971
3714
  return /* @__PURE__ */ jsx7(
2972
3715
  "div",
2973
3716
  {
2974
3717
  className: "rvg-header-title",
2975
- style: { left, top: fixedHeader ? 0 : -currentScrollTop, width: metrics[columnIndex].width, height: headerHeight, justifyContent: "center", padding: 0, textAlign: "center" },
3718
+ style: { left, top: headerTop, width: metrics[columnIndex].width, height: headerHeight, justifyContent: "center", padding: 0, textAlign: "center" },
2976
3719
  children: /* @__PURE__ */ jsx7("span", { children: column.title })
2977
3720
  },
2978
3721
  column.key
@@ -2986,7 +3729,7 @@ function Table({
2986
3729
  "div",
2987
3730
  {
2988
3731
  className: "rvg-header-selection-icon",
2989
- style: { left, top: fixedHeader ? 0 : -currentScrollTop, width: metrics[columnIndex].width, height: headerHeight },
3732
+ style: { left, top: headerTop, width: metrics[columnIndex].width, height: headerHeight },
2990
3733
  children: /* @__PURE__ */ jsx7(SelectionIcon, { checked: allSelected, indeterminate })
2991
3734
  },
2992
3735
  column.key
@@ -2999,11 +3742,13 @@ function Table({
2999
3742
  "div",
3000
3743
  {
3001
3744
  className: "rvg-header-title is-custom",
3745
+ "data-level": headerDepth - 1,
3746
+ "data-measure-key": column.key,
3002
3747
  style: {
3003
3748
  left,
3004
- top: fixedHeader ? 0 : -currentScrollTop,
3749
+ top: leafTop,
3005
3750
  width: Math.max(0, metrics[columnIndex].width - actionWidth),
3006
- height: headerHeight,
3751
+ height: headerLeafHeight,
3007
3752
  justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
3008
3753
  textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
3009
3754
  },
@@ -3011,8 +3756,10 @@ function Table({
3011
3756
  "span",
3012
3757
  {
3013
3758
  ref: (node) => {
3014
- if (node) customHeaderContentRefs.current.set(column.key, node);
3015
- else customHeaderContentRefs.current.delete(column.key);
3759
+ if (node) {
3760
+ customHeaderContentRefs.current.set(column.key, node);
3761
+ requestAnimationFrame(measureCustomHeaderHeight);
3762
+ } else customHeaderContentRefs.current.delete(column.key);
3016
3763
  },
3017
3764
  children: column.renderHeader(column)
3018
3765
  }
@@ -3023,6 +3770,61 @@ function Table({
3023
3770
  }
3024
3771
  return null;
3025
3772
  };
3773
+ const renderGroupedHeaderCell = (cell) => {
3774
+ const startMetric = metrics[cell.startIndex];
3775
+ const endMetric = metrics[cell.endIndex];
3776
+ if (!startMetric || !endMetric) return null;
3777
+ const column = cell.column;
3778
+ const fixedSide = columns[cell.startIndex]?.fixed;
3779
+ const currentScrollTop = scrollRef.current.top;
3780
+ const rowHeightForLevel = headerRowHeights[cell.level] ?? baseHeaderHeight;
3781
+ const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
3782
+ const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
3783
+ const width2 = cell.leaf ? startMetric.width : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1);
3784
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex)) : null;
3785
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable && !cell.leaf ? 18 : 0;
3786
+ return /* @__PURE__ */ jsxs7(
3787
+ "div",
3788
+ {
3789
+ className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}`,
3790
+ "data-level": cell.level,
3791
+ "data-measure-key": `group:${cell.key}:${cell.level}`,
3792
+ style: {
3793
+ left,
3794
+ top: headerTop,
3795
+ width: width2,
3796
+ height: rowHeightForLevel,
3797
+ paddingRight: cell.leaf ? actionWidth + 10 : 10,
3798
+ justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
3799
+ textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
3800
+ },
3801
+ children: [
3802
+ /* @__PURE__ */ jsx7(
3803
+ "span",
3804
+ {
3805
+ ref: (node) => {
3806
+ if (!column.renderHeader) return;
3807
+ const key = `group:${cell.key}:${cell.level}`;
3808
+ if (node) {
3809
+ customHeaderContentRefs.current.set(key, node);
3810
+ requestAnimationFrame(measureCustomHeaderHeight);
3811
+ } else customHeaderContentRefs.current.delete(key);
3812
+ },
3813
+ children: column.renderHeader ? column.renderHeader(column) : column.title
3814
+ }
3815
+ ),
3816
+ !cell.leaf && columnDraggable && /* @__PURE__ */ jsx7(
3817
+ HeaderDragIcon,
3818
+ {
3819
+ className: `rvg-header-icon rvg-header-drag-icon${hoveredHeaderAction?.columnIndex === cell.startIndex && hoveredHeaderAction.action === "drag" ? " is-hovered" : ""}`,
3820
+ style: { left: width2 - 16, top: (rowHeightForLevel - 14) / 2 }
3821
+ }
3822
+ )
3823
+ ]
3824
+ },
3825
+ `group:${cell.key}:${cell.level}`
3826
+ );
3827
+ };
3026
3828
  const rangeHandlePosition = (() => {
3027
3829
  if (!rangeSelection) return null;
3028
3830
  const bounds = getRangeBounds();
@@ -3054,9 +3856,9 @@ function Table({
3054
3856
  const verticalStart = fixedHeader ? bodyTop : 0;
3055
3857
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) return null;
3056
3858
  const borderWidth = 2;
3057
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
3859
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
3058
3860
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
3059
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
3861
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
3060
3862
  const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
3061
3863
  return {
3062
3864
  left,
@@ -3065,6 +3867,19 @@ function Table({
3065
3867
  height: Math.max(0, bottom - top)
3066
3868
  };
3067
3869
  })();
3870
+ const selectionEdgeIndicator = getSelectionEdgeIndicator(scrollPosition.left, scrollPosition.top);
3871
+ const selectionEdgeStyle = selectionEdgeIndicator ? {
3872
+ ...selectionEdgeIndicator.style,
3873
+ ...selectionEdgeIndicator.corner ? {
3874
+ "--rvg-corner-width": `${selectionEdgeIndicator.corner.width}px`,
3875
+ "--rvg-corner-height": `${selectionEdgeIndicator.corner.height}px`,
3876
+ "--rvg-corner-hover-width-extension": "4px",
3877
+ "--rvg-corner-hover-height-extension": "4px"
3878
+ } : null
3879
+ } : void 0;
3880
+ useLayoutEffect2(() => {
3881
+ syncSelectionEdgeTarget(scrollPosition.left, scrollPosition.top);
3882
+ }, [scrollPosition.left, scrollPosition.top, syncSelectionEdgeTarget]);
3068
3883
  const handleContextMenu = (event) => {
3069
3884
  event.preventDefault();
3070
3885
  event.stopPropagation();
@@ -3214,15 +4029,19 @@ function Table({
3214
4029
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
3215
4030
  const columnIndex = locateColumn(event.clientX);
3216
4031
  const inHeader = localY >= headerY && localY < headerY + headerHeight;
3217
- const hoveredAction = inHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
3218
- const hoveredDragHandle = inHeader && locateHeaderDragHandle(event.clientX, columnIndex);
3219
- const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && columnIndex >= 0 && Boolean(columns[columnIndex]?.title) && (tooltipConfig.header || isHeaderTitleTruncated(columnIndex));
3220
- const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex, action: "drag" } : hoveredTitle ? { columnIndex, action: "title" } : null;
4032
+ const inLeafHeader = inHeader && localY >= headerY + headerLeafTop;
4033
+ const hoveredHeaderCell = inHeader ? locateHeaderCell(event.clientX, event.clientY) : null;
4034
+ const hoveredAction = inLeafHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
4035
+ const hoveredDragHandle = Boolean(hoveredHeaderCell && locateHeaderCellDragHandle(event.clientX, hoveredHeaderCell));
4036
+ const hoveredDragColumnIndex = hoveredHeaderCell?.startIndex ?? columnIndex;
4037
+ const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
4038
+ const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
4039
+ const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
3221
4040
  const previousHover = hoveredHeaderActionRef.current;
3222
4041
  if (previousHover?.columnIndex !== nextHover?.columnIndex || previousHover?.action !== nextHover?.action) {
3223
4042
  hoveredHeaderActionRef.current = nextHover;
3224
4043
  setHoveredHeaderAction(nextHover);
3225
- showHeaderTooltipAfterDelay(nextHover);
4044
+ showHeaderTooltipAfterDelay(nextTooltip);
3226
4045
  scheduleDraw();
3227
4046
  }
3228
4047
  if (inHeader) {
@@ -3246,8 +4065,8 @@ function Table({
3246
4065
  if (annotation?.content.trim()) {
3247
4066
  const cellLeft = getDisplayedColumnLeft(cell.columnIndex);
3248
4067
  const cellTop = bodyTop + cell.rowIndex * rowHeight - scrollRef.current.top;
3249
- const nextTooltip = { rowIndex: cell.rowIndex, columnIndex: cell.columnIndex, left: cellLeft + metrics[cell.columnIndex].width + 6, top: cellTop + rowHeight / 2, label: annotation.content, color: annotation.color, annotation: true };
3250
- showCellTooltip(nextTooltip);
4068
+ const nextTooltip2 = { rowIndex: cell.rowIndex, columnIndex: cell.columnIndex, left: cellLeft + metrics[cell.columnIndex].width + 6, top: cellTop + rowHeight / 2, label: annotation.content, color: annotation.color, annotation: true };
4069
+ showCellTooltip(nextTooltip2);
3251
4070
  } else {
3252
4071
  const label = getCellLabel(cell.rowIndex, cell.columnIndex);
3253
4072
  const context = event.currentTarget.getContext("2d");
@@ -3274,7 +4093,7 @@ function Table({
3274
4093
  showCellTooltipAfterDelay(null);
3275
4094
  } else {
3276
4095
  const placement = textRight + 330 > viewport.width ? "left" : "right";
3277
- const nextTooltip = {
4096
+ const nextTooltip2 = {
3278
4097
  rowIndex: cell.rowIndex,
3279
4098
  columnIndex: cell.columnIndex,
3280
4099
  left: placement === "right" ? textRight + 16 : Math.max(8, textLeft - 16),
@@ -3282,13 +4101,13 @@ function Table({
3282
4101
  label,
3283
4102
  placement
3284
4103
  };
3285
- showCellTooltipAfterDelay(nextTooltip);
4104
+ showCellTooltipAfterDelay(nextTooltip2);
3286
4105
  }
3287
4106
  }
3288
4107
  }
3289
4108
  }
3290
4109
  }
3291
- const cursor = inHeader && locateResizeHandle(event.clientX) >= 0 ? "col-resize" : hoveredAction ? "pointer" : hoveredDragHandle ? "move" : columnIndex >= 0 && columns[columnIndex].rowSelection ? "pointer" : !inHeader && rowDraggable && columnIndex >= 0 && columns[columnIndex].rowDragHandle ? "move" : "default";
4110
+ const cursor = inHeader && locateHeaderResizeCell(event.clientX, event.clientY) ? "col-resize" : hoveredAction ? "pointer" : hoveredDragHandle ? "move" : columnIndex >= 0 && columns[columnIndex].rowSelection ? "pointer" : !inHeader && rowDraggable && columnIndex >= 0 && columns[columnIndex].rowDragHandle ? "move" : "default";
3292
4111
  if (event.currentTarget.style.cursor !== cursor) event.currentTarget.style.cursor = cursor;
3293
4112
  },
3294
4113
  onMouseLeave: (event) => {
@@ -3331,6 +4150,7 @@ function Table({
3331
4150
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
3332
4151
  if (localY >= headerY && localY < headerY + headerHeight) {
3333
4152
  clickedCellRef.current = null;
4153
+ if (localY < headerY + headerLeafTop) return;
3334
4154
  const columnIndex = locateColumn(event.clientX);
3335
4155
  const action = locateHeaderAction(event.clientX, columnIndex);
3336
4156
  if (action === "sort") {
@@ -3392,21 +4212,30 @@ function Table({
3392
4212
  className: "rvg-text-scroll-content",
3393
4213
  style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
3394
4214
  children: [
3395
- columns.map((column, columnIndex) => column.fixed ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ jsx7("span", { children: renderHeaderTitle(columnIndex, metrics[columnIndex].left) }, column.key) : null),
4215
+ columns.map((column, columnIndex) => column.fixed || columnIndex < utilityColumnCount || headerDepth > 1 ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ jsx7("span", { children: renderHeaderTitle(columnIndex, getScrollableColumnLeft(columnIndex)) }, column.key) : null),
4216
+ headerCells.map((cell) => {
4217
+ if (columns[cell.startIndex]?.fixed) return null;
4218
+ if (cell.endIndex < domRange.columnStart || cell.startIndex >= domRange.columnEnd) return null;
4219
+ return renderGroupedHeaderCell(cell);
4220
+ }),
3396
4221
  /* @__PURE__ */ jsx7("div", { className: "rvg-body-text-clip", style: { top: fixedHeader ? bodyTop : 0, bottom: 0 }, children: Array.from({ length: domRange.rowEnd - domRange.rowStart }, (_, offset) => domRange.rowStart + offset).flatMap((rowIndex) => columns.map((column, columnIndex) => {
3397
4222
  if (column.fixed || columnIndex < domRange.columnStart || columnIndex >= domRange.columnEnd) return null;
3398
- return renderCellText(rowIndex, columnIndex, metrics[columnIndex].left);
4223
+ return renderCellText(rowIndex, columnIndex, getScrollableColumnLeft(columnIndex));
3399
4224
  })) })
3400
4225
  ]
3401
4226
  }
3402
4227
  ) }),
3403
4228
  /* @__PURE__ */ jsx7("div", { className: "rvg-body-text-clip", style: { top: fixedHeader ? bodyTop : 0, bottom: 0 }, children: Array.from({ length: domRange.rowEnd - domRange.rowStart }, (_, offset) => domRange.rowStart + offset).flatMap((rowIndex) => columns.map((column, columnIndex) => {
3404
4229
  if (!column.fixed) return null;
3405
- const left = column.fixed === "left" ? metrics[columnIndex].left : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
4230
+ const left = column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
3406
4231
  return renderCellText(rowIndex, columnIndex, left);
3407
4232
  })) }),
3408
- columns.map((column, columnIndex) => column.fixed === "left" ? renderHeaderTitle(columnIndex, metrics[columnIndex].left) : null),
3409
- columns.map((column, columnIndex) => column.fixed === "right" ? renderHeaderTitle(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
4233
+ columns.map((column, columnIndex) => columnIndex < utilityColumnCount || headerDepth === 1 && (column.fixed === "left" || column.fixed === "right") ? renderHeaderTitle(columnIndex, column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width : column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : getScrollableColumnLeft(columnIndex)) : null),
4234
+ headerCells.map((cell) => columns[cell.startIndex]?.fixed ? renderGroupedHeaderCell(cell) : null),
4235
+ fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-text-fixed-shadow is-left", style: { left: fixedWidth } }),
4236
+ rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ jsx7("div", { className: "rvg-text-fixed-shadow is-right", style: { right: rightFixedWidth } }),
4237
+ hasVerticalBorders && fixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-text-fixed-border is-left", style: { left: fixedWidth - 1 } }),
4238
+ hasVerticalBorders && rightFixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-text-fixed-border is-right", style: { right: rightFixedWidth } })
3410
4239
  ] }),
3411
4240
  /* @__PURE__ */ jsx7("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -renderHeight } })
3412
4241
  ] }),
@@ -3414,18 +4243,18 @@ function Table({
3414
4243
  "div",
3415
4244
  {
3416
4245
  className: `rvg-summary-row is-${summaryPosition}${hasVerticalBorders ? "" : " is-vertical-borderless"}`,
3417
- style: { height: rowHeight, top: summaryPosition === "top" ? headerHeight : void 0 },
4246
+ style: { height: rowHeight, top: summaryPosition === "top" ? fixedHeader ? headerHeight : headerHeight - currentScroll.top : void 0 },
3418
4247
  children: [
3419
4248
  /* @__PURE__ */ jsx7("div", { className: "rvg-summary-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ jsx7(
3420
4249
  "div",
3421
4250
  {
3422
4251
  className: "rvg-summary-scroll-content",
3423
4252
  style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
3424
- children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex, metrics[columnIndex].left))
4253
+ children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex, getScrollableColumnLeft(columnIndex)))
3425
4254
  }
3426
4255
  ) }),
3427
4256
  /* @__PURE__ */ jsxs7("div", { className: "rvg-summary-fixed-layer", children: [
3428
- columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex, metrics[columnIndex].left) : null),
4257
+ columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex, leftFixedOffsets.get(columnIndex) ?? 0) : null),
3429
4258
  columns.map((column, columnIndex) => column.fixed === "right" ? renderSummaryCell(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
3430
4259
  ] }),
3431
4260
  fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-summary-fixed-shadow is-left", style: { left: fixedWidth } }),
@@ -3442,11 +4271,11 @@ function Table({
3442
4271
  ref: scrollingHeaderIconsRef,
3443
4272
  className: "rvg-header-icon-layer rvg-header-icon-layer-scroll",
3444
4273
  style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-scrollRef.current.left}px)` },
3445
- children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, metrics[index].left) }, column.key))
4274
+ children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, getScrollableColumnLeft(index)) }, column.key))
3446
4275
  }
3447
4276
  ) }),
3448
4277
  /* @__PURE__ */ jsxs7("div", { className: "rvg-header-icon-layer", children: [
3449
- columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, metrics[index].left) }, column.key) : null),
4278
+ columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, leftFixedOffsets.get(index) ?? 0) }, column.key) : null),
3450
4279
  columns.map((column, index) => column.fixed === "right" ? /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, viewport.width - (rightFixedOffsets.get(index) ?? 0) - metrics[index].width) }, column.key) : null)
3451
4280
  ] })
3452
4281
  ] }),
@@ -3498,6 +4327,27 @@ function Table({
3498
4327
  }
3499
4328
  ),
3500
4329
  selectionOutlineStyle && /* @__PURE__ */ jsx7("div", { ref: selectionFocusRef, className: "rvg-selection-focus", style: selectionOutlineStyle, "aria-hidden": "true" }),
4330
+ selectionEdgeIndicator && selection && /* @__PURE__ */ jsx7(
4331
+ "button",
4332
+ {
4333
+ ref: selectionEdgeTargetRef,
4334
+ type: "button",
4335
+ className: `rvg-selection-edge-target is-${selectionEdgeIndicator.orientation} is-${selectionEdgeIndicator.side}${selectionEdgeIndicator.viewportEdge ? ` is-viewport-${selectionEdgeIndicator.viewportEdge}` : ""}`,
4336
+ style: selectionEdgeStyle,
4337
+ "aria-label": labels.revealSelectedCell,
4338
+ title: labels.revealSelectedCell,
4339
+ onPointerDown: (event) => {
4340
+ event.preventDefault();
4341
+ event.stopPropagation();
4342
+ },
4343
+ onClick: (event) => {
4344
+ event.stopPropagation();
4345
+ scrollerRef.current?.focus({ preventScroll: true });
4346
+ revealCell(selection);
4347
+ },
4348
+ children: /* @__PURE__ */ jsx7("span", { className: "rvg-selection-edge-halo", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", children: /* @__PURE__ */ jsx7("path", { d: "M 0 100 L 0 0 L 100 0" }) }) })
4349
+ }
4350
+ ),
3501
4351
  contextMenu && /* @__PURE__ */ jsx7("div", { ref: contextMenuRef, className: "rvg-context-menu", role: "menu", style: { left: contextMenu.left, top: contextMenu.top }, children: contextMenu.type === "header" ? (() => {
3502
4352
  const column = columns[contextMenu.columnIndex];
3503
4353
  const headerContext = { column, columnIndex: contextMenu.columnIndex };
@@ -3723,12 +4573,40 @@ function Table({
3723
4573
  ] })
3724
4574
  ] }) }),
3725
4575
  toast && /* @__PURE__ */ jsx7("div", { className: `rvg-toast is-${toast.type}`, role: "status", children: toast.message }),
4576
+ /* @__PURE__ */ jsx7(
4577
+ "div",
4578
+ {
4579
+ ref: verticalScrollbarRef,
4580
+ className: "rvg-vertical-scrollbar",
4581
+ onPointerEnter: () => keepCustomScrollbarVisible("vertical"),
4582
+ onPointerLeave: () => releaseCustomScrollbarVisible("vertical"),
4583
+ onPointerDown: handleVerticalScrollbarPointerDown,
4584
+ onPointerMove: handleVerticalScrollbarPointerMove,
4585
+ onPointerUp: handleVerticalScrollbarPointerUp,
4586
+ onPointerCancel: handleVerticalScrollbarPointerUp,
4587
+ children: /* @__PURE__ */ jsx7("div", { ref: verticalScrollbarThumbRef, className: "rvg-vertical-scrollbar-thumb" })
4588
+ }
4589
+ ),
4590
+ /* @__PURE__ */ jsx7(
4591
+ "div",
4592
+ {
4593
+ ref: horizontalScrollbarRef,
4594
+ className: "rvg-horizontal-scrollbar",
4595
+ onPointerEnter: () => keepCustomScrollbarVisible("horizontal"),
4596
+ onPointerLeave: () => releaseCustomScrollbarVisible("horizontal"),
4597
+ onPointerDown: handleHorizontalScrollbarPointerDown,
4598
+ onPointerMove: handleHorizontalScrollbarPointerMove,
4599
+ onPointerUp: handleHorizontalScrollbarPointerUp,
4600
+ onPointerCancel: handleHorizontalScrollbarPointerUp,
4601
+ children: /* @__PURE__ */ jsx7("div", { ref: horizontalScrollbarThumbRef, className: "rvg-horizontal-scrollbar-thumb" })
4602
+ }
4603
+ ),
3726
4604
  /* @__PURE__ */ jsx7("div", { ref: dragGuideRef, className: "rvg-column-guide" }),
3727
4605
  resizeGuideX !== null && /* @__PURE__ */ jsx7(
3728
4606
  "div",
3729
4607
  {
3730
4608
  className: "rvg-column-guide is-resizing",
3731
- style: { display: "block", transform: `translateX(${Math.round(resizeGuideX)}px)` }
4609
+ style: { display: "block", top: resizeGuideTop, transform: `translateX(${Math.round(resizeGuideX)}px)` }
3732
4610
  }
3733
4611
  ),
3734
4612
  /* @__PURE__ */ jsx7("div", { ref: rowDragGuideRef, className: "rvg-row-guide" }),
@@ -3794,8 +4672,8 @@ function Table({
3794
4672
  column.key
3795
4673
  );
3796
4674
  }) }, rowIndex)),
3797
- fixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
3798
- rightFixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
4675
+ fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
4676
+ rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
3799
4677
  ] }),
3800
4678
  loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ jsx7("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ jsx7(LoadingSpinnerIcon, {}) })
3801
4679
  ] });