@lensui/lens-table 0.1.24 → 0.1.27

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
@@ -882,7 +882,7 @@ var COLORS = {
882
882
  rowHoverFill: "#f6f9fc",
883
883
  editedFill: "#fff1b8",
884
884
  insertedFill: "#c8ead4",
885
- columnDropTargetFill: "#eeeeee",
885
+ columnDropTargetFill: "#e2edf9",
886
886
  stripe: "#fafbfc"
887
887
  };
888
888
  function ellipsizeText(context, text, maxWidth) {
@@ -959,13 +959,24 @@ function paintGrid(options) {
959
959
  return rowIndex >= rowDragPreview.targetIndex && rowIndex < rowDragPreview.sourceIndex ? rowHeight : 0;
960
960
  };
961
961
  const rowBelowDragGap = rowDragPreview ? rowDragPreview.sourceIndex <= rowDragPreview.targetIndex ? rowDragPreview.targetIndex + 1 : rowDragPreview.targetIndex : -1;
962
- if (striped && trailingBlankStart !== null) {
962
+ if (trailingBlankStart !== null) {
963
963
  const rowStart = Math.max(0, range.rowStart);
964
964
  for (let rowIndex = rowStart; rowIndex < range.rowEnd; rowIndex += 1) {
965
- if (rowIndex % 2 === 0 || rowDragPreview?.sourceIndex === rowIndex) continue;
965
+ if (rowDragPreview?.sourceIndex === rowIndex) continue;
966
+ const row = rows[rowIndex];
967
+ const rowKey = getRowKey(row, rowIndex);
968
+ const trailingRowStyle = options.rowStyle?.(row, rowIndex);
966
969
  const y = bodyTop + rowIndex * rowHeight - scrollTop + getRowDragOffset(rowIndex);
967
- ctx.fillStyle = colors.stripe;
968
- ctx.fillRect(trailingBlankStart, y, width - trailingBlankStart, rowHeight);
970
+ const paintTrailingLayer = (fill) => {
971
+ if (!fill) return;
972
+ ctx.fillStyle = fill;
973
+ ctx.fillRect(trailingBlankStart, y, width - trailingBlankStart, rowHeight);
974
+ };
975
+ if (striped && rowIndex % 2 === 1) paintTrailingLayer(colors.stripe);
976
+ paintTrailingLayer(trailingRowStyle?.backgroundColor);
977
+ if (selectedRowKeys.has(rowKey)) paintTrailingLayer(colors.axisSelectionFill);
978
+ if (hoveredRowIndex === rowIndex) paintTrailingLayer(colors.rowHoverFill);
979
+ if (highlightInsertedRows && insertedRowKeys.has(rowKey)) paintTrailingLayer(colors.insertedFill);
969
980
  }
970
981
  }
971
982
  const paintBodyColumns = (layer) => {
@@ -990,6 +1001,7 @@ function paintGrid(options) {
990
1001
  const cellWidth = getCellWidth(columnIndex, colSpan);
991
1002
  const cellHeight = rowHeight * rowSpan;
992
1003
  const isAxisSelected = selectedRowKeys.has(rowKey) || selectedColumnKeys.has(column.key);
1004
+ const isColumnDropTarget = Boolean(columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex);
993
1005
  const rangeRowStart = selectionRange ? Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex) : -1;
994
1006
  const rangeRowEnd = selectionRange ? Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex) : -1;
995
1007
  const rangeColumnStart = selectionRange ? Math.min(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex) : -1;
@@ -1011,10 +1023,6 @@ function paintGrid(options) {
1011
1023
  ctx.fillStyle = colors.rowHoverFill;
1012
1024
  ctx.fillRect(x, y, cellWidth, cellHeight);
1013
1025
  }
1014
- if (columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex) {
1015
- ctx.fillStyle = colors.columnDropTargetFill;
1016
- ctx.fillRect(x, y, cellWidth, cellHeight);
1017
- }
1018
1026
  if (highlightInsertedRows && insertedRowKeys.has(rowKey)) {
1019
1027
  ctx.fillStyle = colors.insertedFill;
1020
1028
  ctx.fillRect(x, y, cellWidth, cellHeight);
@@ -1039,6 +1047,10 @@ function paintGrid(options) {
1039
1047
  ctx.fillStyle = annotation.color;
1040
1048
  ctx.fillRect(x, y, cellWidth, cellHeight);
1041
1049
  }
1050
+ if (isColumnDropTarget) {
1051
+ ctx.fillStyle = colors.columnDropTargetFill;
1052
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1053
+ }
1042
1054
  ctx.fillStyle = colors.grid;
1043
1055
  if (!verticalBorderless && !(frameBorderless && columnIndex + colSpan >= columns.length)) {
1044
1056
  ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
@@ -1048,14 +1060,14 @@ function paintGrid(options) {
1048
1060
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
1049
1061
  }
1050
1062
  if (rowSpan > 1) {
1051
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1063
+ const mergedFill = isColumnDropTarget ? colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1052
1064
  ctx.fillStyle = mergedFill;
1053
1065
  for (let offset = 1; offset < rowSpan; offset += 1) {
1054
1066
  ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
1055
1067
  }
1056
1068
  }
1057
1069
  if (colSpan > 1 && !verticalBorderless) {
1058
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1070
+ const mergedFill = isColumnDropTarget ? colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1059
1071
  ctx.fillStyle = mergedFill;
1060
1072
  let offsetLeft = 0;
1061
1073
  for (let offset = 1; offset < colSpan; offset += 1) {
@@ -1152,11 +1164,11 @@ function paintGrid(options) {
1152
1164
  ctx.beginPath();
1153
1165
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1154
1166
  ctx.clip();
1155
- ctx.fillStyle = isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1167
+ ctx.fillStyle = isColumnDropTarget ? colors.text : isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1156
1168
  ctx.font = createCellFont(bodyFontSize);
1157
1169
  ctx.textBaseline = "alphabetic";
1158
1170
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
1159
- ctx.fillText(label, textX, y + cellHeight / 2 + cellBaselineOffset);
1171
+ ctx.fillText(ellipsizeText(ctx, label, Math.max(0, cellWidth - padding * 2)), textX, y + cellHeight / 2 + cellBaselineOffset);
1160
1172
  ctx.restore();
1161
1173
  }
1162
1174
  }
@@ -1215,6 +1227,25 @@ function paintGrid(options) {
1215
1227
  paintExtendedColumnSelection("scroll");
1216
1228
  paintExtendedColumnSelection("left");
1217
1229
  paintExtendedColumnSelection("right");
1230
+ const paintExtendedColumnDropTarget = (layer) => {
1231
+ if (!columnDropTarget) return;
1232
+ ctx.save();
1233
+ if (layer === "scroll") {
1234
+ ctx.beginPath();
1235
+ ctx.rect(leftFixedWidth, extensionTop, Math.max(0, rightFixedLeft - leftFixedWidth), extensionHeight);
1236
+ ctx.clip();
1237
+ }
1238
+ ctx.fillStyle = colors.columnDropTargetFill;
1239
+ for (let columnIndex = columnDropTarget.startIndex; columnIndex <= columnDropTarget.endIndex; columnIndex += 1) {
1240
+ if (layer === "scroll" ? columns[columnIndex]?.fixed !== void 0 : columns[columnIndex]?.fixed !== layer) continue;
1241
+ if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
1242
+ ctx.fillRect(getColumnX(columnIndex), extensionTop, metrics[columnIndex].width, extensionHeight);
1243
+ }
1244
+ ctx.restore();
1245
+ };
1246
+ paintExtendedColumnDropTarget("scroll");
1247
+ paintExtendedColumnDropTarget("left");
1248
+ paintExtendedColumnDropTarget("right");
1218
1249
  if (!verticalBorderless) {
1219
1250
  paintExtendedColumnLines("scroll");
1220
1251
  paintExtendedColumnLines("left");
@@ -1236,11 +1267,12 @@ function paintGrid(options) {
1236
1267
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1237
1268
  }
1238
1269
  const isAxisSelected = selectedColumnKeys.has(columns[columnIndex].key);
1270
+ const isColumnDropTarget = Boolean(columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex);
1239
1271
  if (isAxisSelected) {
1240
1272
  ctx.fillStyle = colors.axisSelectionFill;
1241
1273
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1242
1274
  }
1243
- if (columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex) {
1275
+ if (isColumnDropTarget) {
1244
1276
  ctx.fillStyle = colors.columnDropTargetFill;
1245
1277
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1246
1278
  }
@@ -1269,7 +1301,7 @@ function paintGrid(options) {
1269
1301
  ctx.beginPath();
1270
1302
  ctx.rect(x + 2, headerY + headerLeafTop + 1, Math.max(0, clipWidth - 4), headerLeafHeight - 2);
1271
1303
  ctx.clip();
1272
- ctx.fillStyle = isAxisSelected ? colors.axisSelectionText : colors.muted;
1304
+ ctx.fillStyle = isColumnDropTarget ? colors.muted : isAxisSelected ? colors.axisSelectionText : colors.muted;
1273
1305
  ctx.textAlign = titleAlign === "right" ? "right" : titleAlign === "center" ? "center" : "left";
1274
1306
  ctx.fillText(title, textX, headerY + headerLeafTop + headerLeafHeight / 2);
1275
1307
  ctx.restore();
@@ -1428,6 +1460,11 @@ function resolveColumnDropIndex(sourceIndex, targetIndex, edge, columnCount) {
1428
1460
  function resolveColumnDropPlacement(edge) {
1429
1461
  return edge === "right" ? "after" : "before";
1430
1462
  }
1463
+ function resolveColumnDropHighlightRange(target, edge, cells) {
1464
+ if (edge === "right") return { startIndex: target.startIndex, endIndex: target.endIndex };
1465
+ const previous = cells.find((cell) => cell.endIndex === target.startIndex - 1 && cell.level === target.level && cell.parentKey === target.parentKey);
1466
+ return previous ? { startIndex: previous.startIndex, endIndex: previous.endIndex } : { startIndex: target.startIndex, endIndex: target.endIndex };
1467
+ }
1431
1468
  var ANNOTATION_COLORS = ["#f44336", "#ff7a45", "#f5a623", "#f2c94c", "#52c41a", "#13a8a8", "#1677ff", "#597ef7", "#9254de", "#eb2f96"];
1432
1469
  var FALLBACK_HEIGHT = 480;
1433
1470
  var EMPTY_BODY_HEIGHT = 240;
@@ -2307,7 +2344,7 @@ function TableInner({
2307
2344
  rowHoverFill: readThemeColor(themeStyles, "--rvg-color-row-hover-fill", "#f6f9fc"),
2308
2345
  editedFill: editedCellHighlightColor ?? readThemeColor(themeStyles, "--rvg-color-edited-fill", "#fff1b8"),
2309
2346
  insertedFill: insertedRowHighlightColor ?? readThemeColor(themeStyles, "--rvg-color-inserted-fill", "#c8ead4"),
2310
- columnDropTargetFill: readThemeColor(themeStyles, "--rvg-color-column-drop-target-fill", "#eeeeee"),
2347
+ columnDropTargetFill: readThemeColor(themeStyles, "--rvg-color-column-drop-target-fill", "#e2edf9"),
2311
2348
  stripe: stripedColor ?? readThemeColor(themeStyles, "--rvg-color-stripe", "#fafbfc")
2312
2349
  };
2313
2350
  const scroll = scrollRef.current;
@@ -3585,6 +3622,8 @@ function TableInner({
3585
3622
  columnDragRef.current = null;
3586
3623
  setResizeGuideX(null);
3587
3624
  setResizeGuideTop(0);
3625
+ document.documentElement.classList.remove("rvg-is-dragging");
3626
+ event.currentTarget.style.cursor = "default";
3588
3627
  }, []);
3589
3628
  const clearColumnOrderPointerDrag = (0, import_react5.useCallback)((canvas) => {
3590
3629
  columnOrderDragRef.current = null;
@@ -3666,7 +3705,11 @@ function TableInner({
3666
3705
  fixed: targetColumn.fixed,
3667
3706
  columnEdge
3668
3707
  };
3669
- setColumnDropTarget({ startIndex: targetCell.startIndex, endIndex: targetCell.endIndex });
3708
+ const compatibleHighlightCells = headerCells.filter((cell) => {
3709
+ const cellColumn = columns[cell.startIndex];
3710
+ return cellColumn && !cellColumn.rowSelection && !cellColumn.rowDragHandle && !cellColumn.rowNumber && cellColumn.fixed === targetColumn.fixed;
3711
+ });
3712
+ setColumnDropTarget(resolveColumnDropHighlightRange(targetCell, columnEdge, compatibleHighlightCells));
3670
3713
  setDragGuide(columnEdge === "right" ? targetLeft + targetWidth : targetLeft, headerRowOffsets[sourceCell.level] ?? 0);
3671
3714
  }, [columns, getHeaderCellLeft, getHeaderCellWidth, headerCells, headerRowOffsets, hideDragTooltips, locateColumn, locateHeaderCell, setDragGuide]);
3672
3715
  const finishColumnOrderPointerDrag = (0, import_react5.useCallback)((event, apply) => {
@@ -3811,6 +3854,34 @@ function TableInner({
3811
3854
  const textTop = rowTop + 8;
3812
3855
  return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3813
3856
  }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerRowOffsets]);
3857
+ const locateCustomHeaderTooltip = (0, import_react5.useCallback)((clientX, clientY, columnIndex) => {
3858
+ const root = rootRef.current;
3859
+ const canvas = canvasRef.current;
3860
+ if (!root || !canvas || columnIndex < 0) return null;
3861
+ const canvasRect = canvas.getBoundingClientRect();
3862
+ const targets = root.querySelectorAll(".rvg-header-title [data-rvg-tooltip]");
3863
+ for (const target of targets) {
3864
+ const rect = target.getBoundingClientRect();
3865
+ if (clientX < rect.left || clientX > rect.right || clientY < rect.top || clientY > rect.bottom) continue;
3866
+ const label = target.dataset.rvgTooltip?.trim();
3867
+ if (!label) return false;
3868
+ const visibleText = target.textContent?.replace(/\s+/g, " ").trim() ?? "";
3869
+ const candidates = [target, ...target.querySelectorAll("*")];
3870
+ const truncated = candidates.some((candidate) => candidate.scrollWidth > candidate.clientWidth + 1 || candidate.scrollHeight > candidate.clientHeight + 1);
3871
+ if (!truncated && label.replace(/\s+/g, " ").trim() === visibleText) return false;
3872
+ const rawLeft = rect.left - canvasRect.left;
3873
+ return {
3874
+ key: `content:${columnIndex}:${label}:${Math.round(rawLeft)}:${Math.round(rect.top - canvasRect.top)}`,
3875
+ columnIndex,
3876
+ action: "content",
3877
+ left: Math.max(8, Math.min(rawLeft, Math.max(8, viewport.width - 328))),
3878
+ top: rect.bottom - canvasRect.top + 4,
3879
+ label,
3880
+ placement: "below"
3881
+ };
3882
+ }
3883
+ return null;
3884
+ }, [viewport.width]);
3814
3885
  const headerTooltip = (() => {
3815
3886
  if (!visibleHeaderTooltip) return null;
3816
3887
  return visibleHeaderTooltip;
@@ -4136,23 +4207,26 @@ function TableInner({
4136
4207
  if (column.renderHeader) {
4137
4208
  const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
4138
4209
  const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
4210
+ const isHeaderHovered = hoveredHeaderColumnIndex === columnIndex;
4139
4211
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4140
4212
  "div",
4141
4213
  {
4142
- className: `rvg-header-title is-custom${isAxisSelected ? " is-axis-selected" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4214
+ className: `rvg-header-title is-custom${isHeaderHovered ? " is-header-hovered" : ""}${isAxisSelected ? " is-axis-selected" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4143
4215
  "data-level": headerDepth - 1,
4144
4216
  "data-measure-key": column.key,
4145
4217
  style: {
4146
4218
  left,
4147
4219
  top: leafTop,
4148
- width: Math.max(0, metrics[columnIndex].width - actionWidth),
4220
+ width: metrics[columnIndex].width,
4149
4221
  height: headerLeafHeight,
4222
+ "--rvg-header-action-width": `${actionWidth}px`,
4150
4223
  justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
4151
4224
  textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
4152
4225
  },
4153
4226
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4154
4227
  "span",
4155
4228
  {
4229
+ style: { width: "100%" },
4156
4230
  ref: (node) => {
4157
4231
  if (node) {
4158
4232
  customHeaderContentRefs.current.set(column.key, node);
@@ -4519,16 +4593,19 @@ function TableInner({
4519
4593
  const hoveredAction = inLeafHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
4520
4594
  const hoveredDragHandle = Boolean(hoveredHeaderCell && locateHeaderCellDragHandle(event.clientX, hoveredHeaderCell));
4521
4595
  const hoveredDragColumnIndex = hoveredHeaderCell?.startIndex ?? columnIndex;
4522
- const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
4523
- const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
4524
- const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
4596
+ const customHeaderTooltipMatch = inHeader && !hoveredAction && !hoveredDragHandle ? locateCustomHeaderTooltip(event.clientX, event.clientY, columnIndex) : null;
4597
+ const hoveredContentTooltip = customHeaderTooltipMatch || null;
4598
+ const hoveredCustomContent = customHeaderTooltipMatch !== null;
4599
+ const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && !hoveredCustomContent && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
4600
+ const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredContentTooltip ? { columnIndex, action: "content" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
4601
+ const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredContentTooltip ? hoveredContentTooltip : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
4525
4602
  const previousHover = hoveredHeaderActionRef.current;
4526
4603
  if (previousHover?.columnIndex !== nextHover?.columnIndex || previousHover?.action !== nextHover?.action) {
4527
4604
  hoveredHeaderActionRef.current = nextHover;
4528
4605
  setHoveredHeaderAction(nextHover);
4529
- showHeaderTooltipAfterDelay(nextTooltip);
4530
4606
  scheduleDraw();
4531
4607
  }
4608
+ showHeaderTooltipAfterDelay(nextTooltip);
4532
4609
  if (inHeader) {
4533
4610
  if (hoveredRowIndexRef.current !== null) {
4534
4611
  hoveredRowIndexRef.current = null;
@@ -4580,12 +4657,12 @@ function TableInner({
4580
4657
  if (!isInsideText) {
4581
4658
  showCellTooltipAfterDelay(null);
4582
4659
  } else {
4583
- const placement = textRight + 330 > viewport.width ? "left" : "right";
4660
+ const placement = cellTop + rowHeight + 36 > renderHeight ? "above" : "below";
4584
4661
  const nextTooltip2 = {
4585
4662
  rowIndex: cell.rowIndex,
4586
4663
  columnIndex: cell.columnIndex,
4587
- left: placement === "right" ? textRight + 16 : Math.max(8, textLeft - 16),
4588
- top: cellTop + rowHeight / 2,
4664
+ left: Math.max(8, Math.min(textLeft, Math.max(8, viewport.width - 328))),
4665
+ top: placement === "below" ? cellTop + rowHeight + 4 : cellTop - 4,
4589
4666
  label,
4590
4667
  placement
4591
4668
  };
@@ -4614,6 +4691,8 @@ function TableInner({
4614
4691
  }
4615
4692
  },
4616
4693
  onPointerDown: (event) => {
4694
+ handleColumnPointerDown(event);
4695
+ if (columnDragRef.current) return;
4617
4696
  const headerDragCell = columnDraggable ? locateHeaderCell(event.clientX, event.clientY) : null;
4618
4697
  const columnIndex = locateColumn(event.clientX);
4619
4698
  const rowDragHandle = rowDraggable && columnIndex >= 0 && Boolean(columns[columnIndex].rowDragHandle) && locateRow(event.clientY) >= 0;
@@ -4622,8 +4701,7 @@ function TableInner({
4622
4701
  document.documentElement.classList.add("rvg-is-dragging");
4623
4702
  event.currentTarget.style.cursor = "move";
4624
4703
  }
4625
- handleColumnPointerDown(event);
4626
- if (!columnDragRef.current) handleColumnOrderPointerDown(event);
4704
+ handleColumnOrderPointerDown(event);
4627
4705
  beginRangeDrag(event);
4628
4706
  },
4629
4707
  onPointerMove: (event) => {
@@ -4681,6 +4759,12 @@ function TableInner({
4681
4759
  }
4682
4760
  const cell = locateCell(event.clientX, event.clientY);
4683
4761
  if (!cell) {
4762
+ const localX = event.clientX - rect.left;
4763
+ const trailingRowIndex = trailingColumnBorderLeft !== null && localX > trailingColumnBorderLeft ? locateRow(event.clientY) : -1;
4764
+ if (trailingRowIndex >= 0 && rowSelection) {
4765
+ selectRow(trailingRowIndex, event);
4766
+ return;
4767
+ }
4684
4768
  clickedCellRef.current = null;
4685
4769
  setSelectionRange(null);
4686
4770
  setSelection(null);
package/dist/index.css CHANGED
@@ -17,7 +17,7 @@
17
17
  --rvg-color-selection-fill: #edf4ff;
18
18
  --rvg-color-axis-selection-fill: #e8f2ff;
19
19
  --rvg-color-axis-selection-text: var(--rvg-color-text);
20
- --rvg-color-column-drop-target-fill: #eeeeee;
20
+ --rvg-color-column-drop-target-fill: #e2edf9;
21
21
  --rvg-color-range-fill: #e8f1ff;
22
22
  --rvg-color-row-hover-fill: #f6f9fc;
23
23
  --rvg-color-hover: #f1f4f8;
@@ -903,7 +903,7 @@
903
903
  .rvg-header-tooltip,
904
904
  .rvg-cell-tooltip {
905
905
  position: absolute;
906
- z-index: 5;
906
+ z-index: 8;
907
907
  max-width: 320px;
908
908
  padding: 4px 7px;
909
909
  transform: translateY(-50%);
@@ -947,6 +947,33 @@
947
947
  right: -4px;
948
948
  border-left: 5px solid var(--rvg-color-tooltip-bg);
949
949
  }
950
+ .rvg-header-tooltip.is-below,
951
+ .rvg-cell-tooltip.is-below {
952
+ transform: none;
953
+ }
954
+ .rvg-header-tooltip.is-below::after,
955
+ .rvg-cell-tooltip.is-below::after {
956
+ top: -4px;
957
+ left: 10px;
958
+ transform: none;
959
+ border-right: 5px solid transparent;
960
+ border-bottom: 5px solid var(--rvg-color-tooltip-bg);
961
+ border-left: 5px solid transparent;
962
+ border-top: 0;
963
+ }
964
+ .rvg-cell-tooltip.is-above {
965
+ transform: translateY(-100%);
966
+ }
967
+ .rvg-cell-tooltip.is-above::after {
968
+ top: auto;
969
+ bottom: -4px;
970
+ left: 10px;
971
+ transform: none;
972
+ border-right: 5px solid transparent;
973
+ border-top: 5px solid var(--rvg-color-tooltip-bg);
974
+ border-bottom: 0;
975
+ border-left: 5px solid transparent;
976
+ }
950
977
  .rvg-cell-tooltip.is-annotation::after {
951
978
  display: none;
952
979
  }
@@ -985,6 +1012,9 @@
985
1012
  pointer-events: none;
986
1013
  background: var(--rvg-color-primary);
987
1014
  }
1015
+ .rvg-column-guide.is-resizing {
1016
+ z-index: 7;
1017
+ }
988
1018
  .rvg-row-guide {
989
1019
  position: absolute;
990
1020
  z-index: 3;
package/dist/index.js CHANGED
@@ -852,7 +852,7 @@ var COLORS = {
852
852
  rowHoverFill: "#f6f9fc",
853
853
  editedFill: "#fff1b8",
854
854
  insertedFill: "#c8ead4",
855
- columnDropTargetFill: "#eeeeee",
855
+ columnDropTargetFill: "#e2edf9",
856
856
  stripe: "#fafbfc"
857
857
  };
858
858
  function ellipsizeText(context, text, maxWidth) {
@@ -929,13 +929,24 @@ function paintGrid(options) {
929
929
  return rowIndex >= rowDragPreview.targetIndex && rowIndex < rowDragPreview.sourceIndex ? rowHeight : 0;
930
930
  };
931
931
  const rowBelowDragGap = rowDragPreview ? rowDragPreview.sourceIndex <= rowDragPreview.targetIndex ? rowDragPreview.targetIndex + 1 : rowDragPreview.targetIndex : -1;
932
- if (striped && trailingBlankStart !== null) {
932
+ if (trailingBlankStart !== null) {
933
933
  const rowStart = Math.max(0, range.rowStart);
934
934
  for (let rowIndex = rowStart; rowIndex < range.rowEnd; rowIndex += 1) {
935
- if (rowIndex % 2 === 0 || rowDragPreview?.sourceIndex === rowIndex) continue;
935
+ if (rowDragPreview?.sourceIndex === rowIndex) continue;
936
+ const row = rows[rowIndex];
937
+ const rowKey = getRowKey(row, rowIndex);
938
+ const trailingRowStyle = options.rowStyle?.(row, rowIndex);
936
939
  const y = bodyTop + rowIndex * rowHeight - scrollTop + getRowDragOffset(rowIndex);
937
- ctx.fillStyle = colors.stripe;
938
- ctx.fillRect(trailingBlankStart, y, width - trailingBlankStart, rowHeight);
940
+ const paintTrailingLayer = (fill) => {
941
+ if (!fill) return;
942
+ ctx.fillStyle = fill;
943
+ ctx.fillRect(trailingBlankStart, y, width - trailingBlankStart, rowHeight);
944
+ };
945
+ if (striped && rowIndex % 2 === 1) paintTrailingLayer(colors.stripe);
946
+ paintTrailingLayer(trailingRowStyle?.backgroundColor);
947
+ if (selectedRowKeys.has(rowKey)) paintTrailingLayer(colors.axisSelectionFill);
948
+ if (hoveredRowIndex === rowIndex) paintTrailingLayer(colors.rowHoverFill);
949
+ if (highlightInsertedRows && insertedRowKeys.has(rowKey)) paintTrailingLayer(colors.insertedFill);
939
950
  }
940
951
  }
941
952
  const paintBodyColumns = (layer) => {
@@ -960,6 +971,7 @@ function paintGrid(options) {
960
971
  const cellWidth = getCellWidth(columnIndex, colSpan);
961
972
  const cellHeight = rowHeight * rowSpan;
962
973
  const isAxisSelected = selectedRowKeys.has(rowKey) || selectedColumnKeys.has(column.key);
974
+ const isColumnDropTarget = Boolean(columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex);
963
975
  const rangeRowStart = selectionRange ? Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex) : -1;
964
976
  const rangeRowEnd = selectionRange ? Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex) : -1;
965
977
  const rangeColumnStart = selectionRange ? Math.min(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex) : -1;
@@ -981,10 +993,6 @@ function paintGrid(options) {
981
993
  ctx.fillStyle = colors.rowHoverFill;
982
994
  ctx.fillRect(x, y, cellWidth, cellHeight);
983
995
  }
984
- if (columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex) {
985
- ctx.fillStyle = colors.columnDropTargetFill;
986
- ctx.fillRect(x, y, cellWidth, cellHeight);
987
- }
988
996
  if (highlightInsertedRows && insertedRowKeys.has(rowKey)) {
989
997
  ctx.fillStyle = colors.insertedFill;
990
998
  ctx.fillRect(x, y, cellWidth, cellHeight);
@@ -1009,6 +1017,10 @@ function paintGrid(options) {
1009
1017
  ctx.fillStyle = annotation.color;
1010
1018
  ctx.fillRect(x, y, cellWidth, cellHeight);
1011
1019
  }
1020
+ if (isColumnDropTarget) {
1021
+ ctx.fillStyle = colors.columnDropTargetFill;
1022
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1023
+ }
1012
1024
  ctx.fillStyle = colors.grid;
1013
1025
  if (!verticalBorderless && !(frameBorderless && columnIndex + colSpan >= columns.length)) {
1014
1026
  ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
@@ -1018,14 +1030,14 @@ function paintGrid(options) {
1018
1030
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
1019
1031
  }
1020
1032
  if (rowSpan > 1) {
1021
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1033
+ const mergedFill = isColumnDropTarget ? colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1022
1034
  ctx.fillStyle = mergedFill;
1023
1035
  for (let offset = 1; offset < rowSpan; offset += 1) {
1024
1036
  ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
1025
1037
  }
1026
1038
  }
1027
1039
  if (colSpan > 1 && !verticalBorderless) {
1028
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1040
+ const mergedFill = isColumnDropTarget ? colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1029
1041
  ctx.fillStyle = mergedFill;
1030
1042
  let offsetLeft = 0;
1031
1043
  for (let offset = 1; offset < colSpan; offset += 1) {
@@ -1122,11 +1134,11 @@ function paintGrid(options) {
1122
1134
  ctx.beginPath();
1123
1135
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1124
1136
  ctx.clip();
1125
- ctx.fillStyle = isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1137
+ ctx.fillStyle = isColumnDropTarget ? colors.text : isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1126
1138
  ctx.font = createCellFont(bodyFontSize);
1127
1139
  ctx.textBaseline = "alphabetic";
1128
1140
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
1129
- ctx.fillText(label, textX, y + cellHeight / 2 + cellBaselineOffset);
1141
+ ctx.fillText(ellipsizeText(ctx, label, Math.max(0, cellWidth - padding * 2)), textX, y + cellHeight / 2 + cellBaselineOffset);
1130
1142
  ctx.restore();
1131
1143
  }
1132
1144
  }
@@ -1185,6 +1197,25 @@ function paintGrid(options) {
1185
1197
  paintExtendedColumnSelection("scroll");
1186
1198
  paintExtendedColumnSelection("left");
1187
1199
  paintExtendedColumnSelection("right");
1200
+ const paintExtendedColumnDropTarget = (layer) => {
1201
+ if (!columnDropTarget) return;
1202
+ ctx.save();
1203
+ if (layer === "scroll") {
1204
+ ctx.beginPath();
1205
+ ctx.rect(leftFixedWidth, extensionTop, Math.max(0, rightFixedLeft - leftFixedWidth), extensionHeight);
1206
+ ctx.clip();
1207
+ }
1208
+ ctx.fillStyle = colors.columnDropTargetFill;
1209
+ for (let columnIndex = columnDropTarget.startIndex; columnIndex <= columnDropTarget.endIndex; columnIndex += 1) {
1210
+ if (layer === "scroll" ? columns[columnIndex]?.fixed !== void 0 : columns[columnIndex]?.fixed !== layer) continue;
1211
+ if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
1212
+ ctx.fillRect(getColumnX(columnIndex), extensionTop, metrics[columnIndex].width, extensionHeight);
1213
+ }
1214
+ ctx.restore();
1215
+ };
1216
+ paintExtendedColumnDropTarget("scroll");
1217
+ paintExtendedColumnDropTarget("left");
1218
+ paintExtendedColumnDropTarget("right");
1188
1219
  if (!verticalBorderless) {
1189
1220
  paintExtendedColumnLines("scroll");
1190
1221
  paintExtendedColumnLines("left");
@@ -1206,11 +1237,12 @@ function paintGrid(options) {
1206
1237
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1207
1238
  }
1208
1239
  const isAxisSelected = selectedColumnKeys.has(columns[columnIndex].key);
1240
+ const isColumnDropTarget = Boolean(columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex);
1209
1241
  if (isAxisSelected) {
1210
1242
  ctx.fillStyle = colors.axisSelectionFill;
1211
1243
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1212
1244
  }
1213
- if (columnDropTarget && columnIndex >= columnDropTarget.startIndex && columnIndex <= columnDropTarget.endIndex) {
1245
+ if (isColumnDropTarget) {
1214
1246
  ctx.fillStyle = colors.columnDropTargetFill;
1215
1247
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1216
1248
  }
@@ -1239,7 +1271,7 @@ function paintGrid(options) {
1239
1271
  ctx.beginPath();
1240
1272
  ctx.rect(x + 2, headerY + headerLeafTop + 1, Math.max(0, clipWidth - 4), headerLeafHeight - 2);
1241
1273
  ctx.clip();
1242
- ctx.fillStyle = isAxisSelected ? colors.axisSelectionText : colors.muted;
1274
+ ctx.fillStyle = isColumnDropTarget ? colors.muted : isAxisSelected ? colors.axisSelectionText : colors.muted;
1243
1275
  ctx.textAlign = titleAlign === "right" ? "right" : titleAlign === "center" ? "center" : "left";
1244
1276
  ctx.fillText(title, textX, headerY + headerLeafTop + headerLeafHeight / 2);
1245
1277
  ctx.restore();
@@ -1398,6 +1430,11 @@ function resolveColumnDropIndex(sourceIndex, targetIndex, edge, columnCount) {
1398
1430
  function resolveColumnDropPlacement(edge) {
1399
1431
  return edge === "right" ? "after" : "before";
1400
1432
  }
1433
+ function resolveColumnDropHighlightRange(target, edge, cells) {
1434
+ if (edge === "right") return { startIndex: target.startIndex, endIndex: target.endIndex };
1435
+ const previous = cells.find((cell) => cell.endIndex === target.startIndex - 1 && cell.level === target.level && cell.parentKey === target.parentKey);
1436
+ return previous ? { startIndex: previous.startIndex, endIndex: previous.endIndex } : { startIndex: target.startIndex, endIndex: target.endIndex };
1437
+ }
1401
1438
  var ANNOTATION_COLORS = ["#f44336", "#ff7a45", "#f5a623", "#f2c94c", "#52c41a", "#13a8a8", "#1677ff", "#597ef7", "#9254de", "#eb2f96"];
1402
1439
  var FALLBACK_HEIGHT = 480;
1403
1440
  var EMPTY_BODY_HEIGHT = 240;
@@ -2277,7 +2314,7 @@ function TableInner({
2277
2314
  rowHoverFill: readThemeColor(themeStyles, "--rvg-color-row-hover-fill", "#f6f9fc"),
2278
2315
  editedFill: editedCellHighlightColor ?? readThemeColor(themeStyles, "--rvg-color-edited-fill", "#fff1b8"),
2279
2316
  insertedFill: insertedRowHighlightColor ?? readThemeColor(themeStyles, "--rvg-color-inserted-fill", "#c8ead4"),
2280
- columnDropTargetFill: readThemeColor(themeStyles, "--rvg-color-column-drop-target-fill", "#eeeeee"),
2317
+ columnDropTargetFill: readThemeColor(themeStyles, "--rvg-color-column-drop-target-fill", "#e2edf9"),
2281
2318
  stripe: stripedColor ?? readThemeColor(themeStyles, "--rvg-color-stripe", "#fafbfc")
2282
2319
  };
2283
2320
  const scroll = scrollRef.current;
@@ -3555,6 +3592,8 @@ function TableInner({
3555
3592
  columnDragRef.current = null;
3556
3593
  setResizeGuideX(null);
3557
3594
  setResizeGuideTop(0);
3595
+ document.documentElement.classList.remove("rvg-is-dragging");
3596
+ event.currentTarget.style.cursor = "default";
3558
3597
  }, []);
3559
3598
  const clearColumnOrderPointerDrag = useCallback2((canvas) => {
3560
3599
  columnOrderDragRef.current = null;
@@ -3636,7 +3675,11 @@ function TableInner({
3636
3675
  fixed: targetColumn.fixed,
3637
3676
  columnEdge
3638
3677
  };
3639
- setColumnDropTarget({ startIndex: targetCell.startIndex, endIndex: targetCell.endIndex });
3678
+ const compatibleHighlightCells = headerCells.filter((cell) => {
3679
+ const cellColumn = columns[cell.startIndex];
3680
+ return cellColumn && !cellColumn.rowSelection && !cellColumn.rowDragHandle && !cellColumn.rowNumber && cellColumn.fixed === targetColumn.fixed;
3681
+ });
3682
+ setColumnDropTarget(resolveColumnDropHighlightRange(targetCell, columnEdge, compatibleHighlightCells));
3640
3683
  setDragGuide(columnEdge === "right" ? targetLeft + targetWidth : targetLeft, headerRowOffsets[sourceCell.level] ?? 0);
3641
3684
  }, [columns, getHeaderCellLeft, getHeaderCellWidth, headerCells, headerRowOffsets, hideDragTooltips, locateColumn, locateHeaderCell, setDragGuide]);
3642
3685
  const finishColumnOrderPointerDrag = useCallback2((event, apply) => {
@@ -3781,6 +3824,34 @@ function TableInner({
3781
3824
  const textTop = rowTop + 8;
3782
3825
  return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3783
3826
  }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerRowOffsets]);
3827
+ const locateCustomHeaderTooltip = useCallback2((clientX, clientY, columnIndex) => {
3828
+ const root = rootRef.current;
3829
+ const canvas = canvasRef.current;
3830
+ if (!root || !canvas || columnIndex < 0) return null;
3831
+ const canvasRect = canvas.getBoundingClientRect();
3832
+ const targets = root.querySelectorAll(".rvg-header-title [data-rvg-tooltip]");
3833
+ for (const target of targets) {
3834
+ const rect = target.getBoundingClientRect();
3835
+ if (clientX < rect.left || clientX > rect.right || clientY < rect.top || clientY > rect.bottom) continue;
3836
+ const label = target.dataset.rvgTooltip?.trim();
3837
+ if (!label) return false;
3838
+ const visibleText = target.textContent?.replace(/\s+/g, " ").trim() ?? "";
3839
+ const candidates = [target, ...target.querySelectorAll("*")];
3840
+ const truncated = candidates.some((candidate) => candidate.scrollWidth > candidate.clientWidth + 1 || candidate.scrollHeight > candidate.clientHeight + 1);
3841
+ if (!truncated && label.replace(/\s+/g, " ").trim() === visibleText) return false;
3842
+ const rawLeft = rect.left - canvasRect.left;
3843
+ return {
3844
+ key: `content:${columnIndex}:${label}:${Math.round(rawLeft)}:${Math.round(rect.top - canvasRect.top)}`,
3845
+ columnIndex,
3846
+ action: "content",
3847
+ left: Math.max(8, Math.min(rawLeft, Math.max(8, viewport.width - 328))),
3848
+ top: rect.bottom - canvasRect.top + 4,
3849
+ label,
3850
+ placement: "below"
3851
+ };
3852
+ }
3853
+ return null;
3854
+ }, [viewport.width]);
3784
3855
  const headerTooltip = (() => {
3785
3856
  if (!visibleHeaderTooltip) return null;
3786
3857
  return visibleHeaderTooltip;
@@ -4106,23 +4177,26 @@ function TableInner({
4106
4177
  if (column.renderHeader) {
4107
4178
  const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
4108
4179
  const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
4180
+ const isHeaderHovered = hoveredHeaderColumnIndex === columnIndex;
4109
4181
  return /* @__PURE__ */ jsx7(
4110
4182
  "div",
4111
4183
  {
4112
- className: `rvg-header-title is-custom${isAxisSelected ? " is-axis-selected" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4184
+ className: `rvg-header-title is-custom${isHeaderHovered ? " is-header-hovered" : ""}${isAxisSelected ? " is-axis-selected" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4113
4185
  "data-level": headerDepth - 1,
4114
4186
  "data-measure-key": column.key,
4115
4187
  style: {
4116
4188
  left,
4117
4189
  top: leafTop,
4118
- width: Math.max(0, metrics[columnIndex].width - actionWidth),
4190
+ width: metrics[columnIndex].width,
4119
4191
  height: headerLeafHeight,
4192
+ "--rvg-header-action-width": `${actionWidth}px`,
4120
4193
  justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
4121
4194
  textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
4122
4195
  },
4123
4196
  children: /* @__PURE__ */ jsx7(
4124
4197
  "span",
4125
4198
  {
4199
+ style: { width: "100%" },
4126
4200
  ref: (node) => {
4127
4201
  if (node) {
4128
4202
  customHeaderContentRefs.current.set(column.key, node);
@@ -4489,16 +4563,19 @@ function TableInner({
4489
4563
  const hoveredAction = inLeafHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
4490
4564
  const hoveredDragHandle = Boolean(hoveredHeaderCell && locateHeaderCellDragHandle(event.clientX, hoveredHeaderCell));
4491
4565
  const hoveredDragColumnIndex = hoveredHeaderCell?.startIndex ?? columnIndex;
4492
- const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
4493
- const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
4494
- const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
4566
+ const customHeaderTooltipMatch = inHeader && !hoveredAction && !hoveredDragHandle ? locateCustomHeaderTooltip(event.clientX, event.clientY, columnIndex) : null;
4567
+ const hoveredContentTooltip = customHeaderTooltipMatch || null;
4568
+ const hoveredCustomContent = customHeaderTooltipMatch !== null;
4569
+ const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && !hoveredCustomContent && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
4570
+ const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredContentTooltip ? { columnIndex, action: "content" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
4571
+ const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredContentTooltip ? hoveredContentTooltip : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
4495
4572
  const previousHover = hoveredHeaderActionRef.current;
4496
4573
  if (previousHover?.columnIndex !== nextHover?.columnIndex || previousHover?.action !== nextHover?.action) {
4497
4574
  hoveredHeaderActionRef.current = nextHover;
4498
4575
  setHoveredHeaderAction(nextHover);
4499
- showHeaderTooltipAfterDelay(nextTooltip);
4500
4576
  scheduleDraw();
4501
4577
  }
4578
+ showHeaderTooltipAfterDelay(nextTooltip);
4502
4579
  if (inHeader) {
4503
4580
  if (hoveredRowIndexRef.current !== null) {
4504
4581
  hoveredRowIndexRef.current = null;
@@ -4550,12 +4627,12 @@ function TableInner({
4550
4627
  if (!isInsideText) {
4551
4628
  showCellTooltipAfterDelay(null);
4552
4629
  } else {
4553
- const placement = textRight + 330 > viewport.width ? "left" : "right";
4630
+ const placement = cellTop + rowHeight + 36 > renderHeight ? "above" : "below";
4554
4631
  const nextTooltip2 = {
4555
4632
  rowIndex: cell.rowIndex,
4556
4633
  columnIndex: cell.columnIndex,
4557
- left: placement === "right" ? textRight + 16 : Math.max(8, textLeft - 16),
4558
- top: cellTop + rowHeight / 2,
4634
+ left: Math.max(8, Math.min(textLeft, Math.max(8, viewport.width - 328))),
4635
+ top: placement === "below" ? cellTop + rowHeight + 4 : cellTop - 4,
4559
4636
  label,
4560
4637
  placement
4561
4638
  };
@@ -4584,6 +4661,8 @@ function TableInner({
4584
4661
  }
4585
4662
  },
4586
4663
  onPointerDown: (event) => {
4664
+ handleColumnPointerDown(event);
4665
+ if (columnDragRef.current) return;
4587
4666
  const headerDragCell = columnDraggable ? locateHeaderCell(event.clientX, event.clientY) : null;
4588
4667
  const columnIndex = locateColumn(event.clientX);
4589
4668
  const rowDragHandle = rowDraggable && columnIndex >= 0 && Boolean(columns[columnIndex].rowDragHandle) && locateRow(event.clientY) >= 0;
@@ -4592,8 +4671,7 @@ function TableInner({
4592
4671
  document.documentElement.classList.add("rvg-is-dragging");
4593
4672
  event.currentTarget.style.cursor = "move";
4594
4673
  }
4595
- handleColumnPointerDown(event);
4596
- if (!columnDragRef.current) handleColumnOrderPointerDown(event);
4674
+ handleColumnOrderPointerDown(event);
4597
4675
  beginRangeDrag(event);
4598
4676
  },
4599
4677
  onPointerMove: (event) => {
@@ -4651,6 +4729,12 @@ function TableInner({
4651
4729
  }
4652
4730
  const cell = locateCell(event.clientX, event.clientY);
4653
4731
  if (!cell) {
4732
+ const localX = event.clientX - rect.left;
4733
+ const trailingRowIndex = trailingColumnBorderLeft !== null && localX > trailingColumnBorderLeft ? locateRow(event.clientY) : -1;
4734
+ if (trailingRowIndex >= 0 && rowSelection) {
4735
+ selectRow(trailingRowIndex, event);
4736
+ return;
4737
+ }
4654
4738
  clickedCellRef.current = null;
4655
4739
  setSelectionRange(null);
4656
4740
  setSelection(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.24",
3
+ "version": "0.1.27",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",