@lensui/lens-table 0.1.9 → 0.1.11

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
@@ -96,6 +96,7 @@ export function Example() {
96
96
  | `locale` | `'zh-CN' \| 'en-US' \| LocaleConfig` | `'zh-CN'` | 国际化配置。传字符串时使用内置中英文文案;传对象时可通过`language` 指定语言,并通过 `labels` 覆盖文案。 |
97
97
  | `borderless` | `boolean` | `false` | 是否弱化竖向边框。开启后隐藏单元格之间的竖线,但保留横向分隔线。 |
98
98
  | `verticalBorderless` | `boolean` | `false` | 已兼容保留,建议改用 `borderless`。 |
99
+ | `horizontalBorderless` | `boolean` | `false` | 是否隐藏横向行分隔线及上下边框,同时保留纵向列分隔线。可与 `striped` 配合使用。 |
99
100
  | `striped` | `boolean \| string` | `false` | 是否显示斑马纹行背景。传`true` 使用主题变量,传颜色字符串可自定义斑马纹颜色,例如 `'#f6faff'`。 |
100
101
  | `highlight` | `{ editedCells?: boolean \| string; insertedRows?: boolean \| string }` | `{}` | 高亮反馈配置。`editedCells` 控制编辑过的单元格底色,`insertedRows` 控制乐观插入行底色;传 `true` 使用默认色,传颜色字符串可自定义。 |
101
102
  | `cellSpans` | `TableCellSpan[] \| ((ctx) => TableCellSpan \| { rowSpan?: number; colSpan?: number } \| null)` | `[]` | 合并主体单元格配置。可传数组,也可传函数按单元格生成规则。推荐使用 `rowKey` 定位;横向合并会限制在同一个固定列分区内,工具列不会参与合并。 |
@@ -578,6 +579,7 @@ const columns: GridColumn<Row>[] = [
578
579
  '--rvg-color-bg': '#0f172a',
579
580
  '--rvg-color-text': '#e5e7eb',
580
581
  '--rvg-color-grid': '#334155',
582
+ '--rvg-font-size-body': '12px',
581
583
  } as React.CSSProperties}
582
584
  />
583
585
  ```
@@ -586,6 +588,7 @@ const columns: GridColumn<Row>[] = [
586
588
 
587
589
  | 变量 | 说明 |
588
590
  | ----------------------------------- | ---------------------------------------------------------------------------------- |
591
+ | `--rvg-font-size-body` | 数据正文及单元格编辑器字号,默认 `13px`;不影响表头字号。 |
589
592
  | `--rvg-color-bg` | 表格背景色。 |
590
593
  | `--rvg-color-header-bg` | 表头背景色。 |
591
594
  | `--rvg-color-text` | 主文本颜色。 |
package/dist/index.cjs CHANGED
@@ -705,6 +705,7 @@ function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }
705
705
  "input",
706
706
  {
707
707
  type,
708
+ style: { font: "var(--rvg-font-size-body) Inter, ui-sans-serif, system-ui, sans-serif", textAlign: style.textAlign },
708
709
  value,
709
710
  autoFocus: true,
710
711
  onChange: (event) => onChange(event.target.value),
@@ -893,6 +894,18 @@ function hitTestColumn(metrics, contentX) {
893
894
  return index < metrics.length ? index : -1;
894
895
  }
895
896
 
897
+ // src/core/typography.ts
898
+ var createCellFont = (fontSize = 13) => `${fontSize}px Inter, ui-sans-serif, system-ui, sans-serif`;
899
+ function getCellBaselineOffset(context) {
900
+ context.save();
901
+ context.textBaseline = "alphabetic";
902
+ const metrics = context.measureText("Mg");
903
+ context.restore();
904
+ const ascent = metrics.fontBoundingBoxAscent;
905
+ const descent = metrics.fontBoundingBoxDescent;
906
+ return Number.isFinite(ascent) && Number.isFinite(descent) ? (ascent - descent) / 2 : 0;
907
+ }
908
+
896
909
  // src/core/render.ts
897
910
  var getCellCoordKey = (rowIndex, columnIndex) => `${rowIndex}:${columnIndex}`;
898
911
  var COLORS = {
@@ -929,7 +942,7 @@ function ellipsizeText(context, text, maxWidth) {
929
942
  return `${text.slice(0, start)}${ellipsis}`;
930
943
  }
931
944
  function paintGrid(options) {
932
- const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, headerHeight, fixedHeader, verticalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = 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;
933
946
  const headerLeafTop = options.headerLeafTop ?? 0;
934
947
  const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
935
948
  const colors = { ...COLORS, ...options.colors };
@@ -946,7 +959,8 @@ function paintGrid(options) {
946
959
  ctx.clearRect(0, 0, width, height);
947
960
  ctx.fillStyle = colors.background;
948
961
  ctx.fillRect(0, 0, width, height);
949
- ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
962
+ ctx.font = createCellFont(bodyFontSize);
963
+ const cellBaselineOffset = getCellBaselineOffset(ctx);
950
964
  ctx.textBaseline = "middle";
951
965
  const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? value + metrics[index].width : value, 0);
952
966
  const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
@@ -1051,7 +1065,7 @@ function paintGrid(options) {
1051
1065
  ctx.fillStyle = colors.grid;
1052
1066
  if (!verticalBorderless) ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
1053
1067
  if (rowIndex === rowBelowDragGap) ctx.fillRect(x, y, cellWidth, 1);
1054
- if (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1) {
1068
+ if (!horizontalBorderless && (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1)) {
1055
1069
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
1056
1070
  }
1057
1071
  if (rowSpan > 1) {
@@ -1145,9 +1159,10 @@ function paintGrid(options) {
1145
1159
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1146
1160
  ctx.clip();
1147
1161
  ctx.fillStyle = cellStyle?.color ?? colors.text;
1148
- ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
1162
+ ctx.font = createCellFont(bodyFontSize);
1163
+ ctx.textBaseline = "alphabetic";
1149
1164
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
1150
- ctx.fillText(label, textX, y + cellHeight / 2);
1165
+ ctx.fillText(label, textX, y + cellHeight / 2 + cellBaselineOffset);
1151
1166
  ctx.restore();
1152
1167
  }
1153
1168
  }
@@ -1191,7 +1206,7 @@ function paintGrid(options) {
1191
1206
  }
1192
1207
  ctx.fillStyle = colors.grid;
1193
1208
  if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1194
- ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1209
+ if (!horizontalBorderless) ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1195
1210
  const column = columns[columnIndex];
1196
1211
  if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1197
1212
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
@@ -1227,12 +1242,12 @@ function paintGrid(options) {
1227
1242
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1228
1243
  }
1229
1244
  ctx.fillStyle = colors.grid;
1230
- ctx.fillRect(0, 0, width, 1);
1245
+ if (!horizontalBorderless) ctx.fillRect(0, 0, width, 1);
1231
1246
  if (!verticalBorderless) {
1232
1247
  ctx.fillRect(0, 0, 1, height);
1233
1248
  ctx.fillRect(width - 1, 0, 1, height);
1234
1249
  }
1235
- if (!options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1250
+ if (!horizontalBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1236
1251
  if (selectionRange) {
1237
1252
  const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1238
1253
  const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
@@ -1453,6 +1468,10 @@ var waitForPaint = () => new Promise((resolve) => {
1453
1468
  function readThemeColor(styles, name, fallback) {
1454
1469
  return styles.getPropertyValue(name).trim() || fallback;
1455
1470
  }
1471
+ function readThemePixel(styles, name, fallback) {
1472
+ const value = Number.parseFloat(styles.getPropertyValue(name));
1473
+ return Number.isFinite(value) && value > 0 ? value : fallback;
1474
+ }
1456
1475
  function getDefaultRowKey(row, index) {
1457
1476
  const id = row.id;
1458
1477
  return typeof id === "string" || typeof id === "number" ? id : index;
@@ -1536,6 +1555,7 @@ function TableInner({
1536
1555
  locale = "zh-CN",
1537
1556
  borderless,
1538
1557
  verticalBorderless,
1558
+ horizontalBorderless = false,
1539
1559
  striped = false,
1540
1560
  highlight,
1541
1561
  cellSpans = [],
@@ -2196,6 +2216,7 @@ function TableInner({
2196
2216
  const context = canvas.getContext("2d");
2197
2217
  if (!context) return;
2198
2218
  const themeStyles = getComputedStyle(canvas);
2219
+ const bodyFontSize = readThemePixel(themeStyles, "--rvg-font-size-body", 13);
2199
2220
  const themeColors = {
2200
2221
  background: readThemeColor(themeStyles, "--rvg-color-bg", "#ffffff"),
2201
2222
  header: readThemeColor(themeStyles, "--rvg-color-header-bg", "#f5f6f7"),
@@ -2216,8 +2237,8 @@ function TableInner({
2216
2237
  const scroll = scrollRef.current;
2217
2238
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2218
2239
  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 };
2219
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2220
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
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]);
2221
2242
  const scheduleDraw = (0, import_react5.useCallback)(() => {
2222
2243
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2223
2244
  }, [draw]);
@@ -3581,11 +3602,21 @@ function TableInner({
3581
3602
  const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
3582
3603
  const visibleTop = Math.max(cellTop, verticalStart);
3583
3604
  const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
3605
+ if (column.editor && column.editor.type !== "text") {
3606
+ return {
3607
+ left: visibleLeft,
3608
+ top: visibleTop,
3609
+ width: Math.max(0, visibleRight - visibleLeft),
3610
+ height: Math.max(0, visibleBottom - visibleTop)
3611
+ };
3612
+ }
3584
3613
  return {
3585
- left: visibleLeft,
3586
- top: visibleTop,
3587
- width: Math.max(0, visibleRight - visibleLeft),
3588
- height: Math.max(0, visibleBottom - visibleTop)
3614
+ left: cellLeft,
3615
+ top: cellTop,
3616
+ width: cellWidth,
3617
+ height: cellHeight,
3618
+ textAlign: column.align ?? "left",
3619
+ clipPath: `inset(${Math.max(0, visibleTop - cellTop)}px ${Math.max(0, cellLeft + cellWidth - visibleRight)}px ${Math.max(0, cellTop + cellHeight - visibleBottom)}px ${Math.max(0, visibleLeft - cellLeft)}px)`
3589
3620
  };
3590
3621
  }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
3591
3622
  const handleColumnPointerDown = (0, import_react5.useCallback)((event) => {
@@ -4461,7 +4492,7 @@ function TableInner({
4461
4492
  };
4462
4493
  const currentScroll = scrollRef.current;
4463
4494
  const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
4464
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
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: [
4465
4496
  /* @__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: [
4466
4497
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4467
4498
  "canvas",
@@ -4521,7 +4552,10 @@ function TableInner({
4521
4552
  } else {
4522
4553
  const label = getCellLabel(cell.rowIndex, cell.columnIndex);
4523
4554
  const context = event.currentTarget.getContext("2d");
4524
- if (context) context.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
4555
+ if (context) {
4556
+ const bodyFontSize = readThemePixel(getComputedStyle(event.currentTarget), "--rvg-font-size-body", 13);
4557
+ context.font = `${bodyFontSize}px Inter, ui-sans-serif, system-ui, sans-serif`;
4558
+ }
4525
4559
  const truncated = Boolean(label && context && context.measureText(label).width + 20 > metrics[cell.columnIndex].width);
4526
4560
  if (!label || !tooltipConfig.cell && !truncated) {
4527
4561
  showCellTooltipAfterDelay(null);
package/dist/index.css CHANGED
@@ -1,5 +1,6 @@
1
1
  /* src/style.css */
2
2
  :root {
3
+ --rvg-font-size-body: 13px;
3
4
  --rvg-color-bg: #fff;
4
5
  --rvg-color-header-bg: #f5f6f7;
5
6
  --rvg-color-grid: #e3e6e8;
@@ -105,6 +106,9 @@
105
106
  content: "";
106
107
  pointer-events: none;
107
108
  }
109
+ .rvg-root.is-horizontal-borderless .rvg-text-layer::before {
110
+ display: none;
111
+ }
108
112
  .rvg-vertical-scrollbar,
109
113
  .rvg-horizontal-scrollbar {
110
114
  position: absolute;
@@ -202,11 +206,12 @@
202
206
  padding: 0 10px;
203
207
  overflow: hidden;
204
208
  color: var(--rvg-color-text);
205
- font:
206
- 13px Inter,
209
+ font-family:
210
+ Inter,
207
211
  ui-sans-serif,
208
212
  system-ui,
209
213
  sans-serif;
214
+ font-size: 13px;
210
215
  white-space: nowrap;
211
216
  user-select: text;
212
217
  pointer-events: auto;
@@ -217,6 +222,7 @@
217
222
  cursor: default;
218
223
  }
219
224
  .rvg-cell-text {
225
+ font-size: var(--rvg-font-size-body);
220
226
  transition: transform 140ms ease, opacity 100ms ease;
221
227
  }
222
228
  .rvg-row-drop-settling .rvg-cell-text {
@@ -248,11 +254,12 @@
248
254
  align-items: center;
249
255
  overflow: hidden;
250
256
  color: var(--rvg-color-text);
251
- font:
252
- 13px Inter,
257
+ font-family:
258
+ Inter,
253
259
  ui-sans-serif,
254
260
  system-ui,
255
261
  sans-serif;
262
+ font-size: var(--rvg-font-size-body);
256
263
  white-space: nowrap;
257
264
  pointer-events: none;
258
265
  box-sizing: border-box;
@@ -294,6 +301,10 @@
294
301
  .rvg-summary-row.is-top {
295
302
  border-bottom: 1px solid var(--rvg-color-grid);
296
303
  }
304
+ .rvg-root.is-horizontal-borderless .rvg-summary-row {
305
+ border-top: 0;
306
+ border-bottom: 0;
307
+ }
297
308
  .rvg-summary-scroll-clip {
298
309
  position: absolute;
299
310
  top: 0;
@@ -746,6 +757,9 @@
746
757
  background: var(--rvg-color-header-bg);
747
758
  box-sizing: border-box;
748
759
  }
760
+ .rvg-root.is-horizontal-borderless .rvg-header-group {
761
+ border-bottom: 0;
762
+ }
749
763
  .rvg-header-group.is-leaf {
750
764
  z-index: 2;
751
765
  }
@@ -1577,7 +1591,7 @@
1577
1591
  background: var(--rvg-color-bg);
1578
1592
  color: var(--rvg-color-text);
1579
1593
  font:
1580
- 13px Inter,
1594
+ var(--rvg-font-size-body) Inter,
1581
1595
  ui-sans-serif,
1582
1596
  system-ui,
1583
1597
  sans-serif;
@@ -1625,7 +1639,7 @@
1625
1639
  background: transparent;
1626
1640
  color: var(--rvg-color-text);
1627
1641
  font:
1628
- 13px Inter,
1642
+ var(--rvg-font-size-body) Inter,
1629
1643
  ui-sans-serif,
1630
1644
  system-ui,
1631
1645
  sans-serif;
@@ -1644,21 +1658,23 @@
1644
1658
  box-sizing: border-box;
1645
1659
  }
1646
1660
  .rvg-native-editor.has-clear {
1647
- padding-right: 32px;
1661
+ padding-right: 10px;
1648
1662
  }
1649
1663
  .rvg-native-editor input {
1650
1664
  min-width: 0;
1651
1665
  width: 100%;
1652
1666
  height: 100%;
1667
+ margin: 0;
1653
1668
  padding: 0;
1654
- transform: translateY(-1px);
1655
1669
  flex: 1;
1670
+ appearance: none;
1671
+ border-radius: 0;
1656
1672
  border: 0;
1657
1673
  outline: 0;
1658
1674
  background: transparent;
1659
1675
  color: var(--rvg-color-text);
1660
1676
  font:
1661
- 13px Inter,
1677
+ var(--rvg-font-size-body) Inter,
1662
1678
  ui-sans-serif,
1663
1679
  system-ui,
1664
1680
  sans-serif;
@@ -1891,7 +1907,7 @@
1891
1907
  background: var(--rvg-color-bg);
1892
1908
  color: var(--rvg-color-text);
1893
1909
  font:
1894
- 13px Inter,
1910
+ var(--rvg-font-size-body) Inter,
1895
1911
  ui-sans-serif,
1896
1912
  system-ui,
1897
1913
  sans-serif;
@@ -2419,12 +2435,18 @@
2419
2435
  .rvg-root.is-vertical-borderless .rvg-loading-row span {
2420
2436
  border-right: 0;
2421
2437
  }
2438
+ .rvg-root.is-horizontal-borderless .rvg-loading-row span {
2439
+ border-bottom: 0;
2440
+ }
2422
2441
  .rvg-root.is-striped .rvg-loading-row:nth-child(even) span {
2423
2442
  background: var(--rvg-color-stripe) !important;
2424
2443
  }
2425
2444
  .rvg-root.is-vertical-borderless .rvg-initial-loading::after {
2426
2445
  border-left: 0;
2427
2446
  }
2447
+ .rvg-root.is-horizontal-borderless .rvg-initial-loading::after {
2448
+ border-bottom: 0;
2449
+ }
2428
2450
  .rvg-loading-row i {
2429
2451
  display: block;
2430
2452
  height: 7px;
@@ -2528,3 +2550,14 @@
2528
2550
  opacity: 1;
2529
2551
  }
2530
2552
  }
2553
+ .rvg-native-editor input[type=number] {
2554
+ -moz-appearance: textfield;
2555
+ }
2556
+ .rvg-native-editor input::-webkit-inner-spin-button,
2557
+ .rvg-native-editor input::-webkit-outer-spin-button {
2558
+ margin: 0;
2559
+ -webkit-appearance: none;
2560
+ }
2561
+ .rvg-native-editor .rvg-clear-trigger {
2562
+ background: var(--rvg-color-bg);
2563
+ }
package/dist/index.d.cts CHANGED
@@ -347,6 +347,8 @@ interface TableProps<Row extends object> {
347
347
  borderless?: boolean;
348
348
  /** @deprecated Use borderless instead. */
349
349
  verticalBorderless?: boolean;
350
+ /** Hide horizontal row and frame separators while keeping vertical grid lines. */
351
+ horizontalBorderless?: boolean;
350
352
  /** Paint alternating row backgrounds. Pass a color string to customize the stripe color. */
351
353
  striped?: boolean | string;
352
354
  /** Visual highlight feedback configuration. All entries default to false. */
package/dist/index.d.ts CHANGED
@@ -347,6 +347,8 @@ interface TableProps<Row extends object> {
347
347
  borderless?: boolean;
348
348
  /** @deprecated Use borderless instead. */
349
349
  verticalBorderless?: boolean;
350
+ /** Hide horizontal row and frame separators while keeping vertical grid lines. */
351
+ horizontalBorderless?: boolean;
350
352
  /** Paint alternating row backgrounds. Pass a color string to customize the stripe color. */
351
353
  striped?: boolean | string;
352
354
  /** Visual highlight feedback configuration. All entries default to false. */
package/dist/index.js CHANGED
@@ -675,6 +675,7 @@ function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }
675
675
  "input",
676
676
  {
677
677
  type,
678
+ style: { font: "var(--rvg-font-size-body) Inter, ui-sans-serif, system-ui, sans-serif", textAlign: style.textAlign },
678
679
  value,
679
680
  autoFocus: true,
680
681
  onChange: (event) => onChange(event.target.value),
@@ -863,6 +864,18 @@ function hitTestColumn(metrics, contentX) {
863
864
  return index < metrics.length ? index : -1;
864
865
  }
865
866
 
867
+ // src/core/typography.ts
868
+ var createCellFont = (fontSize = 13) => `${fontSize}px Inter, ui-sans-serif, system-ui, sans-serif`;
869
+ function getCellBaselineOffset(context) {
870
+ context.save();
871
+ context.textBaseline = "alphabetic";
872
+ const metrics = context.measureText("Mg");
873
+ context.restore();
874
+ const ascent = metrics.fontBoundingBoxAscent;
875
+ const descent = metrics.fontBoundingBoxDescent;
876
+ return Number.isFinite(ascent) && Number.isFinite(descent) ? (ascent - descent) / 2 : 0;
877
+ }
878
+
866
879
  // src/core/render.ts
867
880
  var getCellCoordKey = (rowIndex, columnIndex) => `${rowIndex}:${columnIndex}`;
868
881
  var COLORS = {
@@ -899,7 +912,7 @@ function ellipsizeText(context, text, maxWidth) {
899
912
  return `${text.slice(0, start)}${ellipsis}`;
900
913
  }
901
914
  function paintGrid(options) {
902
- const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, headerHeight, fixedHeader, verticalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = 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;
903
916
  const headerLeafTop = options.headerLeafTop ?? 0;
904
917
  const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
905
918
  const colors = { ...COLORS, ...options.colors };
@@ -916,7 +929,8 @@ function paintGrid(options) {
916
929
  ctx.clearRect(0, 0, width, height);
917
930
  ctx.fillStyle = colors.background;
918
931
  ctx.fillRect(0, 0, width, height);
919
- ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
932
+ ctx.font = createCellFont(bodyFontSize);
933
+ const cellBaselineOffset = getCellBaselineOffset(ctx);
920
934
  ctx.textBaseline = "middle";
921
935
  const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? value + metrics[index].width : value, 0);
922
936
  const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
@@ -1021,7 +1035,7 @@ function paintGrid(options) {
1021
1035
  ctx.fillStyle = colors.grid;
1022
1036
  if (!verticalBorderless) ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
1023
1037
  if (rowIndex === rowBelowDragGap) ctx.fillRect(x, y, cellWidth, 1);
1024
- if (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1) {
1038
+ if (!horizontalBorderless && (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1)) {
1025
1039
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
1026
1040
  }
1027
1041
  if (rowSpan > 1) {
@@ -1115,9 +1129,10 @@ function paintGrid(options) {
1115
1129
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1116
1130
  ctx.clip();
1117
1131
  ctx.fillStyle = cellStyle?.color ?? colors.text;
1118
- ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
1132
+ ctx.font = createCellFont(bodyFontSize);
1133
+ ctx.textBaseline = "alphabetic";
1119
1134
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
1120
- ctx.fillText(label, textX, y + cellHeight / 2);
1135
+ ctx.fillText(label, textX, y + cellHeight / 2 + cellBaselineOffset);
1121
1136
  ctx.restore();
1122
1137
  }
1123
1138
  }
@@ -1161,7 +1176,7 @@ function paintGrid(options) {
1161
1176
  }
1162
1177
  ctx.fillStyle = colors.grid;
1163
1178
  if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1164
- ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1179
+ if (!horizontalBorderless) ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1165
1180
  const column = columns[columnIndex];
1166
1181
  if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
1167
1182
  ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
@@ -1197,12 +1212,12 @@ function paintGrid(options) {
1197
1212
  ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1198
1213
  }
1199
1214
  ctx.fillStyle = colors.grid;
1200
- ctx.fillRect(0, 0, width, 1);
1215
+ if (!horizontalBorderless) ctx.fillRect(0, 0, width, 1);
1201
1216
  if (!verticalBorderless) {
1202
1217
  ctx.fillRect(0, 0, 1, height);
1203
1218
  ctx.fillRect(width - 1, 0, 1, height);
1204
1219
  }
1205
- if (!options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1220
+ if (!horizontalBorderless && !options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1206
1221
  if (selectionRange) {
1207
1222
  const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1208
1223
  const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
@@ -1423,6 +1438,10 @@ var waitForPaint = () => new Promise((resolve) => {
1423
1438
  function readThemeColor(styles, name, fallback) {
1424
1439
  return styles.getPropertyValue(name).trim() || fallback;
1425
1440
  }
1441
+ function readThemePixel(styles, name, fallback) {
1442
+ const value = Number.parseFloat(styles.getPropertyValue(name));
1443
+ return Number.isFinite(value) && value > 0 ? value : fallback;
1444
+ }
1426
1445
  function getDefaultRowKey(row, index) {
1427
1446
  const id = row.id;
1428
1447
  return typeof id === "string" || typeof id === "number" ? id : index;
@@ -1506,6 +1525,7 @@ function TableInner({
1506
1525
  locale = "zh-CN",
1507
1526
  borderless,
1508
1527
  verticalBorderless,
1528
+ horizontalBorderless = false,
1509
1529
  striped = false,
1510
1530
  highlight,
1511
1531
  cellSpans = [],
@@ -2166,6 +2186,7 @@ function TableInner({
2166
2186
  const context = canvas.getContext("2d");
2167
2187
  if (!context) return;
2168
2188
  const themeStyles = getComputedStyle(canvas);
2189
+ const bodyFontSize = readThemePixel(themeStyles, "--rvg-font-size-body", 13);
2169
2190
  const themeColors = {
2170
2191
  background: readThemeColor(themeStyles, "--rvg-color-bg", "#ffffff"),
2171
2192
  header: readThemeColor(themeStyles, "--rvg-color-header-bg", "#f5f6f7"),
@@ -2186,8 +2207,8 @@ function TableInner({
2186
2207
  const scroll = scrollRef.current;
2187
2208
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2188
2209
  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 };
2189
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2190
- }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
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]);
2191
2212
  const scheduleDraw = useCallback2(() => {
2192
2213
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2193
2214
  }, [draw]);
@@ -3551,11 +3572,21 @@ function TableInner({
3551
3572
  const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
3552
3573
  const visibleTop = Math.max(cellTop, verticalStart);
3553
3574
  const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
3575
+ if (column.editor && column.editor.type !== "text") {
3576
+ return {
3577
+ left: visibleLeft,
3578
+ top: visibleTop,
3579
+ width: Math.max(0, visibleRight - visibleLeft),
3580
+ height: Math.max(0, visibleBottom - visibleTop)
3581
+ };
3582
+ }
3554
3583
  return {
3555
- left: visibleLeft,
3556
- top: visibleTop,
3557
- width: Math.max(0, visibleRight - visibleLeft),
3558
- height: Math.max(0, visibleBottom - visibleTop)
3584
+ left: cellLeft,
3585
+ top: cellTop,
3586
+ width: cellWidth,
3587
+ height: cellHeight,
3588
+ textAlign: column.align ?? "left",
3589
+ clipPath: `inset(${Math.max(0, visibleTop - cellTop)}px ${Math.max(0, cellLeft + cellWidth - visibleRight)}px ${Math.max(0, cellTop + cellHeight - visibleBottom)}px ${Math.max(0, visibleLeft - cellLeft)}px)`
3559
3590
  };
3560
3591
  }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
3561
3592
  const handleColumnPointerDown = useCallback2((event) => {
@@ -4431,7 +4462,7 @@ function TableInner({
4431
4462
  };
4432
4463
  const currentScroll = scrollRef.current;
4433
4464
  const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
4434
- return /* @__PURE__ */ jsxs7("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
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: [
4435
4466
  /* @__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: [
4436
4467
  /* @__PURE__ */ jsx7(
4437
4468
  "canvas",
@@ -4491,7 +4522,10 @@ function TableInner({
4491
4522
  } else {
4492
4523
  const label = getCellLabel(cell.rowIndex, cell.columnIndex);
4493
4524
  const context = event.currentTarget.getContext("2d");
4494
- if (context) context.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
4525
+ if (context) {
4526
+ const bodyFontSize = readThemePixel(getComputedStyle(event.currentTarget), "--rvg-font-size-body", 13);
4527
+ context.font = `${bodyFontSize}px Inter, ui-sans-serif, system-ui, sans-serif`;
4528
+ }
4495
4529
  const truncated = Boolean(label && context && context.measureText(label).width + 20 > metrics[cell.columnIndex].width);
4496
4530
  if (!label || !tooltipConfig.cell && !truncated) {
4497
4531
  showCellTooltipAfterDelay(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",