@lensui/lens-table 0.1.6 → 0.1.8
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 +93 -4
- package/dist/index.cjs +134 -18
- package/dist/index.d.cts +43 -7
- package/dist/index.d.ts +43 -7
- package/dist/index.js +135 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,12 +104,12 @@ export function Example() {
|
|
|
104
104
|
| `virtualized` | `boolean \| { enabled?: boolean; overscan?: number }` | `true` | 虚拟渲染配置。传`false` 时渲染全部行列;传对象时可通过 `enabled` 开关,并用 `overscan` 配置可见范围外的缓冲行列数量,默认 `4`。 |
|
|
105
105
|
| `tooltip` | `boolean \| { header?: boolean; cell?: boolean }` | `true` | 文本 tooltip 总配置。传`true` 时表头和单元格 hover 都显示完整文本;传 `false` 可关闭;传对象可分别控制表头和单元格。 |
|
|
106
106
|
| `summary` | `boolean \| { position?: 'top' \| 'bottom'; verticalBordered?: boolean; emptyValue?: ReactNode }` | `false` | 表格级合计行配置。默认不显示;传 `true` 或对象即开启合计行,对象可控制显示在表头下方或底部、是否显示内部竖线,以及无合计值单元格的填充内容。 |
|
|
107
|
-
| `rowNumber` | `boolean` | `true` | 是否显示序号列。默认开启并自动生成左侧序号列,不需要在`columns` 中配置;传 `false`
|
|
107
|
+
| `rowNumber` | `boolean \| TableRowNumberConfig` | `true` | 是否显示序号列。默认开启并自动生成左侧序号列,不需要在`columns` 中配置;传 `false` 可关闭。传 `{ fixed: false }` 可取消序号列固定,`fixed` 默认 `true`。 |
|
|
108
108
|
| `selectedCell` | `GridSelectionTarget \| null` | 非受控 | 受控单元格选中状态。传 `columnKey`,行可使用 `rowKey` 或 `rowIndex` 定位。 |
|
|
109
109
|
| `defaultSelectedCell` | `GridSelectionTarget \| null` | `null` | 默认选中单元格。传 `columnKey`,行可使用 `rowKey` 或 `rowIndex` 定位。 |
|
|
110
110
|
| `onSelectedCellChange` | `(selection) => void` | - | 单元格选中变化回调,`selection.value` 为当前单元格的原始值。 |
|
|
111
111
|
| `rangeSelection` | `boolean` | `false` | 是否允许鼠标拖动选择多个单元格。开启后支持范围复制、范围右键菜单和右下角拖拽扩展范围。 |
|
|
112
|
-
| `rowSelection` | `boolean \|
|
|
112
|
+
| `rowSelection` | `boolean \| RowSelectionConfig` | `false` | 开启行选择。可传`{ mode: 'single' \| 'multiple', showCheckbox: false }` 隐藏选择框并通过点击数据单元格选择行;默认显示选择框。默认自动生成左侧选择列,不需要在 `columns` 中配置。 |
|
|
113
113
|
| `selectedRowKeys` | `GridKey[]` | 非受控 | 受控行选择 key 列表。 |
|
|
114
114
|
| `defaultSelectedRowKeys` | `GridKey[]` | `[]` | 非受控模式下默认选中的行 key。 |
|
|
115
115
|
| `onSelectedRowChange` | `(keys, rows, indices) => void` | - | 行选择变化回调。 |
|
|
@@ -128,6 +128,8 @@ export function Example() {
|
|
|
128
128
|
| `onSortChange` | `(rows, state) => Row[]` | - | 自定义排序方法。排序状态由组件内部管理;返回排序后的行。 |
|
|
129
129
|
| `onFilterChange` | `(rows, values) => Row[]` | - | 自定义筛选方法。筛选值由组件内部管理;返回符合条件的行。 |
|
|
130
130
|
| `onCellChange` | `(change) => void` | - | 单元格编辑提交回调。 |
|
|
131
|
+
| `onCellClick` | `(cell: TableCellContext<Row>, event: React.MouseEvent<HTMLElement>) => void` | - | 数据单元格单击回调;调用 `event.preventDefault()` 可阻止默认选择行为。 |
|
|
132
|
+
| `onCellDoubleClick` | `(cell: TableCellContext<Row>, event: React.MouseEvent<HTMLElement>) => void` | - | 数据单元格双击回调,在编辑前触发;调用 `event.preventDefault()` 可阻止进入编辑。 |
|
|
131
133
|
| `onCellContextMenu` | `(event) => void` | - | 单元格右键回调,可用于扩展自定义菜单逻辑。 |
|
|
132
134
|
| `contextMenu` | `boolean \| { header?, cell?, range? }` | `true` | `true` 开启默认右键菜单,`false` 关闭右键菜单,传对象可自定义菜单项、顺序和分割线。 |
|
|
133
135
|
| `emptyContent` | `ReactNode` | 内置空状态 | 自定义空数据内容。 |
|
|
@@ -206,7 +208,7 @@ export function Example() {
|
|
|
206
208
|
| `width` | `number` | `140` | 列宽,单位 px。最小宽度由内部布局保护。 |
|
|
207
209
|
| `fixed` | `'left' \| 'right'` | - | 固定列位置。左/右固定列会覆盖滚动列并显示阴影。 |
|
|
208
210
|
| `align` | `'left' \| 'center' \| 'right'` | `'left'` | 单元格文本和拖拽预览文本对齐方式。 |
|
|
209
|
-
| `editable` | `boolean` | `false` |
|
|
211
|
+
| `editable` | `boolean \| ((value: unknown, row: Row, rowIndex: number) => boolean)` | `false` | 是否允许双击、Enter 或右键菜单进入编辑。函数按当前单元格动态判断,提交时再次检查权限。 |
|
|
210
212
|
| `editor` | `EditorConfig` | `{ type: 'text' }` | 内置编辑器配置。详见下方编辑器表。未配置时默认使用文本编辑器。 |
|
|
211
213
|
| `sortable` | `boolean` | `true` | 表头显示排序按钮。普通数据列默认开启,传 `false` 可关闭。 |
|
|
212
214
|
| `filterable` | `boolean` | `true` | 表头显示筛选按钮。普通数据列默认开启,传 `false` 可关闭。 |
|
|
@@ -494,7 +496,7 @@ const columns: GridColumn<Row>[] = [
|
|
|
494
496
|
|
|
495
497
|
拖拽后组件会在内部维护新的行列顺序,因此排序回调不是必需的。需要持久化或同步结果时,可以通过回调直接取得排序后的 `rows` 或 `columns`。列宽调整默认开启并支持非受控使用,传 `columnResizable={false}` 可关闭。
|
|
496
498
|
|
|
497
|
-
`columnDraggable` 默认开启,普通列会自动显示表头拖拽入口。行选择和行拖拽是表格级配置:开启 `rowSelection`
|
|
499
|
+
`columnDraggable` 默认开启,普通列会自动显示表头拖拽入口。行选择和行拖拽是表格级配置:开启 `rowSelection` 后默认自动生成左侧选择列(`showCheckbox: false` 时隐藏),开启 `rowDraggable` 后自动生成左侧拖拽手柄列,不需要在 `columns` 里配置 `rowSelection` 或 `rowDragHandle`。
|
|
498
500
|
|
|
499
501
|
```tsx
|
|
500
502
|
<Table
|
|
@@ -514,6 +516,20 @@ const columns: GridColumn<Row>[] = [
|
|
|
514
516
|
/>;
|
|
515
517
|
```
|
|
516
518
|
|
|
519
|
+
隐藏选择框时不生成选择列,表头全选框也会隐藏;行选择状态、回调和右键菜单仍然可用。多选模式支持 Ctrl / Command 点击追加或取消选择,以及 Shift 点击连续选择。
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
<Table
|
|
523
|
+
columns={columns}
|
|
524
|
+
rows={rows}
|
|
525
|
+
rowNumber={{ fixed: false }}
|
|
526
|
+
rowSelection={{ mode: 'multiple', showCheckbox: false }}
|
|
527
|
+
onSelectedRowChange={(keys, selectedRows) => console.log(keys, selectedRows)}
|
|
528
|
+
/>
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`rowNumber={true}` 或 `rowNumber={{ fixed: true }}` 固定序号列;`rowNumber={false}` 隐藏序号列。
|
|
532
|
+
|
|
517
533
|
## 虚拟渲染
|
|
518
534
|
|
|
519
535
|
默认开启虚拟渲染,只绘制可见区域和少量缓冲区域。缓冲数量默认是 `4`,可以通过对象形式调整。
|
|
@@ -693,3 +709,76 @@ pnpm check
|
|
|
693
709
|
## License
|
|
694
710
|
|
|
695
711
|
MIT
|
|
712
|
+
|
|
713
|
+
## 单元格事件与动态编辑权限
|
|
714
|
+
|
|
715
|
+
```tsx
|
|
716
|
+
const columns: TableColumn<MyRow>[] = [
|
|
717
|
+
{
|
|
718
|
+
key: 'name',
|
|
719
|
+
title: '姓名',
|
|
720
|
+
dataIndex: 'name',
|
|
721
|
+
editable: (_value, row, _rowIndex) => row.status === 'draft',
|
|
722
|
+
},
|
|
723
|
+
];
|
|
724
|
+
|
|
725
|
+
<Table
|
|
726
|
+
columns={columns}
|
|
727
|
+
rows={rows}
|
|
728
|
+
onCellClick={(cell, event) => {
|
|
729
|
+
console.log(cell.row, cell.column, cell.value, cell.rowIndex, cell.columnIndex);
|
|
730
|
+
}}
|
|
731
|
+
onCellDoubleClick={(cell, event) => {
|
|
732
|
+
console.log(cell.rowKey, cell.columnKey);
|
|
733
|
+
// event.preventDefault(); // 阻止本次双击进入编辑
|
|
734
|
+
}}
|
|
735
|
+
/>
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
事件只针对数据单元格,表头、序号列、选择列和拖拽手柄不触发;只读单元格也会触发。双击时先触发一次单击回调,再触发双击回调,不延迟单击。合并单元格返回其起始单元格,索引基于当前显示顺序,`columnIndex` 包含工具列。
|
|
739
|
+
|
|
740
|
+
`editable` 保持兼容布尔值;函数接收当前值、行数据和当前行索引,返回 `true` 才允许编辑。编辑期间权限变为 `false` 时,提交会关闭编辑器并丢弃草稿,不触发 `onCellChange`。
|
|
741
|
+
|
|
742
|
+
## Ref 方法
|
|
743
|
+
|
|
744
|
+
支持 React 18+ 的对象 ref 和回调 ref,保留行数据的泛型推导;`VelocityGrid` 别名同样支持。可导入 `TableRef<Row>`(或 `VelocityGridRef<Row>`)。
|
|
745
|
+
|
|
746
|
+
```tsx
|
|
747
|
+
import { useRef } from 'react';
|
|
748
|
+
import { Table, type TableRef } from '@lensui/lens-table';
|
|
749
|
+
|
|
750
|
+
function Example() {
|
|
751
|
+
const tableRef = useRef<TableRef<MyRow>>(null);
|
|
752
|
+
return (
|
|
753
|
+
<>
|
|
754
|
+
<button onClick={() => tableRef.current?.scrollToCell({ rowKey: 1, columnKey: 'name' })}>
|
|
755
|
+
定位
|
|
756
|
+
</button>
|
|
757
|
+
<button onClick={() => tableRef.current?.startEdit({ rowKey: 1, columnKey: 'name' })}>
|
|
758
|
+
编辑
|
|
759
|
+
</button>
|
|
760
|
+
<Table ref={tableRef} columns={columns} rows={rows} rowSelection />
|
|
761
|
+
</>
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
| 方法 | 说明 |
|
|
767
|
+
| --- | --- |
|
|
768
|
+
| `focus()` | 聚焦表格,启用键盘操作。 |
|
|
769
|
+
| `scrollToCell(target)` | 将目标数据单元格滚入可见区域,不改变选择;目标不存在时返回 `false`。 |
|
|
770
|
+
| `getSelectedCell()` | 返回当前单元格的 `TableCellContext<Row>`,未选择时返回 `null`。 |
|
|
771
|
+
| `setSelectedCell(target \| null)` | 设置或清除单元格选择,同时清除范围选择;无效目标返回 `false` 并保留原选择。 |
|
|
772
|
+
| `getSelectedRowKeys()` | 返回所选行 key 的数组副本。 |
|
|
773
|
+
| `getSelectedRows()` | 返回当前筛选和排序结果中选中的行数据,按显示顺序排列。 |
|
|
774
|
+
| `setSelectedRowKeys(keys)` | 设置行选择并触发现有回调;需要开启 `rowSelection`,单选模式只取第一个 key。 |
|
|
775
|
+
| `getSelectedColumnKeys()` | 返回所选列 key 的数组副本。 |
|
|
776
|
+
| `setSelectedColumnKeys(keys)` | 设置数据列选择,忽略无效列;需要开启 `columnSelection`,单选模式只取第一个有效 key。 |
|
|
777
|
+
| `clearSelection()` | 清空单元格、范围、行和列选择,并取消编辑。 |
|
|
778
|
+
| `startEdit(target?)` | 选择并编辑目标单元格;省略参数时编辑当前选择。目标无效或 `editable` 不允许时返回 `false`。 |
|
|
779
|
+
| `commitEdit()` | 提交当前草稿并关闭编辑器,遵循 `editable` 动态权限和 `onCellChange`。 |
|
|
780
|
+
| `cancelEdit()` | 丢弃当前草稿并关闭编辑器。 |
|
|
781
|
+
|
|
782
|
+
`target` 使用 `{ rowKey, columnKey }` 或 `{ rowIndex, columnKey }`;`rowKey` 优先,`rowIndex` 为当前显示顺序中的零基索引。工具列不能作为目标;合并单元格自动定位到起始单元格。
|
|
783
|
+
|
|
784
|
+
选择方法遵守受控/非受控约定:传入 `selectedCell`、`selectedRowKeys` 或 `selectedColumnKeys` 后,需要在相应回调中更新这些 props。尤其是受控单元格选择下的 `startEdit`,外部需要接受新的选择才能保持编辑器打开。读取方法反映最近一次 React 渲染完成后的状态,不保证在调用设置方法的同一同步代码段内立即更新;返回的行和列对象是原数据引用。
|
package/dist/index.cjs
CHANGED
|
@@ -1522,7 +1522,7 @@ function resolveContextMenuSection(section, defaults) {
|
|
|
1522
1522
|
}
|
|
1523
1523
|
return defaults;
|
|
1524
1524
|
}
|
|
1525
|
-
function
|
|
1525
|
+
function TableInner({
|
|
1526
1526
|
columns: columnProps,
|
|
1527
1527
|
rows: sourceRows,
|
|
1528
1528
|
rowKey,
|
|
@@ -1568,13 +1568,15 @@ function Table({
|
|
|
1568
1568
|
onSortChange,
|
|
1569
1569
|
onFilterChange,
|
|
1570
1570
|
onCellChange,
|
|
1571
|
+
onCellClick,
|
|
1572
|
+
onCellDoubleClick,
|
|
1571
1573
|
onCellContextMenu,
|
|
1572
1574
|
contextMenu: contextMenuConfig = true,
|
|
1573
1575
|
emptyContent,
|
|
1574
1576
|
className = "",
|
|
1575
1577
|
style,
|
|
1576
1578
|
ariaLabel = "Data grid"
|
|
1577
|
-
}) {
|
|
1579
|
+
}, ref) {
|
|
1578
1580
|
const [sourceColumns, setSourceColumns] = (0, import_react5.useState)(columnProps);
|
|
1579
1581
|
(0, import_react5.useEffect)(() => {
|
|
1580
1582
|
setSourceColumns((current) => reconcileColumns(current, columnProps));
|
|
@@ -1620,6 +1622,8 @@ function Table({
|
|
|
1620
1622
|
const customContextMenuConfig = typeof contextMenuConfig === "object" ? contextMenuConfig : void 0;
|
|
1621
1623
|
const [resizedColumnWidths, setResizedColumnWidths] = (0, import_react5.useState)({});
|
|
1622
1624
|
const leafSourceColumns = (0, import_react5.useMemo)(() => flattenDataColumns(sourceColumns), [sourceColumns]);
|
|
1625
|
+
const sourceColumnWidthsRef = (0, import_react5.useRef)(new Map(leafSourceColumns.map((column) => [column.key, column.width])));
|
|
1626
|
+
const showRowCheckbox = Boolean(rowSelection) && (typeof rowSelection !== "object" || rowSelection.showCheckbox !== false);
|
|
1623
1627
|
(0, import_react5.useEffect)(() => {
|
|
1624
1628
|
if (!isProductionRuntime() && hasNestedFixedColumns(sourceColumns)) {
|
|
1625
1629
|
console.warn("[Table] Multi-level headers only support fixed columns on top-level columns. Nested fixed values are ignored.");
|
|
@@ -1630,19 +1634,19 @@ function Table({
|
|
|
1630
1634
|
if (rowDraggable) {
|
|
1631
1635
|
utilityColumns.push({ key: "__rvg_row_drag__", title: "", width: 36, align: "center", fixed: "left", rowDragHandle: true });
|
|
1632
1636
|
}
|
|
1633
|
-
if (
|
|
1637
|
+
if (showRowCheckbox) {
|
|
1634
1638
|
utilityColumns.push({ key: "__rvg_row_selection__", title: "", width: 44, align: "center", fixed: "left", rowSelection: true });
|
|
1635
1639
|
}
|
|
1636
1640
|
if (rowNumber) {
|
|
1637
|
-
utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: "left", rowNumber: true });
|
|
1641
|
+
utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: typeof rowNumber === "object" && rowNumber.fixed === false ? void 0 : "left", rowNumber: true });
|
|
1638
1642
|
}
|
|
1639
1643
|
const dataColumns = leafSourceColumns.map((column) => {
|
|
1640
1644
|
const resizedWidth = resizedColumnWidths[column.key];
|
|
1641
1645
|
return resizedWidth === void 0 ? column : { ...column, width: resizedWidth };
|
|
1642
1646
|
});
|
|
1643
1647
|
return [...utilityColumns, ...dataColumns];
|
|
1644
|
-
}, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber,
|
|
1645
|
-
const utilityColumnCount = (rowDraggable ? 1 : 0) + (
|
|
1648
|
+
}, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber, showRowCheckbox, sourceRows.length]);
|
|
1649
|
+
const utilityColumnCount = (rowDraggable ? 1 : 0) + (showRowCheckbox ? 1 : 0) + (rowNumber ? 1 : 0);
|
|
1646
1650
|
const headerCells = (0, import_react5.useMemo)(() => buildHeaderCells(sourceColumns, utilityColumnCount, headerDepth), [headerDepth, sourceColumns, utilityColumnCount]);
|
|
1647
1651
|
const activeCustomHeaderLevels = (0, import_react5.useMemo)(() => {
|
|
1648
1652
|
const levels = /* @__PURE__ */ new Map();
|
|
@@ -1731,6 +1735,9 @@ function Table({
|
|
|
1731
1735
|
setSelectionRange(null);
|
|
1732
1736
|
}, [rangeSelection]);
|
|
1733
1737
|
(0, import_react5.useEffect)(() => {
|
|
1738
|
+
const previousSourceWidths = sourceColumnWidthsRef.current;
|
|
1739
|
+
const nextSourceWidths = new Map(leafSourceColumns.map((column) => [column.key, column.width]));
|
|
1740
|
+
sourceColumnWidthsRef.current = nextSourceWidths;
|
|
1734
1741
|
setResizedColumnWidths((current) => {
|
|
1735
1742
|
const sourceColumnByKey = new Map(leafSourceColumns.map((column) => [column.key, column]));
|
|
1736
1743
|
let changed = false;
|
|
@@ -1741,7 +1748,8 @@ function Table({
|
|
|
1741
1748
|
changed = true;
|
|
1742
1749
|
return;
|
|
1743
1750
|
}
|
|
1744
|
-
|
|
1751
|
+
const sourceWidthChanged = previousSourceWidths.get(key) !== sourceColumn.width;
|
|
1752
|
+
if (sourceWidthChanged && sourceColumn.width !== void 0 && sourceColumn.width !== value) {
|
|
1745
1753
|
changed = true;
|
|
1746
1754
|
return;
|
|
1747
1755
|
}
|
|
@@ -1978,7 +1986,11 @@ function Table({
|
|
|
1978
1986
|
if (selection?.rowKey === editing.rowKey && selection.columnKey === editing.columnKey) return;
|
|
1979
1987
|
setEditing(null);
|
|
1980
1988
|
}, [editing, selection]);
|
|
1981
|
-
const
|
|
1989
|
+
const hasManualColumnWidths = Object.keys(resizedColumnWidths).length > 0;
|
|
1990
|
+
const metrics = (0, import_react5.useMemo)(() => buildColumnMetrics(columns, {
|
|
1991
|
+
columnDraggable,
|
|
1992
|
+
viewportWidth: hasManualColumnWidths ? 0 : viewport.width
|
|
1993
|
+
}), [columnDraggable, columns, hasManualColumnWidths, viewport.width]);
|
|
1982
1994
|
const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
|
|
1983
1995
|
const contentHeight = rows.length * rowHeight;
|
|
1984
1996
|
const fixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "left" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
|
|
@@ -2482,7 +2494,8 @@ function Table({
|
|
|
2482
2494
|
const horizontalThumb = horizontalScrollbarThumbRef.current;
|
|
2483
2495
|
if (!scroller || !verticalTrack || !verticalThumb || !horizontalTrack || !horizontalThumb) return;
|
|
2484
2496
|
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
|
|
2485
|
-
|
|
2497
|
+
const contentFitsAutoHeight = autoHeight && effectiveHeight >= realContentHeight - 1;
|
|
2498
|
+
if (contentFitsAutoHeight || maxScrollTop <= 1 || renderHeight <= 0) {
|
|
2486
2499
|
verticalTrack.style.display = "none";
|
|
2487
2500
|
} else {
|
|
2488
2501
|
const top = fixedHeader ? bodyTop : 0;
|
|
@@ -2522,7 +2535,7 @@ function Table({
|
|
|
2522
2535
|
horizontalTrack.style.bottom = `${bottomSummaryHeight + 2}px`;
|
|
2523
2536
|
horizontalThumb.style.width = `${Math.min(trackWidth, thumbWidth)}px`;
|
|
2524
2537
|
horizontalThumb.style.transform = `translateX(${thumbLeft}px)`;
|
|
2525
|
-
}, [bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, renderHeight, rightFixedWidth, viewport.width]);
|
|
2538
|
+
}, [autoHeight, bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, realContentHeight, renderHeight, rightFixedWidth, viewport.width]);
|
|
2526
2539
|
(0, import_react5.useLayoutEffect)(() => {
|
|
2527
2540
|
updateCustomScrollbars();
|
|
2528
2541
|
}, [contentHeight, contentWidth, updateCustomScrollbars, viewport.height, viewport.width]);
|
|
@@ -3348,9 +3361,15 @@ function Table({
|
|
|
3348
3361
|
scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
|
|
3349
3362
|
scheduleDraw();
|
|
3350
3363
|
}, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
|
|
3364
|
+
const isCellEditable = (0, import_react5.useCallback)((cell) => {
|
|
3365
|
+
const column = columns[cell.columnIndex];
|
|
3366
|
+
const row = rows[cell.rowIndex];
|
|
3367
|
+
if (!column || !row || column.dataIndex === void 0 || column.rowNumber || column.rowSelection || column.rowDragHandle) return false;
|
|
3368
|
+
return typeof column.editable === "function" ? column.editable(row[column.dataIndex], row, cell.rowIndex) : column.editable === true;
|
|
3369
|
+
}, [columns, rows]);
|
|
3351
3370
|
const beginEdit = (0, import_react5.useCallback)((cell) => {
|
|
3352
3371
|
const column = columns[cell.columnIndex];
|
|
3353
|
-
if (!
|
|
3372
|
+
if (!isCellEditable(cell) || column.dataIndex === void 0) return;
|
|
3354
3373
|
const value = rows[cell.rowIndex]?.[column.dataIndex];
|
|
3355
3374
|
setDraft(value == null ? "" : String(value));
|
|
3356
3375
|
const scroller = scrollerRef.current;
|
|
@@ -3392,7 +3411,7 @@ function Table({
|
|
|
3392
3411
|
return;
|
|
3393
3412
|
}
|
|
3394
3413
|
setEditing(cell);
|
|
3395
|
-
}, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
|
|
3414
|
+
}, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, isCellEditable, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
|
|
3396
3415
|
const getCellEditKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
|
|
3397
3416
|
const markCellEdited = (cell, previousValue, nextValue) => {
|
|
3398
3417
|
const key = getCellEditKey(cell);
|
|
@@ -3420,6 +3439,10 @@ function Table({
|
|
|
3420
3439
|
};
|
|
3421
3440
|
const commitEdit = (0, import_react5.useCallback)((nextDraft, close = true) => {
|
|
3422
3441
|
if (!editing) return;
|
|
3442
|
+
if (!isCellEditable(editing)) {
|
|
3443
|
+
setEditing(null);
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3423
3446
|
const committedDraft = nextDraft ?? draft;
|
|
3424
3447
|
const column = columns[editing.columnIndex];
|
|
3425
3448
|
const row = rows[editing.rowIndex];
|
|
@@ -3434,7 +3457,7 @@ function Table({
|
|
|
3434
3457
|
onCellChange?.({ ...editing, row, value: committedValue, previousValue });
|
|
3435
3458
|
}
|
|
3436
3459
|
if (close) setEditing(null);
|
|
3437
|
-
}, [columns, draft, editing, onCellChange, rows]);
|
|
3460
|
+
}, [columns, draft, editing, isCellEditable, onCellChange, rows]);
|
|
3438
3461
|
const renderEditor = () => {
|
|
3439
3462
|
if (!editing || !editorStyle) return null;
|
|
3440
3463
|
const column = columns[editing.columnIndex];
|
|
@@ -3565,6 +3588,18 @@ function Table({
|
|
|
3565
3588
|
const { cell: resizeCell, edge: resizeEdge } = resizeHit;
|
|
3566
3589
|
event.preventDefault();
|
|
3567
3590
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
3591
|
+
setResizedColumnWidths((current) => {
|
|
3592
|
+
let changed = false;
|
|
3593
|
+
const next = { ...current };
|
|
3594
|
+
columns.forEach((column, index) => {
|
|
3595
|
+
if (column.rowSelection || column.rowDragHandle || column.rowNumber) return;
|
|
3596
|
+
const displayedWidth = metrics[index]?.width;
|
|
3597
|
+
if (displayedWidth === void 0 || next[column.key] === displayedWidth) return;
|
|
3598
|
+
next[column.key] = displayedWidth;
|
|
3599
|
+
changed = true;
|
|
3600
|
+
});
|
|
3601
|
+
return changed ? next : current;
|
|
3602
|
+
});
|
|
3568
3603
|
const resizeIndex = resizeCell.endIndex;
|
|
3569
3604
|
const resizeIndices = resizeCell.leaf ? [resizeIndex] : Array.from({ length: resizeCell.endIndex - resizeCell.startIndex + 1 }, (_, index) => resizeCell.startIndex + index);
|
|
3570
3605
|
const startWidths = resizeIndices.map((index) => metrics[index].width);
|
|
@@ -3597,7 +3632,7 @@ function Table({
|
|
|
3597
3632
|
const setGuideFromWidths = (nextWidths) => {
|
|
3598
3633
|
const widthByIndex = new Map(drag.resizeIndices.map((columnIndex, index) => [columnIndex, nextWidths[index]]));
|
|
3599
3634
|
const nextColumns = columns.map((column2, index) => widthByIndex.has(index) ? { ...column2, width: widthByIndex.get(index) } : column2);
|
|
3600
|
-
const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth:
|
|
3635
|
+
const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth: 0 });
|
|
3601
3636
|
setResizeGuideX(getHeaderCellEdgeFromMetrics(drag.resizeStartIndex, drag.resizeEndIndex, drag.resizeEdge, nextMetrics));
|
|
3602
3637
|
};
|
|
3603
3638
|
if (drag.resizeIndices.length > 1) {
|
|
@@ -4336,6 +4371,77 @@ function Table({
|
|
|
4336
4371
|
`summary:${column.key}`
|
|
4337
4372
|
);
|
|
4338
4373
|
};
|
|
4374
|
+
(0, import_react5.useImperativeHandle)(ref, () => {
|
|
4375
|
+
const resolveDataCell = (target) => {
|
|
4376
|
+
const cell = resolveSelectionTarget(target);
|
|
4377
|
+
if (!cell) return null;
|
|
4378
|
+
const column = columns[cell.columnIndex];
|
|
4379
|
+
if (column.rowNumber || column.rowSelection || column.rowDragHandle) return null;
|
|
4380
|
+
const span = getCellSpan(cell.rowIndex, cell.columnIndex);
|
|
4381
|
+
if (!span) return cell;
|
|
4382
|
+
return {
|
|
4383
|
+
rowIndex: span.rowIndex,
|
|
4384
|
+
columnIndex: span.columnIndex,
|
|
4385
|
+
rowKey: getRowKey(rows[span.rowIndex], span.rowIndex),
|
|
4386
|
+
columnKey: columns[span.columnIndex].key
|
|
4387
|
+
};
|
|
4388
|
+
};
|
|
4389
|
+
return {
|
|
4390
|
+
focus: () => scrollerRef.current?.focus({ preventScroll: true }),
|
|
4391
|
+
scrollToCell: (target) => {
|
|
4392
|
+
const cell = resolveDataCell(target);
|
|
4393
|
+
if (!cell) return false;
|
|
4394
|
+
revealCell(cell);
|
|
4395
|
+
return true;
|
|
4396
|
+
},
|
|
4397
|
+
getSelectedCell: () => {
|
|
4398
|
+
if (!selection) return null;
|
|
4399
|
+
const cell = resolveDataCell(selection);
|
|
4400
|
+
if (!cell) return null;
|
|
4401
|
+
const column = columns[cell.columnIndex];
|
|
4402
|
+
const row = rows[cell.rowIndex];
|
|
4403
|
+
return { ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] };
|
|
4404
|
+
},
|
|
4405
|
+
setSelectedCell: (target) => {
|
|
4406
|
+
const cell = target === null ? null : resolveDataCell(target);
|
|
4407
|
+
if (target !== null && !cell) return false;
|
|
4408
|
+
setSelectionRange(null);
|
|
4409
|
+
setSelection(cell);
|
|
4410
|
+
return true;
|
|
4411
|
+
},
|
|
4412
|
+
getSelectedRowKeys: () => [...rowKeys],
|
|
4413
|
+
getSelectedRows: () => rows.filter((row, index) => selectedRowKeySet.has(getRowKey(row, index))),
|
|
4414
|
+
setSelectedRowKeys: (keys) => {
|
|
4415
|
+
if (!rowSelection) return;
|
|
4416
|
+
const next = [...new Set(keys)];
|
|
4417
|
+
setRowKeys(rowSelectionMode === "single" ? next.slice(0, 1) : next);
|
|
4418
|
+
},
|
|
4419
|
+
getSelectedColumnKeys: () => [...columnKeys],
|
|
4420
|
+
setSelectedColumnKeys: (keys) => {
|
|
4421
|
+
if (!columnSelection) return;
|
|
4422
|
+
const next = [...new Set(keys)].filter((key) => columns.some((column) => column.key === key && !column.rowNumber && !column.rowSelection && !column.rowDragHandle));
|
|
4423
|
+
setColumnKeys(typeof columnSelection === "object" && columnSelection.mode === "single" ? next.slice(0, 1) : next);
|
|
4424
|
+
},
|
|
4425
|
+
clearSelection: () => {
|
|
4426
|
+
setSelectionRange(null);
|
|
4427
|
+
setSelection(null);
|
|
4428
|
+
setRowKeys([]);
|
|
4429
|
+
setColumnKeys([]);
|
|
4430
|
+
rowAnchorRef.current = null;
|
|
4431
|
+
setEditing(null);
|
|
4432
|
+
},
|
|
4433
|
+
startEdit: (target) => {
|
|
4434
|
+
const cell = target ? resolveDataCell(target) : selection ? resolveDataCell(selection) : null;
|
|
4435
|
+
if (!cell || !isCellEditable(cell)) return false;
|
|
4436
|
+
setSelectionRange(null);
|
|
4437
|
+
setSelection(cell);
|
|
4438
|
+
beginEdit(cell);
|
|
4439
|
+
return true;
|
|
4440
|
+
},
|
|
4441
|
+
commitEdit: () => commitEdit(),
|
|
4442
|
+
cancelEdit: () => setEditing(null)
|
|
4443
|
+
};
|
|
4444
|
+
});
|
|
4339
4445
|
const rootStyle = {
|
|
4340
4446
|
width,
|
|
4341
4447
|
height: autoHeight ? effectiveHeight : height,
|
|
@@ -4534,6 +4640,10 @@ function Table({
|
|
|
4534
4640
|
}
|
|
4535
4641
|
if (event.detail > 1) return;
|
|
4536
4642
|
clickedCellRef.current = cell;
|
|
4643
|
+
const column = columns[cell.columnIndex];
|
|
4644
|
+
const row = rows[cell.rowIndex];
|
|
4645
|
+
onCellClick?.({ ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] }, event);
|
|
4646
|
+
if (event.defaultPrevented) return;
|
|
4537
4647
|
setSelectionRange(null);
|
|
4538
4648
|
scrollerRef.current?.focus({ preventScroll: true });
|
|
4539
4649
|
setSelection(cell);
|
|
@@ -4544,7 +4654,12 @@ function Table({
|
|
|
4544
4654
|
},
|
|
4545
4655
|
onDoubleClick: (event) => {
|
|
4546
4656
|
const cell = clickedCellRef.current ?? locateCell(event.clientX, event.clientY);
|
|
4547
|
-
if (cell)
|
|
4657
|
+
if (!cell) return;
|
|
4658
|
+
const column = columns[cell.columnIndex];
|
|
4659
|
+
if (column.rowNumber || column.rowSelection || column.rowDragHandle) return;
|
|
4660
|
+
const row = rows[cell.rowIndex];
|
|
4661
|
+
onCellDoubleClick?.({ ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] }, event);
|
|
4662
|
+
if (!event.defaultPrevented) beginEdit(cell);
|
|
4548
4663
|
},
|
|
4549
4664
|
onContextMenu: handleContextMenu
|
|
4550
4665
|
}
|
|
@@ -4557,7 +4672,7 @@ function Table({
|
|
|
4557
4672
|
className: "rvg-text-scroll-content",
|
|
4558
4673
|
style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
|
|
4559
4674
|
children: [
|
|
4560
|
-
columns.map((column, columnIndex) => column.fixed || columnIndex
|
|
4675
|
+
columns.map((column, columnIndex) => column.fixed || columnIndex >= utilityColumnCount && headerDepth > 1 ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderTitle(columnIndex, getScrollableColumnLeft(columnIndex)) }, column.key) : null),
|
|
4561
4676
|
headerCells.map((cell) => {
|
|
4562
4677
|
if (columns[cell.startIndex]?.fixed) return null;
|
|
4563
4678
|
if (cell.endIndex < domRange.columnStart || cell.startIndex >= domRange.columnEnd) return null;
|
|
@@ -4575,7 +4690,7 @@ function Table({
|
|
|
4575
4690
|
const left = column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
|
|
4576
4691
|
return renderCellText(rowIndex, columnIndex, left);
|
|
4577
4692
|
})) }),
|
|
4578
|
-
columns.map((column, columnIndex) => columnIndex < utilityColumnCount || headerDepth === 1
|
|
4693
|
+
columns.map((column, columnIndex) => column.fixed && (columnIndex < utilityColumnCount || headerDepth === 1) ? renderHeaderTitle(columnIndex, column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width : column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : getScrollableColumnLeft(columnIndex)) : null),
|
|
4579
4694
|
headerCells.map((cell) => columns[cell.startIndex]?.fixed ? renderGroupedHeaderCell(cell) : null),
|
|
4580
4695
|
fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-shadow is-left", style: { left: fixedWidth } }),
|
|
4581
4696
|
rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-shadow is-right", style: { right: rightFixedWidth } }),
|
|
@@ -4792,7 +4907,7 @@ function Table({
|
|
|
4792
4907
|
const affectedRows = selectedIndices.includes(cell.rowIndex) ? selectedIndices : [cell.rowIndex];
|
|
4793
4908
|
const cellContext = { ...cell, row, column, value: dataIndex === void 0 ? void 0 : row[dataIndex] };
|
|
4794
4909
|
return renderConfiguredContextMenu(resolveContextMenuSection(customContextMenuConfig?.cell, DEFAULT_CELL_CONTEXT_MENU), cellContext, (item, override) => {
|
|
4795
|
-
if (item === "edit") return renderContextMenuButton("cell-edit", cellContext, labels.edit, () => beginEdit(cell), override, { disabled: !
|
|
4910
|
+
if (item === "edit") return renderContextMenuButton("cell-edit", cellContext, labels.edit, () => beginEdit(cell), override, { disabled: !isCellEditable(cell), icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ContextMenuIcon, { type: "edit" }) });
|
|
4796
4911
|
if (item === "copy") return renderContextMenuButton("cell-copy", cellContext, labels.copyContent, async () => {
|
|
4797
4912
|
try {
|
|
4798
4913
|
await navigator.clipboard.writeText(getCellLabel(cell.rowIndex, cell.columnIndex));
|
|
@@ -5032,6 +5147,7 @@ function Table({
|
|
|
5032
5147
|
loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LoadingSpinnerIcon, {}) })
|
|
5033
5148
|
] });
|
|
5034
5149
|
}
|
|
5150
|
+
var Table = (0, import_react5.forwardRef)(TableInner);
|
|
5035
5151
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5036
5152
|
0 && (module.exports = {
|
|
5037
5153
|
Table,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ReactNode, CSSProperties } from 'react';
|
|
1
|
+
import { ReactNode, MouseEvent, CSSProperties, RefAttributes, ReactElement } from 'react';
|
|
3
2
|
|
|
4
3
|
type GridKey = string | number;
|
|
5
4
|
type SelectionMode = 'single' | 'multiple';
|
|
@@ -155,6 +154,14 @@ interface AxisSelectionConfig {
|
|
|
155
154
|
/** single uses radio-style selection, multiple uses checkbox-style selection. */
|
|
156
155
|
mode?: SelectionMode;
|
|
157
156
|
}
|
|
157
|
+
interface RowSelectionConfig extends AxisSelectionConfig {
|
|
158
|
+
/** Show the selection column. Defaults to true; when false, click body cells to select rows. */
|
|
159
|
+
showCheckbox?: boolean;
|
|
160
|
+
}
|
|
161
|
+
interface TableRowNumberConfig {
|
|
162
|
+
/** Fix the row number column to the left. Defaults to true. */
|
|
163
|
+
fixed?: boolean;
|
|
164
|
+
}
|
|
158
165
|
/**
|
|
159
166
|
* Column definition consumed by both the canvas renderer and DOM editor layer.
|
|
160
167
|
*
|
|
@@ -179,7 +186,7 @@ interface GridColumn<Row> {
|
|
|
179
186
|
/** Text alignment for body cells and drag previews. */
|
|
180
187
|
align?: 'left' | 'center' | 'right';
|
|
181
188
|
/** Enables double-click / Enter editing when an editor or fallback input exists. */
|
|
182
|
-
editable?: boolean;
|
|
189
|
+
editable?: boolean | ((value: unknown, row: Row, rowIndex: number) => boolean);
|
|
183
190
|
/** Built-in editor configuration for text, selectable, date, time, and range cells. */
|
|
184
191
|
editor?: {
|
|
185
192
|
type: 'text';
|
|
@@ -241,6 +248,31 @@ interface CellChange<Row> extends GridSelection {
|
|
|
241
248
|
previousValue: unknown;
|
|
242
249
|
row: Row;
|
|
243
250
|
}
|
|
251
|
+
/** Data cell identity and content passed to mouse callbacks. */
|
|
252
|
+
interface TableCellContext<Row> extends GridSelection {
|
|
253
|
+
row: Row;
|
|
254
|
+
column: GridColumn<Row>;
|
|
255
|
+
value: unknown;
|
|
256
|
+
}
|
|
257
|
+
/** Imperative Table API. Selection getters reflect the latest committed render. */
|
|
258
|
+
interface TableRef<Row extends object> {
|
|
259
|
+
focus: () => void;
|
|
260
|
+
/** Reveal a data cell without changing selection; false if the target is unavailable. */
|
|
261
|
+
scrollToCell: (target: GridSelectionTarget) => boolean;
|
|
262
|
+
getSelectedCell: () => TableCellContext<Row> | null;
|
|
263
|
+
setSelectedCell: (target: GridSelectionTarget | null) => boolean;
|
|
264
|
+
getSelectedRowKeys: () => GridKey[];
|
|
265
|
+
getSelectedRows: () => Row[];
|
|
266
|
+
setSelectedRowKeys: (keys: GridKey[]) => void;
|
|
267
|
+
getSelectedColumnKeys: () => string[];
|
|
268
|
+
setSelectedColumnKeys: (keys: string[]) => void;
|
|
269
|
+
/** Clear cell, range, row, and column selections. */
|
|
270
|
+
clearSelection: () => void;
|
|
271
|
+
/** Select and edit a cell, or edit the current selection. Returns false for invalid or read-only cells. */
|
|
272
|
+
startEdit: (target?: GridSelectionTarget) => boolean;
|
|
273
|
+
commitEdit: () => void;
|
|
274
|
+
cancelEdit: () => void;
|
|
275
|
+
}
|
|
244
276
|
/** Fired when the consumer wants to observe or extend a cell context menu. */
|
|
245
277
|
interface ContextMenuEvent<Row> extends GridSelection {
|
|
246
278
|
row: Row;
|
|
@@ -331,13 +363,13 @@ interface TableProps<Row extends object> {
|
|
|
331
363
|
/** Show and position the summary row for columns with summary configured. */
|
|
332
364
|
summary?: TableSummaryConfig;
|
|
333
365
|
/** Show an automatically generated row number column on the left. */
|
|
334
|
-
rowNumber?: boolean;
|
|
366
|
+
rowNumber?: boolean | TableRowNumberConfig;
|
|
335
367
|
selectedCell?: GridSelectionTarget | null;
|
|
336
368
|
defaultSelectedCell?: GridSelectionTarget | null;
|
|
337
369
|
onSelectedCellChange?: (selection: SelectedCellChange | null) => void;
|
|
338
370
|
/** Enable spreadsheet-style mouse drag selection across multiple body cells. Defaults to false. */
|
|
339
371
|
rangeSelection?: boolean;
|
|
340
|
-
rowSelection?: boolean |
|
|
372
|
+
rowSelection?: boolean | RowSelectionConfig;
|
|
341
373
|
selectedRowKeys?: GridKey[];
|
|
342
374
|
defaultSelectedRowKeys?: GridKey[];
|
|
343
375
|
onSelectedRowChange?: (keys: GridKey[], rows: Row[], indices: number[]) => void;
|
|
@@ -371,6 +403,10 @@ interface TableProps<Row extends object> {
|
|
|
371
403
|
/** Overrides the built-in text filtering. Return the rows that match the internally managed filter values. */
|
|
372
404
|
onFilterChange?: (rows: Row[], values: Readonly<Record<string, string>>) => Row[];
|
|
373
405
|
onCellChange?: (change: CellChange<Row>) => void | Promise<void>;
|
|
406
|
+
/** Fires on a data cell click, before the default selection behavior. */
|
|
407
|
+
onCellClick?: (cell: TableCellContext<Row>, event: MouseEvent<HTMLElement>) => void;
|
|
408
|
+
/** Fires before double-click editing. preventDefault() cancels editing. */
|
|
409
|
+
onCellDoubleClick?: (cell: TableCellContext<Row>, event: MouseEvent<HTMLElement>) => void;
|
|
374
410
|
onCellContextMenu?: (event: ContextMenuEvent<Row>) => void;
|
|
375
411
|
/** true uses the built-in menu, false disables custom menus, object customizes menu content and order. */
|
|
376
412
|
contextMenu?: TableContextMenu<Row>;
|
|
@@ -386,7 +422,7 @@ interface ViewportRange {
|
|
|
386
422
|
columnEnd: number;
|
|
387
423
|
}
|
|
388
424
|
|
|
389
|
-
declare
|
|
425
|
+
declare const Table: <Row extends object>(props: TableProps<Row> & RefAttributes<TableRef<Row>>) => ReactElement;
|
|
390
426
|
|
|
391
427
|
type LayoutColumn<Row> = GridColumn<Row> & {
|
|
392
428
|
rowSelection?: boolean;
|
|
@@ -433,4 +469,4 @@ declare function hitTestColumn(metrics: ColumnMetric[], contentX: number): numbe
|
|
|
433
469
|
|
|
434
470
|
type TableFilterValues = Record<string, string>;
|
|
435
471
|
|
|
436
|
-
export { type AxisSelectionConfig, type CellChange, type ContextMenuEvent, type GridCellPosition, type GridColumn, type GridKey, type GridSelection, type GridSelectionTarget, type GridSortState, type SelectedCellChange, type SelectionMode, type SortDirection, Table, type CellChange as TableCellChange, type GridCellPosition as TableCellPosition, type TableCellSpan, type TableCellSpanRule, type TableCellSpanRuleContext, type TableCellSpans, type GridColumn as TableColumn, type TableContextMenu, type TableContextMenuConfig, type ContextMenuEvent as TableContextMenuEvent, type GridSelection as TableEditingCell, type TableFilterValues, type TableHighlightConfig, type GridKey as TableKey, type TableLabels, type TableLayoutConfig, type TableLocaleCode, type TableLocaleConfig, type TableProps, type TableResolvedCellSpan, type GridSortState as TableSorter, type TableSummaryConfig, type TableTooltipConfig, type ViewportRange as TableViewportRange, type TableVirtualizedConfig, Table as VelocityGrid, type TableCellSpan as VelocityGridCellSpan, type TableCellSpanRule as VelocityGridCellSpanRule, type TableCellSpanRuleContext as VelocityGridCellSpanRuleContext, type TableCellSpans as VelocityGridCellSpans, type TableContextMenu as VelocityGridContextMenu, type TableContextMenuConfig as VelocityGridContextMenuConfig, type TableHighlightConfig as VelocityGridHighlightConfig, type TableLabels as VelocityGridLabels, type TableLayoutConfig as VelocityGridLayoutConfig, type TableLocaleCode as VelocityGridLocaleCode, type TableLocaleConfig as VelocityGridLocaleConfig, type TableProps as VelocityGridProps, type TableResolvedCellSpan as VelocityGridResolvedCellSpan, type TableSummaryConfig as VelocityGridSummaryConfig, type TableTooltipConfig as VelocityGridTooltipConfig, type TableVirtualizedConfig as VelocityGridVirtualizedConfig, type ViewportRange, buildColumnMetrics, getViewportRange, hitTestColumn };
|
|
472
|
+
export { type AxisSelectionConfig, type CellChange, type ContextMenuEvent, type GridCellPosition, type GridColumn, type GridKey, type GridSelection, type GridSelectionTarget, type GridSortState, type RowSelectionConfig, type SelectedCellChange, type SelectionMode, type SortDirection, Table, type CellChange as TableCellChange, type TableCellContext, type GridCellPosition as TableCellPosition, type TableCellSpan, type TableCellSpanRule, type TableCellSpanRuleContext, type TableCellSpans, type GridColumn as TableColumn, type TableContextMenu, type TableContextMenuConfig, type ContextMenuEvent as TableContextMenuEvent, type GridSelection as TableEditingCell, type TableFilterValues, type TableHighlightConfig, type GridKey as TableKey, type TableLabels, type TableLayoutConfig, type TableLocaleCode, type TableLocaleConfig, type TableProps, type TableRef, type TableResolvedCellSpan, type TableRowNumberConfig, type GridSortState as TableSorter, type TableSummaryConfig, type TableTooltipConfig, type ViewportRange as TableViewportRange, type TableVirtualizedConfig, Table as VelocityGrid, type TableCellSpan as VelocityGridCellSpan, type TableCellSpanRule as VelocityGridCellSpanRule, type TableCellSpanRuleContext as VelocityGridCellSpanRuleContext, type TableCellSpans as VelocityGridCellSpans, type TableContextMenu as VelocityGridContextMenu, type TableContextMenuConfig as VelocityGridContextMenuConfig, type TableHighlightConfig as VelocityGridHighlightConfig, type TableLabels as VelocityGridLabels, type TableLayoutConfig as VelocityGridLayoutConfig, type TableLocaleCode as VelocityGridLocaleCode, type TableLocaleConfig as VelocityGridLocaleConfig, type TableProps as VelocityGridProps, type TableRef as VelocityGridRef, type TableResolvedCellSpan as VelocityGridResolvedCellSpan, type TableSummaryConfig as VelocityGridSummaryConfig, type TableTooltipConfig as VelocityGridTooltipConfig, type TableVirtualizedConfig as VelocityGridVirtualizedConfig, type ViewportRange, buildColumnMetrics, getViewportRange, hitTestColumn };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ReactNode, CSSProperties } from 'react';
|
|
1
|
+
import { ReactNode, MouseEvent, CSSProperties, RefAttributes, ReactElement } from 'react';
|
|
3
2
|
|
|
4
3
|
type GridKey = string | number;
|
|
5
4
|
type SelectionMode = 'single' | 'multiple';
|
|
@@ -155,6 +154,14 @@ interface AxisSelectionConfig {
|
|
|
155
154
|
/** single uses radio-style selection, multiple uses checkbox-style selection. */
|
|
156
155
|
mode?: SelectionMode;
|
|
157
156
|
}
|
|
157
|
+
interface RowSelectionConfig extends AxisSelectionConfig {
|
|
158
|
+
/** Show the selection column. Defaults to true; when false, click body cells to select rows. */
|
|
159
|
+
showCheckbox?: boolean;
|
|
160
|
+
}
|
|
161
|
+
interface TableRowNumberConfig {
|
|
162
|
+
/** Fix the row number column to the left. Defaults to true. */
|
|
163
|
+
fixed?: boolean;
|
|
164
|
+
}
|
|
158
165
|
/**
|
|
159
166
|
* Column definition consumed by both the canvas renderer and DOM editor layer.
|
|
160
167
|
*
|
|
@@ -179,7 +186,7 @@ interface GridColumn<Row> {
|
|
|
179
186
|
/** Text alignment for body cells and drag previews. */
|
|
180
187
|
align?: 'left' | 'center' | 'right';
|
|
181
188
|
/** Enables double-click / Enter editing when an editor or fallback input exists. */
|
|
182
|
-
editable?: boolean;
|
|
189
|
+
editable?: boolean | ((value: unknown, row: Row, rowIndex: number) => boolean);
|
|
183
190
|
/** Built-in editor configuration for text, selectable, date, time, and range cells. */
|
|
184
191
|
editor?: {
|
|
185
192
|
type: 'text';
|
|
@@ -241,6 +248,31 @@ interface CellChange<Row> extends GridSelection {
|
|
|
241
248
|
previousValue: unknown;
|
|
242
249
|
row: Row;
|
|
243
250
|
}
|
|
251
|
+
/** Data cell identity and content passed to mouse callbacks. */
|
|
252
|
+
interface TableCellContext<Row> extends GridSelection {
|
|
253
|
+
row: Row;
|
|
254
|
+
column: GridColumn<Row>;
|
|
255
|
+
value: unknown;
|
|
256
|
+
}
|
|
257
|
+
/** Imperative Table API. Selection getters reflect the latest committed render. */
|
|
258
|
+
interface TableRef<Row extends object> {
|
|
259
|
+
focus: () => void;
|
|
260
|
+
/** Reveal a data cell without changing selection; false if the target is unavailable. */
|
|
261
|
+
scrollToCell: (target: GridSelectionTarget) => boolean;
|
|
262
|
+
getSelectedCell: () => TableCellContext<Row> | null;
|
|
263
|
+
setSelectedCell: (target: GridSelectionTarget | null) => boolean;
|
|
264
|
+
getSelectedRowKeys: () => GridKey[];
|
|
265
|
+
getSelectedRows: () => Row[];
|
|
266
|
+
setSelectedRowKeys: (keys: GridKey[]) => void;
|
|
267
|
+
getSelectedColumnKeys: () => string[];
|
|
268
|
+
setSelectedColumnKeys: (keys: string[]) => void;
|
|
269
|
+
/** Clear cell, range, row, and column selections. */
|
|
270
|
+
clearSelection: () => void;
|
|
271
|
+
/** Select and edit a cell, or edit the current selection. Returns false for invalid or read-only cells. */
|
|
272
|
+
startEdit: (target?: GridSelectionTarget) => boolean;
|
|
273
|
+
commitEdit: () => void;
|
|
274
|
+
cancelEdit: () => void;
|
|
275
|
+
}
|
|
244
276
|
/** Fired when the consumer wants to observe or extend a cell context menu. */
|
|
245
277
|
interface ContextMenuEvent<Row> extends GridSelection {
|
|
246
278
|
row: Row;
|
|
@@ -331,13 +363,13 @@ interface TableProps<Row extends object> {
|
|
|
331
363
|
/** Show and position the summary row for columns with summary configured. */
|
|
332
364
|
summary?: TableSummaryConfig;
|
|
333
365
|
/** Show an automatically generated row number column on the left. */
|
|
334
|
-
rowNumber?: boolean;
|
|
366
|
+
rowNumber?: boolean | TableRowNumberConfig;
|
|
335
367
|
selectedCell?: GridSelectionTarget | null;
|
|
336
368
|
defaultSelectedCell?: GridSelectionTarget | null;
|
|
337
369
|
onSelectedCellChange?: (selection: SelectedCellChange | null) => void;
|
|
338
370
|
/** Enable spreadsheet-style mouse drag selection across multiple body cells. Defaults to false. */
|
|
339
371
|
rangeSelection?: boolean;
|
|
340
|
-
rowSelection?: boolean |
|
|
372
|
+
rowSelection?: boolean | RowSelectionConfig;
|
|
341
373
|
selectedRowKeys?: GridKey[];
|
|
342
374
|
defaultSelectedRowKeys?: GridKey[];
|
|
343
375
|
onSelectedRowChange?: (keys: GridKey[], rows: Row[], indices: number[]) => void;
|
|
@@ -371,6 +403,10 @@ interface TableProps<Row extends object> {
|
|
|
371
403
|
/** Overrides the built-in text filtering. Return the rows that match the internally managed filter values. */
|
|
372
404
|
onFilterChange?: (rows: Row[], values: Readonly<Record<string, string>>) => Row[];
|
|
373
405
|
onCellChange?: (change: CellChange<Row>) => void | Promise<void>;
|
|
406
|
+
/** Fires on a data cell click, before the default selection behavior. */
|
|
407
|
+
onCellClick?: (cell: TableCellContext<Row>, event: MouseEvent<HTMLElement>) => void;
|
|
408
|
+
/** Fires before double-click editing. preventDefault() cancels editing. */
|
|
409
|
+
onCellDoubleClick?: (cell: TableCellContext<Row>, event: MouseEvent<HTMLElement>) => void;
|
|
374
410
|
onCellContextMenu?: (event: ContextMenuEvent<Row>) => void;
|
|
375
411
|
/** true uses the built-in menu, false disables custom menus, object customizes menu content and order. */
|
|
376
412
|
contextMenu?: TableContextMenu<Row>;
|
|
@@ -386,7 +422,7 @@ interface ViewportRange {
|
|
|
386
422
|
columnEnd: number;
|
|
387
423
|
}
|
|
388
424
|
|
|
389
|
-
declare
|
|
425
|
+
declare const Table: <Row extends object>(props: TableProps<Row> & RefAttributes<TableRef<Row>>) => ReactElement;
|
|
390
426
|
|
|
391
427
|
type LayoutColumn<Row> = GridColumn<Row> & {
|
|
392
428
|
rowSelection?: boolean;
|
|
@@ -433,4 +469,4 @@ declare function hitTestColumn(metrics: ColumnMetric[], contentX: number): numbe
|
|
|
433
469
|
|
|
434
470
|
type TableFilterValues = Record<string, string>;
|
|
435
471
|
|
|
436
|
-
export { type AxisSelectionConfig, type CellChange, type ContextMenuEvent, type GridCellPosition, type GridColumn, type GridKey, type GridSelection, type GridSelectionTarget, type GridSortState, type SelectedCellChange, type SelectionMode, type SortDirection, Table, type CellChange as TableCellChange, type GridCellPosition as TableCellPosition, type TableCellSpan, type TableCellSpanRule, type TableCellSpanRuleContext, type TableCellSpans, type GridColumn as TableColumn, type TableContextMenu, type TableContextMenuConfig, type ContextMenuEvent as TableContextMenuEvent, type GridSelection as TableEditingCell, type TableFilterValues, type TableHighlightConfig, type GridKey as TableKey, type TableLabels, type TableLayoutConfig, type TableLocaleCode, type TableLocaleConfig, type TableProps, type TableResolvedCellSpan, type GridSortState as TableSorter, type TableSummaryConfig, type TableTooltipConfig, type ViewportRange as TableViewportRange, type TableVirtualizedConfig, Table as VelocityGrid, type TableCellSpan as VelocityGridCellSpan, type TableCellSpanRule as VelocityGridCellSpanRule, type TableCellSpanRuleContext as VelocityGridCellSpanRuleContext, type TableCellSpans as VelocityGridCellSpans, type TableContextMenu as VelocityGridContextMenu, type TableContextMenuConfig as VelocityGridContextMenuConfig, type TableHighlightConfig as VelocityGridHighlightConfig, type TableLabels as VelocityGridLabels, type TableLayoutConfig as VelocityGridLayoutConfig, type TableLocaleCode as VelocityGridLocaleCode, type TableLocaleConfig as VelocityGridLocaleConfig, type TableProps as VelocityGridProps, type TableResolvedCellSpan as VelocityGridResolvedCellSpan, type TableSummaryConfig as VelocityGridSummaryConfig, type TableTooltipConfig as VelocityGridTooltipConfig, type TableVirtualizedConfig as VelocityGridVirtualizedConfig, type ViewportRange, buildColumnMetrics, getViewportRange, hitTestColumn };
|
|
472
|
+
export { type AxisSelectionConfig, type CellChange, type ContextMenuEvent, type GridCellPosition, type GridColumn, type GridKey, type GridSelection, type GridSelectionTarget, type GridSortState, type RowSelectionConfig, type SelectedCellChange, type SelectionMode, type SortDirection, Table, type CellChange as TableCellChange, type TableCellContext, type GridCellPosition as TableCellPosition, type TableCellSpan, type TableCellSpanRule, type TableCellSpanRuleContext, type TableCellSpans, type GridColumn as TableColumn, type TableContextMenu, type TableContextMenuConfig, type ContextMenuEvent as TableContextMenuEvent, type GridSelection as TableEditingCell, type TableFilterValues, type TableHighlightConfig, type GridKey as TableKey, type TableLabels, type TableLayoutConfig, type TableLocaleCode, type TableLocaleConfig, type TableProps, type TableRef, type TableResolvedCellSpan, type TableRowNumberConfig, type GridSortState as TableSorter, type TableSummaryConfig, type TableTooltipConfig, type ViewportRange as TableViewportRange, type TableVirtualizedConfig, Table as VelocityGrid, type TableCellSpan as VelocityGridCellSpan, type TableCellSpanRule as VelocityGridCellSpanRule, type TableCellSpanRuleContext as VelocityGridCellSpanRuleContext, type TableCellSpans as VelocityGridCellSpans, type TableContextMenu as VelocityGridContextMenu, type TableContextMenuConfig as VelocityGridContextMenuConfig, type TableHighlightConfig as VelocityGridHighlightConfig, type TableLabels as VelocityGridLabels, type TableLayoutConfig as VelocityGridLayoutConfig, type TableLocaleCode as VelocityGridLocaleCode, type TableLocaleConfig as VelocityGridLocaleConfig, type TableProps as VelocityGridProps, type TableRef as VelocityGridRef, type TableResolvedCellSpan as VelocityGridResolvedCellSpan, type TableSummaryConfig as VelocityGridSummaryConfig, type TableTooltipConfig as VelocityGridTooltipConfig, type TableVirtualizedConfig as VelocityGridVirtualizedConfig, type ViewportRange, buildColumnMetrics, getViewportRange, hitTestColumn };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/Table.tsx
|
|
2
|
-
import { Fragment as Fragment2, useCallback as useCallback2, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
|
|
2
|
+
import { forwardRef, useImperativeHandle, Fragment as Fragment2, useCallback as useCallback2, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
|
|
3
3
|
import { flushSync } from "react-dom";
|
|
4
4
|
|
|
5
5
|
// src/components/EmptyState.tsx
|
|
@@ -1492,7 +1492,7 @@ function resolveContextMenuSection(section, defaults) {
|
|
|
1492
1492
|
}
|
|
1493
1493
|
return defaults;
|
|
1494
1494
|
}
|
|
1495
|
-
function
|
|
1495
|
+
function TableInner({
|
|
1496
1496
|
columns: columnProps,
|
|
1497
1497
|
rows: sourceRows,
|
|
1498
1498
|
rowKey,
|
|
@@ -1538,13 +1538,15 @@ function Table({
|
|
|
1538
1538
|
onSortChange,
|
|
1539
1539
|
onFilterChange,
|
|
1540
1540
|
onCellChange,
|
|
1541
|
+
onCellClick,
|
|
1542
|
+
onCellDoubleClick,
|
|
1541
1543
|
onCellContextMenu,
|
|
1542
1544
|
contextMenu: contextMenuConfig = true,
|
|
1543
1545
|
emptyContent,
|
|
1544
1546
|
className = "",
|
|
1545
1547
|
style,
|
|
1546
1548
|
ariaLabel = "Data grid"
|
|
1547
|
-
}) {
|
|
1549
|
+
}, ref) {
|
|
1548
1550
|
const [sourceColumns, setSourceColumns] = useState4(columnProps);
|
|
1549
1551
|
useEffect2(() => {
|
|
1550
1552
|
setSourceColumns((current) => reconcileColumns(current, columnProps));
|
|
@@ -1590,6 +1592,8 @@ function Table({
|
|
|
1590
1592
|
const customContextMenuConfig = typeof contextMenuConfig === "object" ? contextMenuConfig : void 0;
|
|
1591
1593
|
const [resizedColumnWidths, setResizedColumnWidths] = useState4({});
|
|
1592
1594
|
const leafSourceColumns = useMemo(() => flattenDataColumns(sourceColumns), [sourceColumns]);
|
|
1595
|
+
const sourceColumnWidthsRef = useRef2(new Map(leafSourceColumns.map((column) => [column.key, column.width])));
|
|
1596
|
+
const showRowCheckbox = Boolean(rowSelection) && (typeof rowSelection !== "object" || rowSelection.showCheckbox !== false);
|
|
1593
1597
|
useEffect2(() => {
|
|
1594
1598
|
if (!isProductionRuntime() && hasNestedFixedColumns(sourceColumns)) {
|
|
1595
1599
|
console.warn("[Table] Multi-level headers only support fixed columns on top-level columns. Nested fixed values are ignored.");
|
|
@@ -1600,19 +1604,19 @@ function Table({
|
|
|
1600
1604
|
if (rowDraggable) {
|
|
1601
1605
|
utilityColumns.push({ key: "__rvg_row_drag__", title: "", width: 36, align: "center", fixed: "left", rowDragHandle: true });
|
|
1602
1606
|
}
|
|
1603
|
-
if (
|
|
1607
|
+
if (showRowCheckbox) {
|
|
1604
1608
|
utilityColumns.push({ key: "__rvg_row_selection__", title: "", width: 44, align: "center", fixed: "left", rowSelection: true });
|
|
1605
1609
|
}
|
|
1606
1610
|
if (rowNumber) {
|
|
1607
|
-
utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: "left", rowNumber: true });
|
|
1611
|
+
utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: typeof rowNumber === "object" && rowNumber.fixed === false ? void 0 : "left", rowNumber: true });
|
|
1608
1612
|
}
|
|
1609
1613
|
const dataColumns = leafSourceColumns.map((column) => {
|
|
1610
1614
|
const resizedWidth = resizedColumnWidths[column.key];
|
|
1611
1615
|
return resizedWidth === void 0 ? column : { ...column, width: resizedWidth };
|
|
1612
1616
|
});
|
|
1613
1617
|
return [...utilityColumns, ...dataColumns];
|
|
1614
|
-
}, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber,
|
|
1615
|
-
const utilityColumnCount = (rowDraggable ? 1 : 0) + (
|
|
1618
|
+
}, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber, showRowCheckbox, sourceRows.length]);
|
|
1619
|
+
const utilityColumnCount = (rowDraggable ? 1 : 0) + (showRowCheckbox ? 1 : 0) + (rowNumber ? 1 : 0);
|
|
1616
1620
|
const headerCells = useMemo(() => buildHeaderCells(sourceColumns, utilityColumnCount, headerDepth), [headerDepth, sourceColumns, utilityColumnCount]);
|
|
1617
1621
|
const activeCustomHeaderLevels = useMemo(() => {
|
|
1618
1622
|
const levels = /* @__PURE__ */ new Map();
|
|
@@ -1701,6 +1705,9 @@ function Table({
|
|
|
1701
1705
|
setSelectionRange(null);
|
|
1702
1706
|
}, [rangeSelection]);
|
|
1703
1707
|
useEffect2(() => {
|
|
1708
|
+
const previousSourceWidths = sourceColumnWidthsRef.current;
|
|
1709
|
+
const nextSourceWidths = new Map(leafSourceColumns.map((column) => [column.key, column.width]));
|
|
1710
|
+
sourceColumnWidthsRef.current = nextSourceWidths;
|
|
1704
1711
|
setResizedColumnWidths((current) => {
|
|
1705
1712
|
const sourceColumnByKey = new Map(leafSourceColumns.map((column) => [column.key, column]));
|
|
1706
1713
|
let changed = false;
|
|
@@ -1711,7 +1718,8 @@ function Table({
|
|
|
1711
1718
|
changed = true;
|
|
1712
1719
|
return;
|
|
1713
1720
|
}
|
|
1714
|
-
|
|
1721
|
+
const sourceWidthChanged = previousSourceWidths.get(key) !== sourceColumn.width;
|
|
1722
|
+
if (sourceWidthChanged && sourceColumn.width !== void 0 && sourceColumn.width !== value) {
|
|
1715
1723
|
changed = true;
|
|
1716
1724
|
return;
|
|
1717
1725
|
}
|
|
@@ -1948,7 +1956,11 @@ function Table({
|
|
|
1948
1956
|
if (selection?.rowKey === editing.rowKey && selection.columnKey === editing.columnKey) return;
|
|
1949
1957
|
setEditing(null);
|
|
1950
1958
|
}, [editing, selection]);
|
|
1951
|
-
const
|
|
1959
|
+
const hasManualColumnWidths = Object.keys(resizedColumnWidths).length > 0;
|
|
1960
|
+
const metrics = useMemo(() => buildColumnMetrics(columns, {
|
|
1961
|
+
columnDraggable,
|
|
1962
|
+
viewportWidth: hasManualColumnWidths ? 0 : viewport.width
|
|
1963
|
+
}), [columnDraggable, columns, hasManualColumnWidths, viewport.width]);
|
|
1952
1964
|
const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
|
|
1953
1965
|
const contentHeight = rows.length * rowHeight;
|
|
1954
1966
|
const fixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "left" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
|
|
@@ -2452,7 +2464,8 @@ function Table({
|
|
|
2452
2464
|
const horizontalThumb = horizontalScrollbarThumbRef.current;
|
|
2453
2465
|
if (!scroller || !verticalTrack || !verticalThumb || !horizontalTrack || !horizontalThumb) return;
|
|
2454
2466
|
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
|
|
2455
|
-
|
|
2467
|
+
const contentFitsAutoHeight = autoHeight && effectiveHeight >= realContentHeight - 1;
|
|
2468
|
+
if (contentFitsAutoHeight || maxScrollTop <= 1 || renderHeight <= 0) {
|
|
2456
2469
|
verticalTrack.style.display = "none";
|
|
2457
2470
|
} else {
|
|
2458
2471
|
const top = fixedHeader ? bodyTop : 0;
|
|
@@ -2492,7 +2505,7 @@ function Table({
|
|
|
2492
2505
|
horizontalTrack.style.bottom = `${bottomSummaryHeight + 2}px`;
|
|
2493
2506
|
horizontalThumb.style.width = `${Math.min(trackWidth, thumbWidth)}px`;
|
|
2494
2507
|
horizontalThumb.style.transform = `translateX(${thumbLeft}px)`;
|
|
2495
|
-
}, [bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, renderHeight, rightFixedWidth, viewport.width]);
|
|
2508
|
+
}, [autoHeight, bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, realContentHeight, renderHeight, rightFixedWidth, viewport.width]);
|
|
2496
2509
|
useLayoutEffect2(() => {
|
|
2497
2510
|
updateCustomScrollbars();
|
|
2498
2511
|
}, [contentHeight, contentWidth, updateCustomScrollbars, viewport.height, viewport.width]);
|
|
@@ -3318,9 +3331,15 @@ function Table({
|
|
|
3318
3331
|
scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
|
|
3319
3332
|
scheduleDraw();
|
|
3320
3333
|
}, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
|
|
3334
|
+
const isCellEditable = useCallback2((cell) => {
|
|
3335
|
+
const column = columns[cell.columnIndex];
|
|
3336
|
+
const row = rows[cell.rowIndex];
|
|
3337
|
+
if (!column || !row || column.dataIndex === void 0 || column.rowNumber || column.rowSelection || column.rowDragHandle) return false;
|
|
3338
|
+
return typeof column.editable === "function" ? column.editable(row[column.dataIndex], row, cell.rowIndex) : column.editable === true;
|
|
3339
|
+
}, [columns, rows]);
|
|
3321
3340
|
const beginEdit = useCallback2((cell) => {
|
|
3322
3341
|
const column = columns[cell.columnIndex];
|
|
3323
|
-
if (!
|
|
3342
|
+
if (!isCellEditable(cell) || column.dataIndex === void 0) return;
|
|
3324
3343
|
const value = rows[cell.rowIndex]?.[column.dataIndex];
|
|
3325
3344
|
setDraft(value == null ? "" : String(value));
|
|
3326
3345
|
const scroller = scrollerRef.current;
|
|
@@ -3362,7 +3381,7 @@ function Table({
|
|
|
3362
3381
|
return;
|
|
3363
3382
|
}
|
|
3364
3383
|
setEditing(cell);
|
|
3365
|
-
}, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
|
|
3384
|
+
}, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, isCellEditable, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
|
|
3366
3385
|
const getCellEditKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
|
|
3367
3386
|
const markCellEdited = (cell, previousValue, nextValue) => {
|
|
3368
3387
|
const key = getCellEditKey(cell);
|
|
@@ -3390,6 +3409,10 @@ function Table({
|
|
|
3390
3409
|
};
|
|
3391
3410
|
const commitEdit = useCallback2((nextDraft, close = true) => {
|
|
3392
3411
|
if (!editing) return;
|
|
3412
|
+
if (!isCellEditable(editing)) {
|
|
3413
|
+
setEditing(null);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3393
3416
|
const committedDraft = nextDraft ?? draft;
|
|
3394
3417
|
const column = columns[editing.columnIndex];
|
|
3395
3418
|
const row = rows[editing.rowIndex];
|
|
@@ -3404,7 +3427,7 @@ function Table({
|
|
|
3404
3427
|
onCellChange?.({ ...editing, row, value: committedValue, previousValue });
|
|
3405
3428
|
}
|
|
3406
3429
|
if (close) setEditing(null);
|
|
3407
|
-
}, [columns, draft, editing, onCellChange, rows]);
|
|
3430
|
+
}, [columns, draft, editing, isCellEditable, onCellChange, rows]);
|
|
3408
3431
|
const renderEditor = () => {
|
|
3409
3432
|
if (!editing || !editorStyle) return null;
|
|
3410
3433
|
const column = columns[editing.columnIndex];
|
|
@@ -3535,6 +3558,18 @@ function Table({
|
|
|
3535
3558
|
const { cell: resizeCell, edge: resizeEdge } = resizeHit;
|
|
3536
3559
|
event.preventDefault();
|
|
3537
3560
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
3561
|
+
setResizedColumnWidths((current) => {
|
|
3562
|
+
let changed = false;
|
|
3563
|
+
const next = { ...current };
|
|
3564
|
+
columns.forEach((column, index) => {
|
|
3565
|
+
if (column.rowSelection || column.rowDragHandle || column.rowNumber) return;
|
|
3566
|
+
const displayedWidth = metrics[index]?.width;
|
|
3567
|
+
if (displayedWidth === void 0 || next[column.key] === displayedWidth) return;
|
|
3568
|
+
next[column.key] = displayedWidth;
|
|
3569
|
+
changed = true;
|
|
3570
|
+
});
|
|
3571
|
+
return changed ? next : current;
|
|
3572
|
+
});
|
|
3538
3573
|
const resizeIndex = resizeCell.endIndex;
|
|
3539
3574
|
const resizeIndices = resizeCell.leaf ? [resizeIndex] : Array.from({ length: resizeCell.endIndex - resizeCell.startIndex + 1 }, (_, index) => resizeCell.startIndex + index);
|
|
3540
3575
|
const startWidths = resizeIndices.map((index) => metrics[index].width);
|
|
@@ -3567,7 +3602,7 @@ function Table({
|
|
|
3567
3602
|
const setGuideFromWidths = (nextWidths) => {
|
|
3568
3603
|
const widthByIndex = new Map(drag.resizeIndices.map((columnIndex, index) => [columnIndex, nextWidths[index]]));
|
|
3569
3604
|
const nextColumns = columns.map((column2, index) => widthByIndex.has(index) ? { ...column2, width: widthByIndex.get(index) } : column2);
|
|
3570
|
-
const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth:
|
|
3605
|
+
const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth: 0 });
|
|
3571
3606
|
setResizeGuideX(getHeaderCellEdgeFromMetrics(drag.resizeStartIndex, drag.resizeEndIndex, drag.resizeEdge, nextMetrics));
|
|
3572
3607
|
};
|
|
3573
3608
|
if (drag.resizeIndices.length > 1) {
|
|
@@ -4306,6 +4341,77 @@ function Table({
|
|
|
4306
4341
|
`summary:${column.key}`
|
|
4307
4342
|
);
|
|
4308
4343
|
};
|
|
4344
|
+
useImperativeHandle(ref, () => {
|
|
4345
|
+
const resolveDataCell = (target) => {
|
|
4346
|
+
const cell = resolveSelectionTarget(target);
|
|
4347
|
+
if (!cell) return null;
|
|
4348
|
+
const column = columns[cell.columnIndex];
|
|
4349
|
+
if (column.rowNumber || column.rowSelection || column.rowDragHandle) return null;
|
|
4350
|
+
const span = getCellSpan(cell.rowIndex, cell.columnIndex);
|
|
4351
|
+
if (!span) return cell;
|
|
4352
|
+
return {
|
|
4353
|
+
rowIndex: span.rowIndex,
|
|
4354
|
+
columnIndex: span.columnIndex,
|
|
4355
|
+
rowKey: getRowKey(rows[span.rowIndex], span.rowIndex),
|
|
4356
|
+
columnKey: columns[span.columnIndex].key
|
|
4357
|
+
};
|
|
4358
|
+
};
|
|
4359
|
+
return {
|
|
4360
|
+
focus: () => scrollerRef.current?.focus({ preventScroll: true }),
|
|
4361
|
+
scrollToCell: (target) => {
|
|
4362
|
+
const cell = resolveDataCell(target);
|
|
4363
|
+
if (!cell) return false;
|
|
4364
|
+
revealCell(cell);
|
|
4365
|
+
return true;
|
|
4366
|
+
},
|
|
4367
|
+
getSelectedCell: () => {
|
|
4368
|
+
if (!selection) return null;
|
|
4369
|
+
const cell = resolveDataCell(selection);
|
|
4370
|
+
if (!cell) return null;
|
|
4371
|
+
const column = columns[cell.columnIndex];
|
|
4372
|
+
const row = rows[cell.rowIndex];
|
|
4373
|
+
return { ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] };
|
|
4374
|
+
},
|
|
4375
|
+
setSelectedCell: (target) => {
|
|
4376
|
+
const cell = target === null ? null : resolveDataCell(target);
|
|
4377
|
+
if (target !== null && !cell) return false;
|
|
4378
|
+
setSelectionRange(null);
|
|
4379
|
+
setSelection(cell);
|
|
4380
|
+
return true;
|
|
4381
|
+
},
|
|
4382
|
+
getSelectedRowKeys: () => [...rowKeys],
|
|
4383
|
+
getSelectedRows: () => rows.filter((row, index) => selectedRowKeySet.has(getRowKey(row, index))),
|
|
4384
|
+
setSelectedRowKeys: (keys) => {
|
|
4385
|
+
if (!rowSelection) return;
|
|
4386
|
+
const next = [...new Set(keys)];
|
|
4387
|
+
setRowKeys(rowSelectionMode === "single" ? next.slice(0, 1) : next);
|
|
4388
|
+
},
|
|
4389
|
+
getSelectedColumnKeys: () => [...columnKeys],
|
|
4390
|
+
setSelectedColumnKeys: (keys) => {
|
|
4391
|
+
if (!columnSelection) return;
|
|
4392
|
+
const next = [...new Set(keys)].filter((key) => columns.some((column) => column.key === key && !column.rowNumber && !column.rowSelection && !column.rowDragHandle));
|
|
4393
|
+
setColumnKeys(typeof columnSelection === "object" && columnSelection.mode === "single" ? next.slice(0, 1) : next);
|
|
4394
|
+
},
|
|
4395
|
+
clearSelection: () => {
|
|
4396
|
+
setSelectionRange(null);
|
|
4397
|
+
setSelection(null);
|
|
4398
|
+
setRowKeys([]);
|
|
4399
|
+
setColumnKeys([]);
|
|
4400
|
+
rowAnchorRef.current = null;
|
|
4401
|
+
setEditing(null);
|
|
4402
|
+
},
|
|
4403
|
+
startEdit: (target) => {
|
|
4404
|
+
const cell = target ? resolveDataCell(target) : selection ? resolveDataCell(selection) : null;
|
|
4405
|
+
if (!cell || !isCellEditable(cell)) return false;
|
|
4406
|
+
setSelectionRange(null);
|
|
4407
|
+
setSelection(cell);
|
|
4408
|
+
beginEdit(cell);
|
|
4409
|
+
return true;
|
|
4410
|
+
},
|
|
4411
|
+
commitEdit: () => commitEdit(),
|
|
4412
|
+
cancelEdit: () => setEditing(null)
|
|
4413
|
+
};
|
|
4414
|
+
});
|
|
4309
4415
|
const rootStyle = {
|
|
4310
4416
|
width,
|
|
4311
4417
|
height: autoHeight ? effectiveHeight : height,
|
|
@@ -4504,6 +4610,10 @@ function Table({
|
|
|
4504
4610
|
}
|
|
4505
4611
|
if (event.detail > 1) return;
|
|
4506
4612
|
clickedCellRef.current = cell;
|
|
4613
|
+
const column = columns[cell.columnIndex];
|
|
4614
|
+
const row = rows[cell.rowIndex];
|
|
4615
|
+
onCellClick?.({ ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] }, event);
|
|
4616
|
+
if (event.defaultPrevented) return;
|
|
4507
4617
|
setSelectionRange(null);
|
|
4508
4618
|
scrollerRef.current?.focus({ preventScroll: true });
|
|
4509
4619
|
setSelection(cell);
|
|
@@ -4514,7 +4624,12 @@ function Table({
|
|
|
4514
4624
|
},
|
|
4515
4625
|
onDoubleClick: (event) => {
|
|
4516
4626
|
const cell = clickedCellRef.current ?? locateCell(event.clientX, event.clientY);
|
|
4517
|
-
if (cell)
|
|
4627
|
+
if (!cell) return;
|
|
4628
|
+
const column = columns[cell.columnIndex];
|
|
4629
|
+
if (column.rowNumber || column.rowSelection || column.rowDragHandle) return;
|
|
4630
|
+
const row = rows[cell.rowIndex];
|
|
4631
|
+
onCellDoubleClick?.({ ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] }, event);
|
|
4632
|
+
if (!event.defaultPrevented) beginEdit(cell);
|
|
4518
4633
|
},
|
|
4519
4634
|
onContextMenu: handleContextMenu
|
|
4520
4635
|
}
|
|
@@ -4527,7 +4642,7 @@ function Table({
|
|
|
4527
4642
|
className: "rvg-text-scroll-content",
|
|
4528
4643
|
style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
|
|
4529
4644
|
children: [
|
|
4530
|
-
columns.map((column, columnIndex) => column.fixed || columnIndex
|
|
4645
|
+
columns.map((column, columnIndex) => column.fixed || columnIndex >= utilityColumnCount && headerDepth > 1 ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ jsx7("span", { children: renderHeaderTitle(columnIndex, getScrollableColumnLeft(columnIndex)) }, column.key) : null),
|
|
4531
4646
|
headerCells.map((cell) => {
|
|
4532
4647
|
if (columns[cell.startIndex]?.fixed) return null;
|
|
4533
4648
|
if (cell.endIndex < domRange.columnStart || cell.startIndex >= domRange.columnEnd) return null;
|
|
@@ -4545,7 +4660,7 @@ function Table({
|
|
|
4545
4660
|
const left = column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
|
|
4546
4661
|
return renderCellText(rowIndex, columnIndex, left);
|
|
4547
4662
|
})) }),
|
|
4548
|
-
columns.map((column, columnIndex) => columnIndex < utilityColumnCount || headerDepth === 1
|
|
4663
|
+
columns.map((column, columnIndex) => column.fixed && (columnIndex < utilityColumnCount || headerDepth === 1) ? renderHeaderTitle(columnIndex, column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width : column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : getScrollableColumnLeft(columnIndex)) : null),
|
|
4549
4664
|
headerCells.map((cell) => columns[cell.startIndex]?.fixed ? renderGroupedHeaderCell(cell) : null),
|
|
4550
4665
|
fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-text-fixed-shadow is-left", style: { left: fixedWidth } }),
|
|
4551
4666
|
rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ jsx7("div", { className: "rvg-text-fixed-shadow is-right", style: { right: rightFixedWidth } }),
|
|
@@ -4762,7 +4877,7 @@ function Table({
|
|
|
4762
4877
|
const affectedRows = selectedIndices.includes(cell.rowIndex) ? selectedIndices : [cell.rowIndex];
|
|
4763
4878
|
const cellContext = { ...cell, row, column, value: dataIndex === void 0 ? void 0 : row[dataIndex] };
|
|
4764
4879
|
return renderConfiguredContextMenu(resolveContextMenuSection(customContextMenuConfig?.cell, DEFAULT_CELL_CONTEXT_MENU), cellContext, (item, override) => {
|
|
4765
|
-
if (item === "edit") return renderContextMenuButton("cell-edit", cellContext, labels.edit, () => beginEdit(cell), override, { disabled: !
|
|
4880
|
+
if (item === "edit") return renderContextMenuButton("cell-edit", cellContext, labels.edit, () => beginEdit(cell), override, { disabled: !isCellEditable(cell), icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "edit" }) });
|
|
4766
4881
|
if (item === "copy") return renderContextMenuButton("cell-copy", cellContext, labels.copyContent, async () => {
|
|
4767
4882
|
try {
|
|
4768
4883
|
await navigator.clipboard.writeText(getCellLabel(cell.rowIndex, cell.columnIndex));
|
|
@@ -5002,6 +5117,7 @@ function Table({
|
|
|
5002
5117
|
loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ jsx7("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ jsx7(LoadingSpinnerIcon, {}) })
|
|
5003
5118
|
] });
|
|
5004
5119
|
}
|
|
5120
|
+
var Table = forwardRef(TableInner);
|
|
5005
5121
|
export {
|
|
5006
5122
|
Table,
|
|
5007
5123
|
Table as VelocityGrid,
|