@lensui/lens-table 0.1.8 → 0.1.10
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 +1 -0
- package/dist/index.cjs +52 -16
- package/dist/index.css +31 -2
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -16
- package/package.json +1 -1
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` 定位;横向合并会限制在同一个固定列分区内,工具列不会参与合并。 |
|
package/dist/index.cjs
CHANGED
|
@@ -692,6 +692,18 @@ function DateEditor({ editor, value: draft, style, locale, labels, onChange, onC
|
|
|
692
692
|
);
|
|
693
693
|
}
|
|
694
694
|
|
|
695
|
+
// src/core/typography.ts
|
|
696
|
+
var CELL_FONT = "13px Inter, ui-sans-serif, system-ui, sans-serif";
|
|
697
|
+
function getCellBaselineOffset(context) {
|
|
698
|
+
context.save();
|
|
699
|
+
context.textBaseline = "alphabetic";
|
|
700
|
+
const metrics = context.measureText("Mg");
|
|
701
|
+
context.restore();
|
|
702
|
+
const ascent = metrics.fontBoundingBoxAscent;
|
|
703
|
+
const descent = metrics.fontBoundingBoxDescent;
|
|
704
|
+
return Number.isFinite(ascent) && Number.isFinite(descent) ? (ascent - descent) / 2 : 0;
|
|
705
|
+
}
|
|
706
|
+
|
|
695
707
|
// src/components/editors/TextEditor.tsx
|
|
696
708
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
697
709
|
function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }) {
|
|
@@ -705,6 +717,7 @@ function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }
|
|
|
705
717
|
"input",
|
|
706
718
|
{
|
|
707
719
|
type,
|
|
720
|
+
style: { font: CELL_FONT, textAlign: style.textAlign },
|
|
708
721
|
value,
|
|
709
722
|
autoFocus: true,
|
|
710
723
|
onChange: (event) => onChange(event.target.value),
|
|
@@ -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, 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 =
|
|
962
|
+
ctx.font = CELL_FONT;
|
|
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 =
|
|
1162
|
+
ctx.font = CELL_FONT;
|
|
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);
|
|
@@ -1536,6 +1551,7 @@ function TableInner({
|
|
|
1536
1551
|
locale = "zh-CN",
|
|
1537
1552
|
borderless,
|
|
1538
1553
|
verticalBorderless,
|
|
1554
|
+
horizontalBorderless = false,
|
|
1539
1555
|
striped = false,
|
|
1540
1556
|
highlight,
|
|
1541
1557
|
cellSpans = [],
|
|
@@ -1698,7 +1714,8 @@ function TableInner({
|
|
|
1698
1714
|
const hoveredRowIndexRef = (0, import_react5.useRef)(null);
|
|
1699
1715
|
const [viewport, setViewport] = (0, import_react5.useState)({ width: 0, height: typeof height === "number" ? height : FALLBACK_HEIGHT });
|
|
1700
1716
|
const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = (0, import_react5.useState)(0);
|
|
1701
|
-
const
|
|
1717
|
+
const [availableHeight, setAvailableHeight] = (0, import_react5.useState)(FALLBACK_HEIGHT);
|
|
1718
|
+
const resolvedHeight = typeof height === "number" ? height : availableHeight;
|
|
1702
1719
|
const [scrollPosition, setScrollPosition] = (0, import_react5.useState)({ left: 0, top: 0 });
|
|
1703
1720
|
const scrollRef = (0, import_react5.useRef)({ left: 0, top: 0 });
|
|
1704
1721
|
const [rowKeys, setRowKeys] = useControllableKeys(selectedRowKeys, defaultSelectedRowKeys, (keys) => {
|
|
@@ -2215,8 +2232,8 @@ function TableInner({
|
|
|
2215
2232
|
const scroll = scrollRef.current;
|
|
2216
2233
|
const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
|
|
2217
2234
|
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 };
|
|
2218
|
-
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 });
|
|
2219
|
-
}, [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]);
|
|
2235
|
+
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, 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 });
|
|
2236
|
+
}, [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]);
|
|
2220
2237
|
const scheduleDraw = (0, import_react5.useCallback)(() => {
|
|
2221
2238
|
if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
|
|
2222
2239
|
}, [draw]);
|
|
@@ -2375,7 +2392,15 @@ function TableInner({
|
|
|
2375
2392
|
(0, import_react5.useLayoutEffect)(() => {
|
|
2376
2393
|
const element = scrollerRef.current;
|
|
2377
2394
|
if (!element) return;
|
|
2395
|
+
const root = element.parentElement;
|
|
2378
2396
|
const update = () => {
|
|
2397
|
+
if (root && typeof height !== "number") {
|
|
2398
|
+
const renderedHeight = root.style.height;
|
|
2399
|
+
root.style.height = height;
|
|
2400
|
+
const nextAvailableHeight = root.clientHeight || FALLBACK_HEIGHT;
|
|
2401
|
+
root.style.height = renderedHeight;
|
|
2402
|
+
setAvailableHeight(nextAvailableHeight);
|
|
2403
|
+
}
|
|
2379
2404
|
const measuredHeight = element.clientHeight || (typeof height === "number" ? height : FALLBACK_HEIGHT);
|
|
2380
2405
|
setViewport({ width: element.clientWidth, height: measuredHeight });
|
|
2381
2406
|
setHorizontalScrollbarHeight(Math.max(0, element.offsetHeight - element.clientHeight));
|
|
@@ -2387,6 +2412,7 @@ function TableInner({
|
|
|
2387
2412
|
}
|
|
2388
2413
|
const observer = new ResizeObserver(update);
|
|
2389
2414
|
observer.observe(element);
|
|
2415
|
+
if (root?.parentElement) observer.observe(root.parentElement);
|
|
2390
2416
|
return () => observer.disconnect();
|
|
2391
2417
|
}, [height]);
|
|
2392
2418
|
(0, import_react5.useEffect)(() => {
|
|
@@ -3571,11 +3597,21 @@ function TableInner({
|
|
|
3571
3597
|
const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
|
|
3572
3598
|
const visibleTop = Math.max(cellTop, verticalStart);
|
|
3573
3599
|
const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
|
|
3600
|
+
if (column.editor && column.editor.type !== "text") {
|
|
3601
|
+
return {
|
|
3602
|
+
left: visibleLeft,
|
|
3603
|
+
top: visibleTop,
|
|
3604
|
+
width: Math.max(0, visibleRight - visibleLeft),
|
|
3605
|
+
height: Math.max(0, visibleBottom - visibleTop)
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3574
3608
|
return {
|
|
3575
|
-
left:
|
|
3576
|
-
top:
|
|
3577
|
-
width:
|
|
3578
|
-
height:
|
|
3609
|
+
left: cellLeft,
|
|
3610
|
+
top: cellTop,
|
|
3611
|
+
width: cellWidth,
|
|
3612
|
+
height: cellHeight,
|
|
3613
|
+
textAlign: column.align ?? "left",
|
|
3614
|
+
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)`
|
|
3579
3615
|
};
|
|
3580
3616
|
}, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
|
|
3581
3617
|
const handleColumnPointerDown = (0, import_react5.useCallback)((event) => {
|
|
@@ -4451,7 +4487,7 @@ function TableInner({
|
|
|
4451
4487
|
};
|
|
4452
4488
|
const currentScroll = scrollRef.current;
|
|
4453
4489
|
const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
|
|
4454
|
-
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: [
|
|
4490
|
+
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: [
|
|
4455
4491
|
/* @__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: [
|
|
4456
4492
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
4457
4493
|
"canvas",
|
package/dist/index.css
CHANGED
|
@@ -105,6 +105,9 @@
|
|
|
105
105
|
content: "";
|
|
106
106
|
pointer-events: none;
|
|
107
107
|
}
|
|
108
|
+
.rvg-root.is-horizontal-borderless .rvg-text-layer::before {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
108
111
|
.rvg-vertical-scrollbar,
|
|
109
112
|
.rvg-horizontal-scrollbar {
|
|
110
113
|
position: absolute;
|
|
@@ -294,6 +297,10 @@
|
|
|
294
297
|
.rvg-summary-row.is-top {
|
|
295
298
|
border-bottom: 1px solid var(--rvg-color-grid);
|
|
296
299
|
}
|
|
300
|
+
.rvg-root.is-horizontal-borderless .rvg-summary-row {
|
|
301
|
+
border-top: 0;
|
|
302
|
+
border-bottom: 0;
|
|
303
|
+
}
|
|
297
304
|
.rvg-summary-scroll-clip {
|
|
298
305
|
position: absolute;
|
|
299
306
|
top: 0;
|
|
@@ -746,6 +753,9 @@
|
|
|
746
753
|
background: var(--rvg-color-header-bg);
|
|
747
754
|
box-sizing: border-box;
|
|
748
755
|
}
|
|
756
|
+
.rvg-root.is-horizontal-borderless .rvg-header-group {
|
|
757
|
+
border-bottom: 0;
|
|
758
|
+
}
|
|
749
759
|
.rvg-header-group.is-leaf {
|
|
750
760
|
z-index: 2;
|
|
751
761
|
}
|
|
@@ -1644,15 +1654,17 @@
|
|
|
1644
1654
|
box-sizing: border-box;
|
|
1645
1655
|
}
|
|
1646
1656
|
.rvg-native-editor.has-clear {
|
|
1647
|
-
padding-right:
|
|
1657
|
+
padding-right: 10px;
|
|
1648
1658
|
}
|
|
1649
1659
|
.rvg-native-editor input {
|
|
1650
1660
|
min-width: 0;
|
|
1651
1661
|
width: 100%;
|
|
1652
1662
|
height: 100%;
|
|
1663
|
+
margin: 0;
|
|
1653
1664
|
padding: 0;
|
|
1654
|
-
transform: translateY(-1px);
|
|
1655
1665
|
flex: 1;
|
|
1666
|
+
appearance: none;
|
|
1667
|
+
border-radius: 0;
|
|
1656
1668
|
border: 0;
|
|
1657
1669
|
outline: 0;
|
|
1658
1670
|
background: transparent;
|
|
@@ -2419,12 +2431,18 @@
|
|
|
2419
2431
|
.rvg-root.is-vertical-borderless .rvg-loading-row span {
|
|
2420
2432
|
border-right: 0;
|
|
2421
2433
|
}
|
|
2434
|
+
.rvg-root.is-horizontal-borderless .rvg-loading-row span {
|
|
2435
|
+
border-bottom: 0;
|
|
2436
|
+
}
|
|
2422
2437
|
.rvg-root.is-striped .rvg-loading-row:nth-child(even) span {
|
|
2423
2438
|
background: var(--rvg-color-stripe) !important;
|
|
2424
2439
|
}
|
|
2425
2440
|
.rvg-root.is-vertical-borderless .rvg-initial-loading::after {
|
|
2426
2441
|
border-left: 0;
|
|
2427
2442
|
}
|
|
2443
|
+
.rvg-root.is-horizontal-borderless .rvg-initial-loading::after {
|
|
2444
|
+
border-bottom: 0;
|
|
2445
|
+
}
|
|
2428
2446
|
.rvg-loading-row i {
|
|
2429
2447
|
display: block;
|
|
2430
2448
|
height: 7px;
|
|
@@ -2528,3 +2546,14 @@
|
|
|
2528
2546
|
opacity: 1;
|
|
2529
2547
|
}
|
|
2530
2548
|
}
|
|
2549
|
+
.rvg-native-editor input[type=number] {
|
|
2550
|
+
-moz-appearance: textfield;
|
|
2551
|
+
}
|
|
2552
|
+
.rvg-native-editor input::-webkit-inner-spin-button,
|
|
2553
|
+
.rvg-native-editor input::-webkit-outer-spin-button {
|
|
2554
|
+
margin: 0;
|
|
2555
|
+
-webkit-appearance: none;
|
|
2556
|
+
}
|
|
2557
|
+
.rvg-native-editor .rvg-clear-trigger {
|
|
2558
|
+
background: var(--rvg-color-bg);
|
|
2559
|
+
}
|
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
|
@@ -662,6 +662,18 @@ function DateEditor({ editor, value: draft, style, locale, labels, onChange, onC
|
|
|
662
662
|
);
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
+
// src/core/typography.ts
|
|
666
|
+
var CELL_FONT = "13px Inter, ui-sans-serif, system-ui, sans-serif";
|
|
667
|
+
function getCellBaselineOffset(context) {
|
|
668
|
+
context.save();
|
|
669
|
+
context.textBaseline = "alphabetic";
|
|
670
|
+
const metrics = context.measureText("Mg");
|
|
671
|
+
context.restore();
|
|
672
|
+
const ascent = metrics.fontBoundingBoxAscent;
|
|
673
|
+
const descent = metrics.fontBoundingBoxDescent;
|
|
674
|
+
return Number.isFinite(ascent) && Number.isFinite(descent) ? (ascent - descent) / 2 : 0;
|
|
675
|
+
}
|
|
676
|
+
|
|
665
677
|
// src/components/editors/TextEditor.tsx
|
|
666
678
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
667
679
|
function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }) {
|
|
@@ -675,6 +687,7 @@ function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }
|
|
|
675
687
|
"input",
|
|
676
688
|
{
|
|
677
689
|
type,
|
|
690
|
+
style: { font: CELL_FONT, textAlign: style.textAlign },
|
|
678
691
|
value,
|
|
679
692
|
autoFocus: true,
|
|
680
693
|
onChange: (event) => onChange(event.target.value),
|
|
@@ -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, 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 =
|
|
932
|
+
ctx.font = CELL_FONT;
|
|
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 =
|
|
1132
|
+
ctx.font = CELL_FONT;
|
|
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);
|
|
@@ -1506,6 +1521,7 @@ function TableInner({
|
|
|
1506
1521
|
locale = "zh-CN",
|
|
1507
1522
|
borderless,
|
|
1508
1523
|
verticalBorderless,
|
|
1524
|
+
horizontalBorderless = false,
|
|
1509
1525
|
striped = false,
|
|
1510
1526
|
highlight,
|
|
1511
1527
|
cellSpans = [],
|
|
@@ -1668,7 +1684,8 @@ function TableInner({
|
|
|
1668
1684
|
const hoveredRowIndexRef = useRef2(null);
|
|
1669
1685
|
const [viewport, setViewport] = useState4({ width: 0, height: typeof height === "number" ? height : FALLBACK_HEIGHT });
|
|
1670
1686
|
const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = useState4(0);
|
|
1671
|
-
const
|
|
1687
|
+
const [availableHeight, setAvailableHeight] = useState4(FALLBACK_HEIGHT);
|
|
1688
|
+
const resolvedHeight = typeof height === "number" ? height : availableHeight;
|
|
1672
1689
|
const [scrollPosition, setScrollPosition] = useState4({ left: 0, top: 0 });
|
|
1673
1690
|
const scrollRef = useRef2({ left: 0, top: 0 });
|
|
1674
1691
|
const [rowKeys, setRowKeys] = useControllableKeys(selectedRowKeys, defaultSelectedRowKeys, (keys) => {
|
|
@@ -2185,8 +2202,8 @@ function TableInner({
|
|
|
2185
2202
|
const scroll = scrollRef.current;
|
|
2186
2203
|
const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
|
|
2187
2204
|
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 };
|
|
2188
|
-
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 });
|
|
2189
|
-
}, [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]);
|
|
2205
|
+
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, 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 });
|
|
2206
|
+
}, [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]);
|
|
2190
2207
|
const scheduleDraw = useCallback2(() => {
|
|
2191
2208
|
if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
|
|
2192
2209
|
}, [draw]);
|
|
@@ -2345,7 +2362,15 @@ function TableInner({
|
|
|
2345
2362
|
useLayoutEffect2(() => {
|
|
2346
2363
|
const element = scrollerRef.current;
|
|
2347
2364
|
if (!element) return;
|
|
2365
|
+
const root = element.parentElement;
|
|
2348
2366
|
const update = () => {
|
|
2367
|
+
if (root && typeof height !== "number") {
|
|
2368
|
+
const renderedHeight = root.style.height;
|
|
2369
|
+
root.style.height = height;
|
|
2370
|
+
const nextAvailableHeight = root.clientHeight || FALLBACK_HEIGHT;
|
|
2371
|
+
root.style.height = renderedHeight;
|
|
2372
|
+
setAvailableHeight(nextAvailableHeight);
|
|
2373
|
+
}
|
|
2349
2374
|
const measuredHeight = element.clientHeight || (typeof height === "number" ? height : FALLBACK_HEIGHT);
|
|
2350
2375
|
setViewport({ width: element.clientWidth, height: measuredHeight });
|
|
2351
2376
|
setHorizontalScrollbarHeight(Math.max(0, element.offsetHeight - element.clientHeight));
|
|
@@ -2357,6 +2382,7 @@ function TableInner({
|
|
|
2357
2382
|
}
|
|
2358
2383
|
const observer = new ResizeObserver(update);
|
|
2359
2384
|
observer.observe(element);
|
|
2385
|
+
if (root?.parentElement) observer.observe(root.parentElement);
|
|
2360
2386
|
return () => observer.disconnect();
|
|
2361
2387
|
}, [height]);
|
|
2362
2388
|
useEffect2(() => {
|
|
@@ -3541,11 +3567,21 @@ function TableInner({
|
|
|
3541
3567
|
const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
|
|
3542
3568
|
const visibleTop = Math.max(cellTop, verticalStart);
|
|
3543
3569
|
const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
|
|
3570
|
+
if (column.editor && column.editor.type !== "text") {
|
|
3571
|
+
return {
|
|
3572
|
+
left: visibleLeft,
|
|
3573
|
+
top: visibleTop,
|
|
3574
|
+
width: Math.max(0, visibleRight - visibleLeft),
|
|
3575
|
+
height: Math.max(0, visibleBottom - visibleTop)
|
|
3576
|
+
};
|
|
3577
|
+
}
|
|
3544
3578
|
return {
|
|
3545
|
-
left:
|
|
3546
|
-
top:
|
|
3547
|
-
width:
|
|
3548
|
-
height:
|
|
3579
|
+
left: cellLeft,
|
|
3580
|
+
top: cellTop,
|
|
3581
|
+
width: cellWidth,
|
|
3582
|
+
height: cellHeight,
|
|
3583
|
+
textAlign: column.align ?? "left",
|
|
3584
|
+
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)`
|
|
3549
3585
|
};
|
|
3550
3586
|
}, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
|
|
3551
3587
|
const handleColumnPointerDown = useCallback2((event) => {
|
|
@@ -4421,7 +4457,7 @@ function TableInner({
|
|
|
4421
4457
|
};
|
|
4422
4458
|
const currentScroll = scrollRef.current;
|
|
4423
4459
|
const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
|
|
4424
|
-
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: [
|
|
4460
|
+
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: [
|
|
4425
4461
|
/* @__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: [
|
|
4426
4462
|
/* @__PURE__ */ jsx7(
|
|
4427
4463
|
"canvas",
|