@lensui/lens-table 0.1.11 → 0.1.13

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/README.md CHANGED
@@ -89,7 +89,7 @@ export function Example() {
89
89
  | `rowKey` | `keyof Row \| ((row, index) => string \| number)` | `id` | 行唯一标识。默认读取每行的`id` 字段;如果数据不是 `id` 主键,请显式传入字段名或函数。 |
90
90
  | `width` | `number \| string` | `100%` | 表格宽度。数字按像素处理,字符串可传`100%`、`80vw` 等。 |
91
91
  | `height` | `number \| string` | `100%` | 表格最大高度。默认继承父容器高度;如果父容器没有可计算高度,则回落到`480px`。数据较少时默认按真实内容高度收缩,数据较多时不超过该高度。 |
92
- | `layout` | `{ rowHeight?: number; headerHeight?: number }` | `{ rowHeight: 36, headerHeight: 40 }` | 表格布局尺寸配置。推荐把行高、表头高度等样式相关参数放在这里;自定义多行表头会以 `headerHeight` 为最小高度自动撑开。 |
92
+ | `layout` | `{ rowHeight?: number; headerHeight?: number; headerActionSize?: number }` | `{ rowHeight: 36, headerHeight: 40, headerActionSize: 14 }` | 表格布局尺寸配置。`headerActionSize` 同步控制表头搜索、排序和拖拽图标的尺寸、占位及点击区域;自定义多行表头会以 `headerHeight` 为最小高度自动撑开。 |
93
93
  | `rowHeight` | `number` | `36` | 已兼容保留,建议改用 `layout.rowHeight`。 |
94
94
  | `headerHeight` | `number` | `40` | 已兼容保留,建议改用 `layout.headerHeight`。 |
95
95
  | `fixedHeader` | `boolean` | `true` | 表头是否固定在顶部。 |
@@ -97,6 +97,7 @@ export function Example() {
97
97
  | `borderless` | `boolean` | `false` | 是否弱化竖向边框。开启后隐藏单元格之间的竖线,但保留横向分隔线。 |
98
98
  | `verticalBorderless` | `boolean` | `false` | 已兼容保留,建议改用 `borderless`。 |
99
99
  | `horizontalBorderless` | `boolean` | `false` | 是否隐藏横向行分隔线及上下边框,同时保留纵向列分隔线。可与 `striped` 配合使用。 |
100
+ | `frameBorderless` | `boolean` | `false` | 是否隐藏表格四周外框,同时保留内部行列分隔线。 |
100
101
  | `striped` | `boolean \| string` | `false` | 是否显示斑马纹行背景。传`true` 使用主题变量,传颜色字符串可自定义斑马纹颜色,例如 `'#f6faff'`。 |
101
102
  | `highlight` | `{ editedCells?: boolean \| string; insertedRows?: boolean \| string }` | `{}` | 高亮反馈配置。`editedCells` 控制编辑过的单元格底色,`insertedRows` 控制乐观插入行底色;传 `true` 使用默认色,传颜色字符串可自定义。 |
102
103
  | `cellSpans` | `TableCellSpan[] \| ((ctx) => TableCellSpan \| { rowSpan?: number; colSpan?: number } \| null)` | `[]` | 合并主体单元格配置。可传数组,也可传函数按单元格生成规则。推荐使用 `rowKey` 定位;横向合并会限制在同一个固定列分区内,工具列不会参与合并。 |
package/dist/index.cjs CHANGED
@@ -822,8 +822,8 @@ function getHeaderTitleRequiredWidth(column, titleWidth) {
822
822
  const minimumReadableTitleWidth = Math.min(Math.ceil(titleWidth), 28);
823
823
  return minimumReadableTitleWidth + (column.align === "center" ? 20 : 22);
824
824
  }
825
- function getVisibleHeaderActions(column, width, columnDraggable = false, titleWidth = 26) {
826
- const capacity = Math.max(0, Math.floor((width - getHeaderTitleRequiredWidth(column, titleWidth)) / HEADER_ACTION_SLOT_WIDTH));
825
+ function getVisibleHeaderActions(column, width, columnDraggable = false, titleWidth = 26, actionSlotWidth = HEADER_ACTION_SLOT_WIDTH) {
826
+ const capacity = Math.max(0, Math.floor((width - getHeaderTitleRequiredWidth(column, titleWidth)) / actionSlotWidth));
827
827
  const candidates = [
828
828
  isColumnFilterable(column) ? "filter" : null,
829
829
  isColumnSortable(column) ? "sort" : null,
@@ -942,7 +942,7 @@ function ellipsizeText(context, text, maxWidth) {
942
942
  return `${text.slice(0, start)}${ellipsis}`;
943
943
  }
944
944
  function paintGrid(options) {
945
- const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, bodyFontSize, headerHeight, fixedHeader, verticalBorderless, horizontalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = options;
945
+ const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, fixedHeader, verticalBorderless, horizontalBorderless, frameBorderless, extendVerticalGridLines, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = options;
946
946
  const headerLeafTop = options.headerLeafTop ?? 0;
947
947
  const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
948
948
  const colors = { ...COLORS, ...options.colors };
@@ -1063,7 +1063,9 @@ function paintGrid(options) {
1063
1063
  ctx.fillRect(x, y, cellWidth, cellHeight);
1064
1064
  }
1065
1065
  ctx.fillStyle = colors.grid;
1066
- if (!verticalBorderless) ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
1066
+ if (!verticalBorderless && !(frameBorderless && columnIndex + colSpan >= columns.length)) {
1067
+ ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
1068
+ }
1067
1069
  if (rowIndex === rowBelowDragGap) ctx.fillRect(x, y, cellWidth, 1);
1068
1070
  if (!horizontalBorderless && (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1)) {
1069
1071
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
@@ -1182,6 +1184,30 @@ function paintGrid(options) {
1182
1184
  paintBodyColumns("scroll");
1183
1185
  paintBodyColumns("left");
1184
1186
  paintBodyColumns("right");
1187
+ if (extendVerticalGridLines && !verticalBorderless) {
1188
+ const extensionTop = Math.max(bodyTop, Math.min(height, bodyTop + rows.length * rowHeight - scrollTop));
1189
+ const extensionHeight = Math.max(0, height - extensionTop);
1190
+ const paintExtendedColumnLines = (layer) => {
1191
+ ctx.save();
1192
+ if (layer === "scroll") {
1193
+ ctx.beginPath();
1194
+ ctx.rect(leftFixedWidth, extensionTop, Math.max(0, rightFixedLeft - leftFixedWidth), extensionHeight);
1195
+ ctx.clip();
1196
+ }
1197
+ ctx.fillStyle = colors.grid;
1198
+ for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
1199
+ if (layer === "scroll" ? columns[columnIndex].fixed !== void 0 : columns[columnIndex].fixed !== layer) continue;
1200
+ if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
1201
+ if (frameBorderless && columnIndex === columns.length - 1) continue;
1202
+ const x = getColumnX(columnIndex) + metrics[columnIndex].width - 1;
1203
+ ctx.fillRect(x, extensionTop, 1, extensionHeight);
1204
+ }
1205
+ ctx.restore();
1206
+ };
1207
+ paintExtendedColumnLines("scroll");
1208
+ paintExtendedColumnLines("left");
1209
+ paintExtendedColumnLines("right");
1210
+ }
1185
1211
  const headerY = fixedHeader ? 0 : -scrollTop;
1186
1212
  ctx.fillStyle = colors.header;
1187
1213
  ctx.fillRect(0, headerY, width, headerHeight);
@@ -1205,14 +1231,16 @@ function paintGrid(options) {
1205
1231
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1206
1232
  }
1207
1233
  ctx.fillStyle = colors.grid;
1208
- if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1234
+ if (!verticalBorderless && !(frameBorderless && columnIndex === columns.length - 1)) {
1235
+ ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1236
+ }
1209
1237
  if (!horizontalBorderless) ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1210
1238
  const column = columns[columnIndex];
1211
1239
  if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1212
1240
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1213
1241
  const titleWidth = ctx.measureText(column.title).width;
1214
- const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
1215
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH;
1242
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
1243
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
1216
1244
  const contentWidth = Math.max(0, metric.width - actionWidth);
1217
1245
  const padding = 10;
1218
1246
  const clipWidth = contentWidth;
@@ -1242,12 +1270,12 @@ function paintGrid(options) {
1242
1270
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1243
1271
  }
1244
1272
  ctx.fillStyle = colors.grid;
1245
- if (!horizontalBorderless) ctx.fillRect(0, 0, width, 1);
1246
- if (!verticalBorderless) {
1273
+ if (!horizontalBorderless && !frameBorderless) ctx.fillRect(0, 0, width, 1);
1274
+ if (!verticalBorderless && !frameBorderless) {
1247
1275
  ctx.fillRect(0, 0, 1, height);
1248
1276
  ctx.fillRect(width - 1, 0, 1, height);
1249
1277
  }
1250
- if (!horizontalBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1278
+ if (!horizontalBorderless && !frameBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1251
1279
  if (selectionRange) {
1252
1280
  const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1253
1281
  const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
@@ -1556,6 +1584,7 @@ function TableInner({
1556
1584
  borderless,
1557
1585
  verticalBorderless,
1558
1586
  horizontalBorderless = false,
1587
+ frameBorderless = false,
1559
1588
  striped = false,
1560
1589
  highlight,
1561
1590
  cellSpans = [],
@@ -1602,6 +1631,8 @@ function TableInner({
1602
1631
  setSourceColumns((current) => reconcileColumns(current, columnProps));
1603
1632
  }, [columnProps]);
1604
1633
  const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
1634
+ const headerActionSize = Math.max(8, layout?.headerActionSize ?? 14);
1635
+ const headerActionSlotWidth = headerActionSize + 2;
1605
1636
  const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
1606
1637
  const headerDepth = (0, import_react5.useMemo)(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
1607
1638
  const { language, labels } = (0, import_react5.useMemo)(() => resolveGridLocale(locale), [locale]);
@@ -2237,8 +2268,8 @@ function TableInner({
2237
2268
  const scroll = scrollRef.current;
2238
2269
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2239
2270
  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 };
2240
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2241
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2271
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2272
+ }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2242
2273
  const scheduleDraw = (0, import_react5.useCallback)(() => {
2243
2274
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2244
2275
  }, [draw]);
@@ -2804,34 +2835,34 @@ function TableInner({
2804
2835
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2805
2836
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2806
2837
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2807
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2808
- let right = columnRight - (visibleActions.drag ? HEADER_ACTION_SLOT_WIDTH : 2);
2838
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
2839
+ let right = columnRight - (visibleActions.drag ? headerActionSlotWidth : 2);
2809
2840
  if (visibleActions.sort) {
2810
- if (localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "sort";
2811
- right -= HEADER_ACTION_SLOT_WIDTH;
2841
+ if (localX >= right - headerActionSlotWidth && localX < right) return "sort";
2842
+ right -= headerActionSlotWidth;
2812
2843
  }
2813
- if (visibleActions.filter && localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "filter";
2844
+ if (visibleActions.filter && localX >= right - headerActionSlotWidth && localX < right) return "filter";
2814
2845
  return null;
2815
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2846
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2816
2847
  const isHeaderTitleTruncated = (0, import_react5.useCallback)((columnIndex) => {
2817
2848
  const canvas = canvasRef.current;
2818
2849
  if (columnIndex < 0 || !canvas) return false;
2819
2850
  const column = columns[columnIndex];
2820
2851
  const metric = metrics[columnIndex];
2821
2852
  const titleWidth = measureHeaderTitleWidth(columnIndex);
2822
- const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
2823
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH;
2853
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
2854
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
2824
2855
  return getHeaderTitleRequiredWidth(column, titleWidth) > Math.max(0, metric.width - actionWidth);
2825
- }, [columnDraggable, columns, measureHeaderTitleWidth, metrics]);
2856
+ }, [columnDraggable, columns, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2826
2857
  const locateHeaderDragHandle = (0, import_react5.useCallback)((clientX, columnIndex) => {
2827
2858
  if (!columnDraggable || columnIndex < 0 || !canvasRef.current) return false;
2828
2859
  const column = columns[columnIndex];
2829
2860
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return false;
2830
- if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex)).drag) return false;
2861
+ if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth).drag) return false;
2831
2862
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2832
2863
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2833
- return localX >= columnRight - 16 && localX < columnRight - 5;
2834
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2864
+ return localX >= columnRight - headerActionSlotWidth && localX < columnRight - 2;
2865
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2835
2866
  const getHeaderCellLeft = (0, import_react5.useCallback)((cell) => {
2836
2867
  const startMetric = metrics[cell.startIndex];
2837
2868
  const fixedSide = columns[cell.startIndex]?.fixed;
@@ -2896,8 +2927,8 @@ function TableInner({
2896
2927
  if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
2897
2928
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2898
2929
  const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
2899
- return localX >= right - 16 && localX < right - 5;
2900
- }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
2930
+ return localX >= right - headerActionSlotWidth && localX < right - 2;
2931
+ }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, locateHeaderDragHandle]);
2901
2932
  const locateHeaderCell = (0, import_react5.useCallback)((clientX, clientY) => {
2902
2933
  const canvas = canvasRef.current;
2903
2934
  if (!canvas) return null;
@@ -3053,26 +3084,32 @@ function TableInner({
3053
3084
  header.style.height = `${headerHeight}px`;
3054
3085
  header.style.justifyContent = column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start";
3055
3086
  header.textContent = column.title;
3056
- const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex));
3087
+ const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex), headerActionSlotWidth);
3057
3088
  let actionRight = 2;
3058
3089
  if (visibleActions.drag) {
3059
3090
  const icon = createHeaderDragHandleSvg();
3060
3091
  icon.classList.add("rvg-preview-header-icon");
3092
+ icon.style.width = `${headerActionSize}px`;
3093
+ icon.style.height = `${headerActionSize}px`;
3061
3094
  icon.style.right = `${actionRight}px`;
3062
3095
  header.appendChild(icon);
3063
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3096
+ actionRight += headerActionSlotWidth;
3064
3097
  }
3065
3098
  if (visibleActions.sort) {
3066
3099
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3067
3100
  const icon = createHeaderSortSvg(direction);
3068
3101
  icon.classList.add("rvg-preview-header-icon");
3102
+ icon.style.width = `${headerActionSize}px`;
3103
+ icon.style.height = `${headerActionSize}px`;
3069
3104
  icon.style.right = `${actionRight}px`;
3070
3105
  header.appendChild(icon);
3071
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3106
+ actionRight += headerActionSlotWidth;
3072
3107
  }
3073
3108
  if (visibleActions.filter) {
3074
3109
  const icon = createHeaderSearchSvg(Boolean(filterValues[column.key]));
3075
3110
  icon.classList.add("rvg-preview-header-icon", "rvg-preview-header-search-icon");
3111
+ icon.style.width = `${headerActionSize}px`;
3112
+ icon.style.height = `${headerActionSize}px`;
3076
3113
  icon.style.right = `${actionRight}px`;
3077
3114
  header.appendChild(icon);
3078
3115
  }
@@ -3269,7 +3306,7 @@ function TableInner({
3269
3306
  cleanupDrag();
3270
3307
  cleanupDrop();
3271
3308
  };
3272
- }, [applyColumnOrder, applyRowOrder, bodyTop, columnDraggable, columns, filterValues, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerCells, headerHeight, hideDragTooltips, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, measureHeaderTitleWidth, metrics, rowDraggable, rowHeight, rows, selectedRowKeySet, selection, setDragGuide, setRowDragGuide, sortState, utilityColumnCount]);
3309
+ }, [applyColumnOrder, applyRowOrder, bodyTop, columnDraggable, columns, filterValues, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerActionSize, headerActionSlotWidth, headerCells, headerHeight, hideDragTooltips, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, measureHeaderTitleWidth, metrics, rowDraggable, rowHeight, rows, selectedRowKeySet, selection, setDragGuide, setRowDragGuide, sortState, utilityColumnCount]);
3273
3310
  const locateCell = (0, import_react5.useCallback)((clientX, clientY) => {
3274
3311
  const canvas = canvasRef.current;
3275
3312
  if (!canvas) return null;
@@ -3713,28 +3750,28 @@ function TableInner({
3713
3750
  const renderHeaderIcons = (columnIndex, left) => {
3714
3751
  const column = columns[columnIndex];
3715
3752
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
3716
- const top = headerLeafTop + (headerLeafHeight - 14) / 2;
3717
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
3718
- let right = left + metrics[columnIndex].width - (visibleActions.drag ? HEADER_ACTION_SLOT_WIDTH : 2);
3753
+ const top = headerLeafTop + (headerLeafHeight - headerActionSize) / 2;
3754
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3755
+ let right = left + metrics[columnIndex].width - (visibleActions.drag ? headerActionSlotWidth : 2);
3719
3756
  const icons = [];
3720
3757
  if (visibleActions.drag) {
3721
3758
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "drag";
3722
3759
  icons.push(
3723
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderDragIcon, { className: `rvg-header-icon rvg-header-drag-icon${hovered ? " is-hovered" : ""}`, style: { left: left + metrics[columnIndex].width - 16, top } }, "drag")
3760
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderDragIcon, { className: `rvg-header-icon rvg-header-drag-icon${hovered ? " is-hovered" : ""}`, style: { left: left + metrics[columnIndex].width - headerActionSlotWidth, top } }, "drag")
3724
3761
  );
3725
3762
  }
3726
3763
  if (visibleActions.sort) {
3727
3764
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "sort";
3728
3765
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3729
3766
  icons.push(
3730
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - 16, top }, direction }, "sort")
3767
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - headerActionSlotWidth, top }, direction }, "sort")
3731
3768
  );
3732
- right -= HEADER_ACTION_SLOT_WIDTH;
3769
+ right -= headerActionSlotWidth;
3733
3770
  }
3734
3771
  if (visibleActions.filter) {
3735
3772
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "filter";
3736
3773
  icons.push(
3737
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderSearchIcon, { className: `rvg-header-icon rvg-header-search-icon${hovered ? " is-hovered" : ""}${filterValues[column.key] ? " is-active" : ""}`, style: { left: right - 16, top: top + 1 } }, "filter")
3774
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderSearchIcon, { className: `rvg-header-icon rvg-header-search-icon${hovered ? " is-hovered" : ""}${filterValues[column.key] ? " is-active" : ""}`, style: { left: right - headerActionSlotWidth, top: top + 1 } }, "filter")
3738
3775
  );
3739
3776
  }
3740
3777
  return icons;
@@ -3755,8 +3792,8 @@ function TableInner({
3755
3792
  const context = canvas?.getContext("2d");
3756
3793
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3757
3794
  const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3758
- const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3759
- const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH : columnDraggable ? HEADER_ACTION_SLOT_WIDTH : 0;
3795
+ const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3796
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3760
3797
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3761
3798
  const maxTextWidth = Math.max(0, contentWidth2 - 20);
3762
3799
  const textWidth = Math.min(titleWidth, maxTextWidth);
@@ -3777,12 +3814,17 @@ function TableInner({
3777
3814
  } else if (action === "sort") {
3778
3815
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3779
3816
  label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
3780
- anchorX = cellLeft + cellWidth - 22;
3817
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3818
+ const right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3819
+ anchorX = right - headerActionSlotWidth / 2;
3781
3820
  anchorLeft = anchorX;
3782
3821
  } else if (action === "filter") {
3783
3822
  const value = filterValues[column.key];
3784
3823
  label = value ? labels.filterWithValue(value) : labels.filter;
3785
- anchorX = cellLeft + cellWidth - 40;
3824
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3825
+ let right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3826
+ if (visibleActions.sort) right -= headerActionSlotWidth;
3827
+ anchorX = right - headerActionSlotWidth / 2;
3786
3828
  anchorLeft = anchorX;
3787
3829
  }
3788
3830
  const placement = anchorX + 328 > viewport.width ? "left" : "right";
@@ -3795,7 +3837,7 @@ function TableInner({
3795
3837
  label,
3796
3838
  placement
3797
3839
  };
3798
- }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, sortState, viewport.width]);
3840
+ }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, measureHeaderTitleWidth, metrics, scrollPosition.top, sortState, viewport.width]);
3799
3841
  const isPointerOnHeaderTitle = (0, import_react5.useCallback)((clientX, clientY, cell) => {
3800
3842
  const canvas = canvasRef.current;
3801
3843
  if (!canvas) return false;
@@ -3809,14 +3851,14 @@ function TableInner({
3809
3851
  const context = canvas.getContext("2d");
3810
3852
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3811
3853
  const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3812
- const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3813
- const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH : columnDraggable ? HEADER_ACTION_SLOT_WIDTH : 0;
3854
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3855
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3814
3856
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3815
3857
  const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
3816
3858
  const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
3817
3859
  const textTop = rowTop + 8;
3818
3860
  return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3819
- }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
3861
+ }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerRowOffsets]);
3820
3862
  const headerTooltip = (() => {
3821
3863
  if (!visibleHeaderTooltip) return null;
3822
3864
  return visibleHeaderTooltip;
@@ -4139,8 +4181,8 @@ function TableInner({
4139
4181
  );
4140
4182
  }
4141
4183
  if (column.renderHeader) {
4142
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
4143
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH;
4184
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
4185
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
4144
4186
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4145
4187
  "div",
4146
4188
  {
@@ -4184,13 +4226,13 @@ function TableInner({
4184
4226
  const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
4185
4227
  const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
4186
4228
  const width2 = cell.leaf ? startMetric.width : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1);
4187
- const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex)) : null;
4188
- const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH : columnDraggable && !cell.leaf ? HEADER_ACTION_SLOT_WIDTH : 0;
4229
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex), headerActionSlotWidth) : null;
4230
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable && !cell.leaf ? headerActionSlotWidth : 0;
4189
4231
  const isDropTarget = Boolean(columnDropTarget && cell.endIndex >= columnDropTarget.startIndex && cell.startIndex <= columnDropTarget.endIndex);
4190
4232
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4191
4233
  "div",
4192
4234
  {
4193
- className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4235
+ className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}${cell.endIndex === columns.length - 1 ? " is-frame-right" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4194
4236
  "data-level": cell.level,
4195
4237
  "data-measure-key": `group:${cell.key}:${cell.level}`,
4196
4238
  style: {
@@ -4221,7 +4263,7 @@ function TableInner({
4221
4263
  HeaderDragIcon,
4222
4264
  {
4223
4265
  className: `rvg-header-icon rvg-header-drag-icon${hoveredHeaderAction?.columnIndex === cell.startIndex && hoveredHeaderAction.action === "drag" ? " is-hovered" : ""}`,
4224
- style: { left: width2 - 16, top: (rowHeightForLevel - 14) / 2 }
4266
+ style: { left: width2 - headerActionSlotWidth, top: (rowHeightForLevel - headerActionSize) / 2 }
4225
4267
  }
4226
4268
  )
4227
4269
  ]
@@ -4487,12 +4529,13 @@ function TableInner({
4487
4529
  width,
4488
4530
  height: autoHeight ? effectiveHeight : height,
4489
4531
  minHeight: autoHeight || typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
4532
+ "--rvg-header-action-size": `${headerActionSize}px`,
4490
4533
  ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
4491
4534
  ...style
4492
4535
  };
4493
4536
  const currentScroll = scrollRef.current;
4494
4537
  const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
4495
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4538
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${frameBorderless ? " is-frame-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4496
4539
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { ref: scrollerRef, className: `rvg-scroller${contextMenu ? " is-context-menu-open" : ""}`, style: { bottom: scrollerBottom }, onScroll: handleScroll, onCopy: handleCopy, onContextMenu: handleContextMenu, tabIndex: 0, role: "grid", "aria-label": ariaLabel, "aria-rowcount": rows.length, "aria-colcount": columns.length, onKeyDown: handleKeyDown, children: [
4497
4540
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4498
4541
  "canvas",
package/dist/index.css CHANGED
@@ -757,6 +757,9 @@
757
757
  background: var(--rvg-color-header-bg);
758
758
  box-sizing: border-box;
759
759
  }
760
+ .rvg-root.is-frame-borderless .rvg-header-group.is-frame-right {
761
+ border-right: 0;
762
+ }
760
763
  .rvg-root.is-horizontal-borderless .rvg-header-group {
761
764
  border-bottom: 0;
762
765
  }
@@ -817,6 +820,9 @@
817
820
  content: "";
818
821
  pointer-events: none;
819
822
  }
823
+ .rvg-root.is-frame-borderless .rvg-header-icons::after {
824
+ display: none;
825
+ }
820
826
  .rvg-root.is-vertical-borderless .rvg-header-icons::after {
821
827
  display: none;
822
828
  }
@@ -836,8 +842,8 @@
836
842
  }
837
843
  .rvg-header-icon {
838
844
  position: absolute;
839
- width: 14px;
840
- height: 14px;
845
+ width: var(--rvg-header-action-size, 14px);
846
+ height: var(--rvg-header-action-size, 14px);
841
847
  overflow: visible;
842
848
  color: var(--rvg-color-icon);
843
849
  }
package/dist/index.d.cts CHANGED
@@ -121,6 +121,8 @@ interface TableLayoutConfig {
121
121
  rowHeight?: number;
122
122
  /** Header height in pixels. Defaults to 40. */
123
123
  headerHeight?: number;
124
+ /** Header action icon size in pixels. Defaults to 14. */
125
+ headerActionSize?: number;
124
126
  }
125
127
  interface TableCellSpan {
126
128
  /** Row index in the current rows array. Prefer rowKey when row order can change. */
@@ -349,6 +351,8 @@ interface TableProps<Row extends object> {
349
351
  verticalBorderless?: boolean;
350
352
  /** Hide horizontal row and frame separators while keeping vertical grid lines. */
351
353
  horizontalBorderless?: boolean;
354
+ /** Hide the outer table frame while keeping internal row and column separators. */
355
+ frameBorderless?: boolean;
352
356
  /** Paint alternating row backgrounds. Pass a color string to customize the stripe color. */
353
357
  striped?: boolean | string;
354
358
  /** Visual highlight feedback configuration. All entries default to false. */
package/dist/index.d.ts CHANGED
@@ -121,6 +121,8 @@ interface TableLayoutConfig {
121
121
  rowHeight?: number;
122
122
  /** Header height in pixels. Defaults to 40. */
123
123
  headerHeight?: number;
124
+ /** Header action icon size in pixels. Defaults to 14. */
125
+ headerActionSize?: number;
124
126
  }
125
127
  interface TableCellSpan {
126
128
  /** Row index in the current rows array. Prefer rowKey when row order can change. */
@@ -349,6 +351,8 @@ interface TableProps<Row extends object> {
349
351
  verticalBorderless?: boolean;
350
352
  /** Hide horizontal row and frame separators while keeping vertical grid lines. */
351
353
  horizontalBorderless?: boolean;
354
+ /** Hide the outer table frame while keeping internal row and column separators. */
355
+ frameBorderless?: boolean;
352
356
  /** Paint alternating row backgrounds. Pass a color string to customize the stripe color. */
353
357
  striped?: boolean | string;
354
358
  /** Visual highlight feedback configuration. All entries default to false. */
package/dist/index.js CHANGED
@@ -792,8 +792,8 @@ function getHeaderTitleRequiredWidth(column, titleWidth) {
792
792
  const minimumReadableTitleWidth = Math.min(Math.ceil(titleWidth), 28);
793
793
  return minimumReadableTitleWidth + (column.align === "center" ? 20 : 22);
794
794
  }
795
- function getVisibleHeaderActions(column, width, columnDraggable = false, titleWidth = 26) {
796
- const capacity = Math.max(0, Math.floor((width - getHeaderTitleRequiredWidth(column, titleWidth)) / HEADER_ACTION_SLOT_WIDTH));
795
+ function getVisibleHeaderActions(column, width, columnDraggable = false, titleWidth = 26, actionSlotWidth = HEADER_ACTION_SLOT_WIDTH) {
796
+ const capacity = Math.max(0, Math.floor((width - getHeaderTitleRequiredWidth(column, titleWidth)) / actionSlotWidth));
797
797
  const candidates = [
798
798
  isColumnFilterable(column) ? "filter" : null,
799
799
  isColumnSortable(column) ? "sort" : null,
@@ -912,7 +912,7 @@ function ellipsizeText(context, text, maxWidth) {
912
912
  return `${text.slice(0, start)}${ellipsis}`;
913
913
  }
914
914
  function paintGrid(options) {
915
- const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, bodyFontSize, headerHeight, fixedHeader, verticalBorderless, horizontalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = options;
915
+ const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, fixedHeader, verticalBorderless, horizontalBorderless, frameBorderless, extendVerticalGridLines, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = options;
916
916
  const headerLeafTop = options.headerLeafTop ?? 0;
917
917
  const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
918
918
  const colors = { ...COLORS, ...options.colors };
@@ -1033,7 +1033,9 @@ function paintGrid(options) {
1033
1033
  ctx.fillRect(x, y, cellWidth, cellHeight);
1034
1034
  }
1035
1035
  ctx.fillStyle = colors.grid;
1036
- if (!verticalBorderless) ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
1036
+ if (!verticalBorderless && !(frameBorderless && columnIndex + colSpan >= columns.length)) {
1037
+ ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
1038
+ }
1037
1039
  if (rowIndex === rowBelowDragGap) ctx.fillRect(x, y, cellWidth, 1);
1038
1040
  if (!horizontalBorderless && (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1)) {
1039
1041
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
@@ -1152,6 +1154,30 @@ function paintGrid(options) {
1152
1154
  paintBodyColumns("scroll");
1153
1155
  paintBodyColumns("left");
1154
1156
  paintBodyColumns("right");
1157
+ if (extendVerticalGridLines && !verticalBorderless) {
1158
+ const extensionTop = Math.max(bodyTop, Math.min(height, bodyTop + rows.length * rowHeight - scrollTop));
1159
+ const extensionHeight = Math.max(0, height - extensionTop);
1160
+ const paintExtendedColumnLines = (layer) => {
1161
+ ctx.save();
1162
+ if (layer === "scroll") {
1163
+ ctx.beginPath();
1164
+ ctx.rect(leftFixedWidth, extensionTop, Math.max(0, rightFixedLeft - leftFixedWidth), extensionHeight);
1165
+ ctx.clip();
1166
+ }
1167
+ ctx.fillStyle = colors.grid;
1168
+ for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
1169
+ if (layer === "scroll" ? columns[columnIndex].fixed !== void 0 : columns[columnIndex].fixed !== layer) continue;
1170
+ if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
1171
+ if (frameBorderless && columnIndex === columns.length - 1) continue;
1172
+ const x = getColumnX(columnIndex) + metrics[columnIndex].width - 1;
1173
+ ctx.fillRect(x, extensionTop, 1, extensionHeight);
1174
+ }
1175
+ ctx.restore();
1176
+ };
1177
+ paintExtendedColumnLines("scroll");
1178
+ paintExtendedColumnLines("left");
1179
+ paintExtendedColumnLines("right");
1180
+ }
1155
1181
  const headerY = fixedHeader ? 0 : -scrollTop;
1156
1182
  ctx.fillStyle = colors.header;
1157
1183
  ctx.fillRect(0, headerY, width, headerHeight);
@@ -1175,14 +1201,16 @@ function paintGrid(options) {
1175
1201
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1176
1202
  }
1177
1203
  ctx.fillStyle = colors.grid;
1178
- if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1204
+ if (!verticalBorderless && !(frameBorderless && columnIndex === columns.length - 1)) {
1205
+ ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1206
+ }
1179
1207
  if (!horizontalBorderless) ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1180
1208
  const column = columns[columnIndex];
1181
1209
  if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1182
1210
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1183
1211
  const titleWidth = ctx.measureText(column.title).width;
1184
- const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
1185
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH;
1212
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
1213
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
1186
1214
  const contentWidth = Math.max(0, metric.width - actionWidth);
1187
1215
  const padding = 10;
1188
1216
  const clipWidth = contentWidth;
@@ -1212,12 +1240,12 @@ function paintGrid(options) {
1212
1240
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1213
1241
  }
1214
1242
  ctx.fillStyle = colors.grid;
1215
- if (!horizontalBorderless) ctx.fillRect(0, 0, width, 1);
1216
- if (!verticalBorderless) {
1243
+ if (!horizontalBorderless && !frameBorderless) ctx.fillRect(0, 0, width, 1);
1244
+ if (!verticalBorderless && !frameBorderless) {
1217
1245
  ctx.fillRect(0, 0, 1, height);
1218
1246
  ctx.fillRect(width - 1, 0, 1, height);
1219
1247
  }
1220
- if (!horizontalBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1248
+ if (!horizontalBorderless && !frameBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1221
1249
  if (selectionRange) {
1222
1250
  const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1223
1251
  const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
@@ -1526,6 +1554,7 @@ function TableInner({
1526
1554
  borderless,
1527
1555
  verticalBorderless,
1528
1556
  horizontalBorderless = false,
1557
+ frameBorderless = false,
1529
1558
  striped = false,
1530
1559
  highlight,
1531
1560
  cellSpans = [],
@@ -1572,6 +1601,8 @@ function TableInner({
1572
1601
  setSourceColumns((current) => reconcileColumns(current, columnProps));
1573
1602
  }, [columnProps]);
1574
1603
  const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
1604
+ const headerActionSize = Math.max(8, layout?.headerActionSize ?? 14);
1605
+ const headerActionSlotWidth = headerActionSize + 2;
1575
1606
  const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
1576
1607
  const headerDepth = useMemo(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
1577
1608
  const { language, labels } = useMemo(() => resolveGridLocale(locale), [locale]);
@@ -2207,8 +2238,8 @@ function TableInner({
2207
2238
  const scroll = scrollRef.current;
2208
2239
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2209
2240
  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 };
2210
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2211
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2241
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2242
+ }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2212
2243
  const scheduleDraw = useCallback2(() => {
2213
2244
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2214
2245
  }, [draw]);
@@ -2774,34 +2805,34 @@ function TableInner({
2774
2805
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2775
2806
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2776
2807
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2777
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2778
- let right = columnRight - (visibleActions.drag ? HEADER_ACTION_SLOT_WIDTH : 2);
2808
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
2809
+ let right = columnRight - (visibleActions.drag ? headerActionSlotWidth : 2);
2779
2810
  if (visibleActions.sort) {
2780
- if (localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "sort";
2781
- right -= HEADER_ACTION_SLOT_WIDTH;
2811
+ if (localX >= right - headerActionSlotWidth && localX < right) return "sort";
2812
+ right -= headerActionSlotWidth;
2782
2813
  }
2783
- if (visibleActions.filter && localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "filter";
2814
+ if (visibleActions.filter && localX >= right - headerActionSlotWidth && localX < right) return "filter";
2784
2815
  return null;
2785
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2816
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2786
2817
  const isHeaderTitleTruncated = useCallback2((columnIndex) => {
2787
2818
  const canvas = canvasRef.current;
2788
2819
  if (columnIndex < 0 || !canvas) return false;
2789
2820
  const column = columns[columnIndex];
2790
2821
  const metric = metrics[columnIndex];
2791
2822
  const titleWidth = measureHeaderTitleWidth(columnIndex);
2792
- const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
2793
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH;
2823
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
2824
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
2794
2825
  return getHeaderTitleRequiredWidth(column, titleWidth) > Math.max(0, metric.width - actionWidth);
2795
- }, [columnDraggable, columns, measureHeaderTitleWidth, metrics]);
2826
+ }, [columnDraggable, columns, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2796
2827
  const locateHeaderDragHandle = useCallback2((clientX, columnIndex) => {
2797
2828
  if (!columnDraggable || columnIndex < 0 || !canvasRef.current) return false;
2798
2829
  const column = columns[columnIndex];
2799
2830
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return false;
2800
- if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex)).drag) return false;
2831
+ if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth).drag) return false;
2801
2832
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2802
2833
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2803
- return localX >= columnRight - 16 && localX < columnRight - 5;
2804
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2834
+ return localX >= columnRight - headerActionSlotWidth && localX < columnRight - 2;
2835
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2805
2836
  const getHeaderCellLeft = useCallback2((cell) => {
2806
2837
  const startMetric = metrics[cell.startIndex];
2807
2838
  const fixedSide = columns[cell.startIndex]?.fixed;
@@ -2866,8 +2897,8 @@ function TableInner({
2866
2897
  if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
2867
2898
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2868
2899
  const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
2869
- return localX >= right - 16 && localX < right - 5;
2870
- }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
2900
+ return localX >= right - headerActionSlotWidth && localX < right - 2;
2901
+ }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, locateHeaderDragHandle]);
2871
2902
  const locateHeaderCell = useCallback2((clientX, clientY) => {
2872
2903
  const canvas = canvasRef.current;
2873
2904
  if (!canvas) return null;
@@ -3023,26 +3054,32 @@ function TableInner({
3023
3054
  header.style.height = `${headerHeight}px`;
3024
3055
  header.style.justifyContent = column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start";
3025
3056
  header.textContent = column.title;
3026
- const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex));
3057
+ const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex), headerActionSlotWidth);
3027
3058
  let actionRight = 2;
3028
3059
  if (visibleActions.drag) {
3029
3060
  const icon = createHeaderDragHandleSvg();
3030
3061
  icon.classList.add("rvg-preview-header-icon");
3062
+ icon.style.width = `${headerActionSize}px`;
3063
+ icon.style.height = `${headerActionSize}px`;
3031
3064
  icon.style.right = `${actionRight}px`;
3032
3065
  header.appendChild(icon);
3033
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3066
+ actionRight += headerActionSlotWidth;
3034
3067
  }
3035
3068
  if (visibleActions.sort) {
3036
3069
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3037
3070
  const icon = createHeaderSortSvg(direction);
3038
3071
  icon.classList.add("rvg-preview-header-icon");
3072
+ icon.style.width = `${headerActionSize}px`;
3073
+ icon.style.height = `${headerActionSize}px`;
3039
3074
  icon.style.right = `${actionRight}px`;
3040
3075
  header.appendChild(icon);
3041
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3076
+ actionRight += headerActionSlotWidth;
3042
3077
  }
3043
3078
  if (visibleActions.filter) {
3044
3079
  const icon = createHeaderSearchSvg(Boolean(filterValues[column.key]));
3045
3080
  icon.classList.add("rvg-preview-header-icon", "rvg-preview-header-search-icon");
3081
+ icon.style.width = `${headerActionSize}px`;
3082
+ icon.style.height = `${headerActionSize}px`;
3046
3083
  icon.style.right = `${actionRight}px`;
3047
3084
  header.appendChild(icon);
3048
3085
  }
@@ -3239,7 +3276,7 @@ function TableInner({
3239
3276
  cleanupDrag();
3240
3277
  cleanupDrop();
3241
3278
  };
3242
- }, [applyColumnOrder, applyRowOrder, bodyTop, columnDraggable, columns, filterValues, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerCells, headerHeight, hideDragTooltips, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, measureHeaderTitleWidth, metrics, rowDraggable, rowHeight, rows, selectedRowKeySet, selection, setDragGuide, setRowDragGuide, sortState, utilityColumnCount]);
3279
+ }, [applyColumnOrder, applyRowOrder, bodyTop, columnDraggable, columns, filterValues, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerActionSize, headerActionSlotWidth, headerCells, headerHeight, hideDragTooltips, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, measureHeaderTitleWidth, metrics, rowDraggable, rowHeight, rows, selectedRowKeySet, selection, setDragGuide, setRowDragGuide, sortState, utilityColumnCount]);
3243
3280
  const locateCell = useCallback2((clientX, clientY) => {
3244
3281
  const canvas = canvasRef.current;
3245
3282
  if (!canvas) return null;
@@ -3683,28 +3720,28 @@ function TableInner({
3683
3720
  const renderHeaderIcons = (columnIndex, left) => {
3684
3721
  const column = columns[columnIndex];
3685
3722
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
3686
- const top = headerLeafTop + (headerLeafHeight - 14) / 2;
3687
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
3688
- let right = left + metrics[columnIndex].width - (visibleActions.drag ? HEADER_ACTION_SLOT_WIDTH : 2);
3723
+ const top = headerLeafTop + (headerLeafHeight - headerActionSize) / 2;
3724
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3725
+ let right = left + metrics[columnIndex].width - (visibleActions.drag ? headerActionSlotWidth : 2);
3689
3726
  const icons = [];
3690
3727
  if (visibleActions.drag) {
3691
3728
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "drag";
3692
3729
  icons.push(
3693
- /* @__PURE__ */ jsx7(HeaderDragIcon, { className: `rvg-header-icon rvg-header-drag-icon${hovered ? " is-hovered" : ""}`, style: { left: left + metrics[columnIndex].width - 16, top } }, "drag")
3730
+ /* @__PURE__ */ jsx7(HeaderDragIcon, { className: `rvg-header-icon rvg-header-drag-icon${hovered ? " is-hovered" : ""}`, style: { left: left + metrics[columnIndex].width - headerActionSlotWidth, top } }, "drag")
3694
3731
  );
3695
3732
  }
3696
3733
  if (visibleActions.sort) {
3697
3734
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "sort";
3698
3735
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3699
3736
  icons.push(
3700
- /* @__PURE__ */ jsx7(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - 16, top }, direction }, "sort")
3737
+ /* @__PURE__ */ jsx7(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - headerActionSlotWidth, top }, direction }, "sort")
3701
3738
  );
3702
- right -= HEADER_ACTION_SLOT_WIDTH;
3739
+ right -= headerActionSlotWidth;
3703
3740
  }
3704
3741
  if (visibleActions.filter) {
3705
3742
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "filter";
3706
3743
  icons.push(
3707
- /* @__PURE__ */ jsx7(HeaderSearchIcon, { className: `rvg-header-icon rvg-header-search-icon${hovered ? " is-hovered" : ""}${filterValues[column.key] ? " is-active" : ""}`, style: { left: right - 16, top: top + 1 } }, "filter")
3744
+ /* @__PURE__ */ jsx7(HeaderSearchIcon, { className: `rvg-header-icon rvg-header-search-icon${hovered ? " is-hovered" : ""}${filterValues[column.key] ? " is-active" : ""}`, style: { left: right - headerActionSlotWidth, top: top + 1 } }, "filter")
3708
3745
  );
3709
3746
  }
3710
3747
  return icons;
@@ -3725,8 +3762,8 @@ function TableInner({
3725
3762
  const context = canvas?.getContext("2d");
3726
3763
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3727
3764
  const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3728
- const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3729
- const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH : columnDraggable ? HEADER_ACTION_SLOT_WIDTH : 0;
3765
+ const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3766
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3730
3767
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3731
3768
  const maxTextWidth = Math.max(0, contentWidth2 - 20);
3732
3769
  const textWidth = Math.min(titleWidth, maxTextWidth);
@@ -3747,12 +3784,17 @@ function TableInner({
3747
3784
  } else if (action === "sort") {
3748
3785
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3749
3786
  label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
3750
- anchorX = cellLeft + cellWidth - 22;
3787
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3788
+ const right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3789
+ anchorX = right - headerActionSlotWidth / 2;
3751
3790
  anchorLeft = anchorX;
3752
3791
  } else if (action === "filter") {
3753
3792
  const value = filterValues[column.key];
3754
3793
  label = value ? labels.filterWithValue(value) : labels.filter;
3755
- anchorX = cellLeft + cellWidth - 40;
3794
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3795
+ let right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3796
+ if (visibleActions.sort) right -= headerActionSlotWidth;
3797
+ anchorX = right - headerActionSlotWidth / 2;
3756
3798
  anchorLeft = anchorX;
3757
3799
  }
3758
3800
  const placement = anchorX + 328 > viewport.width ? "left" : "right";
@@ -3765,7 +3807,7 @@ function TableInner({
3765
3807
  label,
3766
3808
  placement
3767
3809
  };
3768
- }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, sortState, viewport.width]);
3810
+ }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, measureHeaderTitleWidth, metrics, scrollPosition.top, sortState, viewport.width]);
3769
3811
  const isPointerOnHeaderTitle = useCallback2((clientX, clientY, cell) => {
3770
3812
  const canvas = canvasRef.current;
3771
3813
  if (!canvas) return false;
@@ -3779,14 +3821,14 @@ function TableInner({
3779
3821
  const context = canvas.getContext("2d");
3780
3822
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3781
3823
  const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
3782
- const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
3783
- const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH : columnDraggable ? HEADER_ACTION_SLOT_WIDTH : 0;
3824
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3825
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3784
3826
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3785
3827
  const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
3786
3828
  const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
3787
3829
  const textTop = rowTop + 8;
3788
3830
  return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3789
- }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
3831
+ }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerRowOffsets]);
3790
3832
  const headerTooltip = (() => {
3791
3833
  if (!visibleHeaderTooltip) return null;
3792
3834
  return visibleHeaderTooltip;
@@ -4109,8 +4151,8 @@ function TableInner({
4109
4151
  );
4110
4152
  }
4111
4153
  if (column.renderHeader) {
4112
- const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
4113
- const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH;
4154
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
4155
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
4114
4156
  return /* @__PURE__ */ jsx7(
4115
4157
  "div",
4116
4158
  {
@@ -4154,13 +4196,13 @@ function TableInner({
4154
4196
  const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
4155
4197
  const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
4156
4198
  const width2 = cell.leaf ? startMetric.width : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1);
4157
- const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex)) : null;
4158
- const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * HEADER_ACTION_SLOT_WIDTH : columnDraggable && !cell.leaf ? HEADER_ACTION_SLOT_WIDTH : 0;
4199
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex), headerActionSlotWidth) : null;
4200
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable && !cell.leaf ? headerActionSlotWidth : 0;
4159
4201
  const isDropTarget = Boolean(columnDropTarget && cell.endIndex >= columnDropTarget.startIndex && cell.startIndex <= columnDropTarget.endIndex);
4160
4202
  return /* @__PURE__ */ jsxs7(
4161
4203
  "div",
4162
4204
  {
4163
- className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4205
+ className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}${cell.endIndex === columns.length - 1 ? " is-frame-right" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4164
4206
  "data-level": cell.level,
4165
4207
  "data-measure-key": `group:${cell.key}:${cell.level}`,
4166
4208
  style: {
@@ -4191,7 +4233,7 @@ function TableInner({
4191
4233
  HeaderDragIcon,
4192
4234
  {
4193
4235
  className: `rvg-header-icon rvg-header-drag-icon${hoveredHeaderAction?.columnIndex === cell.startIndex && hoveredHeaderAction.action === "drag" ? " is-hovered" : ""}`,
4194
- style: { left: width2 - 16, top: (rowHeightForLevel - 14) / 2 }
4236
+ style: { left: width2 - headerActionSlotWidth, top: (rowHeightForLevel - headerActionSize) / 2 }
4195
4237
  }
4196
4238
  )
4197
4239
  ]
@@ -4457,12 +4499,13 @@ function TableInner({
4457
4499
  width,
4458
4500
  height: autoHeight ? effectiveHeight : height,
4459
4501
  minHeight: autoHeight || typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
4502
+ "--rvg-header-action-size": `${headerActionSize}px`,
4460
4503
  ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
4461
4504
  ...style
4462
4505
  };
4463
4506
  const currentScroll = scrollRef.current;
4464
4507
  const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
4465
- return /* @__PURE__ */ jsxs7("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4508
+ return /* @__PURE__ */ jsxs7("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${frameBorderless ? " is-frame-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4466
4509
  /* @__PURE__ */ jsxs7("div", { ref: scrollerRef, className: `rvg-scroller${contextMenu ? " is-context-menu-open" : ""}`, style: { bottom: scrollerBottom }, onScroll: handleScroll, onCopy: handleCopy, onContextMenu: handleContextMenu, tabIndex: 0, role: "grid", "aria-label": ariaLabel, "aria-rowcount": rows.length, "aria-colcount": columns.length, onKeyDown: handleKeyDown, children: [
4467
4510
  /* @__PURE__ */ jsx7(
4468
4511
  "canvas",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",