@lensui/lens-table 0.1.0 → 0.1.2

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,6 +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
+ | `autoHeight` | `boolean` | `false` | 是否按真实内容高度收缩表格。开启后数据较少时使用 `表头 + 行数 * 行高 + 合计行` 的高度,数据较多时不超过 `height`。 |
92
93
  | `rowHeight` | `number` | `40` | 每行高度,固定行高用于高性能虚拟滚动。 |
93
94
  | `headerHeight` | `number` | `44` | 表头高度,单位 px。 |
94
95
  | `fixedHeader` | `boolean` | `true` | 表头是否固定在顶部。 |
package/dist/index.cjs CHANGED
@@ -333,6 +333,7 @@ function LoadingSpinnerIcon() {
333
333
  // src/components/editors/ChoiceEditor.tsx
334
334
  var import_jsx_runtime3 = require("react/jsx-runtime");
335
335
  function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
336
+ const editorStyle = typeof style.width === "number" ? { ...style, width: Math.max(0, style.width - 1) } : style;
336
337
  const collection = (0, import_select.createListCollection)({
337
338
  items: options,
338
339
  itemToValue: (item) => item.value,
@@ -342,7 +343,7 @@ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCan
342
343
  import_select.Select.Root,
343
344
  {
344
345
  className: "rvg-choice-editor",
345
- style,
346
+ style: editorStyle,
346
347
  collection,
347
348
  value: value ? [value] : [],
348
349
  defaultOpen: true,
@@ -801,14 +802,38 @@ function getMinimumColumnWidth(column, columnDraggable = false) {
801
802
  if (column.rowSelection || column.rowNumber) return 44;
802
803
  return 60;
803
804
  }
804
- function buildColumnMetrics(columns, columnDraggable = false) {
805
+ function buildColumnMetrics(columns, columnDraggableOrOptions = false) {
806
+ const options = typeof columnDraggableOrOptions === "boolean" ? { columnDraggable: columnDraggableOrOptions } : columnDraggableOrOptions;
807
+ const columnDraggable = options.columnDraggable ?? false;
805
808
  let left = 0;
806
- return columns.map((column) => {
809
+ const metrics = columns.map((column) => {
807
810
  const width = Math.max(getMinimumColumnWidth(column, columnDraggable), column.width ?? DEFAULT_COLUMN_WIDTH);
808
811
  const metric = { left, width, right: left + width };
809
812
  left += width;
810
813
  return metric;
811
814
  });
815
+ const viewportWidth = options.viewportWidth ?? 0;
816
+ if (viewportWidth <= left || columns.length === 0) return metrics;
817
+ const stretchable = columns.map((column, index) => ({ column, index })).filter(({ column }) => column.fixed !== "right" && !column.rowSelection && !column.rowDragHandle && !column.rowNumber);
818
+ const targets = stretchable.length > 0 ? stretchable : columns.map((column, index) => ({ column, index })).filter(({ column }) => column.fixed !== "right");
819
+ if (targets.length === 0) return metrics;
820
+ const extra = viewportWidth - left;
821
+ const baseTotal = targets.reduce((sum, { index }) => sum + metrics[index].width, 0);
822
+ let assigned = 0;
823
+ const targetIndexSet = new Set(targets.map(({ index }) => index));
824
+ const nextWidths = metrics.map((metric, index) => {
825
+ if (!targetIndexSet.has(index)) return metric.width;
826
+ const isLastTarget = index === targets[targets.length - 1].index;
827
+ const addition = isLastTarget ? extra - assigned : Math.floor(extra * (metric.width / baseTotal));
828
+ assigned += addition;
829
+ return metric.width + addition;
830
+ });
831
+ left = 0;
832
+ return nextWidths.map((width) => {
833
+ const metric = { left, width, right: left + width };
834
+ left += width;
835
+ return metric;
836
+ });
812
837
  }
813
838
  function lowerBound(metrics, value) {
814
839
  let low = 0;
@@ -1229,6 +1254,7 @@ function useControllableValue(value, defaultValue, onChange) {
1229
1254
  var import_jsx_runtime7 = require("react/jsx-runtime");
1230
1255
  var ANNOTATION_COLORS = ["#f44336", "#ff7a45", "#f5a623", "#f2c94c", "#52c41a", "#13a8a8", "#1677ff", "#597ef7", "#9254de", "#eb2f96"];
1231
1256
  var FALLBACK_HEIGHT = 480;
1257
+ var EMPTY_BODY_HEIGHT = 240;
1232
1258
  var VIRTUAL_OVERSCAN = 4;
1233
1259
  var TOOLTIP_DELAY = 1e3;
1234
1260
  var SUMMARY_CHUNK_SIZE = 5e3;
@@ -1302,6 +1328,7 @@ function Table({
1302
1328
  rowKey,
1303
1329
  width = "100%",
1304
1330
  height = "100%",
1331
+ autoHeight = false,
1305
1332
  rowHeight = 36,
1306
1333
  headerHeight = 40,
1307
1334
  fixedHeader = true,
@@ -1413,7 +1440,7 @@ function Table({
1413
1440
  const hoveredRowIndexRef = (0, import_react5.useRef)(null);
1414
1441
  const [viewport, setViewport] = (0, import_react5.useState)({ width: 0, height: typeof height === "number" ? height : FALLBACK_HEIGHT });
1415
1442
  const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = (0, import_react5.useState)(0);
1416
- const resolvedHeight = viewport.height || FALLBACK_HEIGHT;
1443
+ const resolvedHeight = typeof height === "number" ? height : viewport.height || FALLBACK_HEIGHT;
1417
1444
  const [scrollPosition, setScrollPosition] = (0, import_react5.useState)({ left: 0, top: 0 });
1418
1445
  const scrollRef = (0, import_react5.useRef)({ left: 0, top: 0 });
1419
1446
  const [selection, setSelection] = useControllableValue(selectedCell, defaultSelectedCell, onSelectedCellChange);
@@ -1544,7 +1571,7 @@ function Table({
1544
1571
  if (selection?.rowKey === editing.rowKey && selection.columnKey === editing.columnKey) return;
1545
1572
  setEditing(null);
1546
1573
  }, [editing, selection]);
1547
- const metrics = (0, import_react5.useMemo)(() => buildColumnMetrics(columns, columnDraggable), [columnDraggable, columns]);
1574
+ const metrics = (0, import_react5.useMemo)(() => buildColumnMetrics(columns, { columnDraggable, viewportWidth: viewport.width }), [columnDraggable, columns, viewport.width]);
1548
1575
  const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
1549
1576
  const contentHeight = rows.length * rowHeight;
1550
1577
  const fixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "left" ? Math.max(width2, metrics[index].right) : width2, 0), [columns, metrics]);
@@ -1561,11 +1588,15 @@ function Table({
1561
1588
  }, [columns, metrics]);
1562
1589
  const hasRowSelectionColumn = (0, import_react5.useMemo)(() => columns.some((column) => column.rowSelection), [columns]);
1563
1590
  const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
1564
- const hasSummaryRow = (0, import_react5.useMemo)(() => summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, summaryEnabled]);
1591
+ const hasSummaryRow = (0, import_react5.useMemo)(() => rows.length > 0 && summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, rows.length, summaryEnabled]);
1565
1592
  const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
1566
1593
  const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
1567
1594
  const bodyTop = headerHeight + topSummaryHeight;
1568
- const bodyViewportHeight = Math.max(0, resolvedHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1595
+ const bodyContentHeight = rows.length === 0 ? EMPTY_BODY_HEIGHT : contentHeight;
1596
+ const realContentHeight = bodyTop + bodyContentHeight + bottomSummaryHeight + (contentWidth > viewport.width ? horizontalScrollbarHeight : 0);
1597
+ const effectiveHeight = autoHeight ? Math.max(bodyTop + bottomSummaryHeight, Math.min(resolvedHeight, realContentHeight)) : resolvedHeight;
1598
+ const renderHeight = Math.max(0, effectiveHeight - bottomSummaryHeight);
1599
+ const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1569
1600
  const [summaryState, setSummaryState] = (0, import_react5.useState)(() => ({ columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1570
1601
  const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.columns !== columns || summaryState.rows !== rows);
1571
1602
  const summaryValues = hasSummaryRow && summaryState.columns === columns && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
@@ -1701,9 +1732,8 @@ function Table({
1701
1732
  const canvas = canvasRef.current;
1702
1733
  if (!canvas || viewport.width <= 0) return;
1703
1734
  const ratio = Math.min(window.devicePixelRatio || 1, 2);
1704
- const displayHeight = resolvedHeight;
1705
1735
  const physicalWidth = Math.round(viewport.width * ratio);
1706
- const physicalHeight = Math.round(displayHeight * ratio);
1736
+ const physicalHeight = Math.round(renderHeight * ratio);
1707
1737
  if (canvas.width !== physicalWidth) canvas.width = physicalWidth;
1708
1738
  if (canvas.height !== physicalHeight) canvas.height = physicalHeight;
1709
1739
  const context = canvas.getContext("2d");
@@ -1726,8 +1756,8 @@ function Table({
1726
1756
  const scroll = scrollRef.current;
1727
1757
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
1728
1758
  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 };
1729
- paintGrid({ context, width: viewport.width, height: resolvedHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1730
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, resolvedHeight, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1759
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1760
+ }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1731
1761
  const scheduleDraw = (0, import_react5.useCallback)(() => {
1732
1762
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
1733
1763
  }, [draw]);
@@ -1836,14 +1866,14 @@ function Table({
1836
1866
  const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
1837
1867
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
1838
1868
  const verticalStart = fixedHeader ? bodyTop : 0;
1839
- if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= resolvedHeight) {
1869
+ if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) {
1840
1870
  focus.style.display = "none";
1841
1871
  } else {
1842
1872
  const borderWidth = 2;
1843
1873
  const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
1844
1874
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
1845
1875
  const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
1846
- const bottom = Math.round(Math.min(rawBottom + 1, resolvedHeight));
1876
+ const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
1847
1877
  focus.style.display = "block";
1848
1878
  focus.style.left = `${left}px`;
1849
1879
  focus.style.top = `${top}px`;
@@ -1868,7 +1898,7 @@ function Table({
1868
1898
  setHoveredCellTooltip(null);
1869
1899
  scheduleDraw();
1870
1900
  setEditing(null);
1871
- }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, resolvedHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
1901
+ }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
1872
1902
  const locateColumn = (0, import_react5.useCallback)((clientX) => {
1873
1903
  const canvas = canvasRef.current;
1874
1904
  if (!canvas) return -1;
@@ -2456,13 +2486,13 @@ function Table({
2456
2486
  const rowTop = (fixedHeader ? 0 : bodyTop) + rowIndex * rowHeight;
2457
2487
  const rowBottom = rowTop + rowHeight * (span?.rowSpan ?? 1);
2458
2488
  const cellWidth = getCellDisplayWidth(columnIndex, span?.colSpan ?? 1);
2459
- const visibleRowHeight = fixedHeader ? resolvedHeight - bodyTop : resolvedHeight;
2489
+ const visibleRowHeight = fixedHeader ? renderHeight - bodyTop : renderHeight;
2460
2490
  if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
2461
2491
  if (rowBottom > scroller.scrollTop + visibleRowHeight) scroller.scrollTop = rowBottom - visibleRowHeight;
2462
2492
  if (columns[columnIndex].fixed === void 0 && metric.left < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, metric.left - fixedWidth);
2463
2493
  if (columns[columnIndex].fixed === void 0 && metric.left + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = metric.left + cellWidth - viewport.width + rightFixedWidth;
2464
2494
  }
2465
- }, [beginEdit, bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, resolvedHeight, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2495
+ }, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2466
2496
  const editorStyle = (0, import_react5.useMemo)(() => {
2467
2497
  if (!editing) return void 0;
2468
2498
  const metric = metrics[editing.columnIndex];
@@ -2686,7 +2716,7 @@ function Table({
2686
2716
  type: "cell",
2687
2717
  ...cell,
2688
2718
  left: Math.min(Math.max(0, clientX - root.left), Math.max(0, viewport.width - 190)),
2689
- top: Math.min(Math.max(0, clientY - root.top), Math.max(0, resolvedHeight - 350))
2719
+ top: Math.min(Math.max(0, clientY - root.top), Math.max(0, renderHeight - 350))
2690
2720
  });
2691
2721
  onCellContextMenu?.({ ...cell, row: rows[cell.rowIndex], clientX, clientY });
2692
2722
  };
@@ -2697,7 +2727,7 @@ function Table({
2697
2727
  type: "header",
2698
2728
  columnIndex,
2699
2729
  left: Math.min(Math.max(0, clientX - root.left), Math.max(0, viewport.width - 180)),
2700
- top: Math.min(Math.max(0, clientY - root.top), Math.max(0, resolvedHeight - 68))
2730
+ top: Math.min(Math.max(0, clientY - root.top), Math.max(0, renderHeight - 68))
2701
2731
  });
2702
2732
  };
2703
2733
  const getRowDragOffset = (rowIndex) => {
@@ -2927,7 +2957,7 @@ function Table({
2927
2957
  const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
2928
2958
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
2929
2959
  const verticalStart = fixedHeader ? bodyTop : 0;
2930
- if (rawLeft <= horizontalStart || rawLeft > horizontalEnd || rawTop <= verticalStart || rawTop > resolvedHeight) return null;
2960
+ if (rawLeft <= horizontalStart || rawLeft > horizontalEnd || rawTop <= verticalStart || rawTop > renderHeight) return null;
2931
2961
  return { left: rawLeft, top: rawTop, bounds };
2932
2962
  })();
2933
2963
  const selectionOutlineStyle = (() => {
@@ -2944,12 +2974,12 @@ function Table({
2944
2974
  const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
2945
2975
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
2946
2976
  const verticalStart = fixedHeader ? bodyTop : 0;
2947
- if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= resolvedHeight) return null;
2977
+ if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) return null;
2948
2978
  const borderWidth = 2;
2949
2979
  const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
2950
2980
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
2951
2981
  const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
2952
- const bottom = Math.round(Math.min(rawBottom + 1, resolvedHeight));
2982
+ const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
2953
2983
  return {
2954
2984
  left,
2955
2985
  top,
@@ -3064,8 +3094,10 @@ function Table({
3064
3094
  const renderSummaryCell = (columnIndex, left) => {
3065
3095
  const column = columns[columnIndex];
3066
3096
  const isUtility = column.rowSelection || column.rowDragHandle || column.rowNumber;
3067
- const summaryContent = summaryValues.has(column.key) ? summaryValues.get(column.key) : summaryEmptyValue;
3068
- const content = column.rowNumber ? language === "zh-CN" ? "\u5408\u8BA1" : "Total" : summaryPending ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rvg-summary-skeleton" }) : summaryContent;
3097
+ const hasSummaryValue = summaryValues.has(column.key);
3098
+ const summaryContent = hasSummaryValue ? summaryValues.get(column.key) : summaryEmptyValue;
3099
+ const showSummarySkeleton = Boolean(column.summary) && summaryPending;
3100
+ const content = columnIndex === 0 ? language === "zh-CN" ? "\u5408\u8BA1" : "Total" : showSummarySkeleton ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rvg-summary-skeleton" }) : summaryContent;
3069
3101
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3070
3102
  "div",
3071
3103
  {
@@ -3082,8 +3114,8 @@ function Table({
3082
3114
  };
3083
3115
  const rootStyle = {
3084
3116
  width,
3085
- height,
3086
- minHeight: typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
3117
+ height: autoHeight ? effectiveHeight : height,
3118
+ minHeight: autoHeight || typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
3087
3119
  ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
3088
3120
  ...style
3089
3121
  };
@@ -3096,7 +3128,7 @@ function Table({
3096
3128
  {
3097
3129
  ref: canvasRef,
3098
3130
  className: "rvg-canvas",
3099
- style: { width: viewport.width, height: resolvedHeight },
3131
+ style: { width: viewport.width, height: renderHeight },
3100
3132
  onMouseMove: (event) => {
3101
3133
  if (columnDragRef.current) return;
3102
3134
  const rect = event.currentTarget.getBoundingClientRect();
@@ -3274,7 +3306,7 @@ function Table({
3274
3306
  onContextMenu: handleContextMenu
3275
3307
  }
3276
3308
  ),
3277
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-text-layer", style: { width: viewport.width, height: resolvedHeight, marginTop: -resolvedHeight }, onContextMenu: handleContextMenu, children: [
3309
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-text-layer", style: { width: viewport.width, height: renderHeight, marginTop: -renderHeight }, onContextMenu: handleContextMenu, children: [
3278
3310
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3279
3311
  "div",
3280
3312
  {
@@ -3298,7 +3330,7 @@ function Table({
3298
3330
  columns.map((column, columnIndex) => column.fixed === "left" ? renderHeaderTitle(columnIndex, metrics[columnIndex].left) : null),
3299
3331
  columns.map((column, columnIndex) => column.fixed === "right" ? renderHeaderTitle(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
3300
3332
  ] }),
3301
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -resolvedHeight } })
3333
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -renderHeight } })
3302
3334
  ] }),
3303
3335
  hasSummaryRow && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3304
3336
  "div",
@@ -3659,10 +3691,10 @@ function Table({
3659
3691
  ] })
3660
3692
  ] }),
3661
3693
  renderEditor(),
3662
- rows.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-empty", children: emptyContent ?? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DefaultEmptyState, { label: labels.empty }) }),
3694
+ rows.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-empty", style: { top: bodyTop }, children: emptyContent ?? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DefaultEmptyState, { label: labels.empty }) }),
3663
3695
  loading && hasCustomLoading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-custom-loading", "aria-label": labels.refreshing, children: loadingContent }),
3664
3696
  loading && !hasCustomLoading && !hasCompletedLoadRef.current && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-initial-loading", style: { top: headerHeight }, "aria-label": labels.initialLoading, children: [
3665
- Array.from({ length: Math.ceil((resolvedHeight - headerHeight) / rowHeight) }, (_, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-row", style: { height: rowHeight }, children: columns.map((column, columnIndex) => {
3697
+ Array.from({ length: Math.ceil((renderHeight - headerHeight) / rowHeight) }, (_, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-row", style: { height: rowHeight }, children: columns.map((column, columnIndex) => {
3666
3698
  const left = getDisplayedColumnLeft(columnIndex);
3667
3699
  const columnWidth = metrics[columnIndex].width;
3668
3700
  if (left + columnWidth <= 0 || left >= viewport.width) return null;
package/dist/index.d.cts CHANGED
@@ -282,6 +282,8 @@ interface TableProps<Row extends object> {
282
282
  width?: number | string;
283
283
  /** Grid height. Defaults to 100%; falls back to 480px when the parent has no height. */
284
284
  height?: number | string;
285
+ /** Shrink the grid to its real content height when there are fewer rows than the configured height can display. */
286
+ autoHeight?: boolean;
285
287
  rowHeight?: number;
286
288
  headerHeight?: number;
287
289
  fixedHeader?: boolean;
@@ -346,8 +348,13 @@ interface ViewportRange {
346
348
  columnEnd: number;
347
349
  }
348
350
 
349
- declare function Table<Row extends object>({ columns: sourceColumns, rows: sourceRows, rowKey, width, height, rowHeight, headerHeight, fixedHeader, locale, verticalBorderless, striped, highlight, cellSpans, loading, loadingContent, virtualized, tooltip, summary, rowNumber, selectedCell, defaultSelectedCell, onSelectedCellChange, rangeSelection, rowSelection, selectedRowKeys, defaultSelectedRowKeys, onSelectedRowKeysChange, columnSelection, selectedColumnKeys, defaultSelectedColumnKeys, onSelectedColumnKeysChange, columnDraggable, columnResizable, onColumnOrderChange, onColumnResize, rowDraggable, onRowOrderChange, onInsertRows, onDeleteRows, sortState, onSortStateChange, filterValues, onFilterValuesChange, onCellChange, onCellContextMenu, contextMenu: contextMenuConfig, emptyContent, className, style, ariaLabel, }: TableProps<Row>): react.JSX.Element;
351
+ declare function Table<Row extends object>({ columns: sourceColumns, rows: sourceRows, rowKey, width, height, autoHeight, rowHeight, headerHeight, fixedHeader, locale, verticalBorderless, striped, highlight, cellSpans, loading, loadingContent, virtualized, tooltip, summary, rowNumber, selectedCell, defaultSelectedCell, onSelectedCellChange, rangeSelection, rowSelection, selectedRowKeys, defaultSelectedRowKeys, onSelectedRowKeysChange, columnSelection, selectedColumnKeys, defaultSelectedColumnKeys, onSelectedColumnKeysChange, columnDraggable, columnResizable, onColumnOrderChange, onColumnResize, rowDraggable, onRowOrderChange, onInsertRows, onDeleteRows, sortState, onSortStateChange, filterValues, onFilterValuesChange, onCellChange, onCellContextMenu, contextMenu: contextMenuConfig, emptyContent, className, style, ariaLabel, }: TableProps<Row>): react.JSX.Element;
350
352
 
353
+ type LayoutColumn<Row> = GridColumn<Row> & {
354
+ rowSelection?: boolean;
355
+ rowDragHandle?: boolean;
356
+ rowNumber?: boolean;
357
+ };
351
358
  /**
352
359
  * Cached horizontal geometry for one column.
353
360
  *
@@ -360,11 +367,15 @@ interface ColumnMetric {
360
367
  width: number;
361
368
  right: number;
362
369
  }
370
+ interface BuildColumnMetricsOptions {
371
+ columnDraggable?: boolean;
372
+ viewportWidth?: number;
373
+ }
363
374
  /**
364
375
  * Build cumulative column positions used by both canvas painting and DOM
365
376
  * overlay placement.
366
377
  */
367
- declare function buildColumnMetrics<Row>(columns: GridColumn<Row>[], columnDraggable?: boolean): ColumnMetric[];
378
+ declare function buildColumnMetrics<Row>(columns: LayoutColumn<Row>[], columnDraggableOrOptions?: boolean | BuildColumnMetricsOptions): ColumnMetric[];
368
379
  /**
369
380
  * Compute the row and column slice that should be painted for the current
370
381
  * viewport.
package/dist/index.d.ts CHANGED
@@ -282,6 +282,8 @@ interface TableProps<Row extends object> {
282
282
  width?: number | string;
283
283
  /** Grid height. Defaults to 100%; falls back to 480px when the parent has no height. */
284
284
  height?: number | string;
285
+ /** Shrink the grid to its real content height when there are fewer rows than the configured height can display. */
286
+ autoHeight?: boolean;
285
287
  rowHeight?: number;
286
288
  headerHeight?: number;
287
289
  fixedHeader?: boolean;
@@ -346,8 +348,13 @@ interface ViewportRange {
346
348
  columnEnd: number;
347
349
  }
348
350
 
349
- declare function Table<Row extends object>({ columns: sourceColumns, rows: sourceRows, rowKey, width, height, rowHeight, headerHeight, fixedHeader, locale, verticalBorderless, striped, highlight, cellSpans, loading, loadingContent, virtualized, tooltip, summary, rowNumber, selectedCell, defaultSelectedCell, onSelectedCellChange, rangeSelection, rowSelection, selectedRowKeys, defaultSelectedRowKeys, onSelectedRowKeysChange, columnSelection, selectedColumnKeys, defaultSelectedColumnKeys, onSelectedColumnKeysChange, columnDraggable, columnResizable, onColumnOrderChange, onColumnResize, rowDraggable, onRowOrderChange, onInsertRows, onDeleteRows, sortState, onSortStateChange, filterValues, onFilterValuesChange, onCellChange, onCellContextMenu, contextMenu: contextMenuConfig, emptyContent, className, style, ariaLabel, }: TableProps<Row>): react.JSX.Element;
351
+ declare function Table<Row extends object>({ columns: sourceColumns, rows: sourceRows, rowKey, width, height, autoHeight, rowHeight, headerHeight, fixedHeader, locale, verticalBorderless, striped, highlight, cellSpans, loading, loadingContent, virtualized, tooltip, summary, rowNumber, selectedCell, defaultSelectedCell, onSelectedCellChange, rangeSelection, rowSelection, selectedRowKeys, defaultSelectedRowKeys, onSelectedRowKeysChange, columnSelection, selectedColumnKeys, defaultSelectedColumnKeys, onSelectedColumnKeysChange, columnDraggable, columnResizable, onColumnOrderChange, onColumnResize, rowDraggable, onRowOrderChange, onInsertRows, onDeleteRows, sortState, onSortStateChange, filterValues, onFilterValuesChange, onCellChange, onCellContextMenu, contextMenu: contextMenuConfig, emptyContent, className, style, ariaLabel, }: TableProps<Row>): react.JSX.Element;
350
352
 
353
+ type LayoutColumn<Row> = GridColumn<Row> & {
354
+ rowSelection?: boolean;
355
+ rowDragHandle?: boolean;
356
+ rowNumber?: boolean;
357
+ };
351
358
  /**
352
359
  * Cached horizontal geometry for one column.
353
360
  *
@@ -360,11 +367,15 @@ interface ColumnMetric {
360
367
  width: number;
361
368
  right: number;
362
369
  }
370
+ interface BuildColumnMetricsOptions {
371
+ columnDraggable?: boolean;
372
+ viewportWidth?: number;
373
+ }
363
374
  /**
364
375
  * Build cumulative column positions used by both canvas painting and DOM
365
376
  * overlay placement.
366
377
  */
367
- declare function buildColumnMetrics<Row>(columns: GridColumn<Row>[], columnDraggable?: boolean): ColumnMetric[];
378
+ declare function buildColumnMetrics<Row>(columns: LayoutColumn<Row>[], columnDraggableOrOptions?: boolean | BuildColumnMetricsOptions): ColumnMetric[];
368
379
  /**
369
380
  * Compute the row and column slice that should be painted for the current
370
381
  * viewport.
package/dist/index.js CHANGED
@@ -303,6 +303,7 @@ function LoadingSpinnerIcon() {
303
303
  // src/components/editors/ChoiceEditor.tsx
304
304
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
305
305
  function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
306
+ const editorStyle = typeof style.width === "number" ? { ...style, width: Math.max(0, style.width - 1) } : style;
306
307
  const collection = createListCollection({
307
308
  items: options,
308
309
  itemToValue: (item) => item.value,
@@ -312,7 +313,7 @@ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCan
312
313
  Select.Root,
313
314
  {
314
315
  className: "rvg-choice-editor",
315
- style,
316
+ style: editorStyle,
316
317
  collection,
317
318
  value: value ? [value] : [],
318
319
  defaultOpen: true,
@@ -771,14 +772,38 @@ function getMinimumColumnWidth(column, columnDraggable = false) {
771
772
  if (column.rowSelection || column.rowNumber) return 44;
772
773
  return 60;
773
774
  }
774
- function buildColumnMetrics(columns, columnDraggable = false) {
775
+ function buildColumnMetrics(columns, columnDraggableOrOptions = false) {
776
+ const options = typeof columnDraggableOrOptions === "boolean" ? { columnDraggable: columnDraggableOrOptions } : columnDraggableOrOptions;
777
+ const columnDraggable = options.columnDraggable ?? false;
775
778
  let left = 0;
776
- return columns.map((column) => {
779
+ const metrics = columns.map((column) => {
777
780
  const width = Math.max(getMinimumColumnWidth(column, columnDraggable), column.width ?? DEFAULT_COLUMN_WIDTH);
778
781
  const metric = { left, width, right: left + width };
779
782
  left += width;
780
783
  return metric;
781
784
  });
785
+ const viewportWidth = options.viewportWidth ?? 0;
786
+ if (viewportWidth <= left || columns.length === 0) return metrics;
787
+ const stretchable = columns.map((column, index) => ({ column, index })).filter(({ column }) => column.fixed !== "right" && !column.rowSelection && !column.rowDragHandle && !column.rowNumber);
788
+ const targets = stretchable.length > 0 ? stretchable : columns.map((column, index) => ({ column, index })).filter(({ column }) => column.fixed !== "right");
789
+ if (targets.length === 0) return metrics;
790
+ const extra = viewportWidth - left;
791
+ const baseTotal = targets.reduce((sum, { index }) => sum + metrics[index].width, 0);
792
+ let assigned = 0;
793
+ const targetIndexSet = new Set(targets.map(({ index }) => index));
794
+ const nextWidths = metrics.map((metric, index) => {
795
+ if (!targetIndexSet.has(index)) return metric.width;
796
+ const isLastTarget = index === targets[targets.length - 1].index;
797
+ const addition = isLastTarget ? extra - assigned : Math.floor(extra * (metric.width / baseTotal));
798
+ assigned += addition;
799
+ return metric.width + addition;
800
+ });
801
+ left = 0;
802
+ return nextWidths.map((width) => {
803
+ const metric = { left, width, right: left + width };
804
+ left += width;
805
+ return metric;
806
+ });
782
807
  }
783
808
  function lowerBound(metrics, value) {
784
809
  let low = 0;
@@ -1199,6 +1224,7 @@ function useControllableValue(value, defaultValue, onChange) {
1199
1224
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1200
1225
  var ANNOTATION_COLORS = ["#f44336", "#ff7a45", "#f5a623", "#f2c94c", "#52c41a", "#13a8a8", "#1677ff", "#597ef7", "#9254de", "#eb2f96"];
1201
1226
  var FALLBACK_HEIGHT = 480;
1227
+ var EMPTY_BODY_HEIGHT = 240;
1202
1228
  var VIRTUAL_OVERSCAN = 4;
1203
1229
  var TOOLTIP_DELAY = 1e3;
1204
1230
  var SUMMARY_CHUNK_SIZE = 5e3;
@@ -1272,6 +1298,7 @@ function Table({
1272
1298
  rowKey,
1273
1299
  width = "100%",
1274
1300
  height = "100%",
1301
+ autoHeight = false,
1275
1302
  rowHeight = 36,
1276
1303
  headerHeight = 40,
1277
1304
  fixedHeader = true,
@@ -1383,7 +1410,7 @@ function Table({
1383
1410
  const hoveredRowIndexRef = useRef2(null);
1384
1411
  const [viewport, setViewport] = useState4({ width: 0, height: typeof height === "number" ? height : FALLBACK_HEIGHT });
1385
1412
  const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = useState4(0);
1386
- const resolvedHeight = viewport.height || FALLBACK_HEIGHT;
1413
+ const resolvedHeight = typeof height === "number" ? height : viewport.height || FALLBACK_HEIGHT;
1387
1414
  const [scrollPosition, setScrollPosition] = useState4({ left: 0, top: 0 });
1388
1415
  const scrollRef = useRef2({ left: 0, top: 0 });
1389
1416
  const [selection, setSelection] = useControllableValue(selectedCell, defaultSelectedCell, onSelectedCellChange);
@@ -1514,7 +1541,7 @@ function Table({
1514
1541
  if (selection?.rowKey === editing.rowKey && selection.columnKey === editing.columnKey) return;
1515
1542
  setEditing(null);
1516
1543
  }, [editing, selection]);
1517
- const metrics = useMemo(() => buildColumnMetrics(columns, columnDraggable), [columnDraggable, columns]);
1544
+ const metrics = useMemo(() => buildColumnMetrics(columns, { columnDraggable, viewportWidth: viewport.width }), [columnDraggable, columns, viewport.width]);
1518
1545
  const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
1519
1546
  const contentHeight = rows.length * rowHeight;
1520
1547
  const fixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "left" ? Math.max(width2, metrics[index].right) : width2, 0), [columns, metrics]);
@@ -1531,11 +1558,15 @@ function Table({
1531
1558
  }, [columns, metrics]);
1532
1559
  const hasRowSelectionColumn = useMemo(() => columns.some((column) => column.rowSelection), [columns]);
1533
1560
  const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
1534
- const hasSummaryRow = useMemo(() => summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, summaryEnabled]);
1561
+ const hasSummaryRow = useMemo(() => rows.length > 0 && summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, rows.length, summaryEnabled]);
1535
1562
  const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
1536
1563
  const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
1537
1564
  const bodyTop = headerHeight + topSummaryHeight;
1538
- const bodyViewportHeight = Math.max(0, resolvedHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1565
+ const bodyContentHeight = rows.length === 0 ? EMPTY_BODY_HEIGHT : contentHeight;
1566
+ const realContentHeight = bodyTop + bodyContentHeight + bottomSummaryHeight + (contentWidth > viewport.width ? horizontalScrollbarHeight : 0);
1567
+ const effectiveHeight = autoHeight ? Math.max(bodyTop + bottomSummaryHeight, Math.min(resolvedHeight, realContentHeight)) : resolvedHeight;
1568
+ const renderHeight = Math.max(0, effectiveHeight - bottomSummaryHeight);
1569
+ const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1539
1570
  const [summaryState, setSummaryState] = useState4(() => ({ columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1540
1571
  const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.columns !== columns || summaryState.rows !== rows);
1541
1572
  const summaryValues = hasSummaryRow && summaryState.columns === columns && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
@@ -1671,9 +1702,8 @@ function Table({
1671
1702
  const canvas = canvasRef.current;
1672
1703
  if (!canvas || viewport.width <= 0) return;
1673
1704
  const ratio = Math.min(window.devicePixelRatio || 1, 2);
1674
- const displayHeight = resolvedHeight;
1675
1705
  const physicalWidth = Math.round(viewport.width * ratio);
1676
- const physicalHeight = Math.round(displayHeight * ratio);
1706
+ const physicalHeight = Math.round(renderHeight * ratio);
1677
1707
  if (canvas.width !== physicalWidth) canvas.width = physicalWidth;
1678
1708
  if (canvas.height !== physicalHeight) canvas.height = physicalHeight;
1679
1709
  const context = canvas.getContext("2d");
@@ -1696,8 +1726,8 @@ function Table({
1696
1726
  const scroll = scrollRef.current;
1697
1727
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
1698
1728
  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 };
1699
- paintGrid({ context, width: viewport.width, height: resolvedHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1700
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, resolvedHeight, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1729
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1730
+ }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1701
1731
  const scheduleDraw = useCallback2(() => {
1702
1732
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
1703
1733
  }, [draw]);
@@ -1806,14 +1836,14 @@ function Table({
1806
1836
  const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
1807
1837
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
1808
1838
  const verticalStart = fixedHeader ? bodyTop : 0;
1809
- if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= resolvedHeight) {
1839
+ if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) {
1810
1840
  focus.style.display = "none";
1811
1841
  } else {
1812
1842
  const borderWidth = 2;
1813
1843
  const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
1814
1844
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
1815
1845
  const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
1816
- const bottom = Math.round(Math.min(rawBottom + 1, resolvedHeight));
1846
+ const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
1817
1847
  focus.style.display = "block";
1818
1848
  focus.style.left = `${left}px`;
1819
1849
  focus.style.top = `${top}px`;
@@ -1838,7 +1868,7 @@ function Table({
1838
1868
  setHoveredCellTooltip(null);
1839
1869
  scheduleDraw();
1840
1870
  setEditing(null);
1841
- }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, resolvedHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
1871
+ }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
1842
1872
  const locateColumn = useCallback2((clientX) => {
1843
1873
  const canvas = canvasRef.current;
1844
1874
  if (!canvas) return -1;
@@ -2426,13 +2456,13 @@ function Table({
2426
2456
  const rowTop = (fixedHeader ? 0 : bodyTop) + rowIndex * rowHeight;
2427
2457
  const rowBottom = rowTop + rowHeight * (span?.rowSpan ?? 1);
2428
2458
  const cellWidth = getCellDisplayWidth(columnIndex, span?.colSpan ?? 1);
2429
- const visibleRowHeight = fixedHeader ? resolvedHeight - bodyTop : resolvedHeight;
2459
+ const visibleRowHeight = fixedHeader ? renderHeight - bodyTop : renderHeight;
2430
2460
  if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
2431
2461
  if (rowBottom > scroller.scrollTop + visibleRowHeight) scroller.scrollTop = rowBottom - visibleRowHeight;
2432
2462
  if (columns[columnIndex].fixed === void 0 && metric.left < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, metric.left - fixedWidth);
2433
2463
  if (columns[columnIndex].fixed === void 0 && metric.left + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = metric.left + cellWidth - viewport.width + rightFixedWidth;
2434
2464
  }
2435
- }, [beginEdit, bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, resolvedHeight, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2465
+ }, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2436
2466
  const editorStyle = useMemo(() => {
2437
2467
  if (!editing) return void 0;
2438
2468
  const metric = metrics[editing.columnIndex];
@@ -2656,7 +2686,7 @@ function Table({
2656
2686
  type: "cell",
2657
2687
  ...cell,
2658
2688
  left: Math.min(Math.max(0, clientX - root.left), Math.max(0, viewport.width - 190)),
2659
- top: Math.min(Math.max(0, clientY - root.top), Math.max(0, resolvedHeight - 350))
2689
+ top: Math.min(Math.max(0, clientY - root.top), Math.max(0, renderHeight - 350))
2660
2690
  });
2661
2691
  onCellContextMenu?.({ ...cell, row: rows[cell.rowIndex], clientX, clientY });
2662
2692
  };
@@ -2667,7 +2697,7 @@ function Table({
2667
2697
  type: "header",
2668
2698
  columnIndex,
2669
2699
  left: Math.min(Math.max(0, clientX - root.left), Math.max(0, viewport.width - 180)),
2670
- top: Math.min(Math.max(0, clientY - root.top), Math.max(0, resolvedHeight - 68))
2700
+ top: Math.min(Math.max(0, clientY - root.top), Math.max(0, renderHeight - 68))
2671
2701
  });
2672
2702
  };
2673
2703
  const getRowDragOffset = (rowIndex) => {
@@ -2897,7 +2927,7 @@ function Table({
2897
2927
  const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
2898
2928
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
2899
2929
  const verticalStart = fixedHeader ? bodyTop : 0;
2900
- if (rawLeft <= horizontalStart || rawLeft > horizontalEnd || rawTop <= verticalStart || rawTop > resolvedHeight) return null;
2930
+ if (rawLeft <= horizontalStart || rawLeft > horizontalEnd || rawTop <= verticalStart || rawTop > renderHeight) return null;
2901
2931
  return { left: rawLeft, top: rawTop, bounds };
2902
2932
  })();
2903
2933
  const selectionOutlineStyle = (() => {
@@ -2914,12 +2944,12 @@ function Table({
2914
2944
  const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
2915
2945
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
2916
2946
  const verticalStart = fixedHeader ? bodyTop : 0;
2917
- if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= resolvedHeight) return null;
2947
+ if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) return null;
2918
2948
  const borderWidth = 2;
2919
2949
  const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
2920
2950
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
2921
2951
  const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
2922
- const bottom = Math.round(Math.min(rawBottom + 1, resolvedHeight));
2952
+ const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
2923
2953
  return {
2924
2954
  left,
2925
2955
  top,
@@ -3034,8 +3064,10 @@ function Table({
3034
3064
  const renderSummaryCell = (columnIndex, left) => {
3035
3065
  const column = columns[columnIndex];
3036
3066
  const isUtility = column.rowSelection || column.rowDragHandle || column.rowNumber;
3037
- const summaryContent = summaryValues.has(column.key) ? summaryValues.get(column.key) : summaryEmptyValue;
3038
- const content = column.rowNumber ? language === "zh-CN" ? "\u5408\u8BA1" : "Total" : summaryPending ? /* @__PURE__ */ jsx7("span", { className: "rvg-summary-skeleton" }) : summaryContent;
3067
+ const hasSummaryValue = summaryValues.has(column.key);
3068
+ const summaryContent = hasSummaryValue ? summaryValues.get(column.key) : summaryEmptyValue;
3069
+ const showSummarySkeleton = Boolean(column.summary) && summaryPending;
3070
+ const content = columnIndex === 0 ? language === "zh-CN" ? "\u5408\u8BA1" : "Total" : showSummarySkeleton ? /* @__PURE__ */ jsx7("span", { className: "rvg-summary-skeleton" }) : summaryContent;
3039
3071
  return /* @__PURE__ */ jsx7(
3040
3072
  "div",
3041
3073
  {
@@ -3052,8 +3084,8 @@ function Table({
3052
3084
  };
3053
3085
  const rootStyle = {
3054
3086
  width,
3055
- height,
3056
- minHeight: typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
3087
+ height: autoHeight ? effectiveHeight : height,
3088
+ minHeight: autoHeight || typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
3057
3089
  ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
3058
3090
  ...style
3059
3091
  };
@@ -3066,7 +3098,7 @@ function Table({
3066
3098
  {
3067
3099
  ref: canvasRef,
3068
3100
  className: "rvg-canvas",
3069
- style: { width: viewport.width, height: resolvedHeight },
3101
+ style: { width: viewport.width, height: renderHeight },
3070
3102
  onMouseMove: (event) => {
3071
3103
  if (columnDragRef.current) return;
3072
3104
  const rect = event.currentTarget.getBoundingClientRect();
@@ -3244,7 +3276,7 @@ function Table({
3244
3276
  onContextMenu: handleContextMenu
3245
3277
  }
3246
3278
  ),
3247
- /* @__PURE__ */ jsxs7("div", { className: "rvg-text-layer", style: { width: viewport.width, height: resolvedHeight, marginTop: -resolvedHeight }, onContextMenu: handleContextMenu, children: [
3279
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-text-layer", style: { width: viewport.width, height: renderHeight, marginTop: -renderHeight }, onContextMenu: handleContextMenu, children: [
3248
3280
  /* @__PURE__ */ jsx7("div", { className: "rvg-text-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ jsxs7(
3249
3281
  "div",
3250
3282
  {
@@ -3268,7 +3300,7 @@ function Table({
3268
3300
  columns.map((column, columnIndex) => column.fixed === "left" ? renderHeaderTitle(columnIndex, metrics[columnIndex].left) : null),
3269
3301
  columns.map((column, columnIndex) => column.fixed === "right" ? renderHeaderTitle(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
3270
3302
  ] }),
3271
- /* @__PURE__ */ jsx7("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -resolvedHeight } })
3303
+ /* @__PURE__ */ jsx7("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -renderHeight } })
3272
3304
  ] }),
3273
3305
  hasSummaryRow && /* @__PURE__ */ jsxs7(
3274
3306
  "div",
@@ -3629,10 +3661,10 @@ function Table({
3629
3661
  ] })
3630
3662
  ] }),
3631
3663
  renderEditor(),
3632
- rows.length === 0 && !loading && /* @__PURE__ */ jsx7("div", { className: "rvg-empty", children: emptyContent ?? /* @__PURE__ */ jsx7(DefaultEmptyState, { label: labels.empty }) }),
3664
+ rows.length === 0 && !loading && /* @__PURE__ */ jsx7("div", { className: "rvg-empty", style: { top: bodyTop }, children: emptyContent ?? /* @__PURE__ */ jsx7(DefaultEmptyState, { label: labels.empty }) }),
3633
3665
  loading && hasCustomLoading && /* @__PURE__ */ jsx7("div", { className: "rvg-custom-loading", "aria-label": labels.refreshing, children: loadingContent }),
3634
3666
  loading && !hasCustomLoading && !hasCompletedLoadRef.current && /* @__PURE__ */ jsxs7("div", { className: "rvg-initial-loading", style: { top: headerHeight }, "aria-label": labels.initialLoading, children: [
3635
- Array.from({ length: Math.ceil((resolvedHeight - headerHeight) / rowHeight) }, (_, rowIndex) => /* @__PURE__ */ jsx7("div", { className: "rvg-loading-row", style: { height: rowHeight }, children: columns.map((column, columnIndex) => {
3667
+ Array.from({ length: Math.ceil((renderHeight - headerHeight) / rowHeight) }, (_, rowIndex) => /* @__PURE__ */ jsx7("div", { className: "rvg-loading-row", style: { height: rowHeight }, children: columns.map((column, columnIndex) => {
3636
3668
  const left = getDisplayedColumnLeft(columnIndex);
3637
3669
  const columnWidth = metrics[columnIndex].width;
3638
3670
  if (left + columnWidth <= 0 || left >= viewport.width) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",