@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.cjs CHANGED
@@ -57,6 +57,7 @@ var ZH_CN_GRID_LABELS = {
57
57
  sortDesc: "\u5F53\u524D\uFF1A\u964D\u5E8F",
58
58
  sortBoth: "\u5347\u5E8F / \u964D\u5E8F",
59
59
  dragColumn: "\u62D6\u52A8\u5217\u6392\u5E8F",
60
+ revealSelectedCell: "\u5B9A\u4F4D\u5230\u9009\u4E2D\u5355\u5143\u683C",
60
61
  previousYear: "\u4E0A\u4E00\u5E74",
61
62
  nextYear: "\u4E0B\u4E00\u5E74",
62
63
  previousMonth: "\u4E0A\u4E2A\u6708",
@@ -125,6 +126,7 @@ var EN_US_GRID_LABELS = {
125
126
  sortDesc: "Current: descending",
126
127
  sortBoth: "Ascending / descending",
127
128
  dragColumn: "Drag column to reorder",
129
+ revealSelectedCell: "Reveal selected cell",
128
130
  previousYear: "Previous year",
129
131
  nextYear: "Next year",
130
132
  previousMonth: "Previous month",
@@ -333,7 +335,6 @@ function LoadingSpinnerIcon() {
333
335
  // src/components/editors/ChoiceEditor.tsx
334
336
  var import_jsx_runtime3 = require("react/jsx-runtime");
335
337
  function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
336
- const editorStyle = typeof style.width === "number" ? { ...style, width: Math.max(0, style.width - 1) } : style;
337
338
  const collection = (0, import_select.createListCollection)({
338
339
  items: options,
339
340
  itemToValue: (item) => item.value,
@@ -343,7 +344,7 @@ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCan
343
344
  import_select.Select.Root,
344
345
  {
345
346
  className: "rvg-choice-editor",
346
- style: editorStyle,
347
+ style,
347
348
  collection,
348
349
  value: value ? [value] : [],
349
350
  defaultOpen: true,
@@ -893,6 +894,8 @@ function ellipsizeText(context, text, maxWidth) {
893
894
  }
894
895
  function paintGrid(options) {
895
896
  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;
897
+ const headerLeafTop = options.headerLeafTop ?? 0;
898
+ const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
896
899
  const colors = { ...COLORS, ...options.colors };
897
900
  const bodyTop = options.bodyTop ?? headerHeight;
898
901
  const getCellSpan = (rowIndex, columnIndex) => options.cellSpans.get(getCellCoordKey(rowIndex, columnIndex));
@@ -909,9 +912,17 @@ function paintGrid(options) {
909
912
  ctx.fillRect(0, 0, width, height);
910
913
  ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
911
914
  ctx.textBaseline = "middle";
912
- const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? Math.max(value, metrics[index].right) : value, 0);
915
+ const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? value + metrics[index].width : value, 0);
913
916
  const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
914
917
  const rightFixedLeft = width - rightFixedWidth;
918
+ const rightFixedBoundaryInset = rightFixedWidth > 0 && !verticalBorderless ? 1 : 0;
919
+ const leftOffsets = /* @__PURE__ */ new Map();
920
+ let leftOffset = 0;
921
+ for (let index = 0; index < columns.length; index += 1) {
922
+ if (columns[index].fixed !== "left") continue;
923
+ leftOffsets.set(index, leftOffset);
924
+ leftOffset += metrics[index].width;
925
+ }
915
926
  const rightOffsets = /* @__PURE__ */ new Map();
916
927
  let rightOffset = 0;
917
928
  for (let index = columns.length - 1; index >= 0; index -= 1) {
@@ -919,8 +930,14 @@ function paintGrid(options) {
919
930
  rightOffsets.set(index, rightOffset);
920
931
  rightOffset += metrics[index].width;
921
932
  }
933
+ const scrollableLefts = /* @__PURE__ */ new Map();
934
+ let scrollableLeft = leftFixedWidth;
935
+ for (let index = 0; index < columns.length; index += 1) {
936
+ scrollableLefts.set(index, scrollableLeft);
937
+ if (columns[index].fixed === void 0) scrollableLeft += metrics[index].width;
938
+ }
922
939
  const getColumnX = (index) => {
923
- return columns[index].fixed === "left" ? metrics[index].left : columns[index].fixed === "right" ? width - (rightOffsets.get(index) ?? 0) - metrics[index].width : metrics[index].left - scrollLeft;
940
+ 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;
924
941
  };
925
942
  const paintBodyColumns = (layer) => {
926
943
  const rowStart = Math.max(0, range.rowStart - Math.max(0, options.maxRowSpan - 1));
@@ -1070,7 +1087,7 @@ function paintGrid(options) {
1070
1087
  if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1071
1088
  ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1072
1089
  const column = columns[columnIndex];
1073
- if (!column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1090
+ if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1074
1091
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1075
1092
  const titleWidth = ctx.measureText(column.title).width;
1076
1093
  const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
@@ -1087,11 +1104,11 @@ function paintGrid(options) {
1087
1104
  const textX = titleAlign === "right" ? x + contentWidth - padding : titleAlign === "center" ? x + centeredTextX : x + padding;
1088
1105
  ctx.save();
1089
1106
  ctx.beginPath();
1090
- ctx.rect(x + 2, headerY + 1, Math.max(0, clipWidth - 4), headerHeight - 2);
1107
+ ctx.rect(x + 2, headerY + headerLeafTop + 1, Math.max(0, clipWidth - 4), headerLeafHeight - 2);
1091
1108
  ctx.clip();
1092
1109
  ctx.fillStyle = colors.muted;
1093
1110
  ctx.textAlign = titleAlign === "right" ? "right" : titleAlign === "center" ? "center" : "left";
1094
- ctx.fillText(title, textX, headerY + headerHeight / 2);
1111
+ ctx.fillText(title, textX, headerY + headerLeafTop + headerLeafHeight / 2);
1095
1112
  ctx.restore();
1096
1113
  }
1097
1114
  }
@@ -1099,22 +1116,6 @@ function paintGrid(options) {
1099
1116
  paintHeaderColumns("scroll");
1100
1117
  paintHeaderColumns("left");
1101
1118
  paintHeaderColumns("right");
1102
- if (leftFixedWidth > 0 && scrollLeft > 0) {
1103
- const shadowWidth = 10;
1104
- const shadow = ctx.createLinearGradient(leftFixedWidth, 0, leftFixedWidth + shadowWidth, 0);
1105
- shadow.addColorStop(0, "rgba(5, 5, 5, 0.06)");
1106
- shadow.addColorStop(1, "rgba(5, 5, 5, 0)");
1107
- ctx.fillStyle = shadow;
1108
- ctx.fillRect(leftFixedWidth, 0, shadowWidth, height);
1109
- }
1110
- if (rightFixedWidth > 0 && scrollLeft < (metrics[metrics.length - 1]?.right ?? 0) - width) {
1111
- const shadowWidth = 10;
1112
- const shadow = ctx.createLinearGradient(rightFixedLeft - shadowWidth, 0, rightFixedLeft, 0);
1113
- shadow.addColorStop(0, "rgba(5, 5, 5, 0)");
1114
- shadow.addColorStop(1, "rgba(5, 5, 5, 0.06)");
1115
- ctx.fillStyle = shadow;
1116
- ctx.fillRect(rightFixedLeft - shadowWidth, 0, shadowWidth, height);
1117
- }
1118
1119
  if (rightFixedWidth > 0 && !verticalBorderless) {
1119
1120
  ctx.fillStyle = colors.grid;
1120
1121
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
@@ -1138,22 +1139,22 @@ function paintGrid(options) {
1138
1139
  const markerBottom = Math.min(bottom, height);
1139
1140
  const startX = getColumnX(columnStart);
1140
1141
  const endX = getColumnX(columnEnd) + metrics[columnEnd].width;
1141
- const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth - 1, startX);
1142
+ const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth, startX);
1142
1143
  const visibleRight = columns[columnEnd].fixed === "right" ? Math.min(width, endX) : Math.min(rightFixedLeft, endX);
1143
1144
  ctx.fillStyle = colors.selection;
1144
1145
  if (markerBottom > markerTop) {
1145
1146
  if (columns[columnStart].fixed === void 0 && startX < leftFixedWidth) {
1146
- ctx.fillRect(Math.max(0, leftFixedWidth - 1), markerTop, 2, markerBottom - markerTop);
1147
+ ctx.fillRect(Math.max(0, leftFixedWidth), markerTop, 2, markerBottom - markerTop);
1147
1148
  }
1148
1149
  if (columns[columnEnd].fixed === void 0 && endX > rightFixedLeft) {
1149
- ctx.fillRect(Math.max(0, rightFixedLeft - 1), markerTop, 2, markerBottom - markerTop);
1150
+ ctx.fillRect(Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset), markerTop, 2, markerBottom - markerTop);
1150
1151
  }
1151
1152
  }
1152
1153
  if (visibleRight > visibleLeft) {
1153
1154
  if (top < clipTop) ctx.fillRect(visibleLeft, clipTop, visibleRight - visibleLeft, 2);
1154
1155
  if (bottom > height) ctx.fillRect(visibleLeft, height - 2, visibleRight - visibleLeft, 2);
1155
1156
  } else if (top < clipTop || bottom > height) {
1156
- const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth - 1) : Math.max(0, rightFixedLeft - 1);
1157
+ const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth) : Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset);
1157
1158
  ctx.fillRect(markerX, top < clipTop ? clipTop : height - 2, 2, 2);
1158
1159
  }
1159
1160
  }
@@ -1175,15 +1176,15 @@ function paintGrid(options) {
1175
1176
  const visibleRight = x + selectionWidth;
1176
1177
  const visibleBottom = y + selectionHeight;
1177
1178
  const selectionTop = fixedHeader ? bodyTop : 0;
1178
- const selectionClipTop = fixedHeader ? Math.max(0, selectionTop - 1) : selectionTop;
1179
+ const selectionClipTop = selectionTop;
1179
1180
  const hiddenAboveHeader = visibleBottom <= selectionTop;
1180
1181
  const hiddenBelowViewport = y >= height;
1181
1182
  const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
1182
1183
  const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
1183
- const leftMarkerX = Math.max(0, leftFixedWidth - 1);
1184
+ const leftMarkerX = Math.max(0, leftFixedWidth);
1184
1185
  if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
1185
1186
  const markerSize = 2;
1186
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1187
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1187
1188
  const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
1188
1189
  ctx.fillStyle = colors.selection;
1189
1190
  ctx.fillRect(markerX, markerY, markerSize, markerSize);
@@ -1191,7 +1192,7 @@ function paintGrid(options) {
1191
1192
  }
1192
1193
  ctx.save();
1193
1194
  ctx.beginPath();
1194
- const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth - 1);
1195
+ const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth);
1195
1196
  const clipRight = Math.min(width, selectionAreaRight + 1);
1196
1197
  ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
1197
1198
  ctx.clip();
@@ -1209,14 +1210,14 @@ function paintGrid(options) {
1209
1210
  if (hiddenBehindLeft || hiddenBehindRight) {
1210
1211
  const markerTop = Math.max(y - borderWidth, selectionTop);
1211
1212
  const markerBottom = Math.min(y + selectionHeight + 1, height);
1212
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1213
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1213
1214
  ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
1214
1215
  }
1215
1216
  if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
1216
1217
  ctx.restore();
1217
1218
  return;
1218
1219
  }
1219
- const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft - 1);
1220
+ const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft);
1220
1221
  const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
1221
1222
  const borderTop = Math.max(y - borderWidth, selectionClipTop);
1222
1223
  const borderBottom = Math.min(y + selectionHeight - 1, height - borderWidth);
@@ -1266,6 +1267,45 @@ var getRowNumberColumnWidth = (rowCount) => {
1266
1267
  const digits = String(Math.max(1, rowCount)).length;
1267
1268
  return digits <= 4 ? 44 : 44 + (digits - 4) * 8;
1268
1269
  };
1270
+ function getColumnDepth(column) {
1271
+ if (!column.children?.length) return 1;
1272
+ return 1 + Math.max(...column.children.map(getColumnDepth));
1273
+ }
1274
+ function flattenDataColumns(columns, inheritedFixed, insideGroup = false) {
1275
+ return columns.flatMap((column) => {
1276
+ if (!column.children?.length) {
1277
+ return insideGroup ? [{ ...column, fixed: inheritedFixed }] : [column];
1278
+ }
1279
+ return flattenDataColumns(column.children, column.fixed ?? inheritedFixed, true);
1280
+ });
1281
+ }
1282
+ function buildHeaderCells(sourceColumns, utilityColumnCount, depth) {
1283
+ const cells = [];
1284
+ let leafIndex = utilityColumnCount;
1285
+ const walk = (column, level, inheritedFixed, insideGroup, rootIndex, parentKey) => {
1286
+ const fixedColumn = insideGroup ? { ...column, fixed: inheritedFixed } : column;
1287
+ if (!fixedColumn.children?.length) {
1288
+ const startIndex2 = leafIndex;
1289
+ leafIndex += 1;
1290
+ cells.push({ key: fixedColumn.key, column: fixedColumn, level: depth - 1, startIndex: startIndex2, endIndex: startIndex2, leaf: true, parentKey, rootIndex });
1291
+ return { startIndex: startIndex2, endIndex: startIndex2 };
1292
+ }
1293
+ const startIndex = leafIndex;
1294
+ fixedColumn.children.forEach((child) => walk(child, level + 1, fixedColumn.fixed, true, rootIndex, fixedColumn.key));
1295
+ const endIndex = leafIndex - 1;
1296
+ cells.push({ key: fixedColumn.key, column: fixedColumn, level, startIndex, endIndex, leaf: false, parentKey, rootIndex });
1297
+ return { startIndex, endIndex };
1298
+ };
1299
+ sourceColumns.forEach((column, index) => walk(column, 0, void 0, false, index));
1300
+ return cells;
1301
+ }
1302
+ function hasNestedFixedColumns(columns, insideGroup = false) {
1303
+ return columns.some((column) => insideGroup && column.fixed !== void 0 || (column.children?.length ? hasNestedFixedColumns(column.children, true) : false));
1304
+ }
1305
+ function isProductionRuntime() {
1306
+ const env = globalThis.process?.env?.NODE_ENV;
1307
+ return env === "production";
1308
+ }
1269
1309
  var createPendingInsertId = () => `table-inserted-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
1270
1310
  var waitForPaint = () => new Promise((resolve) => {
1271
1311
  requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
@@ -1286,6 +1326,25 @@ var createEmptyCellSpanLookup = () => ({
1286
1326
  covered: /* @__PURE__ */ new Map(),
1287
1327
  maxRowSpan: 1
1288
1328
  });
1329
+ var summaryFunctionIds = /* @__PURE__ */ new WeakMap();
1330
+ var nextSummaryFunctionId = 1;
1331
+ function getSummaryFunctionId(fn) {
1332
+ if (!fn) return "";
1333
+ const existing = summaryFunctionIds.get(fn);
1334
+ if (existing !== void 0) return existing;
1335
+ const next = nextSummaryFunctionId;
1336
+ nextSummaryFunctionId += 1;
1337
+ summaryFunctionIds.set(fn, next);
1338
+ return next;
1339
+ }
1340
+ function getSummarySignature(columns) {
1341
+ return columns.filter((column) => Boolean(column.summary)).map((column) => [
1342
+ column.key,
1343
+ String(column.dataIndex ?? ""),
1344
+ typeof column.summary === "function" ? getSummaryFunctionId(column.summary) : "sum",
1345
+ getSummaryFunctionId(column.formatter)
1346
+ ].join("")).join("");
1347
+ }
1289
1348
  async function calculateSummaryValues(columns, rows) {
1290
1349
  const values = /* @__PURE__ */ new Map();
1291
1350
  for (const column of columns) {
@@ -1380,10 +1439,24 @@ function Table({
1380
1439
  }) {
1381
1440
  const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
1382
1441
  const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
1442
+ const headerDepth = (0, import_react5.useMemo)(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
1383
1443
  const { language, labels } = (0, import_react5.useMemo)(() => resolveGridLocale(locale), [locale]);
1384
1444
  const customHeaderContentRefs = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1385
- const [measuredHeaderHeight, setMeasuredHeaderHeight] = (0, import_react5.useState)(0);
1386
- const headerHeight = Math.max(baseHeaderHeight, measuredHeaderHeight);
1445
+ const customHeaderMeasurementsRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1446
+ const [measuredHeaderRowHeights, setMeasuredHeaderRowHeights] = (0, import_react5.useState)([]);
1447
+ const headerRowHeights = (0, import_react5.useMemo)(() => Array.from({ length: headerDepth }, (_, level) => Math.max(baseHeaderHeight, measuredHeaderRowHeights[level] ?? 0)), [baseHeaderHeight, headerDepth, measuredHeaderRowHeights]);
1448
+ const headerRowOffsets = (0, import_react5.useMemo)(() => {
1449
+ const offsets = [];
1450
+ let top = 0;
1451
+ headerRowHeights.forEach((height2) => {
1452
+ offsets.push(top);
1453
+ top += height2;
1454
+ });
1455
+ return offsets;
1456
+ }, [headerRowHeights]);
1457
+ const headerHeight = headerRowHeights.reduce((total, height2) => total + height2, 0);
1458
+ const headerLeafTop = headerRowOffsets[headerDepth - 1] ?? 0;
1459
+ const headerLeafHeight = headerRowHeights[headerDepth - 1] ?? baseHeaderHeight;
1387
1460
  const hasVerticalBorders = !(borderless ?? verticalBorderless ?? false);
1388
1461
  const hasStripedRows = Boolean(striped);
1389
1462
  const stripedColor = typeof striped === "string" ? striped : void 0;
@@ -1403,6 +1476,13 @@ function Table({
1403
1476
  const summaryEmptyValue = typeof summary === "object" ? summary.emptyValue ?? "" : "";
1404
1477
  const contextMenuEnabled = contextMenuConfig !== false;
1405
1478
  const customContextMenuConfig = typeof contextMenuConfig === "object" ? contextMenuConfig : void 0;
1479
+ const [resizedColumnWidths, setResizedColumnWidths] = (0, import_react5.useState)({});
1480
+ const leafSourceColumns = (0, import_react5.useMemo)(() => flattenDataColumns(sourceColumns), [sourceColumns]);
1481
+ (0, import_react5.useEffect)(() => {
1482
+ if (!isProductionRuntime() && hasNestedFixedColumns(sourceColumns)) {
1483
+ console.warn("[Table] Multi-level headers only support fixed columns on top-level columns. Nested fixed values are ignored.");
1484
+ }
1485
+ }, [sourceColumns]);
1406
1486
  const columns = (0, import_react5.useMemo)(() => {
1407
1487
  const utilityColumns = [];
1408
1488
  if (rowDraggable) {
@@ -1414,9 +1494,24 @@ function Table({
1414
1494
  if (rowNumber) {
1415
1495
  utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: "left", rowNumber: true });
1416
1496
  }
1417
- return [...utilityColumns, ...sourceColumns];
1418
- }, [rowDraggable, rowNumber, rowSelection, sourceColumns, sourceRows.length]);
1497
+ const dataColumns = leafSourceColumns.map((column) => {
1498
+ const resizedWidth = resizedColumnWidths[column.key];
1499
+ return resizedWidth === void 0 ? column : { ...column, width: resizedWidth };
1500
+ });
1501
+ return [...utilityColumns, ...dataColumns];
1502
+ }, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber, rowSelection, sourceRows.length]);
1419
1503
  const utilityColumnCount = (rowDraggable ? 1 : 0) + (rowSelection ? 1 : 0) + (rowNumber ? 1 : 0);
1504
+ const headerCells = (0, import_react5.useMemo)(() => buildHeaderCells(sourceColumns, utilityColumnCount, headerDepth), [headerDepth, sourceColumns, utilityColumnCount]);
1505
+ const activeCustomHeaderLevels = (0, import_react5.useMemo)(() => {
1506
+ const levels = /* @__PURE__ */ new Map();
1507
+ columns.forEach((column) => {
1508
+ if (column.renderHeader) levels.set(column.key, headerDepth - 1);
1509
+ });
1510
+ headerCells.forEach((cell) => {
1511
+ if (cell.column.renderHeader) levels.set(`group:${cell.key}:${cell.level}`, cell.level);
1512
+ });
1513
+ return levels;
1514
+ }, [columns, headerCells, headerDepth]);
1420
1515
  const canvasRef = (0, import_react5.useRef)(null);
1421
1516
  const scrollerRef = (0, import_react5.useRef)(null);
1422
1517
  const frameRef = (0, import_react5.useRef)(null);
@@ -1434,6 +1529,15 @@ function Table({
1434
1529
  const dragGuideRef = (0, import_react5.useRef)(null);
1435
1530
  const rowDragGuideRef = (0, import_react5.useRef)(null);
1436
1531
  const selectionFocusRef = (0, import_react5.useRef)(null);
1532
+ const selectionEdgeTargetRef = (0, import_react5.useRef)(null);
1533
+ const verticalScrollbarRef = (0, import_react5.useRef)(null);
1534
+ const verticalScrollbarThumbRef = (0, import_react5.useRef)(null);
1535
+ const verticalScrollbarDragRef = (0, import_react5.useRef)(null);
1536
+ const horizontalScrollbarRef = (0, import_react5.useRef)(null);
1537
+ const horizontalScrollbarThumbRef = (0, import_react5.useRef)(null);
1538
+ const horizontalScrollbarDragRef = (0, import_react5.useRef)(null);
1539
+ const verticalScrollbarVisibilityTimerRef = (0, import_react5.useRef)(null);
1540
+ const horizontalScrollbarVisibilityTimerRef = (0, import_react5.useRef)(null);
1437
1541
  const rowByKeyRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1438
1542
  const rowIndexByKeyRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1439
1543
  const headerIconsRef = (0, import_react5.useRef)(null);
@@ -1486,24 +1590,57 @@ function Table({
1486
1590
  rangeDragRef.current = null;
1487
1591
  setSelectionRange(null);
1488
1592
  }, [rangeSelection]);
1593
+ (0, import_react5.useEffect)(() => {
1594
+ setResizedColumnWidths((current) => {
1595
+ const sourceColumnByKey = new Map(leafSourceColumns.map((column) => [column.key, column]));
1596
+ let changed = false;
1597
+ const next = {};
1598
+ Object.entries(current).forEach(([key, value]) => {
1599
+ const sourceColumn = sourceColumnByKey.get(key);
1600
+ if (!sourceColumn) {
1601
+ changed = true;
1602
+ return;
1603
+ }
1604
+ if (sourceColumn.width !== void 0 && sourceColumn.width !== value) {
1605
+ changed = true;
1606
+ return;
1607
+ }
1608
+ next[key] = value;
1609
+ });
1610
+ return changed ? next : current;
1611
+ });
1612
+ }, [leafSourceColumns]);
1489
1613
  (0, import_react5.useEffect)(() => () => {
1490
1614
  if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
1491
1615
  if (cellTooltipTimerRef.current !== null) clearTimeout(cellTooltipTimerRef.current);
1492
1616
  }, []);
1493
1617
  const measureCustomHeaderHeight = (0, import_react5.useCallback)(() => {
1494
- let nextHeight = 0;
1618
+ const nextHeights = Array.from({ length: headerDepth }, () => baseHeaderHeight);
1495
1619
  customHeaderContentRefs.current.forEach((node) => {
1496
- nextHeight = Math.max(nextHeight, Math.ceil(node.scrollHeight) + 16);
1620
+ const headerCell = node.closest(".rvg-header-title");
1621
+ const key = headerCell?.dataset.measureKey;
1622
+ const level = key ? activeCustomHeaderLevels.get(key) : void 0;
1623
+ if (!key || level === void 0 || level < 0 || level >= nextHeights.length) return;
1624
+ customHeaderMeasurementsRef.current.set(key, { level, height: Math.ceil(node.scrollHeight) + 16 });
1497
1625
  });
1498
- setMeasuredHeaderHeight((current) => current === nextHeight ? current : nextHeight);
1499
- }, []);
1626
+ customHeaderMeasurementsRef.current.forEach((measurement, key) => {
1627
+ if (!activeCustomHeaderLevels.has(key)) return;
1628
+ nextHeights[measurement.level] = Math.max(nextHeights[measurement.level], measurement.height);
1629
+ });
1630
+ setMeasuredHeaderRowHeights((current) => {
1631
+ const changed = nextHeights.length !== current.length || nextHeights.some((height2, index) => height2 !== current[index]);
1632
+ return changed ? nextHeights : current;
1633
+ });
1634
+ }, [activeCustomHeaderLevels, baseHeaderHeight, headerDepth]);
1500
1635
  (0, import_react5.useLayoutEffect)(() => {
1501
- const activeCustomHeaderKeys = new Set(columns.filter((column) => column.renderHeader).map((column) => column.key));
1502
1636
  customHeaderContentRefs.current.forEach((_, key) => {
1503
- if (!activeCustomHeaderKeys.has(key)) customHeaderContentRefs.current.delete(key);
1637
+ if (!activeCustomHeaderLevels.has(key)) customHeaderContentRefs.current.delete(key);
1638
+ });
1639
+ customHeaderMeasurementsRef.current.forEach((_, key) => {
1640
+ if (!activeCustomHeaderLevels.has(key)) customHeaderMeasurementsRef.current.delete(key);
1504
1641
  });
1505
- if (customHeaderContentRefs.current.size === 0) {
1506
- setMeasuredHeaderHeight(0);
1642
+ if (activeCustomHeaderLevels.size === 0) {
1643
+ setMeasuredHeaderRowHeights([]);
1507
1644
  return;
1508
1645
  }
1509
1646
  measureCustomHeaderHeight();
@@ -1511,7 +1648,7 @@ function Table({
1511
1648
  const observer = new ResizeObserver(measureCustomHeaderHeight);
1512
1649
  customHeaderContentRefs.current.forEach((node) => observer.observe(node));
1513
1650
  return () => observer.disconnect();
1514
- }, [columns, measureCustomHeaderHeight, viewport.width]);
1651
+ }, [activeCustomHeaderLevels, measureCustomHeaderHeight, viewport.width]);
1515
1652
  const [filterEditor, setFilterEditor] = (0, import_react5.useState)(null);
1516
1653
  const [contextMenu, setContextMenu] = (0, import_react5.useState)(null);
1517
1654
  const [insertCounts, setInsertCounts] = (0, import_react5.useState)({ before: 1, after: 1 });
@@ -1521,6 +1658,7 @@ function Table({
1521
1658
  const [deleteBusy, setDeleteBusy] = (0, import_react5.useState)(false);
1522
1659
  const [toast, setToast] = (0, import_react5.useState)(null);
1523
1660
  const [resizeGuideX, setResizeGuideX] = (0, import_react5.useState)(null);
1661
+ const [resizeGuideTop, setResizeGuideTop] = (0, import_react5.useState)(0);
1524
1662
  const [hoveredHeaderAction, setHoveredHeaderAction] = (0, import_react5.useState)(null);
1525
1663
  const [visibleHeaderTooltip, setVisibleHeaderTooltip] = (0, import_react5.useState)(null);
1526
1664
  const [hoveredCellTooltip, setHoveredCellTooltip] = (0, import_react5.useState)(null);
@@ -1605,7 +1743,17 @@ function Table({
1605
1743
  const metrics = (0, import_react5.useMemo)(() => buildColumnMetrics(columns, { columnDraggable, viewportWidth: viewport.width }), [columnDraggable, columns, viewport.width]);
1606
1744
  const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
1607
1745
  const contentHeight = rows.length * rowHeight;
1608
- const fixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "left" ? Math.max(width2, metrics[index].right) : width2, 0), [columns, metrics]);
1746
+ const fixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "left" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
1747
+ const leftFixedOffsets = (0, import_react5.useMemo)(() => {
1748
+ const offsets = /* @__PURE__ */ new Map();
1749
+ let offset = 0;
1750
+ for (let index = 0; index < columns.length; index += 1) {
1751
+ if (columns[index].fixed !== "left") continue;
1752
+ offsets.set(index, offset);
1753
+ offset += metrics[index].width;
1754
+ }
1755
+ return offsets;
1756
+ }, [columns, metrics]);
1609
1757
  const rightFixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "right" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
1610
1758
  const rightFixedOffsets = (0, import_react5.useMemo)(() => {
1611
1759
  const offsets = /* @__PURE__ */ new Map();
@@ -1617,9 +1765,20 @@ function Table({
1617
1765
  }
1618
1766
  return offsets;
1619
1767
  }, [columns, metrics]);
1768
+ const scrollableColumnLefts = (0, import_react5.useMemo)(() => {
1769
+ const lefts = [];
1770
+ let width2 = fixedWidth;
1771
+ for (let index = 0; index < columns.length; index += 1) {
1772
+ lefts[index] = width2;
1773
+ if (columns[index].fixed === void 0) width2 += metrics[index].width;
1774
+ }
1775
+ return lefts;
1776
+ }, [columns, fixedWidth, metrics]);
1777
+ const getScrollableColumnLeft = (0, import_react5.useCallback)((columnIndex) => scrollableColumnLefts[columnIndex] ?? fixedWidth, [fixedWidth, scrollableColumnLefts]);
1620
1778
  const hasRowSelectionColumn = (0, import_react5.useMemo)(() => columns.some((column) => column.rowSelection), [columns]);
1621
1779
  const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
1622
- const hasSummaryRow = (0, import_react5.useMemo)(() => rows.length > 0 && summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, rows.length, summaryEnabled]);
1780
+ const summarySignature = (0, import_react5.useMemo)(() => getSummarySignature(columns), [columns]);
1781
+ const hasSummaryRow = rows.length > 0 && summaryEnabled && summarySignature.length > 0;
1623
1782
  const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
1624
1783
  const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
1625
1784
  const bodyTop = headerHeight + topSummaryHeight;
@@ -1627,22 +1786,22 @@ function Table({
1627
1786
  const realContentHeight = bodyTop + bodyContentHeight + bottomSummaryHeight + (contentWidth > viewport.width ? horizontalScrollbarHeight : 0);
1628
1787
  const effectiveHeight = autoHeight ? Math.max(bodyTop + bottomSummaryHeight, Math.min(resolvedHeight, realContentHeight)) : resolvedHeight;
1629
1788
  const renderHeight = Math.max(0, effectiveHeight - bottomSummaryHeight);
1630
- const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1631
- const [summaryState, setSummaryState] = (0, import_react5.useState)(() => ({ columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1632
- const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.columns !== columns || summaryState.rows !== rows);
1633
- const summaryValues = hasSummaryRow && summaryState.columns === columns && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1789
+ const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight + topSummaryHeight : 0));
1790
+ const [summaryState, setSummaryState] = (0, import_react5.useState)(() => ({ signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1791
+ const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.signature !== summarySignature || summaryState.rows !== rows);
1792
+ const summaryValues = hasSummaryRow && summaryState.signature === summarySignature && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1634
1793
  (0, import_react5.useEffect)(() => {
1635
1794
  let cancelled = false;
1636
1795
  if (!hasSummaryRow) {
1637
- 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 });
1796
+ setSummaryState((current) => current.signature === "" && current.rows === null && current.values.size === 0 && !current.pending ? current : { signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false });
1638
1797
  return () => {
1639
1798
  cancelled = true;
1640
1799
  };
1641
1800
  }
1642
1801
  setSummaryState((current) => ({
1643
- columns,
1802
+ signature: summarySignature,
1644
1803
  rows,
1645
- values: current.columns === columns && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1804
+ values: current.signature === summarySignature && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1646
1805
  pending: true
1647
1806
  }));
1648
1807
  if (loading) {
@@ -1655,13 +1814,13 @@ function Table({
1655
1814
  if (cancelled) return;
1656
1815
  const values = await calculateSummaryValues(columns, rows);
1657
1816
  if (cancelled) return;
1658
- setSummaryState({ columns, rows, values, pending: false });
1817
+ setSummaryState({ signature: summarySignature, rows, values, pending: false });
1659
1818
  };
1660
1819
  void run();
1661
1820
  return () => {
1662
1821
  cancelled = true;
1663
1822
  };
1664
- }, [columns, hasSummaryRow, loading, rows]);
1823
+ }, [hasSummaryRow, loading, rows, summarySignature]);
1665
1824
  const domRange = (0, import_react5.useMemo)(() => {
1666
1825
  if (!isVirtualized) return { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
1667
1826
  const currentScroll2 = scrollRef.current;
@@ -1803,11 +1962,163 @@ function Table({
1803
1962
  const scroll = scrollRef.current;
1804
1963
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
1805
1964
  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 };
1806
- 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 });
1807
- }, [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]);
1965
+ 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 });
1966
+ }, [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]);
1808
1967
  const scheduleDraw = (0, import_react5.useCallback)(() => {
1809
1968
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
1810
1969
  }, [draw]);
1970
+ const drawImmediately = (0, import_react5.useCallback)(() => {
1971
+ if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
1972
+ frameRef.current = null;
1973
+ draw();
1974
+ }, [draw]);
1975
+ const getSelectionHorizontalVisibility = (0, import_react5.useCallback)((scrollLeft) => {
1976
+ if (!selection || selectionRange) return "visible";
1977
+ const column = columns[selection.columnIndex];
1978
+ if (!column || column.fixed !== void 0) return "visible";
1979
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1980
+ const left = getScrollableColumnLeft(selection.columnIndex) - scrollLeft;
1981
+ const right = left + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1982
+ if (right <= fixedWidth) return "hidden-left";
1983
+ if (left >= viewport.width - rightFixedWidth) return "hidden-right";
1984
+ return "visible";
1985
+ }, [columns, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, rightFixedWidth, selection, selectionRange, viewport.width]);
1986
+ const getSelectionEdgeIndicator = (0, import_react5.useCallback)((scrollLeft, scrollTop) => {
1987
+ if (!selection || selectionRange) return null;
1988
+ const row = rows[selection.rowIndex];
1989
+ const metric = metrics[selection.columnIndex];
1990
+ const column = columns[selection.columnIndex];
1991
+ if (!row || !metric || !column || getRowKey(row, selection.rowIndex) !== selection.rowKey) return null;
1992
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1993
+ 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;
1994
+ const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1995
+ const rawTop = bodyTop + selection.rowIndex * rowHeight - scrollTop;
1996
+ const rawBottom = rawTop + rowHeight * (span?.rowSpan ?? 1);
1997
+ const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
1998
+ const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
1999
+ const verticalStart = fixedHeader ? bodyTop : 0;
2000
+ const hiddenLeft = column.fixed === void 0 && rawRight <= horizontalStart;
2001
+ const hiddenRight = column.fixed === void 0 && rawLeft >= horizontalEnd;
2002
+ const hiddenTop = rawBottom <= verticalStart;
2003
+ const hiddenBottom = rawTop >= renderHeight;
2004
+ if (!hiddenLeft && !hiddenRight && !hiddenTop && !hiddenBottom) return null;
2005
+ const borderWidth = 2;
2006
+ const cornerLength = 10;
2007
+ const hitSlop = 6;
2008
+ const targetSize = borderWidth + hitSlop * 2;
2009
+ const rightBoundaryInset = rightFixedWidth > 0 && hasVerticalBorders ? 1 : 0;
2010
+ const markerX = hiddenLeft ? horizontalStart : horizontalEnd - borderWidth - rightBoundaryInset;
2011
+ const markerY = hiddenTop ? verticalStart : renderHeight - borderWidth;
2012
+ if (hiddenLeft || hiddenRight) {
2013
+ let markerTop = Math.max(rawTop - borderWidth, verticalStart);
2014
+ let markerBottom = Math.min(rawBottom + 1, renderHeight);
2015
+ const topClip = Math.max(0, verticalStart - (rawTop - borderWidth));
2016
+ const bottomClip = Math.max(0, rawBottom + 1 - renderHeight);
2017
+ if (topClip > 0 || bottomClip > 0) {
2018
+ const foldAtTop = topClip > 0;
2019
+ const fold = Math.min(cornerLength, foldAtTop ? topClip : bottomClip);
2020
+ if (fold > borderWidth) {
2021
+ const cornerWidth = fold;
2022
+ const cornerHeight = Math.max(cornerLength, markerBottom - markerTop);
2023
+ const cornerOnLeft = hiddenLeft;
2024
+ const shapeLeft = cornerOnLeft ? markerX : markerX + borderWidth - cornerWidth;
2025
+ const shapeTop = foldAtTop ? verticalStart : renderHeight - cornerHeight;
2026
+ return {
2027
+ side: `${foldAtTop ? "top" : "bottom"}-${cornerOnLeft ? "left" : "right"}`,
2028
+ orientation: "corner",
2029
+ style: {
2030
+ left: shapeLeft - hitSlop,
2031
+ top: shapeTop - hitSlop,
2032
+ width: cornerWidth + hitSlop * 2,
2033
+ height: cornerHeight + hitSlop * 2
2034
+ },
2035
+ corner: { width: cornerWidth, height: cornerHeight }
2036
+ };
2037
+ }
2038
+ }
2039
+ if (markerBottom <= markerTop) return null;
2040
+ return {
2041
+ side: hiddenLeft ? "left" : "right",
2042
+ orientation: "vertical",
2043
+ viewportEdge: hiddenLeft && horizontalStart === 0 ? "left" : hiddenRight && horizontalEnd === viewport.width ? "right" : void 0,
2044
+ style: {
2045
+ left: markerX - hitSlop,
2046
+ top: markerTop,
2047
+ width: targetSize,
2048
+ height: markerBottom - markerTop
2049
+ }
2050
+ };
2051
+ }
2052
+ const horizontalMarkerEnd = horizontalEnd - rightBoundaryInset;
2053
+ let markerLeft = Math.max(rawLeft - borderWidth, horizontalStart);
2054
+ let markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2055
+ const leftClip = Math.max(0, horizontalStart - (rawLeft - borderWidth));
2056
+ const rightClip = Math.max(0, rawRight + 1 - horizontalMarkerEnd);
2057
+ if (leftClip > 0 || rightClip > 0) {
2058
+ const foldAtLeft = leftClip > 0;
2059
+ const fold = Math.min(cornerLength, foldAtLeft ? leftClip : rightClip);
2060
+ if (fold > borderWidth) {
2061
+ const cornerWidth = Math.max(cornerLength, markerRight - markerLeft);
2062
+ const cornerHeight = fold;
2063
+ const shapeLeft = foldAtLeft ? horizontalStart : horizontalMarkerEnd - cornerWidth;
2064
+ const shapeTop = hiddenTop ? verticalStart : renderHeight - cornerHeight;
2065
+ return {
2066
+ side: `${hiddenTop ? "top" : "bottom"}-${foldAtLeft ? "left" : "right"}`,
2067
+ orientation: "corner",
2068
+ style: {
2069
+ left: shapeLeft - hitSlop,
2070
+ top: shapeTop - hitSlop,
2071
+ width: cornerWidth + hitSlop * 2,
2072
+ height: cornerHeight + hitSlop * 2
2073
+ },
2074
+ corner: { width: cornerWidth, height: cornerHeight }
2075
+ };
2076
+ }
2077
+ }
2078
+ if (markerRight <= markerLeft) return null;
2079
+ return {
2080
+ side: hiddenTop ? "top" : "bottom",
2081
+ orientation: "horizontal",
2082
+ viewportEdge: hiddenTop && verticalStart === 0 ? "top" : hiddenBottom ? "bottom" : void 0,
2083
+ style: {
2084
+ left: markerLeft,
2085
+ top: markerY - hitSlop,
2086
+ width: markerRight - markerLeft,
2087
+ height: targetSize
2088
+ }
2089
+ };
2090
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, hasVerticalBorders, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2091
+ const syncSelectionEdgeTarget = (0, import_react5.useCallback)((scrollLeft, scrollTop) => {
2092
+ const target = selectionEdgeTargetRef.current;
2093
+ if (!target) return;
2094
+ const indicator = getSelectionEdgeIndicator(scrollLeft, scrollTop);
2095
+ if (!indicator) {
2096
+ target.style.visibility = "hidden";
2097
+ return;
2098
+ }
2099
+ const orientationChanged = !target.classList.contains(`is-${indicator.orientation}`);
2100
+ target.className = `rvg-selection-edge-target is-${indicator.orientation} is-${indicator.side}${indicator.viewportEdge ? ` is-viewport-${indicator.viewportEdge}` : ""}${orientationChanged ? " is-shape-syncing" : ""}`;
2101
+ target.style.left = `${Number(indicator.style.left)}px`;
2102
+ target.style.top = `${Number(indicator.style.top)}px`;
2103
+ target.style.width = `${Number(indicator.style.width)}px`;
2104
+ target.style.height = `${Number(indicator.style.height)}px`;
2105
+ if (indicator.corner) {
2106
+ target.style.setProperty("--rvg-corner-width", `${indicator.corner.width}px`);
2107
+ target.style.setProperty("--rvg-corner-height", `${indicator.corner.height}px`);
2108
+ target.style.setProperty("--rvg-corner-hover-width-extension", "4px");
2109
+ target.style.setProperty("--rvg-corner-hover-height-extension", "4px");
2110
+ } else {
2111
+ target.style.removeProperty("--rvg-corner-width");
2112
+ target.style.removeProperty("--rvg-corner-height");
2113
+ target.style.removeProperty("--rvg-corner-hover-width-extension");
2114
+ target.style.removeProperty("--rvg-corner-hover-height-extension");
2115
+ }
2116
+ target.style.visibility = "visible";
2117
+ if (orientationChanged) {
2118
+ void target.offsetWidth;
2119
+ target.classList.remove("is-shape-syncing");
2120
+ }
2121
+ }, [getSelectionEdgeIndicator]);
1811
2122
  (0, import_react5.useLayoutEffect)(() => {
1812
2123
  const element = scrollerRef.current;
1813
2124
  if (!element) return;
@@ -1841,6 +2152,8 @@ function Table({
1841
2152
  if (sortDebounceRef.current !== null) clearTimeout(sortDebounceRef.current);
1842
2153
  if (textFrameRef.current !== null) cancelAnimationFrame(textFrameRef.current);
1843
2154
  if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
2155
+ if (verticalScrollbarVisibilityTimerRef.current !== null) clearTimeout(verticalScrollbarVisibilityTimerRef.current);
2156
+ if (horizontalScrollbarVisibilityTimerRef.current !== null) clearTimeout(horizontalScrollbarVisibilityTimerRef.current);
1844
2157
  }, []);
1845
2158
  (0, import_react5.useEffect)(() => {
1846
2159
  const scroller = scrollerRef.current;
@@ -1892,10 +2205,218 @@ function Table({
1892
2205
  scroller.addEventListener("scroll", lockScroll);
1893
2206
  return () => scroller.removeEventListener("scroll", lockScroll);
1894
2207
  }, [insertBusy]);
2208
+ const showCustomScrollbar = (0, import_react5.useCallback)((axis, persist = false) => {
2209
+ const showVertical = axis === "vertical" || axis === "both";
2210
+ const showHorizontal = axis === "horizontal" || axis === "both";
2211
+ if (showVertical) {
2212
+ verticalScrollbarRef.current?.classList.add("is-active");
2213
+ if (verticalScrollbarVisibilityTimerRef.current !== null) clearTimeout(verticalScrollbarVisibilityTimerRef.current);
2214
+ if (!persist) {
2215
+ verticalScrollbarVisibilityTimerRef.current = setTimeout(() => {
2216
+ verticalScrollbarRef.current?.classList.remove("is-active");
2217
+ verticalScrollbarVisibilityTimerRef.current = null;
2218
+ }, 900);
2219
+ }
2220
+ }
2221
+ if (showHorizontal) {
2222
+ horizontalScrollbarRef.current?.classList.add("is-active");
2223
+ if (horizontalScrollbarVisibilityTimerRef.current !== null) clearTimeout(horizontalScrollbarVisibilityTimerRef.current);
2224
+ if (!persist) {
2225
+ horizontalScrollbarVisibilityTimerRef.current = setTimeout(() => {
2226
+ horizontalScrollbarRef.current?.classList.remove("is-active");
2227
+ horizontalScrollbarVisibilityTimerRef.current = null;
2228
+ }, 900);
2229
+ }
2230
+ }
2231
+ }, []);
2232
+ const keepCustomScrollbarVisible = (0, import_react5.useCallback)((axis) => {
2233
+ showCustomScrollbar(axis, true);
2234
+ }, [showCustomScrollbar]);
2235
+ const releaseCustomScrollbarVisible = (0, import_react5.useCallback)((axis) => {
2236
+ if (axis === "vertical" && verticalScrollbarDragRef.current) return;
2237
+ if (axis === "horizontal" && horizontalScrollbarDragRef.current) return;
2238
+ showCustomScrollbar(axis);
2239
+ }, [showCustomScrollbar]);
2240
+ const updateCustomScrollbars = (0, import_react5.useCallback)(() => {
2241
+ const scroller = scrollerRef.current;
2242
+ const verticalTrack = verticalScrollbarRef.current;
2243
+ const verticalThumb = verticalScrollbarThumbRef.current;
2244
+ const horizontalTrack = horizontalScrollbarRef.current;
2245
+ const horizontalThumb = horizontalScrollbarThumbRef.current;
2246
+ if (!scroller || !verticalTrack || !verticalThumb || !horizontalTrack || !horizontalThumb) return;
2247
+ const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
2248
+ if (maxScrollTop <= 1 || renderHeight <= 0) {
2249
+ verticalTrack.style.display = "none";
2250
+ } else {
2251
+ const top = fixedHeader ? bodyTop : 0;
2252
+ const bottom = bottomSummaryHeight;
2253
+ const trackHeight = Math.max(0, effectiveHeight - top - bottom);
2254
+ if (trackHeight <= 0) {
2255
+ verticalTrack.style.display = "none";
2256
+ } else {
2257
+ const thumbHeight = Math.max(28, Math.round(trackHeight * (scroller.clientHeight / scroller.scrollHeight)));
2258
+ const maxThumbTop = Math.max(0, trackHeight - thumbHeight);
2259
+ const thumbTop = maxScrollTop > 0 ? Math.round(scroller.scrollTop / maxScrollTop * maxThumbTop) : 0;
2260
+ verticalTrack.style.display = "block";
2261
+ verticalTrack.style.top = `${top}px`;
2262
+ verticalTrack.style.bottom = `${bottom}px`;
2263
+ verticalThumb.style.height = `${Math.min(trackHeight, thumbHeight)}px`;
2264
+ verticalThumb.style.transform = `translateY(${thumbTop}px)`;
2265
+ }
2266
+ }
2267
+ const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;
2268
+ if (maxScrollLeft <= 1 || viewport.width <= 0) {
2269
+ horizontalTrack.style.display = "none";
2270
+ return;
2271
+ }
2272
+ const trackLeft = fixedWidth;
2273
+ const trackRight = rightFixedWidth;
2274
+ const trackWidth = Math.max(0, viewport.width - trackLeft - trackRight);
2275
+ if (trackWidth <= 0) {
2276
+ horizontalTrack.style.display = "none";
2277
+ return;
2278
+ }
2279
+ const thumbWidth = Math.max(36, Math.round(trackWidth * (scroller.clientWidth / scroller.scrollWidth)));
2280
+ const maxThumbLeft = Math.max(0, trackWidth - thumbWidth);
2281
+ const thumbLeft = maxScrollLeft > 0 ? Math.round(scroller.scrollLeft / maxScrollLeft * maxThumbLeft) : 0;
2282
+ horizontalTrack.style.display = "block";
2283
+ horizontalTrack.style.left = `${trackLeft}px`;
2284
+ horizontalTrack.style.right = `${trackRight}px`;
2285
+ horizontalTrack.style.bottom = `${bottomSummaryHeight + 2}px`;
2286
+ horizontalThumb.style.width = `${Math.min(trackWidth, thumbWidth)}px`;
2287
+ horizontalThumb.style.transform = `translateX(${thumbLeft}px)`;
2288
+ }, [bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, renderHeight, rightFixedWidth, viewport.width]);
2289
+ (0, import_react5.useLayoutEffect)(() => {
2290
+ updateCustomScrollbars();
2291
+ }, [contentHeight, contentWidth, updateCustomScrollbars, viewport.height, viewport.width]);
2292
+ const handleVerticalScrollbarPointerDown = (0, import_react5.useCallback)((event) => {
2293
+ const scroller = scrollerRef.current;
2294
+ const track = verticalScrollbarRef.current;
2295
+ const thumb = verticalScrollbarThumbRef.current;
2296
+ if (!scroller || !track || !thumb || event.button !== 0) return;
2297
+ event.preventDefault();
2298
+ event.currentTarget.setPointerCapture(event.pointerId);
2299
+ const trackRect = track.getBoundingClientRect();
2300
+ const thumbRect = thumb.getBoundingClientRect();
2301
+ const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
2302
+ const trackHeight = trackRect.height;
2303
+ const thumbHeight = thumbRect.height;
2304
+ showCustomScrollbar("vertical", true);
2305
+ if (event.target === thumb) {
2306
+ verticalScrollbarDragRef.current = {
2307
+ pointerId: event.pointerId,
2308
+ startY: event.clientY,
2309
+ startTop: thumbRect.top - trackRect.top,
2310
+ trackHeight,
2311
+ thumbHeight,
2312
+ maxScrollTop
2313
+ };
2314
+ return;
2315
+ }
2316
+ const nextThumbTop = Math.max(0, Math.min(trackHeight - thumbHeight, event.clientY - trackRect.top - thumbHeight / 2));
2317
+ const maxThumbTop = Math.max(1, trackHeight - thumbHeight);
2318
+ scroller.scrollTop = nextThumbTop / maxThumbTop * maxScrollTop;
2319
+ updateCustomScrollbars();
2320
+ verticalScrollbarDragRef.current = {
2321
+ pointerId: event.pointerId,
2322
+ startY: event.clientY,
2323
+ startTop: nextThumbTop,
2324
+ trackHeight,
2325
+ thumbHeight,
2326
+ maxScrollTop
2327
+ };
2328
+ }, [showCustomScrollbar, updateCustomScrollbars]);
2329
+ const handleVerticalScrollbarPointerMove = (0, import_react5.useCallback)((event) => {
2330
+ const drag = verticalScrollbarDragRef.current;
2331
+ const scroller = scrollerRef.current;
2332
+ if (!drag || !scroller || drag.pointerId !== event.pointerId) return;
2333
+ event.preventDefault();
2334
+ const maxThumbTop = Math.max(1, drag.trackHeight - drag.thumbHeight);
2335
+ const nextThumbTop = Math.max(0, Math.min(maxThumbTop, drag.startTop + event.clientY - drag.startY));
2336
+ scroller.scrollTop = nextThumbTop / maxThumbTop * drag.maxScrollTop;
2337
+ updateCustomScrollbars();
2338
+ }, [updateCustomScrollbars]);
2339
+ const handleVerticalScrollbarPointerUp = (0, import_react5.useCallback)((event) => {
2340
+ const drag = verticalScrollbarDragRef.current;
2341
+ if (!drag || drag.pointerId !== event.pointerId) return;
2342
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2343
+ verticalScrollbarDragRef.current = null;
2344
+ showCustomScrollbar("vertical");
2345
+ }, [showCustomScrollbar]);
2346
+ const handleHorizontalScrollbarPointerDown = (0, import_react5.useCallback)((event) => {
2347
+ const scroller = scrollerRef.current;
2348
+ const track = horizontalScrollbarRef.current;
2349
+ const thumb = horizontalScrollbarThumbRef.current;
2350
+ if (!scroller || !track || !thumb || event.button !== 0) return;
2351
+ event.preventDefault();
2352
+ event.currentTarget.setPointerCapture(event.pointerId);
2353
+ const trackRect = track.getBoundingClientRect();
2354
+ const thumbRect = thumb.getBoundingClientRect();
2355
+ const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;
2356
+ const trackWidth = trackRect.width;
2357
+ const thumbWidth = thumbRect.width;
2358
+ showCustomScrollbar("horizontal", true);
2359
+ if (event.target === thumb) {
2360
+ horizontalScrollbarDragRef.current = {
2361
+ pointerId: event.pointerId,
2362
+ startX: event.clientX,
2363
+ startLeft: thumbRect.left - trackRect.left,
2364
+ trackWidth,
2365
+ thumbWidth,
2366
+ maxScrollLeft
2367
+ };
2368
+ return;
2369
+ }
2370
+ const nextThumbLeft = Math.max(0, Math.min(trackWidth - thumbWidth, event.clientX - trackRect.left - thumbWidth / 2));
2371
+ const maxThumbLeft = Math.max(1, trackWidth - thumbWidth);
2372
+ const previousScrollLeft = scroller.scrollLeft;
2373
+ const nextScrollLeft = nextThumbLeft / maxThumbLeft * maxScrollLeft;
2374
+ const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
2375
+ scroller.scrollLeft = nextScrollLeft;
2376
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2377
+ syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
2378
+ if (visibilityChanged) drawImmediately();
2379
+ updateCustomScrollbars();
2380
+ horizontalScrollbarDragRef.current = {
2381
+ pointerId: event.pointerId,
2382
+ startX: event.clientX,
2383
+ startLeft: nextThumbLeft,
2384
+ trackWidth,
2385
+ thumbWidth,
2386
+ maxScrollLeft
2387
+ };
2388
+ }, [drawImmediately, getSelectionHorizontalVisibility, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars]);
2389
+ const handleHorizontalScrollbarPointerMove = (0, import_react5.useCallback)((event) => {
2390
+ const drag = horizontalScrollbarDragRef.current;
2391
+ const scroller = scrollerRef.current;
2392
+ if (!drag || !scroller || drag.pointerId !== event.pointerId) return;
2393
+ event.preventDefault();
2394
+ const maxThumbLeft = Math.max(1, drag.trackWidth - drag.thumbWidth);
2395
+ const nextThumbLeft = Math.max(0, Math.min(maxThumbLeft, drag.startLeft + event.clientX - drag.startX));
2396
+ const previousScrollLeft = scroller.scrollLeft;
2397
+ const nextScrollLeft = nextThumbLeft / maxThumbLeft * drag.maxScrollLeft;
2398
+ const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
2399
+ scroller.scrollLeft = nextScrollLeft;
2400
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2401
+ syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
2402
+ if (visibilityChanged) drawImmediately();
2403
+ updateCustomScrollbars();
2404
+ }, [drawImmediately, getSelectionHorizontalVisibility, syncSelectionEdgeTarget, updateCustomScrollbars]);
2405
+ const handleHorizontalScrollbarPointerUp = (0, import_react5.useCallback)((event) => {
2406
+ const drag = horizontalScrollbarDragRef.current;
2407
+ if (!drag || drag.pointerId !== event.pointerId) return;
2408
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2409
+ horizontalScrollbarDragRef.current = null;
2410
+ showCustomScrollbar("horizontal");
2411
+ }, [showCustomScrollbar]);
1895
2412
  const handleScroll = (0, import_react5.useCallback)(() => {
1896
2413
  const element = scrollerRef.current;
1897
2414
  if (!element) return;
2415
+ const previousScroll = scrollRef.current;
2416
+ const scrollDeltaX = element.scrollLeft - previousScroll.left;
2417
+ const scrollDeltaY = element.scrollTop - previousScroll.top;
1898
2418
  scrollRef.current = { left: element.scrollLeft, top: element.scrollTop };
2419
+ syncSelectionEdgeTarget(element.scrollLeft, element.scrollTop);
1899
2420
  const focus = selectionFocusRef.current;
1900
2421
  if (focus && selection && !selectionRange) {
1901
2422
  const row = rows[selection.rowIndex];
@@ -1905,7 +2426,7 @@ function Table({
1905
2426
  if (!validSelection) {
1906
2427
  focus.style.display = "none";
1907
2428
  } else {
1908
- const rawLeft = column.fixed === "left" ? metric.left : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : metric.left - element.scrollLeft;
2429
+ 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;
1909
2430
  const rawTop = bodyTop + selection.rowIndex * rowHeight - element.scrollTop;
1910
2431
  const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1911
2432
  const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
@@ -1917,9 +2438,9 @@ function Table({
1917
2438
  focus.style.display = "none";
1918
2439
  } else {
1919
2440
  const borderWidth = 2;
1920
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
2441
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
1921
2442
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
1922
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
2443
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
1923
2444
  const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
1924
2445
  focus.style.display = "block";
1925
2446
  focus.style.left = `${left}px`;
@@ -1938,6 +2459,10 @@ function Table({
1938
2459
  if (scrollingTextRef.current) scrollingTextRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
1939
2460
  if (scrollingHeaderIconsRef.current) scrollingHeaderIconsRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
1940
2461
  if (headerIconsRef.current) headerIconsRef.current.style.transform = `translateY(${fixedHeader ? 0 : -element.scrollTop}px)`;
2462
+ updateCustomScrollbars();
2463
+ if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {
2464
+ showCustomScrollbar(scrollDeltaX !== 0 && scrollDeltaY !== 0 ? "both" : scrollDeltaX !== 0 ? "horizontal" : "vertical");
2465
+ }
1941
2466
  if (hoveredHeaderActionRef.current) {
1942
2467
  hoveredHeaderActionRef.current = null;
1943
2468
  setHoveredHeaderAction(null);
@@ -1945,7 +2470,7 @@ function Table({
1945
2470
  setHoveredCellTooltip(null);
1946
2471
  scheduleDraw();
1947
2472
  setEditing(null);
1948
- }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
2473
+ }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars, viewport.width]);
1949
2474
  const locateColumn = (0, import_react5.useCallback)((clientX) => {
1950
2475
  const canvas = canvasRef.current;
1951
2476
  if (!canvas) return -1;
@@ -1953,7 +2478,11 @@ function Table({
1953
2478
  const localX = clientX - rect.left;
1954
2479
  let columnIndex;
1955
2480
  if (localX < fixedWidth) {
1956
- columnIndex = hitTestColumn(metrics, localX);
2481
+ columnIndex = columns.findIndex((column, index) => {
2482
+ if (column.fixed !== "left") return false;
2483
+ const left = leftFixedOffsets.get(index) ?? 0;
2484
+ return localX >= left && localX < left + metrics[index].width;
2485
+ });
1957
2486
  } else if (rightFixedWidth > 0 && localX >= viewport.width - rightFixedWidth) {
1958
2487
  columnIndex = columns.findIndex((column, index) => {
1959
2488
  if (column.fixed !== "right") return false;
@@ -1961,41 +2490,33 @@ function Table({
1961
2490
  return localX >= left && localX < left + metrics[index].width;
1962
2491
  });
1963
2492
  } else {
1964
- columnIndex = hitTestColumn(metrics, localX + scrollRef.current.left);
2493
+ const scrollX = localX + scrollRef.current.left;
2494
+ columnIndex = columns.findIndex((column, index) => {
2495
+ if (column.fixed !== void 0) return false;
2496
+ const left = getScrollableColumnLeft(index);
2497
+ return scrollX >= left && scrollX < left + metrics[index].width;
2498
+ });
1965
2499
  }
1966
2500
  return columnIndex;
1967
- }, [columns, fixedWidth, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
2501
+ }, [columns, fixedWidth, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
1968
2502
  const getDisplayedColumnLeft = (0, import_react5.useCallback)((columnIndex) => {
1969
2503
  const metric = metrics[columnIndex];
1970
- if (columns[columnIndex].fixed === "left") return metric.left;
2504
+ if (columns[columnIndex].fixed === "left") return leftFixedOffsets.get(columnIndex) ?? 0;
1971
2505
  if (columns[columnIndex].fixed === "right") return viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metric.width;
1972
- return metric.left - scrollRef.current.left;
1973
- }, [columns, metrics, rightFixedOffsets, viewport.width]);
1974
- const locateResizeHandle = (0, import_react5.useCallback)((clientX) => {
1975
- if (!columnResizable || !canvasRef.current) return -1;
1976
- const localX = clientX - canvasRef.current.getBoundingClientRect().left;
1977
- let closest = -1;
1978
- let distance = 6;
1979
- for (let index = 0; index < columns.length; index += 1) {
1980
- if (columns[index].rowSelection || columns[index].rowDragHandle || columns[index].rowNumber) continue;
1981
- const nextDistance = Math.abs(localX - (getDisplayedColumnLeft(index) + metrics[index].width));
1982
- if (nextDistance < distance) {
1983
- distance = nextDistance;
1984
- closest = index;
1985
- }
1986
- }
1987
- return closest;
1988
- }, [columnResizable, columns, getDisplayedColumnLeft, metrics]);
1989
- const findResizeNeighbor = (0, import_react5.useCallback)((columnIndex) => {
1990
- const resizedColumn = columns[columnIndex];
1991
- for (let index = columnIndex + 1; index < columns.length; index += 1) {
1992
- const column = columns[index];
1993
- if (column.rowSelection || column.rowDragHandle || column.rowNumber) continue;
1994
- if (resizedColumn?.fixed && column.fixed !== resizedColumn.fixed) return -1;
1995
- return index;
2506
+ return getScrollableColumnLeft(columnIndex) - scrollRef.current.left;
2507
+ }, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
2508
+ const getHeaderLevelAtY = (0, import_react5.useCallback)((localY, headerY) => {
2509
+ const y = localY - headerY;
2510
+ for (let level = 0; level < headerRowHeights.length; level += 1) {
2511
+ const top = headerRowOffsets[level] ?? 0;
2512
+ if (y >= top && y < top + headerRowHeights[level]) return level;
1996
2513
  }
1997
- return -1;
1998
- }, [columns]);
2514
+ return headerDepth - 1;
2515
+ }, [headerDepth, headerRowHeights, headerRowOffsets]);
2516
+ const resizeColumn = (0, import_react5.useCallback)((columnKey, width2) => {
2517
+ setResizedColumnWidths((current) => current[columnKey] === width2 ? current : { ...current, [columnKey]: width2 });
2518
+ onColumnResize?.(columnKey, width2);
2519
+ }, [onColumnResize]);
1999
2520
  const locateHeaderAction = (0, import_react5.useCallback)((clientX, columnIndex) => {
2000
2521
  if (columnIndex < 0 || !canvasRef.current) return null;
2001
2522
  const column = columns[columnIndex];
@@ -2030,11 +2551,97 @@ function Table({
2030
2551
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2031
2552
  return localX >= columnRight - 16 && localX < columnRight - 5;
2032
2553
  }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2033
- const setDragGuide = (0, import_react5.useCallback)((x) => {
2554
+ const getHeaderCellLeft = (0, import_react5.useCallback)((cell) => {
2555
+ const startMetric = metrics[cell.startIndex];
2556
+ const fixedSide = columns[cell.startIndex]?.fixed;
2557
+ if (!startMetric) return 0;
2558
+ 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;
2559
+ }, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
2560
+ const getHeaderCellWidth = (0, import_react5.useCallback)((cell) => cell.leaf ? metrics[cell.startIndex]?.width ?? 0 : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1), [getCellDisplayWidth, metrics]);
2561
+ const getHeaderCellEdgeFromMetrics = (0, import_react5.useCallback)((startIndex, endIndex, edge, nextMetrics) => {
2562
+ const fixedSide = columns[startIndex]?.fixed;
2563
+ const width2 = nextMetrics.slice(startIndex, endIndex + 1).reduce((total, metric) => total + metric.width, 0);
2564
+ let left;
2565
+ if (fixedSide === "left") {
2566
+ left = 0;
2567
+ for (let index = 0; index < startIndex; index += 1) {
2568
+ if (columns[index].fixed === "left") left += nextMetrics[index]?.width ?? 0;
2569
+ }
2570
+ } else if (fixedSide === "right") {
2571
+ let rightOffset = 0;
2572
+ for (let index = columns.length - 1; index > startIndex; index -= 1) {
2573
+ if (columns[index].fixed === "right") rightOffset += nextMetrics[index]?.width ?? 0;
2574
+ }
2575
+ left = viewport.width - rightOffset - width2;
2576
+ } else {
2577
+ left = 0;
2578
+ for (let index = 0; index < startIndex; index += 1) {
2579
+ if (columns[index].fixed === void 0) left += nextMetrics[index]?.width ?? 0;
2580
+ }
2581
+ left += fixedWidth - scrollRef.current.left;
2582
+ }
2583
+ return left + (edge === "right" ? width2 : 0);
2584
+ }, [columns, fixedWidth, viewport.width]);
2585
+ const locateHeaderResizeHit = (0, import_react5.useCallback)((clientX, clientY) => {
2586
+ if (!columnResizable || !canvasRef.current) return null;
2587
+ const rect = canvasRef.current.getBoundingClientRect();
2588
+ const localX = clientX - rect.left;
2589
+ const localY = clientY - rect.top;
2590
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2591
+ if (localY < headerY || localY >= headerY + headerHeight) return null;
2592
+ const level = getHeaderLevelAtY(localY, headerY);
2593
+ let closest = null;
2594
+ let distance = 6;
2595
+ let priority = -1;
2596
+ for (const cell of headerCells) {
2597
+ if (cell.level !== level) continue;
2598
+ if (columns[cell.startIndex]?.rowSelection || columns[cell.startIndex]?.rowDragHandle || columns[cell.startIndex]?.rowNumber) continue;
2599
+ const fixedSide = columns[cell.startIndex]?.fixed;
2600
+ const edge = fixedSide === "right" && cell.leaf ? "left" : "right";
2601
+ const edgeX = getHeaderCellLeft(cell) + (edge === "right" ? getHeaderCellWidth(cell) : 0);
2602
+ const nextDistance = Math.abs(localX - edgeX);
2603
+ const nextPriority = fixedSide === "right" ? 2 : fixedSide === "left" ? 1 : 0;
2604
+ if (nextDistance < distance || nextDistance === distance && nextPriority > priority) {
2605
+ distance = nextDistance;
2606
+ priority = nextPriority;
2607
+ closest = { cell, edge };
2608
+ }
2609
+ }
2610
+ return closest;
2611
+ }, [columnResizable, columns, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, getHeaderLevelAtY, headerCells, headerHeight]);
2612
+ const locateHeaderResizeCell = (0, import_react5.useCallback)((clientX, clientY) => locateHeaderResizeHit(clientX, clientY)?.cell ?? null, [locateHeaderResizeHit]);
2613
+ const locateHeaderCellDragHandle = (0, import_react5.useCallback)((clientX, cell) => {
2614
+ if (!columnDraggable || !canvasRef.current) return false;
2615
+ if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
2616
+ const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2617
+ const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
2618
+ return localX >= right - 16 && localX < right - 5;
2619
+ }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
2620
+ const locateHeaderCell = (0, import_react5.useCallback)((clientX, clientY) => {
2621
+ const canvas = canvasRef.current;
2622
+ if (!canvas) return null;
2623
+ const rect = canvas.getBoundingClientRect();
2624
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2625
+ const localX = clientX - rect.left;
2626
+ const localY = clientY - rect.top;
2627
+ if (localY < headerY || localY >= headerY + headerHeight) return null;
2628
+ const level = getHeaderLevelAtY(localY, headerY);
2629
+ for (const cell of headerCells) {
2630
+ if (cell.level !== level) continue;
2631
+ const left = getHeaderCellLeft(cell);
2632
+ const width2 = getHeaderCellWidth(cell);
2633
+ if (localX >= left && localX < left + width2) return cell;
2634
+ }
2635
+ return null;
2636
+ }, [fixedHeader, getHeaderCellLeft, getHeaderCellWidth, getHeaderLevelAtY, headerCells, headerHeight]);
2637
+ const setDragGuide = (0, import_react5.useCallback)((x, top = 0) => {
2034
2638
  const guide = dragGuideRef.current;
2035
2639
  if (!guide) return;
2036
2640
  guide.style.display = x === null ? "none" : "block";
2037
- if (x !== null) guide.style.transform = `translateX(${Math.round(x)}px)`;
2641
+ if (x !== null) {
2642
+ guide.style.top = `${top}px`;
2643
+ guide.style.transform = `translateX(${Math.round(x)}px)`;
2644
+ }
2038
2645
  }, []);
2039
2646
  const setRowDragGuide = (0, import_react5.useCallback)((y) => {
2040
2647
  const guide = rowDragGuideRef.current;
@@ -2052,6 +2659,15 @@ function Table({
2052
2659
  (0, import_react5.useEffect)(() => {
2053
2660
  const canvas = canvasRef.current;
2054
2661
  if (!canvas || !columnDraggable && !rowDraggable) return;
2662
+ const getColumnDropIndex = (sourceIndex, targetIndex, edge) => {
2663
+ if (edge === "right") return targetIndex;
2664
+ const destinationIndex = targetIndex - (sourceIndex < targetIndex ? 1 : 0);
2665
+ return Math.max(0, Math.min(columns.length - 1, destinationIndex));
2666
+ };
2667
+ const getColumnDropPlacement = (sourceIndex, targetIndex, edge) => {
2668
+ if (edge !== "right") return "before";
2669
+ return targetIndex < sourceIndex ? "before" : "after";
2670
+ };
2055
2671
  const getHeaderColumn = (clientX, clientY) => {
2056
2672
  const rect = canvas.getBoundingClientRect();
2057
2673
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
@@ -2059,22 +2675,35 @@ function Table({
2059
2675
  if (localY < headerY || localY >= headerY + headerHeight) return -1;
2060
2676
  return locateColumn(clientX);
2061
2677
  };
2678
+ const getHeaderDragCell = (clientX, clientY) => {
2679
+ const cell = locateHeaderCell(clientX, clientY);
2680
+ if (!cell) return null;
2681
+ return locateHeaderCellDragHandle(clientX, cell) && !locateHeaderResizeCell(clientX, clientY) ? cell : null;
2682
+ };
2062
2683
  const cleanupDrag = (0, import_adapter.draggable)({
2063
2684
  element: canvas,
2064
2685
  canDrag: ({ input }) => {
2065
- const index = getHeaderColumn(input.clientX, input.clientY);
2066
- if (columnDraggable && index >= 0) return locateHeaderDragHandle(input.clientX, index) && locateResizeHandle(input.clientX) < 0;
2686
+ const headerDragCell = columnDraggable ? getHeaderDragCell(input.clientX, input.clientY) : null;
2687
+ if (headerDragCell) return true;
2067
2688
  const columnIndex = locateColumn(input.clientX);
2068
2689
  return rowDraggable && columnIndex >= 0 && Boolean(columns[columnIndex].rowDragHandle) && locateRow(input.clientY) >= 0;
2069
2690
  },
2070
2691
  getInitialData: ({ input }) => {
2071
- const headerColumn = getHeaderColumn(input.clientX, input.clientY);
2072
- if (headerColumn < 0) {
2692
+ const headerDragCell = columnDraggable ? getHeaderDragCell(input.clientX, input.clientY) : null;
2693
+ if (!headerDragCell) {
2073
2694
  return { type: "table-row", sourceIndex: locateRow(input.clientY) };
2074
2695
  }
2075
- const sourceIndex = headerColumn;
2696
+ const sourceIndex = headerDragCell.startIndex;
2076
2697
  columnDragPreviewRef.current = { sourceIndex, targetIndex: sourceIndex };
2077
- return { type: "table-column", sourceIndex };
2698
+ return {
2699
+ type: "table-column",
2700
+ sourceIndex,
2701
+ sourceKey: headerDragCell.key,
2702
+ parentKey: headerDragCell.parentKey,
2703
+ headerLevel: headerDragCell.level,
2704
+ headerKind: headerDragCell.leaf ? "column" : "group",
2705
+ fixed: columns[headerDragCell.startIndex]?.fixed
2706
+ };
2078
2707
  },
2079
2708
  onGenerateDragPreview: ({ nativeSetDragImage, source }) => {
2080
2709
  const sourceIndex = Number(source.data.sourceIndex);
@@ -2177,31 +2806,35 @@ function Table({
2177
2806
  return;
2178
2807
  }
2179
2808
  const targetIndex = Number(target?.targetIndex);
2180
- if (!Number.isInteger(targetIndex) || columns[targetIndex]?.fixed !== columns[sourceIndex]?.fixed) {
2809
+ if (!Number.isInteger(targetIndex) || target?.headerKind !== source.data.headerKind || target?.parentKey !== source.data.parentKey || target?.fixed !== source.data.fixed) {
2181
2810
  setDragGuide(null);
2182
2811
  return;
2183
2812
  }
2184
2813
  const edge = target?.columnEdge;
2185
- let destinationIndex = targetIndex + (edge === "right" ? 1 : 0);
2186
- if (sourceIndex < destinationIndex) destinationIndex -= 1;
2187
- destinationIndex = Math.max(0, Math.min(columns.length - 1, destinationIndex));
2814
+ const destinationIndex = getColumnDropIndex(sourceIndex, targetIndex, edge);
2188
2815
  columnDragPreviewRef.current = { sourceIndex, targetIndex: destinationIndex };
2189
- const targetLeft = getDisplayedColumnLeft(targetIndex);
2190
- setDragGuide(edge === "right" ? targetLeft + metrics[targetIndex].width : targetLeft);
2816
+ const targetLeft = Number(target?.targetLeft);
2817
+ const targetWidth = Number(target?.targetWidth);
2818
+ if (!Number.isFinite(targetLeft) || !Number.isFinite(targetWidth)) {
2819
+ setDragGuide(null);
2820
+ return;
2821
+ }
2822
+ const guideTop = headerRowOffsets[Number(source.data.headerLevel)] ?? 0;
2823
+ setDragGuide(edge === "right" ? targetLeft + targetWidth : targetLeft, guideTop);
2191
2824
  },
2192
2825
  onDrop: ({ source, location }) => {
2193
2826
  const target = location.current.dropTargets[0]?.data;
2194
2827
  const sourceIndex = Number(source.data.sourceIndex);
2195
2828
  if (source.data.type === "table-row") {
2196
- const rawTargetIndex = Number(target?.targetIndex);
2197
- let targetIndex2 = rawTargetIndex + (target?.rowEdge === "bottom" ? 1 : 0);
2829
+ const rawTargetIndex2 = Number(target?.targetIndex);
2830
+ let targetIndex2 = rawTargetIndex2 + (target?.rowEdge === "bottom" ? 1 : 0);
2198
2831
  if (sourceIndex < targetIndex2) targetIndex2 -= 1;
2199
2832
  targetIndex2 = Math.max(0, Math.min(rows.length - 1, targetIndex2));
2200
2833
  scrollerRef.current?.classList.add("rvg-row-drop-settling");
2201
2834
  setRowDragPreview(null);
2202
2835
  setRowDragGuide(null);
2203
2836
  canvas.style.cursor = "default";
2204
- if (Number.isInteger(rawTargetIndex) && sourceIndex !== targetIndex2) onRowOrderChange?.(sourceIndex, targetIndex2);
2837
+ if (Number.isInteger(rawTargetIndex2) && sourceIndex !== targetIndex2) onRowOrderChange?.(sourceIndex, targetIndex2);
2205
2838
  requestAnimationFrame(() => {
2206
2839
  requestAnimationFrame(() => scrollerRef.current?.classList.remove("rvg-row-drop-settling"));
2207
2840
  });
@@ -2210,12 +2843,23 @@ function Table({
2210
2843
  }, 0);
2211
2844
  return;
2212
2845
  }
2213
- const targetIndex = columnDragPreviewRef.current?.targetIndex ?? Number(target?.targetIndex);
2846
+ const rawTargetIndex = Number(target?.targetIndex);
2847
+ const targetIndex = columnDragPreviewRef.current?.targetIndex ?? (Number.isInteger(rawTargetIndex) ? getColumnDropIndex(sourceIndex, rawTargetIndex, target?.columnEdge) : rawTargetIndex);
2214
2848
  canvas.style.cursor = "default";
2215
2849
  setDragGuide(null);
2216
2850
  columnDragPreviewRef.current = null;
2217
- if (Number.isInteger(targetIndex) && sourceIndex !== targetIndex && columns[targetIndex]?.fixed === columns[sourceIndex]?.fixed) {
2218
- onColumnOrderChange?.(sourceIndex - utilityColumnCount, targetIndex - utilityColumnCount);
2851
+ if (Number.isInteger(rawTargetIndex) && Number.isInteger(targetIndex) && sourceIndex !== targetIndex && target?.headerKind === source.data.headerKind && target?.parentKey === source.data.parentKey && target?.fixed === source.data.fixed) {
2852
+ onColumnOrderChange?.(
2853
+ sourceIndex - utilityColumnCount,
2854
+ targetIndex - utilityColumnCount,
2855
+ {
2856
+ type: source.data.headerKind === "group" ? "group" : "column",
2857
+ sourceKey: String(source.data.sourceKey),
2858
+ targetKey: String(target.targetKey),
2859
+ parentKey: typeof source.data.parentKey === "string" ? source.data.parentKey : void 0,
2860
+ placement: getColumnDropPlacement(sourceIndex, rawTargetIndex, target.columnEdge)
2861
+ }
2862
+ );
2219
2863
  }
2220
2864
  window.setTimeout(() => {
2221
2865
  suppressClickRef.current = false;
@@ -2233,24 +2877,38 @@ function Table({
2233
2877
  const rowEdge = input.clientY - canvas.getBoundingClientRect().top < rowTop + rowHeight / 2 ? "top" : "bottom";
2234
2878
  return (0, import_closest_edge.attachClosestEdge)({ targetIndex: targetIndex2, rowEdge }, { element, input, allowedEdges: ["top", "bottom"] });
2235
2879
  }
2236
- let targetIndex = getHeaderColumn(input.clientX, input.clientY);
2237
- if (targetIndex < 0) return { targetIndex: -1 };
2880
+ const targetCell = locateHeaderCell(input.clientX, input.clientY);
2881
+ if (!targetCell) return { targetIndex: -1 };
2882
+ let targetIndex = targetCell.startIndex;
2883
+ const targetFixed = columns[targetCell.startIndex]?.fixed;
2884
+ const headerKind = targetCell.leaf ? "column" : "group";
2885
+ if (source.data.headerKind !== headerKind || source.data.parentKey !== targetCell.parentKey || source.data.fixed !== targetFixed) return { targetIndex: -1 };
2238
2886
  if (columns[targetIndex].rowSelection || columns[targetIndex].rowDragHandle || columns[targetIndex].rowNumber) {
2239
2887
  const fixedSide = columns[Number(source.data.sourceIndex)]?.fixed;
2240
2888
  targetIndex = columns.findIndex((column, index) => index > targetIndex && column.fixed === fixedSide && !column.rowSelection && !column.rowDragHandle && !column.rowNumber);
2241
2889
  if (targetIndex < 0) return { targetIndex: -1 };
2242
2890
  return { targetIndex, columnEdge: "left" };
2243
2891
  }
2244
- const left = getDisplayedColumnLeft(targetIndex);
2245
- const columnEdge = input.clientX - canvas.getBoundingClientRect().left < left + metrics[targetIndex].width / 2 ? "left" : "right";
2246
- return (0, import_closest_edge.attachClosestEdge)({ targetIndex, columnEdge }, { element, input, allowedEdges: ["left", "right"] });
2892
+ const left = getHeaderCellLeft(targetCell);
2893
+ const targetWidth = targetCell.leaf ? metrics[targetIndex].width : getHeaderCellWidth(targetCell);
2894
+ const columnEdge = input.clientX - canvas.getBoundingClientRect().left < left + targetWidth / 2 ? "left" : "right";
2895
+ return (0, import_closest_edge.attachClosestEdge)({
2896
+ targetIndex,
2897
+ targetKey: targetCell.key,
2898
+ parentKey: targetCell.parentKey,
2899
+ headerKind,
2900
+ fixed: targetFixed,
2901
+ targetLeft: left,
2902
+ targetWidth,
2903
+ columnEdge
2904
+ }, { element, input, allowedEdges: ["left", "right"] });
2247
2905
  }
2248
2906
  });
2249
2907
  return () => {
2250
2908
  cleanupDrag();
2251
2909
  cleanupDrop();
2252
2910
  };
2253
- }, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getRowKey, headerHeight, locateColumn, locateHeaderDragHandle, locateResizeHandle, locateRow, metrics, onColumnOrderChange, onRowOrderChange, rowDraggable, rowHeight, rows, selectedRowKeySet, setDragGuide, setRowDragGuide, utilityColumnCount]);
2911
+ }, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerHeight, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, metrics, onColumnOrderChange, onRowOrderChange, rowDraggable, rowHeight, rows, selectedRowKeySet, setDragGuide, setRowDragGuide, utilityColumnCount]);
2254
2912
  const locateCell = (0, import_react5.useCallback)((clientX, clientY) => {
2255
2913
  const canvas = canvasRef.current;
2256
2914
  if (!canvas) return null;
@@ -2357,8 +3015,9 @@ function Table({
2357
3015
  const horizontalFocusGap = 0;
2358
3016
  const verticalFocusGap = 0;
2359
3017
  if (column.fixed === void 0) {
2360
- if (metric.left - nextLeft < fixedWidth) nextLeft = Math.max(0, metric.left - fixedWidth - horizontalFocusGap);
2361
- if (metric.left + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = metric.left + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
3018
+ const cellLeft = getScrollableColumnLeft(cell.columnIndex);
3019
+ if (cellLeft - nextLeft < fixedWidth) nextLeft = Math.max(0, cellLeft - fixedWidth - horizontalFocusGap);
3020
+ if (cellLeft + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2362
3021
  }
2363
3022
  const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
2364
3023
  const rowBottom = rowTop + rowHeight * rowSpan;
@@ -2371,7 +3030,7 @@ function Table({
2371
3030
  scroller.scrollTop = nextTop;
2372
3031
  scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2373
3032
  scheduleDraw();
2374
- }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
3033
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
2375
3034
  const beginEdit = (0, import_react5.useCallback)((cell) => {
2376
3035
  const column = columns[cell.columnIndex];
2377
3036
  if (!column?.editable || column.dataIndex === void 0) return;
@@ -2392,8 +3051,9 @@ function Table({
2392
3051
  const horizontalFocusGap = 0;
2393
3052
  const verticalFocusGap = 0;
2394
3053
  if (column.fixed === void 0) {
2395
- if (metric.left - nextLeft < fixedWidth) nextLeft = Math.max(0, metric.left - fixedWidth - horizontalFocusGap);
2396
- if (metric.left + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = metric.left + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
3054
+ const cellLeft = getScrollableColumnLeft(cell.columnIndex);
3055
+ if (cellLeft - nextLeft < fixedWidth) nextLeft = Math.max(0, cellLeft - fixedWidth - horizontalFocusGap);
3056
+ if (cellLeft + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2397
3057
  }
2398
3058
  const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
2399
3059
  const rowBottom = rowTop + rowHeight * rowSpan;
@@ -2415,7 +3075,7 @@ function Table({
2415
3075
  return;
2416
3076
  }
2417
3077
  setEditing(cell);
2418
- }, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
3078
+ }, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
2419
3079
  const getCellEditKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
2420
3080
  const markCellEdited = (cell, previousValue, nextValue) => {
2421
3081
  const key = getCellEditKey(cell);
@@ -2546,75 +3206,113 @@ function Table({
2546
3206
  const visibleRowHeight = fixedHeader ? renderHeight - bodyTop : renderHeight;
2547
3207
  if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
2548
3208
  if (rowBottom > scroller.scrollTop + visibleRowHeight) scroller.scrollTop = rowBottom - visibleRowHeight;
2549
- if (columns[columnIndex].fixed === void 0 && metric.left < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, metric.left - fixedWidth);
2550
- if (columns[columnIndex].fixed === void 0 && metric.left + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = metric.left + cellWidth - viewport.width + rightFixedWidth;
3209
+ if (columns[columnIndex].fixed === void 0) {
3210
+ const cellLeft = getScrollableColumnLeft(columnIndex);
3211
+ if (cellLeft < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, cellLeft - fixedWidth);
3212
+ if (cellLeft + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth;
3213
+ }
2551
3214
  }
2552
- }, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
3215
+ }, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2553
3216
  const editorStyle = (0, import_react5.useMemo)(() => {
2554
3217
  if (!editing) return void 0;
2555
3218
  const metric = metrics[editing.columnIndex];
2556
- const fixedSide = columns[editing.columnIndex].fixed;
3219
+ const column = columns[editing.columnIndex];
3220
+ if (!metric || !column) return void 0;
3221
+ const fixedSide = column.fixed;
2557
3222
  const span = getCellSpan(editing.rowIndex, editing.columnIndex);
2558
3223
  const cellWidth = getCellDisplayWidth(editing.columnIndex, span?.colSpan ?? 1);
2559
3224
  const cellHeight = rowHeight * (span?.rowSpan ?? 1);
2560
- const cellLeft = fixedSide === "left" ? metric.left : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(editing.columnIndex) ?? 0) - metric.width : metric.left - scrollRef.current.left;
3225
+ 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;
3226
+ const cellTop = bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top;
3227
+ const horizontalStart = fixedSide === void 0 ? fixedWidth : 0;
3228
+ const horizontalEnd = fixedSide === void 0 ? viewport.width - rightFixedWidth : viewport.width;
3229
+ const verticalStart = fixedHeader ? bodyTop : 0;
3230
+ const visibleLeft = Math.max(cellLeft, horizontalStart);
3231
+ const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
3232
+ const visibleTop = Math.max(cellTop, verticalStart);
3233
+ const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
2561
3234
  return {
2562
- left: (fixedSide ? cellLeft : Math.max(cellLeft, fixedWidth)) + 1,
2563
- top: Math.max(fixedHeader ? bodyTop : 0, bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top) + 1,
2564
- width: cellWidth - 2,
2565
- height: cellHeight - 2
3235
+ left: visibleLeft,
3236
+ top: visibleTop,
3237
+ width: Math.max(0, visibleRight - visibleLeft),
3238
+ height: Math.max(0, visibleBottom - visibleTop)
2566
3239
  };
2567
- }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedOffsets, rowHeight, viewport.width]);
3240
+ }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
2568
3241
  const handleColumnPointerDown = (0, import_react5.useCallback)((event) => {
2569
3242
  const rect = event.currentTarget.getBoundingClientRect();
2570
3243
  const localY = event.clientY - rect.top;
2571
3244
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2572
3245
  if (localY < headerY || localY >= headerY + headerHeight) return;
2573
- const resizeIndex = locateResizeHandle(event.clientX);
2574
- if (resizeIndex >= 0) {
3246
+ const resizeHit = locateHeaderResizeHit(event.clientX, event.clientY);
3247
+ if (resizeHit) {
3248
+ const { cell: resizeCell, edge: resizeEdge } = resizeHit;
2575
3249
  event.preventDefault();
2576
3250
  event.currentTarget.setPointerCapture(event.pointerId);
2577
- const neighborIndex = findResizeNeighbor(resizeIndex);
2578
- const startGuideX = getDisplayedColumnLeft(resizeIndex) + metrics[resizeIndex].width;
3251
+ const resizeIndex = resizeCell.endIndex;
3252
+ const resizeIndices = resizeCell.leaf ? [resizeIndex] : Array.from({ length: resizeCell.endIndex - resizeCell.startIndex + 1 }, (_, index) => resizeCell.startIndex + index);
3253
+ const startWidths = resizeIndices.map((index) => metrics[index].width);
3254
+ const startGuideX = getHeaderCellLeft(resizeCell) + (resizeEdge === "right" ? getHeaderCellWidth(resizeCell) : 0);
2579
3255
  columnDragRef.current = {
2580
3256
  type: "resize",
2581
3257
  columnIndex: resizeIndex,
2582
3258
  startX: event.clientX,
2583
- startWidth: metrics[resizeIndex].width,
2584
- startGuideX,
2585
- neighborIndex,
2586
- startNeighborWidth: neighborIndex >= 0 ? metrics[neighborIndex].width : 0
3259
+ startWidth: startWidths.reduce((total, width2) => total + width2, 0),
3260
+ guideTop: headerRowOffsets[resizeCell.level] ?? 0,
3261
+ resizeEdge,
3262
+ resizeStartIndex: resizeCell.startIndex,
3263
+ resizeEndIndex: resizeCell.endIndex,
3264
+ resizeIndices,
3265
+ startWidths
2587
3266
  };
2588
3267
  suppressClickRef.current = true;
3268
+ setResizeGuideTop(headerRowOffsets[resizeCell.level] ?? 0);
2589
3269
  setResizeGuideX(startGuideX);
2590
3270
  return;
2591
3271
  }
2592
- }, [findResizeNeighbor, fixedHeader, getDisplayedColumnLeft, headerHeight, locateResizeHandle, metrics]);
3272
+ }, [fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerHeight, headerRowOffsets, locateHeaderResizeHit, metrics]);
2593
3273
  const handleColumnPointerMove = (0, import_react5.useCallback)((event) => {
2594
3274
  const drag = columnDragRef.current;
2595
3275
  if (!drag) return;
2596
3276
  const column = columns[drag.columnIndex];
2597
3277
  const minWidth = getMinimumColumnWidth(column, columnDraggable);
2598
- let delta = Math.round(event.clientX - drag.startX);
2599
- if (drag.neighborIndex >= 0) {
2600
- const neighbor = columns[drag.neighborIndex];
2601
- const minNeighborWidth = getMinimumColumnWidth(neighbor, columnDraggable);
2602
- delta = Math.max(minWidth - drag.startWidth, Math.min(drag.startNeighborWidth - minNeighborWidth, delta));
2603
- onColumnResize?.(column.key, drag.startWidth + delta);
2604
- onColumnResize?.(neighbor.key, drag.startNeighborWidth - delta);
2605
- setResizeGuideX(drag.startGuideX + delta);
3278
+ const pointerDelta = Math.round(event.clientX - drag.startX);
3279
+ const delta = drag.resizeEdge === "left" ? -pointerDelta : pointerDelta;
3280
+ const setGuideFromWidths = (nextWidths) => {
3281
+ const widthByIndex = new Map(drag.resizeIndices.map((columnIndex, index) => [columnIndex, nextWidths[index]]));
3282
+ const nextColumns = columns.map((column2, index) => widthByIndex.has(index) ? { ...column2, width: widthByIndex.get(index) } : column2);
3283
+ const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth: viewport.width });
3284
+ setResizeGuideX(getHeaderCellEdgeFromMetrics(drag.resizeStartIndex, drag.resizeEndIndex, drag.resizeEdge, nextMetrics));
3285
+ };
3286
+ if (drag.resizeIndices.length > 1) {
3287
+ const minWidths = drag.resizeIndices.map((index) => getMinimumColumnWidth(columns[index], columnDraggable));
3288
+ const minTotalWidth = minWidths.reduce((total, width3) => total + width3, 0);
3289
+ const targetTotalWidth = Math.max(minTotalWidth, drag.startWidth + delta);
3290
+ const ratio = drag.startWidth > 0 ? targetTotalWidth / drag.startWidth : 1;
3291
+ const nextWidths = drag.startWidths.map((width3, index) => Math.max(minWidths[index], Math.round(width3 * ratio)));
3292
+ let remainder = targetTotalWidth - nextWidths.reduce((total, width3) => total + width3, 0);
3293
+ for (let index = nextWidths.length - 1; index >= 0 && remainder !== 0; index -= 1) {
3294
+ const next = nextWidths[index] + (remainder > 0 ? 1 : -1);
3295
+ if (next < minWidths[index]) continue;
3296
+ nextWidths[index] = next;
3297
+ remainder += remainder > 0 ? -1 : 1;
3298
+ }
3299
+ drag.resizeIndices.forEach((columnIndex, index) => {
3300
+ resizeColumn(columns[columnIndex].key, nextWidths[index]);
3301
+ });
3302
+ setGuideFromWidths(nextWidths);
2606
3303
  return;
2607
3304
  }
2608
3305
  const width2 = Math.max(minWidth, Math.round(drag.startWidth + delta));
2609
- onColumnResize?.(column.key, width2);
2610
- setResizeGuideX(drag.startGuideX + width2 - drag.startWidth);
2611
- }, [columnDraggable, columns, onColumnResize]);
3306
+ resizeColumn(column.key, width2);
3307
+ setGuideFromWidths([width2]);
3308
+ }, [columnDraggable, columns, getHeaderCellEdgeFromMetrics, resizeColumn, viewport.width]);
2612
3309
  const handleColumnPointerUp = (0, import_react5.useCallback)((event) => {
2613
3310
  const drag = columnDragRef.current;
2614
3311
  if (!drag) return;
2615
3312
  if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2616
3313
  columnDragRef.current = null;
2617
3314
  setResizeGuideX(null);
3315
+ setResizeGuideTop(0);
2618
3316
  }, []);
2619
3317
  const queueSortStateChange = (0, import_react5.useCallback)((next) => {
2620
3318
  setDisplaySortState(next);
@@ -2627,7 +3325,7 @@ function Table({
2627
3325
  const renderHeaderIcons = (columnIndex, left) => {
2628
3326
  const column = columns[columnIndex];
2629
3327
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2630
- const top = (headerHeight - 14) / 2;
3328
+ const top = headerLeafTop + (headerLeafHeight - 14) / 2;
2631
3329
  const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2632
3330
  let right = left + metrics[columnIndex].width - (visibleActions.drag ? 18 : 4);
2633
3331
  const icons = [];
@@ -2653,44 +3351,87 @@ function Table({
2653
3351
  }
2654
3352
  return icons;
2655
3353
  };
2656
- const headerTooltip = (() => {
2657
- if (!visibleHeaderTooltip) return null;
2658
- const { columnIndex, action } = visibleHeaderTooltip;
2659
- const column = columns[columnIndex];
3354
+ const createHeaderTooltip = (0, import_react5.useCallback)((action, columnIndex, cell) => {
3355
+ const column = cell?.column ?? columns[columnIndex];
2660
3356
  if (!column) return null;
2661
- const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2662
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2663
- let anchorX = columnRight - (visibleActions.drag ? 18 : 4);
2664
- let anchorLeft = anchorX;
2665
- let label;
3357
+ const cellLeft = cell ? getHeaderCellLeft(cell) : getDisplayedColumnLeft(columnIndex);
3358
+ const cellWidth = cell ? getHeaderCellWidth(cell) : metrics[columnIndex]?.width ?? 0;
3359
+ const level = cell?.level ?? headerDepth - 1;
3360
+ const rowTop = (fixedHeader ? 0 : -scrollPosition.top) + (headerRowOffsets[level] ?? 0);
3361
+ const rowHeight2 = headerRowHeights[level] ?? baseHeaderHeight;
3362
+ let anchorX = cellLeft + cellWidth / 2;
3363
+ let anchorLeft = cellLeft;
3364
+ let label = column.title;
2666
3365
  if (action === "title") {
2667
- const titleWidth = measureHeaderTitleWidth(columnIndex);
2668
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18;
2669
- const contentWidth2 = Math.max(0, metrics[columnIndex].width - actionWidth);
2670
- const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
2671
- const columnLeft = getDisplayedColumnLeft(columnIndex);
2672
- anchorX = column.align === "right" ? columnLeft + contentWidth2 - 10 : column.align === "center" ? columnLeft + metrics[columnIndex].width / 2 + textWidth / 2 : columnLeft + 10 + textWidth;
2673
- anchorLeft = column.align === "right" ? anchorX - textWidth : column.align === "center" ? columnLeft + metrics[columnIndex].width / 2 - textWidth / 2 : columnLeft + 10;
2674
- label = column.title;
3366
+ const canvas = canvasRef.current;
3367
+ const context = canvas?.getContext("2d");
3368
+ if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3369
+ const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3370
+ const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3371
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable ? 18 : 0;
3372
+ const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3373
+ const maxTextWidth = Math.max(0, contentWidth2 - 20);
3374
+ const textWidth = Math.min(titleWidth, maxTextWidth);
3375
+ if (column.align === "right") {
3376
+ anchorX = cellLeft + contentWidth2 - 10;
3377
+ anchorLeft = anchorX - textWidth;
3378
+ } else if (column.align === "center") {
3379
+ anchorX = cellLeft + contentWidth2 / 2 + textWidth / 2;
3380
+ anchorLeft = cellLeft + contentWidth2 / 2 - textWidth / 2;
3381
+ } else {
3382
+ anchorLeft = cellLeft + 10;
3383
+ anchorX = anchorLeft + textWidth;
3384
+ }
2675
3385
  } else if (action === "drag") {
2676
- anchorX = columnRight;
3386
+ anchorX = cellLeft + cellWidth;
2677
3387
  anchorLeft = anchorX;
2678
3388
  label = labels.dragColumn;
2679
3389
  } else if (action === "sort") {
2680
3390
  const direction = displaySortState?.columnKey === column.key ? displaySortState.direction : null;
2681
3391
  label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
2682
- } else {
2683
- if (visibleActions.sort) anchorX -= 18;
3392
+ anchorX = cellLeft + cellWidth - 22;
3393
+ anchorLeft = anchorX;
3394
+ } else if (action === "filter") {
2684
3395
  const value = filterValues[column.key];
2685
3396
  label = value ? labels.filterWithValue(value) : labels.filter;
3397
+ anchorX = cellLeft + cellWidth - 40;
3398
+ anchorLeft = anchorX;
2686
3399
  }
2687
3400
  const placement = anchorX + 328 > viewport.width ? "left" : "right";
2688
3401
  return {
3402
+ key: `${action}:${cell ? `cell:${cell.key}:${cell.level}` : `column:${column.key}`}:${Math.round(anchorX)}:${Math.round(rowTop)}`,
3403
+ columnIndex,
3404
+ action,
2689
3405
  left: placement === "right" ? anchorX + 8 : Math.max(8, anchorLeft - 8),
2690
- top: (fixedHeader ? 0 : -scrollPosition.top) + headerHeight / 2,
3406
+ top: rowTop + rowHeight2 / 2,
2691
3407
  label,
2692
3408
  placement
2693
3409
  };
3410
+ }, [baseHeaderHeight, columnDraggable, columns, displaySortState, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, viewport.width]);
3411
+ const isPointerOnHeaderTitle = (0, import_react5.useCallback)((clientX, clientY, cell) => {
3412
+ const canvas = canvasRef.current;
3413
+ if (!canvas) return false;
3414
+ const rect = canvas.getBoundingClientRect();
3415
+ const localX = clientX - rect.left;
3416
+ const localY = clientY - rect.top;
3417
+ const column = cell.column;
3418
+ const cellLeft = getHeaderCellLeft(cell);
3419
+ const cellWidth = getHeaderCellWidth(cell);
3420
+ const rowTop = (fixedHeader ? 0 : -scrollRef.current.top) + (headerRowOffsets[cell.level] ?? 0);
3421
+ const context = canvas.getContext("2d");
3422
+ if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3423
+ const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3424
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3425
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable ? 18 : 0;
3426
+ const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3427
+ const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
3428
+ const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
3429
+ const textTop = rowTop + 8;
3430
+ return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3431
+ }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
3432
+ const headerTooltip = (() => {
3433
+ if (!visibleHeaderTooltip) return null;
3434
+ return visibleHeaderTooltip;
2694
3435
  })();
2695
3436
  const getRangeBounds = () => selectionRange ? {
2696
3437
  rowStart: Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex),
@@ -2749,7 +3490,7 @@ function Table({
2749
3490
  }, 1800);
2750
3491
  };
2751
3492
  const showHeaderTooltipAfterDelay = (next) => {
2752
- const nextKey = next ? `${next.columnIndex}:${next.action}` : "";
3493
+ const nextKey = next ? next.key : "";
2753
3494
  if (nextKey && pendingHeaderTooltipKeyRef.current === nextKey) return;
2754
3495
  pendingHeaderTooltipKeyRef.current = nextKey;
2755
3496
  if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
@@ -2997,12 +3738,14 @@ function Table({
2997
3738
  const renderHeaderTitle = (columnIndex, left) => {
2998
3739
  const column = columns[columnIndex];
2999
3740
  const currentScrollTop = scrollRef.current.top;
3741
+ const headerTop = fixedHeader ? 0 : -currentScrollTop;
3742
+ const leafTop = headerTop + headerLeafTop;
3000
3743
  if (column.rowNumber) {
3001
3744
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3002
3745
  "div",
3003
3746
  {
3004
3747
  className: "rvg-header-title",
3005
- style: { left, top: fixedHeader ? 0 : -currentScrollTop, width: metrics[columnIndex].width, height: headerHeight, justifyContent: "center", padding: 0, textAlign: "center" },
3748
+ style: { left, top: headerTop, width: metrics[columnIndex].width, height: headerHeight, justifyContent: "center", padding: 0, textAlign: "center" },
3006
3749
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: column.title })
3007
3750
  },
3008
3751
  column.key
@@ -3016,7 +3759,7 @@ function Table({
3016
3759
  "div",
3017
3760
  {
3018
3761
  className: "rvg-header-selection-icon",
3019
- style: { left, top: fixedHeader ? 0 : -currentScrollTop, width: metrics[columnIndex].width, height: headerHeight },
3762
+ style: { left, top: headerTop, width: metrics[columnIndex].width, height: headerHeight },
3020
3763
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectionIcon, { checked: allSelected, indeterminate })
3021
3764
  },
3022
3765
  column.key
@@ -3029,11 +3772,13 @@ function Table({
3029
3772
  "div",
3030
3773
  {
3031
3774
  className: "rvg-header-title is-custom",
3775
+ "data-level": headerDepth - 1,
3776
+ "data-measure-key": column.key,
3032
3777
  style: {
3033
3778
  left,
3034
- top: fixedHeader ? 0 : -currentScrollTop,
3779
+ top: leafTop,
3035
3780
  width: Math.max(0, metrics[columnIndex].width - actionWidth),
3036
- height: headerHeight,
3781
+ height: headerLeafHeight,
3037
3782
  justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
3038
3783
  textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
3039
3784
  },
@@ -3041,8 +3786,10 @@ function Table({
3041
3786
  "span",
3042
3787
  {
3043
3788
  ref: (node) => {
3044
- if (node) customHeaderContentRefs.current.set(column.key, node);
3045
- else customHeaderContentRefs.current.delete(column.key);
3789
+ if (node) {
3790
+ customHeaderContentRefs.current.set(column.key, node);
3791
+ requestAnimationFrame(measureCustomHeaderHeight);
3792
+ } else customHeaderContentRefs.current.delete(column.key);
3046
3793
  },
3047
3794
  children: column.renderHeader(column)
3048
3795
  }
@@ -3053,6 +3800,61 @@ function Table({
3053
3800
  }
3054
3801
  return null;
3055
3802
  };
3803
+ const renderGroupedHeaderCell = (cell) => {
3804
+ const startMetric = metrics[cell.startIndex];
3805
+ const endMetric = metrics[cell.endIndex];
3806
+ if (!startMetric || !endMetric) return null;
3807
+ const column = cell.column;
3808
+ const fixedSide = columns[cell.startIndex]?.fixed;
3809
+ const currentScrollTop = scrollRef.current.top;
3810
+ const rowHeightForLevel = headerRowHeights[cell.level] ?? baseHeaderHeight;
3811
+ const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
3812
+ const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
3813
+ const width2 = cell.leaf ? startMetric.width : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1);
3814
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex)) : null;
3815
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable && !cell.leaf ? 18 : 0;
3816
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3817
+ "div",
3818
+ {
3819
+ className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}`,
3820
+ "data-level": cell.level,
3821
+ "data-measure-key": `group:${cell.key}:${cell.level}`,
3822
+ style: {
3823
+ left,
3824
+ top: headerTop,
3825
+ width: width2,
3826
+ height: rowHeightForLevel,
3827
+ paddingRight: cell.leaf ? actionWidth + 10 : 10,
3828
+ justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
3829
+ textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
3830
+ },
3831
+ children: [
3832
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3833
+ "span",
3834
+ {
3835
+ ref: (node) => {
3836
+ if (!column.renderHeader) return;
3837
+ const key = `group:${cell.key}:${cell.level}`;
3838
+ if (node) {
3839
+ customHeaderContentRefs.current.set(key, node);
3840
+ requestAnimationFrame(measureCustomHeaderHeight);
3841
+ } else customHeaderContentRefs.current.delete(key);
3842
+ },
3843
+ children: column.renderHeader ? column.renderHeader(column) : column.title
3844
+ }
3845
+ ),
3846
+ !cell.leaf && columnDraggable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3847
+ HeaderDragIcon,
3848
+ {
3849
+ className: `rvg-header-icon rvg-header-drag-icon${hoveredHeaderAction?.columnIndex === cell.startIndex && hoveredHeaderAction.action === "drag" ? " is-hovered" : ""}`,
3850
+ style: { left: width2 - 16, top: (rowHeightForLevel - 14) / 2 }
3851
+ }
3852
+ )
3853
+ ]
3854
+ },
3855
+ `group:${cell.key}:${cell.level}`
3856
+ );
3857
+ };
3056
3858
  const rangeHandlePosition = (() => {
3057
3859
  if (!rangeSelection) return null;
3058
3860
  const bounds = getRangeBounds();
@@ -3084,9 +3886,9 @@ function Table({
3084
3886
  const verticalStart = fixedHeader ? bodyTop : 0;
3085
3887
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) return null;
3086
3888
  const borderWidth = 2;
3087
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
3889
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
3088
3890
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
3089
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
3891
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
3090
3892
  const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
3091
3893
  return {
3092
3894
  left,
@@ -3095,6 +3897,19 @@ function Table({
3095
3897
  height: Math.max(0, bottom - top)
3096
3898
  };
3097
3899
  })();
3900
+ const selectionEdgeIndicator = getSelectionEdgeIndicator(scrollPosition.left, scrollPosition.top);
3901
+ const selectionEdgeStyle = selectionEdgeIndicator ? {
3902
+ ...selectionEdgeIndicator.style,
3903
+ ...selectionEdgeIndicator.corner ? {
3904
+ "--rvg-corner-width": `${selectionEdgeIndicator.corner.width}px`,
3905
+ "--rvg-corner-height": `${selectionEdgeIndicator.corner.height}px`,
3906
+ "--rvg-corner-hover-width-extension": "4px",
3907
+ "--rvg-corner-hover-height-extension": "4px"
3908
+ } : null
3909
+ } : void 0;
3910
+ (0, import_react5.useLayoutEffect)(() => {
3911
+ syncSelectionEdgeTarget(scrollPosition.left, scrollPosition.top);
3912
+ }, [scrollPosition.left, scrollPosition.top, syncSelectionEdgeTarget]);
3098
3913
  const handleContextMenu = (event) => {
3099
3914
  event.preventDefault();
3100
3915
  event.stopPropagation();
@@ -3244,15 +4059,19 @@ function Table({
3244
4059
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
3245
4060
  const columnIndex = locateColumn(event.clientX);
3246
4061
  const inHeader = localY >= headerY && localY < headerY + headerHeight;
3247
- const hoveredAction = inHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
3248
- const hoveredDragHandle = inHeader && locateHeaderDragHandle(event.clientX, columnIndex);
3249
- const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && columnIndex >= 0 && Boolean(columns[columnIndex]?.title) && (tooltipConfig.header || isHeaderTitleTruncated(columnIndex));
3250
- const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex, action: "drag" } : hoveredTitle ? { columnIndex, action: "title" } : null;
4062
+ const inLeafHeader = inHeader && localY >= headerY + headerLeafTop;
4063
+ const hoveredHeaderCell = inHeader ? locateHeaderCell(event.clientX, event.clientY) : null;
4064
+ const hoveredAction = inLeafHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
4065
+ const hoveredDragHandle = Boolean(hoveredHeaderCell && locateHeaderCellDragHandle(event.clientX, hoveredHeaderCell));
4066
+ const hoveredDragColumnIndex = hoveredHeaderCell?.startIndex ?? columnIndex;
4067
+ const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
4068
+ const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
4069
+ const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
3251
4070
  const previousHover = hoveredHeaderActionRef.current;
3252
4071
  if (previousHover?.columnIndex !== nextHover?.columnIndex || previousHover?.action !== nextHover?.action) {
3253
4072
  hoveredHeaderActionRef.current = nextHover;
3254
4073
  setHoveredHeaderAction(nextHover);
3255
- showHeaderTooltipAfterDelay(nextHover);
4074
+ showHeaderTooltipAfterDelay(nextTooltip);
3256
4075
  scheduleDraw();
3257
4076
  }
3258
4077
  if (inHeader) {
@@ -3276,8 +4095,8 @@ function Table({
3276
4095
  if (annotation?.content.trim()) {
3277
4096
  const cellLeft = getDisplayedColumnLeft(cell.columnIndex);
3278
4097
  const cellTop = bodyTop + cell.rowIndex * rowHeight - scrollRef.current.top;
3279
- 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 };
3280
- showCellTooltip(nextTooltip);
4098
+ 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 };
4099
+ showCellTooltip(nextTooltip2);
3281
4100
  } else {
3282
4101
  const label = getCellLabel(cell.rowIndex, cell.columnIndex);
3283
4102
  const context = event.currentTarget.getContext("2d");
@@ -3304,7 +4123,7 @@ function Table({
3304
4123
  showCellTooltipAfterDelay(null);
3305
4124
  } else {
3306
4125
  const placement = textRight + 330 > viewport.width ? "left" : "right";
3307
- const nextTooltip = {
4126
+ const nextTooltip2 = {
3308
4127
  rowIndex: cell.rowIndex,
3309
4128
  columnIndex: cell.columnIndex,
3310
4129
  left: placement === "right" ? textRight + 16 : Math.max(8, textLeft - 16),
@@ -3312,13 +4131,13 @@ function Table({
3312
4131
  label,
3313
4132
  placement
3314
4133
  };
3315
- showCellTooltipAfterDelay(nextTooltip);
4134
+ showCellTooltipAfterDelay(nextTooltip2);
3316
4135
  }
3317
4136
  }
3318
4137
  }
3319
4138
  }
3320
4139
  }
3321
- 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";
4140
+ 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";
3322
4141
  if (event.currentTarget.style.cursor !== cursor) event.currentTarget.style.cursor = cursor;
3323
4142
  },
3324
4143
  onMouseLeave: (event) => {
@@ -3361,6 +4180,7 @@ function Table({
3361
4180
  const headerY = fixedHeader ? 0 : -scrollRef.current.top;
3362
4181
  if (localY >= headerY && localY < headerY + headerHeight) {
3363
4182
  clickedCellRef.current = null;
4183
+ if (localY < headerY + headerLeafTop) return;
3364
4184
  const columnIndex = locateColumn(event.clientX);
3365
4185
  const action = locateHeaderAction(event.clientX, columnIndex);
3366
4186
  if (action === "sort") {
@@ -3422,21 +4242,30 @@ function Table({
3422
4242
  className: "rvg-text-scroll-content",
3423
4243
  style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
3424
4244
  children: [
3425
- columns.map((column, columnIndex) => column.fixed ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderTitle(columnIndex, metrics[columnIndex].left) }, column.key) : null),
4245
+ columns.map((column, columnIndex) => column.fixed || columnIndex < utilityColumnCount || headerDepth > 1 ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderTitle(columnIndex, getScrollableColumnLeft(columnIndex)) }, column.key) : null),
4246
+ headerCells.map((cell) => {
4247
+ if (columns[cell.startIndex]?.fixed) return null;
4248
+ if (cell.endIndex < domRange.columnStart || cell.startIndex >= domRange.columnEnd) return null;
4249
+ return renderGroupedHeaderCell(cell);
4250
+ }),
3426
4251
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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) => {
3427
4252
  if (column.fixed || columnIndex < domRange.columnStart || columnIndex >= domRange.columnEnd) return null;
3428
- return renderCellText(rowIndex, columnIndex, metrics[columnIndex].left);
4253
+ return renderCellText(rowIndex, columnIndex, getScrollableColumnLeft(columnIndex));
3429
4254
  })) })
3430
4255
  ]
3431
4256
  }
3432
4257
  ) }),
3433
4258
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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) => {
3434
4259
  if (!column.fixed) return null;
3435
- const left = column.fixed === "left" ? metrics[columnIndex].left : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
4260
+ const left = column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
3436
4261
  return renderCellText(rowIndex, columnIndex, left);
3437
4262
  })) }),
3438
- columns.map((column, columnIndex) => column.fixed === "left" ? renderHeaderTitle(columnIndex, metrics[columnIndex].left) : null),
3439
- columns.map((column, columnIndex) => column.fixed === "right" ? renderHeaderTitle(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
4263
+ 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),
4264
+ headerCells.map((cell) => columns[cell.startIndex]?.fixed ? renderGroupedHeaderCell(cell) : null),
4265
+ fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-shadow is-left", style: { left: fixedWidth } }),
4266
+ rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-shadow is-right", style: { right: rightFixedWidth } }),
4267
+ hasVerticalBorders && fixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-border is-left", style: { left: fixedWidth - 1 } }),
4268
+ hasVerticalBorders && rightFixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-border is-right", style: { right: rightFixedWidth } })
3440
4269
  ] }),
3441
4270
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -renderHeight } })
3442
4271
  ] }),
@@ -3444,18 +4273,18 @@ function Table({
3444
4273
  "div",
3445
4274
  {
3446
4275
  className: `rvg-summary-row is-${summaryPosition}${hasVerticalBorders ? "" : " is-vertical-borderless"}`,
3447
- style: { height: rowHeight, top: summaryPosition === "top" ? headerHeight : void 0 },
4276
+ style: { height: rowHeight, top: summaryPosition === "top" ? fixedHeader ? headerHeight : headerHeight - currentScroll.top : void 0 },
3448
4277
  children: [
3449
4278
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-summary-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3450
4279
  "div",
3451
4280
  {
3452
4281
  className: "rvg-summary-scroll-content",
3453
4282
  style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
3454
- children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex, metrics[columnIndex].left))
4283
+ children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex, getScrollableColumnLeft(columnIndex)))
3455
4284
  }
3456
4285
  ) }),
3457
4286
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-summary-fixed-layer", children: [
3458
- columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex, metrics[columnIndex].left) : null),
4287
+ columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex, leftFixedOffsets.get(columnIndex) ?? 0) : null),
3459
4288
  columns.map((column, columnIndex) => column.fixed === "right" ? renderSummaryCell(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
3460
4289
  ] }),
3461
4290
  fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-summary-fixed-shadow is-left", style: { left: fixedWidth } }),
@@ -3472,11 +4301,11 @@ function Table({
3472
4301
  ref: scrollingHeaderIconsRef,
3473
4302
  className: "rvg-header-icon-layer rvg-header-icon-layer-scroll",
3474
4303
  style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-scrollRef.current.left}px)` },
3475
- children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, metrics[index].left) }, column.key))
4304
+ children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, getScrollableColumnLeft(index)) }, column.key))
3476
4305
  }
3477
4306
  ) }),
3478
4307
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-header-icon-layer", children: [
3479
- columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, metrics[index].left) }, column.key) : null),
4308
+ columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, leftFixedOffsets.get(index) ?? 0) }, column.key) : null),
3480
4309
  columns.map((column, index) => column.fixed === "right" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, viewport.width - (rightFixedOffsets.get(index) ?? 0) - metrics[index].width) }, column.key) : null)
3481
4310
  ] })
3482
4311
  ] }),
@@ -3528,6 +4357,27 @@ function Table({
3528
4357
  }
3529
4358
  ),
3530
4359
  selectionOutlineStyle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: selectionFocusRef, className: "rvg-selection-focus", style: selectionOutlineStyle, "aria-hidden": "true" }),
4360
+ selectionEdgeIndicator && selection && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4361
+ "button",
4362
+ {
4363
+ ref: selectionEdgeTargetRef,
4364
+ type: "button",
4365
+ className: `rvg-selection-edge-target is-${selectionEdgeIndicator.orientation} is-${selectionEdgeIndicator.side}${selectionEdgeIndicator.viewportEdge ? ` is-viewport-${selectionEdgeIndicator.viewportEdge}` : ""}`,
4366
+ style: selectionEdgeStyle,
4367
+ "aria-label": labels.revealSelectedCell,
4368
+ title: labels.revealSelectedCell,
4369
+ onPointerDown: (event) => {
4370
+ event.preventDefault();
4371
+ event.stopPropagation();
4372
+ },
4373
+ onClick: (event) => {
4374
+ event.stopPropagation();
4375
+ scrollerRef.current?.focus({ preventScroll: true });
4376
+ revealCell(selection);
4377
+ },
4378
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rvg-selection-edge-halo", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M 0 100 L 0 0 L 100 0" }) }) })
4379
+ }
4380
+ ),
3531
4381
  contextMenu && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: contextMenuRef, className: "rvg-context-menu", role: "menu", style: { left: contextMenu.left, top: contextMenu.top }, children: contextMenu.type === "header" ? (() => {
3532
4382
  const column = columns[contextMenu.columnIndex];
3533
4383
  const headerContext = { column, columnIndex: contextMenu.columnIndex };
@@ -3753,12 +4603,40 @@ function Table({
3753
4603
  ] })
3754
4604
  ] }) }),
3755
4605
  toast && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `rvg-toast is-${toast.type}`, role: "status", children: toast.message }),
4606
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4607
+ "div",
4608
+ {
4609
+ ref: verticalScrollbarRef,
4610
+ className: "rvg-vertical-scrollbar",
4611
+ onPointerEnter: () => keepCustomScrollbarVisible("vertical"),
4612
+ onPointerLeave: () => releaseCustomScrollbarVisible("vertical"),
4613
+ onPointerDown: handleVerticalScrollbarPointerDown,
4614
+ onPointerMove: handleVerticalScrollbarPointerMove,
4615
+ onPointerUp: handleVerticalScrollbarPointerUp,
4616
+ onPointerCancel: handleVerticalScrollbarPointerUp,
4617
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: verticalScrollbarThumbRef, className: "rvg-vertical-scrollbar-thumb" })
4618
+ }
4619
+ ),
4620
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4621
+ "div",
4622
+ {
4623
+ ref: horizontalScrollbarRef,
4624
+ className: "rvg-horizontal-scrollbar",
4625
+ onPointerEnter: () => keepCustomScrollbarVisible("horizontal"),
4626
+ onPointerLeave: () => releaseCustomScrollbarVisible("horizontal"),
4627
+ onPointerDown: handleHorizontalScrollbarPointerDown,
4628
+ onPointerMove: handleHorizontalScrollbarPointerMove,
4629
+ onPointerUp: handleHorizontalScrollbarPointerUp,
4630
+ onPointerCancel: handleHorizontalScrollbarPointerUp,
4631
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: horizontalScrollbarThumbRef, className: "rvg-horizontal-scrollbar-thumb" })
4632
+ }
4633
+ ),
3756
4634
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: dragGuideRef, className: "rvg-column-guide" }),
3757
4635
  resizeGuideX !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3758
4636
  "div",
3759
4637
  {
3760
4638
  className: "rvg-column-guide is-resizing",
3761
- style: { display: "block", transform: `translateX(${Math.round(resizeGuideX)}px)` }
4639
+ style: { display: "block", top: resizeGuideTop, transform: `translateX(${Math.round(resizeGuideX)}px)` }
3762
4640
  }
3763
4641
  ),
3764
4642
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: rowDragGuideRef, className: "rvg-row-guide" }),
@@ -3824,8 +4702,8 @@ function Table({
3824
4702
  column.key
3825
4703
  );
3826
4704
  }) }, rowIndex)),
3827
- fixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
3828
- rightFixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
4705
+ fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
4706
+ rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
3829
4707
  ] }),
3830
4708
  loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LoadingSpinnerIcon, {}) })
3831
4709
  ] });