@yester/virtual-table 1.0.0
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 +168 -0
- package/dist/components/Icons/DownCircleOutlined.d.ts +4 -0
- package/dist/components/Icons/UpCircleOutlined.d.ts +4 -0
- package/dist/components/VirtualTable/Cell.d.ts +15 -0
- package/dist/components/VirtualTable/Renderer.d.ts +25 -0
- package/dist/components/VirtualTable/TableHeader.d.ts +10 -0
- package/dist/components/VirtualTable/index.d.ts +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/style.css +1 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/types/index.d.ts +80 -0
- package/dist/utils/cloneDeep.d.ts +1 -0
- package/dist/utils/dataHandler.d.ts +47 -0
- package/dist/utils/pivotHandler.d.ts +15 -0
- package/dist/utils/vars.d.ts +9 -0
- package/dist/virtual-table.es.js +2837 -0
- package/dist/virtual-table.es.js.map +1 -0
- package/dist/virtual-table.umd.js +53 -0
- package/dist/virtual-table.umd.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Virtual Table
|
|
2
|
+
|
|
3
|
+
A high-performance virtual scrolling table component for React, supporting Pivot Table, Group Table, and Detail Table modes. Capable of handling large datasets efficiently using `react-window`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **High Performance**: Renders thousands of rows smoothly using virtual scrolling.
|
|
8
|
+
- 📊 **Pivot Table**: Supports multi-dimensional data analysis with row/column grouping and aggregation.
|
|
9
|
+
- 📑 **Group Table**: Supports row grouping with expandable/collapsible rows.
|
|
10
|
+
- 📋 **Detail Table**: Standard list view for detailed data.
|
|
11
|
+
- 🔄 **Sortable**: Supports sorting on multiple fields.
|
|
12
|
+
- 🎨 **Customizable**: Flexible styling and cell rendering.
|
|
13
|
+
- 📦 **Lightweight**: No heavy dependencies (lodash removed, icons extracted).
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add virtual-table
|
|
19
|
+
# or
|
|
20
|
+
npm install virtual-table
|
|
21
|
+
# or
|
|
22
|
+
yarn add virtual-table
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Basic Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import React from 'react';
|
|
31
|
+
import { VirtualTable } from 'virtual-table';
|
|
32
|
+
import 'virtual-table/dist/style.css'; // Import styles
|
|
33
|
+
|
|
34
|
+
const App = () => {
|
|
35
|
+
const data = [
|
|
36
|
+
{ province: 'Zhejiang', city: 'Hangzhou', type: 'Furniture', amount: 10 },
|
|
37
|
+
// ... more data
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const params = {
|
|
41
|
+
data,
|
|
42
|
+
meta: [],
|
|
43
|
+
sortParams: [],
|
|
44
|
+
fields: {
|
|
45
|
+
rows: [{ field: 'province', title: 'Province', width: 150 }],
|
|
46
|
+
columns: [{ field: 'type', title: 'Type', width: 120 }],
|
|
47
|
+
values: [{ field: 'amount', title: 'Amount', calculateType: 'sum', width: 100 }]
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div style={{ height: 500 }}>
|
|
53
|
+
<VirtualTable
|
|
54
|
+
{...params}
|
|
55
|
+
scroll={{ y: 500 }}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Modes
|
|
63
|
+
|
|
64
|
+
### 1. Pivot Table Mode
|
|
65
|
+
Configure `rows`, `columns`, and `values` in `fields`.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
const pivotFields = {
|
|
69
|
+
rows: [
|
|
70
|
+
{ field: 'province', title: 'Province', width: 120, total: { enabled: true, label: 'Total' } },
|
|
71
|
+
{ field: 'city', title: 'City', width: 120 }
|
|
72
|
+
],
|
|
73
|
+
columns: [
|
|
74
|
+
{ field: 'type', title: 'Type', width: 120 }
|
|
75
|
+
],
|
|
76
|
+
values: [
|
|
77
|
+
{ field: 'amount', title: 'Amount', calculateType: 'sum', width: 100 }
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Group Table Mode
|
|
83
|
+
Configure `rows` and `values`, leave `columns` empty.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const groupFields = {
|
|
87
|
+
rows: [
|
|
88
|
+
{ field: 'province', title: 'Province', width: 120 },
|
|
89
|
+
{ field: 'city', title: 'City', width: 120 }
|
|
90
|
+
],
|
|
91
|
+
columns: [],
|
|
92
|
+
values: [
|
|
93
|
+
{ field: 'amount', title: 'Amount', calculateType: 'sum', width: 100 }
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Detail Table Mode
|
|
99
|
+
Configure only `values` as a flat list of columns.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
const detailFields = {
|
|
103
|
+
rows: [],
|
|
104
|
+
columns: [],
|
|
105
|
+
values: [
|
|
106
|
+
{ field: 'province', title: 'Province', width: 120 },
|
|
107
|
+
{ field: 'city', title: 'City', width: 120 },
|
|
108
|
+
{ field: 'amount', title: 'Amount', width: 100 }
|
|
109
|
+
]
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
### VirtualTable Props
|
|
116
|
+
|
|
117
|
+
| Property | Type | Description |
|
|
118
|
+
|Data | `any[]` | Source data array |
|
|
119
|
+
| `fields` | `PivotFields` | Configuration for rows, columns, and values |
|
|
120
|
+
| `meta` | `any[]` | Meta information (optional) |
|
|
121
|
+
| `sortParams` | `SortParam[]` | Sorting configuration |
|
|
122
|
+
| `scroll` | `{ x?: number \| string; y?: number \| string }` | Scroll configuration. `y` is required for virtual scrolling height |
|
|
123
|
+
| `className` | `string` | Custom CSS class |
|
|
124
|
+
| `style` | `React.CSSProperties` | Custom styles |
|
|
125
|
+
|
|
126
|
+
### PivotFields
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
interface PivotFields {
|
|
130
|
+
rows: CustomTreeNode[]; // Row dimensions
|
|
131
|
+
columns: CustomTreeNode[]; // Column dimensions
|
|
132
|
+
values: CustomTreeNode[]; // Value fields (metrics)
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### CustomTreeNode (Field Configuration)
|
|
137
|
+
|
|
138
|
+
| Property | Type | Description |
|
|
139
|
+
|----------|------|-------------|
|
|
140
|
+
| `field` | `string` | Data field key |
|
|
141
|
+
| `title` | `ReactNode` | Column header title |
|
|
142
|
+
| `width` | `number \| string` | Column width |
|
|
143
|
+
| `calculateType` | `'sum' \| 'avg' \| 'count' ...` | Aggregation type (for values) |
|
|
144
|
+
| `total` | `{ enabled: boolean; label?: string }` | Subtotal configuration (for rows) |
|
|
145
|
+
| `emptyReplace` | `string` | Replacement for empty values |
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Install dependencies
|
|
151
|
+
pnpm install
|
|
152
|
+
|
|
153
|
+
# Run dev server
|
|
154
|
+
pnpm dev
|
|
155
|
+
|
|
156
|
+
# Build library
|
|
157
|
+
pnpm build
|
|
158
|
+
|
|
159
|
+
# Run tests
|
|
160
|
+
pnpm test
|
|
161
|
+
|
|
162
|
+
# Release (Test -> Build -> Publish)
|
|
163
|
+
pnpm release
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { DataCell, CustomTreeNode, MetaItem } from '../../types';
|
|
3
|
+
|
|
4
|
+
export interface CellProps {
|
|
5
|
+
columnIndex: number;
|
|
6
|
+
rowIndex: number;
|
|
7
|
+
style: React.CSSProperties;
|
|
8
|
+
mergedData: DataCell[][];
|
|
9
|
+
columns: CustomTreeNode[];
|
|
10
|
+
data: any[];
|
|
11
|
+
handleExpand: (record: any) => void;
|
|
12
|
+
meta?: MetaItem[];
|
|
13
|
+
}
|
|
14
|
+
declare const Cell: React.FC<CellProps>;
|
|
15
|
+
export default Cell;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { TableRow, CustomTreeNode, MetaItem } from '../../types';
|
|
3
|
+
|
|
4
|
+
interface RendererProps {
|
|
5
|
+
data: TableRow[];
|
|
6
|
+
info: {
|
|
7
|
+
ref: any;
|
|
8
|
+
onScroll: (props: {
|
|
9
|
+
scrollLeft: number;
|
|
10
|
+
scrollTop: number;
|
|
11
|
+
}) => void;
|
|
12
|
+
scrollbarSize?: number;
|
|
13
|
+
};
|
|
14
|
+
scroll?: {
|
|
15
|
+
x?: number | string;
|
|
16
|
+
y?: number | string;
|
|
17
|
+
};
|
|
18
|
+
columns: CustomTreeNode[];
|
|
19
|
+
tableWidth: number;
|
|
20
|
+
gridWidth: number;
|
|
21
|
+
handleExpand: (record: any) => void;
|
|
22
|
+
meta?: MetaItem[];
|
|
23
|
+
}
|
|
24
|
+
declare const Renderer: React.FC<RendererProps>;
|
|
25
|
+
export default Renderer;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { CustomTreeNode } from '../../types';
|
|
3
|
+
|
|
4
|
+
interface TableHeaderProps {
|
|
5
|
+
columns: CustomTreeNode[];
|
|
6
|
+
width: number;
|
|
7
|
+
onScroll: (e: React.UIEvent<HTMLDivElement>) => void;
|
|
8
|
+
}
|
|
9
|
+
declare const TableHeader: React.ForwardRefExoticComponent<TableHeaderProps & React.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
export default TableHeader;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { PivotParams } from '../../types';
|
|
3
|
+
|
|
4
|
+
interface VirtualTableProps extends PivotParams {
|
|
5
|
+
scroll?: {
|
|
6
|
+
x?: number | string;
|
|
7
|
+
y?: number | string;
|
|
8
|
+
};
|
|
9
|
+
className?: string;
|
|
10
|
+
style?: React.CSSProperties;
|
|
11
|
+
}
|
|
12
|
+
declare const _default: React.MemoExoticComponent<(props: VirtualTableProps) => import("react/jsx-runtime").JSX.Element>;
|
|
13
|
+
export default _default;
|
package/dist/index.d.ts
ADDED
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.virtual-table .virtual-table-container:before,.virtual-table .virtual-table-container:after{display:none}.virtual-table,.virtual-table .virtual-table{--virtual-table-scrollbar-width: 0px !important}.virtual-table table{table-layout:fixed!important;width:100%!important;border-spacing:0}.virtual-table .virtual-table-header th.virtual-table-cell{background:#fafafa;border-bottom:1px solid #e8e8e8;font-weight:500;padding:10px 16px;border-right:1px solid #e8e8e8;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.virtual-table .virtual-table-thead>tr:first-child>th{border-top:1px solid #e8e8e8}.virtual-table .virtual-table-thead>tr>th:first-child{border-left:1px solid #e8e8e8}.virtual-table .virtual-table-thead>tr:not(:last-child)>th[colspan]{border-bottom:1px solid #e8e8e8}.virtual-table-cell{box-sizing:border-box;padding:10px 16px;line-height:1.5;border-bottom:1px solid #e8e8e8;background:#fff}[data-theme=dark] .virtual-table-cell{box-sizing:border-box;line-height:1.5;padding:10px 16px;border-bottom:1px solid #303030;background:#141414}[data-theme=dark] .virtual-table .virtual-table-header th.virtual-table-cell{background:#1d1d1d;border-bottom:1px solid #303030;border-right:1px solid #303030}[data-theme=dark] .virtual-table .virtual-table-thead>tr:not(:last-child)>th[colspan]{border-bottom:1px solid #303030}.expand-icon{cursor:pointer;color:var(--primary-color);margin-right:4px;line-height:0}.hide-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.hide-scrollbar::-webkit-scrollbar{display:none}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CustomTreeNode {
|
|
4
|
+
/**
|
|
5
|
+
* 字段唯一标识
|
|
6
|
+
*/
|
|
7
|
+
field: string;
|
|
8
|
+
/**
|
|
9
|
+
* 标题
|
|
10
|
+
*/
|
|
11
|
+
title?: string;
|
|
12
|
+
/**
|
|
13
|
+
* 是否收起(默认都展开)
|
|
14
|
+
* @description 优先级 `collapseFields` > `expandDepth` > `collapseAll` > `collapsed`
|
|
15
|
+
*/
|
|
16
|
+
collapsed?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 字段描述
|
|
19
|
+
*/
|
|
20
|
+
description?: string;
|
|
21
|
+
/**
|
|
22
|
+
* 子节点
|
|
23
|
+
*/
|
|
24
|
+
children?: CustomTreeNode[];
|
|
25
|
+
width?: number | string;
|
|
26
|
+
total?: {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
label?: string;
|
|
29
|
+
position?: 'top' | 'bottom';
|
|
30
|
+
};
|
|
31
|
+
emptyReplace?: string;
|
|
32
|
+
calculateType?: 'sum' | 'avg' | 'count' | 'min' | 'max' | 'd_count' | 'variance' | 'stddev';
|
|
33
|
+
sort?: {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
type: 'asc' | 'desc';
|
|
36
|
+
};
|
|
37
|
+
style?: React.CSSProperties;
|
|
38
|
+
fixed?: boolean | string;
|
|
39
|
+
colSpan?: number;
|
|
40
|
+
dataIndex?: string;
|
|
41
|
+
render?: (val: any, record: any, index: number) => React.ReactNode;
|
|
42
|
+
type?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface DataCell {
|
|
45
|
+
content: string | number | null | undefined;
|
|
46
|
+
rowspan: number;
|
|
47
|
+
colspan: number;
|
|
48
|
+
data?: any;
|
|
49
|
+
rowKey?: string;
|
|
50
|
+
expandable?: boolean;
|
|
51
|
+
expanded?: boolean;
|
|
52
|
+
level?: number;
|
|
53
|
+
onClick?: (record: any) => void;
|
|
54
|
+
style?: React.CSSProperties;
|
|
55
|
+
}
|
|
56
|
+
export interface TableRow {
|
|
57
|
+
cells: DataCell[];
|
|
58
|
+
rowKey: string;
|
|
59
|
+
}
|
|
60
|
+
export interface MetaItem {
|
|
61
|
+
field?: string;
|
|
62
|
+
title?: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
clickHandler?: (data: any) => void;
|
|
65
|
+
}
|
|
66
|
+
export interface SortParam {
|
|
67
|
+
field: string;
|
|
68
|
+
sortType: 'asc' | 'desc';
|
|
69
|
+
}
|
|
70
|
+
export interface PivotFields {
|
|
71
|
+
rows: CustomTreeNode[];
|
|
72
|
+
columns: CustomTreeNode[];
|
|
73
|
+
values: CustomTreeNode[];
|
|
74
|
+
}
|
|
75
|
+
export interface PivotParams {
|
|
76
|
+
data: any[];
|
|
77
|
+
meta: MetaItem[];
|
|
78
|
+
sortParams: SortParam[];
|
|
79
|
+
fields: PivotFields;
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const cloneDeep: <T>(obj: T) => T;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PivotParams, DataCell, TableRow } from '../types';
|
|
2
|
+
|
|
3
|
+
export declare const dataHandler: (params: PivotParams) => {
|
|
4
|
+
list: never[];
|
|
5
|
+
dataExpandFilter: (list: any[]) => any[];
|
|
6
|
+
tableColumns?: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
list: {
|
|
9
|
+
cells: DataCell[];
|
|
10
|
+
rowKey: string;
|
|
11
|
+
}[];
|
|
12
|
+
dataExpandFilter: (list: TableRow[]) => TableRow[];
|
|
13
|
+
tableColumns: import('../types').CustomTreeNode[];
|
|
14
|
+
} | {
|
|
15
|
+
list: never[];
|
|
16
|
+
dataExpandFilter: (l: any) => any;
|
|
17
|
+
tableColumns?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
list: TableRow[];
|
|
20
|
+
dataExpandFilter: (l: any[]) => any[];
|
|
21
|
+
tableColumns: {
|
|
22
|
+
width: string | number;
|
|
23
|
+
key: string;
|
|
24
|
+
field: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
collapsed?: boolean;
|
|
27
|
+
description?: string;
|
|
28
|
+
children?: import('../types').CustomTreeNode[];
|
|
29
|
+
total?: {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
label?: string;
|
|
32
|
+
position?: "top" | "bottom";
|
|
33
|
+
};
|
|
34
|
+
emptyReplace?: string;
|
|
35
|
+
calculateType?: "sum" | "avg" | "count" | "min" | "max" | "d_count" | "variance" | "stddev";
|
|
36
|
+
sort?: {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
type: "asc" | "desc";
|
|
39
|
+
};
|
|
40
|
+
style?: React.CSSProperties;
|
|
41
|
+
fixed?: boolean | string;
|
|
42
|
+
colSpan?: number;
|
|
43
|
+
dataIndex?: string;
|
|
44
|
+
render?: (val: any, record: any, index: number) => React.ReactNode;
|
|
45
|
+
type?: string;
|
|
46
|
+
}[];
|
|
47
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CustomTreeNode, PivotParams, DataCell, TableRow } from '../types';
|
|
2
|
+
|
|
3
|
+
declare const pivotDataHandler: (params: PivotParams) => {
|
|
4
|
+
list: never[];
|
|
5
|
+
dataExpandFilter: (list: any[]) => any[];
|
|
6
|
+
tableColumns?: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
list: {
|
|
9
|
+
cells: DataCell[];
|
|
10
|
+
rowKey: string;
|
|
11
|
+
}[];
|
|
12
|
+
dataExpandFilter: (list: TableRow[]) => TableRow[];
|
|
13
|
+
tableColumns: CustomTreeNode[];
|
|
14
|
+
};
|
|
15
|
+
export default pivotDataHandler;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表格中,单元格的值为该值时不显示单元格
|
|
3
|
+
* 用于与空值区分
|
|
4
|
+
*/
|
|
5
|
+
export declare const DISPLAY_NONE = "display__none";
|
|
6
|
+
export declare const ROW_HEIGHT = 43;
|
|
7
|
+
export declare const COL_WIDTH = 100;
|
|
8
|
+
export declare const EMPTY_VALUE = "-";
|
|
9
|
+
export declare const TOTAL_DEFAULT_VALUE = "\u5408\u8BA1";
|