@lensui/lens-table 0.1.11 → 0.1.12

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, 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);
@@ -1205,14 +1207,16 @@ function paintGrid(options) {
1205
1207
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1206
1208
  }
1207
1209
  ctx.fillStyle = colors.grid;
1208
- if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1210
+ if (!verticalBorderless && !(frameBorderless && columnIndex === columns.length - 1)) {
1211
+ ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1212
+ }
1209
1213
  if (!horizontalBorderless) ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1210
1214
  const column = columns[columnIndex];
1211
1215
  if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1212
1216
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1213
1217
  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;
1218
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
1219
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
1216
1220
  const contentWidth = Math.max(0, metric.width - actionWidth);
1217
1221
  const padding = 10;
1218
1222
  const clipWidth = contentWidth;
@@ -1242,12 +1246,12 @@ function paintGrid(options) {
1242
1246
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1243
1247
  }
1244
1248
  ctx.fillStyle = colors.grid;
1245
- if (!horizontalBorderless) ctx.fillRect(0, 0, width, 1);
1246
- if (!verticalBorderless) {
1249
+ if (!horizontalBorderless && !frameBorderless) ctx.fillRect(0, 0, width, 1);
1250
+ if (!verticalBorderless && !frameBorderless) {
1247
1251
  ctx.fillRect(0, 0, 1, height);
1248
1252
  ctx.fillRect(width - 1, 0, 1, height);
1249
1253
  }
1250
- if (!horizontalBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1254
+ if (!horizontalBorderless && !frameBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1251
1255
  if (selectionRange) {
1252
1256
  const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1253
1257
  const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
@@ -1556,6 +1560,7 @@ function TableInner({
1556
1560
  borderless,
1557
1561
  verticalBorderless,
1558
1562
  horizontalBorderless = false,
1563
+ frameBorderless = false,
1559
1564
  striped = false,
1560
1565
  highlight,
1561
1566
  cellSpans = [],
@@ -1602,6 +1607,8 @@ function TableInner({
1602
1607
  setSourceColumns((current) => reconcileColumns(current, columnProps));
1603
1608
  }, [columnProps]);
1604
1609
  const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
1610
+ const headerActionSize = Math.max(8, layout?.headerActionSize ?? 14);
1611
+ const headerActionSlotWidth = headerActionSize + 2;
1605
1612
  const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
1606
1613
  const headerDepth = (0, import_react5.useMemo)(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
1607
1614
  const { language, labels } = (0, import_react5.useMemo)(() => resolveGridLocale(locale), [locale]);
@@ -2237,8 +2244,8 @@ function TableInner({
2237
2244
  const scroll = scrollRef.current;
2238
2245
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2239
2246
  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]);
2247
+ 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, 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 });
2248
+ }, [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
2249
  const scheduleDraw = (0, import_react5.useCallback)(() => {
2243
2250
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2244
2251
  }, [draw]);
@@ -2804,34 +2811,34 @@ function TableInner({
2804
2811
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2805
2812
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2806
2813
  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);
2814
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
2815
+ let right = columnRight - (visibleActions.drag ? headerActionSlotWidth : 2);
2809
2816
  if (visibleActions.sort) {
2810
- if (localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "sort";
2811
- right -= HEADER_ACTION_SLOT_WIDTH;
2817
+ if (localX >= right - headerActionSlotWidth && localX < right) return "sort";
2818
+ right -= headerActionSlotWidth;
2812
2819
  }
2813
- if (visibleActions.filter && localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "filter";
2820
+ if (visibleActions.filter && localX >= right - headerActionSlotWidth && localX < right) return "filter";
2814
2821
  return null;
2815
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2822
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2816
2823
  const isHeaderTitleTruncated = (0, import_react5.useCallback)((columnIndex) => {
2817
2824
  const canvas = canvasRef.current;
2818
2825
  if (columnIndex < 0 || !canvas) return false;
2819
2826
  const column = columns[columnIndex];
2820
2827
  const metric = metrics[columnIndex];
2821
2828
  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;
2829
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
2830
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
2824
2831
  return getHeaderTitleRequiredWidth(column, titleWidth) > Math.max(0, metric.width - actionWidth);
2825
- }, [columnDraggable, columns, measureHeaderTitleWidth, metrics]);
2832
+ }, [columnDraggable, columns, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2826
2833
  const locateHeaderDragHandle = (0, import_react5.useCallback)((clientX, columnIndex) => {
2827
2834
  if (!columnDraggable || columnIndex < 0 || !canvasRef.current) return false;
2828
2835
  const column = columns[columnIndex];
2829
2836
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return false;
2830
- if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex)).drag) return false;
2837
+ if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth).drag) return false;
2831
2838
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2832
2839
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2833
- return localX >= columnRight - 16 && localX < columnRight - 5;
2834
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2840
+ return localX >= columnRight - headerActionSlotWidth && localX < columnRight - 2;
2841
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2835
2842
  const getHeaderCellLeft = (0, import_react5.useCallback)((cell) => {
2836
2843
  const startMetric = metrics[cell.startIndex];
2837
2844
  const fixedSide = columns[cell.startIndex]?.fixed;
@@ -2896,8 +2903,8 @@ function TableInner({
2896
2903
  if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
2897
2904
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2898
2905
  const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
2899
- return localX >= right - 16 && localX < right - 5;
2900
- }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
2906
+ return localX >= right - headerActionSlotWidth && localX < right - 2;
2907
+ }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, locateHeaderDragHandle]);
2901
2908
  const locateHeaderCell = (0, import_react5.useCallback)((clientX, clientY) => {
2902
2909
  const canvas = canvasRef.current;
2903
2910
  if (!canvas) return null;
@@ -3053,26 +3060,32 @@ function TableInner({
3053
3060
  header.style.height = `${headerHeight}px`;
3054
3061
  header.style.justifyContent = column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start";
3055
3062
  header.textContent = column.title;
3056
- const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex));
3063
+ const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex), headerActionSlotWidth);
3057
3064
  let actionRight = 2;
3058
3065
  if (visibleActions.drag) {
3059
3066
  const icon = createHeaderDragHandleSvg();
3060
3067
  icon.classList.add("rvg-preview-header-icon");
3068
+ icon.style.width = `${headerActionSize}px`;
3069
+ icon.style.height = `${headerActionSize}px`;
3061
3070
  icon.style.right = `${actionRight}px`;
3062
3071
  header.appendChild(icon);
3063
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3072
+ actionRight += headerActionSlotWidth;
3064
3073
  }
3065
3074
  if (visibleActions.sort) {
3066
3075
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3067
3076
  const icon = createHeaderSortSvg(direction);
3068
3077
  icon.classList.add("rvg-preview-header-icon");
3078
+ icon.style.width = `${headerActionSize}px`;
3079
+ icon.style.height = `${headerActionSize}px`;
3069
3080
  icon.style.right = `${actionRight}px`;
3070
3081
  header.appendChild(icon);
3071
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3082
+ actionRight += headerActionSlotWidth;
3072
3083
  }
3073
3084
  if (visibleActions.filter) {
3074
3085
  const icon = createHeaderSearchSvg(Boolean(filterValues[column.key]));
3075
3086
  icon.classList.add("rvg-preview-header-icon", "rvg-preview-header-search-icon");
3087
+ icon.style.width = `${headerActionSize}px`;
3088
+ icon.style.height = `${headerActionSize}px`;
3076
3089
  icon.style.right = `${actionRight}px`;
3077
3090
  header.appendChild(icon);
3078
3091
  }
@@ -3269,7 +3282,7 @@ function TableInner({
3269
3282
  cleanupDrag();
3270
3283
  cleanupDrop();
3271
3284
  };
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]);
3285
+ }, [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
3286
  const locateCell = (0, import_react5.useCallback)((clientX, clientY) => {
3274
3287
  const canvas = canvasRef.current;
3275
3288
  if (!canvas) return null;
@@ -3713,28 +3726,28 @@ function TableInner({
3713
3726
  const renderHeaderIcons = (columnIndex, left) => {
3714
3727
  const column = columns[columnIndex];
3715
3728
  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);
3729
+ const top = headerLeafTop + (headerLeafHeight - headerActionSize) / 2;
3730
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3731
+ let right = left + metrics[columnIndex].width - (visibleActions.drag ? headerActionSlotWidth : 2);
3719
3732
  const icons = [];
3720
3733
  if (visibleActions.drag) {
3721
3734
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "drag";
3722
3735
  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")
3736
+ /* @__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
3737
  );
3725
3738
  }
3726
3739
  if (visibleActions.sort) {
3727
3740
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "sort";
3728
3741
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3729
3742
  icons.push(
3730
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - 16, top }, direction }, "sort")
3743
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - headerActionSlotWidth, top }, direction }, "sort")
3731
3744
  );
3732
- right -= HEADER_ACTION_SLOT_WIDTH;
3745
+ right -= headerActionSlotWidth;
3733
3746
  }
3734
3747
  if (visibleActions.filter) {
3735
3748
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "filter";
3736
3749
  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")
3750
+ /* @__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
3751
  );
3739
3752
  }
3740
3753
  return icons;
@@ -3755,8 +3768,8 @@ function TableInner({
3755
3768
  const context = canvas?.getContext("2d");
3756
3769
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3757
3770
  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;
3771
+ const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3772
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3760
3773
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3761
3774
  const maxTextWidth = Math.max(0, contentWidth2 - 20);
3762
3775
  const textWidth = Math.min(titleWidth, maxTextWidth);
@@ -3777,12 +3790,17 @@ function TableInner({
3777
3790
  } else if (action === "sort") {
3778
3791
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3779
3792
  label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
3780
- anchorX = cellLeft + cellWidth - 22;
3793
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3794
+ const right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3795
+ anchorX = right - headerActionSlotWidth / 2;
3781
3796
  anchorLeft = anchorX;
3782
3797
  } else if (action === "filter") {
3783
3798
  const value = filterValues[column.key];
3784
3799
  label = value ? labels.filterWithValue(value) : labels.filter;
3785
- anchorX = cellLeft + cellWidth - 40;
3800
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3801
+ let right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3802
+ if (visibleActions.sort) right -= headerActionSlotWidth;
3803
+ anchorX = right - headerActionSlotWidth / 2;
3786
3804
  anchorLeft = anchorX;
3787
3805
  }
3788
3806
  const placement = anchorX + 328 > viewport.width ? "left" : "right";
@@ -3795,7 +3813,7 @@ function TableInner({
3795
3813
  label,
3796
3814
  placement
3797
3815
  };
3798
- }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, sortState, viewport.width]);
3816
+ }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, measureHeaderTitleWidth, metrics, scrollPosition.top, sortState, viewport.width]);
3799
3817
  const isPointerOnHeaderTitle = (0, import_react5.useCallback)((clientX, clientY, cell) => {
3800
3818
  const canvas = canvasRef.current;
3801
3819
  if (!canvas) return false;
@@ -3809,14 +3827,14 @@ function TableInner({
3809
3827
  const context = canvas.getContext("2d");
3810
3828
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3811
3829
  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;
3830
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3831
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3814
3832
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3815
3833
  const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
3816
3834
  const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
3817
3835
  const textTop = rowTop + 8;
3818
3836
  return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3819
- }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
3837
+ }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerRowOffsets]);
3820
3838
  const headerTooltip = (() => {
3821
3839
  if (!visibleHeaderTooltip) return null;
3822
3840
  return visibleHeaderTooltip;
@@ -4139,8 +4157,8 @@ function TableInner({
4139
4157
  );
4140
4158
  }
4141
4159
  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;
4160
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
4161
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
4144
4162
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4145
4163
  "div",
4146
4164
  {
@@ -4184,13 +4202,13 @@ function TableInner({
4184
4202
  const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
4185
4203
  const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
4186
4204
  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;
4205
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex), headerActionSlotWidth) : null;
4206
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable && !cell.leaf ? headerActionSlotWidth : 0;
4189
4207
  const isDropTarget = Boolean(columnDropTarget && cell.endIndex >= columnDropTarget.startIndex && cell.startIndex <= columnDropTarget.endIndex);
4190
4208
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4191
4209
  "div",
4192
4210
  {
4193
- className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4211
+ 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
4212
  "data-level": cell.level,
4195
4213
  "data-measure-key": `group:${cell.key}:${cell.level}`,
4196
4214
  style: {
@@ -4221,7 +4239,7 @@ function TableInner({
4221
4239
  HeaderDragIcon,
4222
4240
  {
4223
4241
  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 }
4242
+ style: { left: width2 - headerActionSlotWidth, top: (rowHeightForLevel - headerActionSize) / 2 }
4225
4243
  }
4226
4244
  )
4227
4245
  ]
@@ -4487,12 +4505,13 @@ function TableInner({
4487
4505
  width,
4488
4506
  height: autoHeight ? effectiveHeight : height,
4489
4507
  minHeight: autoHeight || typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
4508
+ "--rvg-header-action-size": `${headerActionSize}px`,
4490
4509
  ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
4491
4510
  ...style
4492
4511
  };
4493
4512
  const currentScroll = scrollRef.current;
4494
4513
  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: [
4514
+ 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
4515
  /* @__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
4516
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4498
4517
  "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, 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);
@@ -1175,14 +1177,16 @@ function paintGrid(options) {
1175
1177
  ctx.fillRect(x, headerY, metric.width, headerHeight);
1176
1178
  }
1177
1179
  ctx.fillStyle = colors.grid;
1178
- if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1180
+ if (!verticalBorderless && !(frameBorderless && columnIndex === columns.length - 1)) {
1181
+ ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1182
+ }
1179
1183
  if (!horizontalBorderless) ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1180
1184
  const column = columns[columnIndex];
1181
1185
  if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1182
1186
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1183
1187
  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;
1188
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
1189
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
1186
1190
  const contentWidth = Math.max(0, metric.width - actionWidth);
1187
1191
  const padding = 10;
1188
1192
  const clipWidth = contentWidth;
@@ -1212,12 +1216,12 @@ function paintGrid(options) {
1212
1216
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1213
1217
  }
1214
1218
  ctx.fillStyle = colors.grid;
1215
- if (!horizontalBorderless) ctx.fillRect(0, 0, width, 1);
1216
- if (!verticalBorderless) {
1219
+ if (!horizontalBorderless && !frameBorderless) ctx.fillRect(0, 0, width, 1);
1220
+ if (!verticalBorderless && !frameBorderless) {
1217
1221
  ctx.fillRect(0, 0, 1, height);
1218
1222
  ctx.fillRect(width - 1, 0, 1, height);
1219
1223
  }
1220
- if (!horizontalBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1224
+ if (!horizontalBorderless && !frameBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1221
1225
  if (selectionRange) {
1222
1226
  const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1223
1227
  const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
@@ -1526,6 +1530,7 @@ function TableInner({
1526
1530
  borderless,
1527
1531
  verticalBorderless,
1528
1532
  horizontalBorderless = false,
1533
+ frameBorderless = false,
1529
1534
  striped = false,
1530
1535
  highlight,
1531
1536
  cellSpans = [],
@@ -1572,6 +1577,8 @@ function TableInner({
1572
1577
  setSourceColumns((current) => reconcileColumns(current, columnProps));
1573
1578
  }, [columnProps]);
1574
1579
  const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
1580
+ const headerActionSize = Math.max(8, layout?.headerActionSize ?? 14);
1581
+ const headerActionSlotWidth = headerActionSize + 2;
1575
1582
  const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
1576
1583
  const headerDepth = useMemo(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
1577
1584
  const { language, labels } = useMemo(() => resolveGridLocale(locale), [locale]);
@@ -2207,8 +2214,8 @@ function TableInner({
2207
2214
  const scroll = scrollRef.current;
2208
2215
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2209
2216
  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]);
2217
+ 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, 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 });
2218
+ }, [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
2219
  const scheduleDraw = useCallback2(() => {
2213
2220
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2214
2221
  }, [draw]);
@@ -2774,34 +2781,34 @@ function TableInner({
2774
2781
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2775
2782
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2776
2783
  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);
2784
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
2785
+ let right = columnRight - (visibleActions.drag ? headerActionSlotWidth : 2);
2779
2786
  if (visibleActions.sort) {
2780
- if (localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "sort";
2781
- right -= HEADER_ACTION_SLOT_WIDTH;
2787
+ if (localX >= right - headerActionSlotWidth && localX < right) return "sort";
2788
+ right -= headerActionSlotWidth;
2782
2789
  }
2783
- if (visibleActions.filter && localX >= right - HEADER_ACTION_SLOT_WIDTH && localX < right) return "filter";
2790
+ if (visibleActions.filter && localX >= right - headerActionSlotWidth && localX < right) return "filter";
2784
2791
  return null;
2785
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2792
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2786
2793
  const isHeaderTitleTruncated = useCallback2((columnIndex) => {
2787
2794
  const canvas = canvasRef.current;
2788
2795
  if (columnIndex < 0 || !canvas) return false;
2789
2796
  const column = columns[columnIndex];
2790
2797
  const metric = metrics[columnIndex];
2791
2798
  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;
2799
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth, headerActionSlotWidth);
2800
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
2794
2801
  return getHeaderTitleRequiredWidth(column, titleWidth) > Math.max(0, metric.width - actionWidth);
2795
- }, [columnDraggable, columns, measureHeaderTitleWidth, metrics]);
2802
+ }, [columnDraggable, columns, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2796
2803
  const locateHeaderDragHandle = useCallback2((clientX, columnIndex) => {
2797
2804
  if (!columnDraggable || columnIndex < 0 || !canvasRef.current) return false;
2798
2805
  const column = columns[columnIndex];
2799
2806
  if (column.rowSelection || column.rowDragHandle || column.rowNumber) return false;
2800
- if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex)).drag) return false;
2807
+ if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth).drag) return false;
2801
2808
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2802
2809
  const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2803
- return localX >= columnRight - 16 && localX < columnRight - 5;
2804
- }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
2810
+ return localX >= columnRight - headerActionSlotWidth && localX < columnRight - 2;
2811
+ }, [columnDraggable, columns, getDisplayedColumnLeft, headerActionSlotWidth, measureHeaderTitleWidth, metrics]);
2805
2812
  const getHeaderCellLeft = useCallback2((cell) => {
2806
2813
  const startMetric = metrics[cell.startIndex];
2807
2814
  const fixedSide = columns[cell.startIndex]?.fixed;
@@ -2866,8 +2873,8 @@ function TableInner({
2866
2873
  if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
2867
2874
  const localX = clientX - canvasRef.current.getBoundingClientRect().left;
2868
2875
  const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
2869
- return localX >= right - 16 && localX < right - 5;
2870
- }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
2876
+ return localX >= right - headerActionSlotWidth && localX < right - 2;
2877
+ }, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, locateHeaderDragHandle]);
2871
2878
  const locateHeaderCell = useCallback2((clientX, clientY) => {
2872
2879
  const canvas = canvasRef.current;
2873
2880
  if (!canvas) return null;
@@ -3023,26 +3030,32 @@ function TableInner({
3023
3030
  header.style.height = `${headerHeight}px`;
3024
3031
  header.style.justifyContent = column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start";
3025
3032
  header.textContent = column.title;
3026
- const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex));
3033
+ const visibleActions = getVisibleHeaderActions(column, columnWidth, columnDraggable, measureHeaderTitleWidth(sourceIndex), headerActionSlotWidth);
3027
3034
  let actionRight = 2;
3028
3035
  if (visibleActions.drag) {
3029
3036
  const icon = createHeaderDragHandleSvg();
3030
3037
  icon.classList.add("rvg-preview-header-icon");
3038
+ icon.style.width = `${headerActionSize}px`;
3039
+ icon.style.height = `${headerActionSize}px`;
3031
3040
  icon.style.right = `${actionRight}px`;
3032
3041
  header.appendChild(icon);
3033
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3042
+ actionRight += headerActionSlotWidth;
3034
3043
  }
3035
3044
  if (visibleActions.sort) {
3036
3045
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3037
3046
  const icon = createHeaderSortSvg(direction);
3038
3047
  icon.classList.add("rvg-preview-header-icon");
3048
+ icon.style.width = `${headerActionSize}px`;
3049
+ icon.style.height = `${headerActionSize}px`;
3039
3050
  icon.style.right = `${actionRight}px`;
3040
3051
  header.appendChild(icon);
3041
- actionRight += HEADER_ACTION_SLOT_WIDTH;
3052
+ actionRight += headerActionSlotWidth;
3042
3053
  }
3043
3054
  if (visibleActions.filter) {
3044
3055
  const icon = createHeaderSearchSvg(Boolean(filterValues[column.key]));
3045
3056
  icon.classList.add("rvg-preview-header-icon", "rvg-preview-header-search-icon");
3057
+ icon.style.width = `${headerActionSize}px`;
3058
+ icon.style.height = `${headerActionSize}px`;
3046
3059
  icon.style.right = `${actionRight}px`;
3047
3060
  header.appendChild(icon);
3048
3061
  }
@@ -3239,7 +3252,7 @@ function TableInner({
3239
3252
  cleanupDrag();
3240
3253
  cleanupDrop();
3241
3254
  };
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]);
3255
+ }, [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
3256
  const locateCell = useCallback2((clientX, clientY) => {
3244
3257
  const canvas = canvasRef.current;
3245
3258
  if (!canvas) return null;
@@ -3683,28 +3696,28 @@ function TableInner({
3683
3696
  const renderHeaderIcons = (columnIndex, left) => {
3684
3697
  const column = columns[columnIndex];
3685
3698
  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);
3699
+ const top = headerLeafTop + (headerLeafHeight - headerActionSize) / 2;
3700
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3701
+ let right = left + metrics[columnIndex].width - (visibleActions.drag ? headerActionSlotWidth : 2);
3689
3702
  const icons = [];
3690
3703
  if (visibleActions.drag) {
3691
3704
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "drag";
3692
3705
  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")
3706
+ /* @__PURE__ */ jsx7(HeaderDragIcon, { className: `rvg-header-icon rvg-header-drag-icon${hovered ? " is-hovered" : ""}`, style: { left: left + metrics[columnIndex].width - headerActionSlotWidth, top } }, "drag")
3694
3707
  );
3695
3708
  }
3696
3709
  if (visibleActions.sort) {
3697
3710
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "sort";
3698
3711
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3699
3712
  icons.push(
3700
- /* @__PURE__ */ jsx7(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - 16, top }, direction }, "sort")
3713
+ /* @__PURE__ */ jsx7(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - headerActionSlotWidth, top }, direction }, "sort")
3701
3714
  );
3702
- right -= HEADER_ACTION_SLOT_WIDTH;
3715
+ right -= headerActionSlotWidth;
3703
3716
  }
3704
3717
  if (visibleActions.filter) {
3705
3718
  const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "filter";
3706
3719
  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")
3720
+ /* @__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
3721
  );
3709
3722
  }
3710
3723
  return icons;
@@ -3725,8 +3738,8 @@ function TableInner({
3725
3738
  const context = canvas?.getContext("2d");
3726
3739
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3727
3740
  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;
3741
+ const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3742
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3730
3743
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3731
3744
  const maxTextWidth = Math.max(0, contentWidth2 - 20);
3732
3745
  const textWidth = Math.min(titleWidth, maxTextWidth);
@@ -3747,12 +3760,17 @@ function TableInner({
3747
3760
  } else if (action === "sort") {
3748
3761
  const direction = sortState?.columnKey === column.key ? sortState.direction : null;
3749
3762
  label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
3750
- anchorX = cellLeft + cellWidth - 22;
3763
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3764
+ const right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3765
+ anchorX = right - headerActionSlotWidth / 2;
3751
3766
  anchorLeft = anchorX;
3752
3767
  } else if (action === "filter") {
3753
3768
  const value = filterValues[column.key];
3754
3769
  label = value ? labels.filterWithValue(value) : labels.filter;
3755
- anchorX = cellLeft + cellWidth - 40;
3770
+ const visibleActions = getVisibleHeaderActions(column, cellWidth, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
3771
+ let right = cellLeft + cellWidth - (visibleActions.drag ? headerActionSlotWidth : 2);
3772
+ if (visibleActions.sort) right -= headerActionSlotWidth;
3773
+ anchorX = right - headerActionSlotWidth / 2;
3756
3774
  anchorLeft = anchorX;
3757
3775
  }
3758
3776
  const placement = anchorX + 328 > viewport.width ? "left" : "right";
@@ -3765,7 +3783,7 @@ function TableInner({
3765
3783
  label,
3766
3784
  placement
3767
3785
  };
3768
- }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, sortState, viewport.width]);
3786
+ }, [baseHeaderHeight, columnDraggable, columns, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, measureHeaderTitleWidth, metrics, scrollPosition.top, sortState, viewport.width]);
3769
3787
  const isPointerOnHeaderTitle = useCallback2((clientX, clientY, cell) => {
3770
3788
  const canvas = canvasRef.current;
3771
3789
  if (!canvas) return false;
@@ -3779,14 +3797,14 @@ function TableInner({
3779
3797
  const context = canvas.getContext("2d");
3780
3798
  if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
3781
3799
  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;
3800
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth, headerActionSlotWidth) : null;
3801
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable ? headerActionSlotWidth : 0;
3784
3802
  const contentWidth2 = Math.max(0, cellWidth - actionWidth);
3785
3803
  const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
3786
3804
  const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
3787
3805
  const textTop = rowTop + 8;
3788
3806
  return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
3789
- }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
3807
+ }, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerActionSlotWidth, headerRowOffsets]);
3790
3808
  const headerTooltip = (() => {
3791
3809
  if (!visibleHeaderTooltip) return null;
3792
3810
  return visibleHeaderTooltip;
@@ -4109,8 +4127,8 @@ function TableInner({
4109
4127
  );
4110
4128
  }
4111
4129
  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;
4130
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex), headerActionSlotWidth);
4131
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth;
4114
4132
  return /* @__PURE__ */ jsx7(
4115
4133
  "div",
4116
4134
  {
@@ -4154,13 +4172,13 @@ function TableInner({
4154
4172
  const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
4155
4173
  const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
4156
4174
  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;
4175
+ const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex), headerActionSlotWidth) : null;
4176
+ const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * headerActionSlotWidth : columnDraggable && !cell.leaf ? headerActionSlotWidth : 0;
4159
4177
  const isDropTarget = Boolean(columnDropTarget && cell.endIndex >= columnDropTarget.startIndex && cell.startIndex <= columnDropTarget.endIndex);
4160
4178
  return /* @__PURE__ */ jsxs7(
4161
4179
  "div",
4162
4180
  {
4163
- className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}${isDropTarget ? " is-drop-target" : ""}`,
4181
+ 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
4182
  "data-level": cell.level,
4165
4183
  "data-measure-key": `group:${cell.key}:${cell.level}`,
4166
4184
  style: {
@@ -4191,7 +4209,7 @@ function TableInner({
4191
4209
  HeaderDragIcon,
4192
4210
  {
4193
4211
  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 }
4212
+ style: { left: width2 - headerActionSlotWidth, top: (rowHeightForLevel - headerActionSize) / 2 }
4195
4213
  }
4196
4214
  )
4197
4215
  ]
@@ -4457,12 +4475,13 @@ function TableInner({
4457
4475
  width,
4458
4476
  height: autoHeight ? effectiveHeight : height,
4459
4477
  minHeight: autoHeight || typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
4478
+ "--rvg-header-action-size": `${headerActionSize}px`,
4460
4479
  ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
4461
4480
  ...style
4462
4481
  };
4463
4482
  const currentScroll = scrollRef.current;
4464
4483
  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: [
4484
+ 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
4485
  /* @__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
4486
  /* @__PURE__ */ jsx7(
4468
4487
  "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.12",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",