├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .umirc.ts
├── LICENSE
├── README.md
├── __mocks__
├── fileMock.js
└── styleMock.js
├── babel.config.js
├── docs
├── demo
│ ├── AsyncTable.md
│ ├── DragHandleRow.md
│ ├── DragRow.md
│ ├── EasyTable.md
│ ├── JumpTreeTable.md
│ ├── PaginationTable.md
│ ├── SinglePageLoading.md
│ ├── TabsDemo.md
│ └── TreeTable.md
├── examples
│ ├── AsyncTable.tsx
│ ├── DragHandleRow.tsx
│ ├── DragRow.tsx
│ ├── EasyTable.tsx
│ ├── JumpTreeTable.tsx
│ ├── PaginationTable.tsx
│ ├── SinglePageLoading.tsx
│ ├── TabsDemo.tsx
│ └── TreeTable.tsx
├── index.md
└── update.md
├── jest.config.js
├── package.json
├── rollup.config.js
├── src
├── index.tsx
└── style.css
├── tests
└── basic.test.js
├── tsconfig.json
└── update.md
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2020: true,
5 | jest: true,
6 | },
7 | globals: {
8 | JSX: true,
9 | jsdom: true,
10 | },
11 | extends: ['plugin:react/recommended', 'airbnb'],
12 | parser: '@typescript-eslint/parser',
13 | parserOptions: {
14 | ecmaFeatures: {
15 | jsx: true,
16 | },
17 | ecmaVersion: 11,
18 | sourceType: 'module',
19 | },
20 | plugins: ['react', 'react-hooks', '@typescript-eslint'],
21 | rules: {
22 | 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
23 | 'react-hooks/exhaustive-deps': 'warn',
24 | 'react/jsx-filename-extension': [
25 | 2,
26 | { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
27 | ],
28 | 'react/jsx-props-no-spreading': 'off',
29 | 'no-use-before-define': 'off',
30 | camelcase: 'off',
31 | 'no-bitwise': 'off',
32 | 'no-underscore-dangle': 'off',
33 | 'jsx-a11y/anchor-is-valid': 'off',
34 | 'jsx-a11y/no-static-element-interactions': 'off',
35 | 'jsx-a11y/click-events-have-key-events': 'off',
36 | 'import/extensions': 'off',
37 | 'import/no-unresolved': 'off',
38 | 'import/no-extraneous-dependencies': 'off',
39 | '@typescript-eslint/no-use-before-define': ['error'],
40 | indent: 'off',
41 | 'react/jsx-indent': 'off',
42 | semi: 'off',
43 | 'react/jsx-indent-props': 'off',
44 | 'implicit-arrow-linebreak': 'off',
45 | 'comma-dangle': 'off',
46 | 'object-curly-newline': 'off',
47 | 'operator-linebreak': 'off',
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | build
13 | dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | yarn.lock
26 | package-lock.json
27 |
28 | # VisualStudioCode
29 | .vscode/*
30 |
31 | # Jetbrains
32 | .idea/*
33 | .umi
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | update.md
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "eslintIntegration": true,
3 | "stylelintIntegration": true,
4 | "tabWidth": 4,
5 | "singleQuote": true,
6 | "semi": false
7 | }
8 |
--------------------------------------------------------------------------------
/.umirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 |
3 | export default defineConfig({
4 | title: 'virtuallist-antd',
5 | favicon: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
6 | logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
7 | outputPath: 'docs-dist',
8 | // more config: https://d.umijs.org/config
9 | });
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-present crawler-django
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # virtuallist-antd
2 |
3 | >
4 |
5 | [](https://www.npmjs.com/package/virtuallist-antd) [](https://standardjs.com)
6 |
7 | > 仓库(github): https://github.com/crawler-django/virtuallist-antd
8 |
9 | > 版本更新记录(update detail): https://github.com/crawler-django/virtuallist-antd/blob/master/update.md
10 |
11 | virtualList for antd-table, 实现 antd-table 的虚拟列表, antd-table 无限滚动, infinite scrolling for antd-table. 支持 antd 树形表格, antd 版本要在 4.17.0 及以上, virtuallist-antd 要再 0.6.3 及以上. (support tree data, after antd v4.17.0, after virtuallist-antd v0.6.3)
12 |
13 | - 支持antd4, antd5.(4.x / 5.x ---- v0.3.0 后, 3.x ---- v0.2.8. antd3对应的版本不再更新)
14 |
15 | (support antdv4, antdv5 --- ^v0.3.0, 3.x --- v0.2.8. 3.x not updated)
16 |
17 | - 你可以像平常一样在 columns 里使用 fixed
18 |
19 | (u can use fixed as usual)
20 |
21 | - 支持进行条件搜索 变更数据.
22 |
23 | (support search data as usual)
24 |
25 | - 目前用了节流 - 60ms 在滚动的时候刷新窗口
26 |
27 | (use throttle, 60ms)
28 |
29 | - 支持分页, calc().
30 |
31 | (support pagination, support scrolly for calc())
32 |
33 | - 只支持纵向虚拟列表.
34 |
35 | (only support vertical virtuallist)
36 |
37 | - 此组件会计算第一行的高度, 并且以第一行的高度为准来固定每行的高度. 组件有自带的 css, 会使每行的 td 不会换行.
38 |
39 | (this component will calculate first line's height and amend following each line's height based on it. It has its own css, which prevents each line's TD from wrapping (td do not wrap))
40 |
41 | ## example
42 |
43 | - [简单的例子(easy example)](https://codesandbox.io/s/festive-worker-wc5wp)
44 | - [简单的分页例子(easy pagination example)](https://codesandbox.io/s/gracious-resonance-tmw44)
45 | - [简单的 resize 例子(easy resize columns example)](https://codesandbox.io/s/vibrant-darkness-kvt56?file=/index.js)
46 | - [简单的单页无限加载例子(easy infinite load data on single page that example)](https://codesandbox.io/s/reachend-wuxianjiazaixunigundong-y9nhd)
47 | - [简单的 scrollTo 例子(easy scrollTo example)](https://codesandbox.io/s/scrollto-jx10t)
48 | - [简单的树形表格例子(easy tree table example)](https://codesandbox.io/s/reachend-wuxianjiazaixunigundong-forked-63iom?file=/src/index.tsx)
49 |
50 | ## complex example
51 |
52 | - [拖拽行(drag row)](https://codesandbox.io/s/drag-row-1fjg4?file=/index.js)
53 | - [拖拽手柄列(drag row in handle-icon)](https://codesandbox.io/s/tuozhuaishoubinglie-antd4156-forked-1d6z1?file=/index.js)
54 | - [编辑列(edit cell)](https://codesandbox.io/s/editable-example-3656ln?file=/src/App.js)
55 | - [异步列表/onListRender 例子(async Table, onListRender demo)](https://codesandbox.io/s/shu-xing-biao-ge-forked-4lt6u?file=/src/index.tsx)
56 |
57 | ## Install
58 |
59 | ```bash
60 | npm install --save virtuallist-antd
61 | ```
62 |
63 | ## Usage
64 |
65 | ```tsx
66 | import React, { useMemo } from 'react'
67 | import ReactDom from 'react-dom'
68 |
69 | import { VList } from 'virtuallist-antd'
70 | import { Table } from 'antd'
71 |
72 | function Example(): JSX.Element {
73 | const dataSource = [...]
74 | const columns = [...]
75 | const rowkey = 'xxx'
76 |
77 | const vComponents = useMemo(() => {
78 | // 使用VList 即可有虚拟列表的效果
79 | return VList({
80 | height: 1000 // 此值和scrollY值相同. 必传. (required). same value for scrolly
81 | })
82 | }, [])
83 |
84 | return (
85 |
94 | )
95 | }
96 |
97 | ReactDom.render(, dom)
98 |
99 | ```
100 |
101 | ## VList
102 |
103 | ```tsx
104 | VList({
105 | height: number | string, // (必填) 对应scrollY.
106 | onReachEnd: () => void, // (可选) 滚动条滚到底部触发api. (scrollbar to the end)
107 | onScroll: () => void, // (可选) 滚动时触发的api. (triggered by scrolling)
108 | vid: string, // (可选, 如果同一页面存在多个虚拟表格时必填.) 唯一标识. (unique vid, required when exist more vitual table on a page)
109 | resetTopWhenDataChange: boolean, // 默认为true. 是否数据变更后重置滚动条 (default true, Whether to reset scrollTop when data changes)
110 | })
111 |
112 | VList returns: {
113 | table: VTable,
114 | body: {
115 | wrapper: VWrapper,
116 | row: VRow,
117 | cell: VCell,
118 | }
119 | }
120 | ```
121 |
122 | ## api
123 |
124 | ```tsx
125 | import { scrollTo } from 'virtuallist-antd'
126 |
127 | // scrollTo
128 | scrollTo({
129 | row: number, // 行数. (row number)
130 | y: number, // y偏移量. (offset Y)
131 | vid: string, // 对应VList的vid. (same as VList vid)
132 | })
133 | ```
134 |
135 | ## License
136 |
137 | MIT © [crawler-django](https://github.com/crawler-django)
138 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: true } }],
4 | '@babel/preset-react',
5 | '@babel/preset-typescript',
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/docs/demo/AsyncTable.md:
--------------------------------------------------------------------------------
1 | ## AsyncTable
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/demo/DragHandleRow.md:
--------------------------------------------------------------------------------
1 | ## DragHandleRow
2 |
3 |
--------------------------------------------------------------------------------
/docs/demo/DragRow.md:
--------------------------------------------------------------------------------
1 | ## DragRow
2 |
3 |
--------------------------------------------------------------------------------
/docs/demo/EasyTable.md:
--------------------------------------------------------------------------------
1 | ## EasyTable
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/demo/JumpTreeTable.md:
--------------------------------------------------------------------------------
1 | ## JumpTreeTable
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/demo/PaginationTable.md:
--------------------------------------------------------------------------------
1 | ## PaginationTable
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/demo/SinglePageLoading.md:
--------------------------------------------------------------------------------
1 | ## SinglePageLoading
2 |
3 |
--------------------------------------------------------------------------------
/docs/demo/TabsDemo.md:
--------------------------------------------------------------------------------
1 | ## TabsDemo
2 |
3 |
--------------------------------------------------------------------------------
/docs/demo/TreeTable.md:
--------------------------------------------------------------------------------
1 | ## TreeTable
2 |
3 |
--------------------------------------------------------------------------------
/docs/examples/AsyncTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react'
2 | import { Skeleton, Table, Button } from 'antd'
3 | import { VList } from '../../src/index'
4 |
5 | import 'antd/dist/antd.css'
6 |
7 | const generateData = () => {
8 | const temp = []
9 |
10 | for (let i = 0; i < 300; i += 1) {
11 | temp.push({
12 | a: i,
13 | b: '233',
14 | c: null,
15 | })
16 | }
17 |
18 | return temp
19 | }
20 |
21 | // const initData = generateData()
22 |
23 | function AsyncTable() {
24 | const [data, setData] = useState(generateData())
25 |
26 | const refreshFlag = useRef(false)
27 |
28 | const handleClick = (record, e) => {
29 | e.preventDefault()
30 | }
31 |
32 | const lastListRenderInfo = useRef({ start: -1, renderLen: -1 })
33 |
34 | // 防抖函数
35 | // const useDebounce = (fn, delay) => {
36 | // const { current } = useRef({ fn, timer: null })
37 | // useEffect(() => {
38 | // current.fn = fn
39 | // }, [fn])
40 |
41 | // return useCallback(function f(...args) {
42 | // if (current.timer) {
43 | // clearTimeout(current.timer)
44 | // }
45 | // current.timer = setTimeout(() => {
46 | // current.fn.call(this, ...args)
47 | // }, delay)
48 | // }, [])
49 | // }
50 |
51 | const onListRender = useCallback((listInfo) => {
52 | const { start, renderLen } = listInfo
53 |
54 | const lastInfo = lastListRenderInfo?.current
55 |
56 | if (
57 | start !== lastInfo?.start ||
58 | renderLen !== lastInfo?.renderLen ||
59 | refreshFlag.current === true
60 | ) {
61 | lastListRenderInfo.current = { start, renderLen }
62 | setData((pre) => {
63 | const currentData = pre.slice(start, start + renderLen)
64 |
65 | currentData.forEach((item, index) => {
66 | item.c = `asyncData${start + index + 1}`
67 | })
68 |
69 | const newData = JSON.parse(JSON.stringify(pre))
70 |
71 | return newData
72 | })
73 |
74 | refreshFlag.current = false
75 | }
76 |
77 | // const currentData = data.slice(start, start + renderLen)
78 | // currentData.forEach((item, index) => {
79 | // item.c = `asyncData${start + index + 1}`
80 | // })
81 | // const newData = JSON.parse(JSON.stringify(data))
82 | // setData(newData)
83 | }, [])
84 |
85 | const columns = [
86 | {
87 | title: 'title1',
88 | dataIndex: 'a',
89 | key: 'a',
90 | width: 150,
91 | },
92 | {
93 | title: 'title2',
94 | dataIndex: 'b',
95 | key: 'b',
96 | width: 200,
97 | },
98 | {
99 | title: 'title3',
100 | dataIndex: 'c',
101 | key: 'c',
102 | width: 200,
103 | render: (t) => t || ,
104 | },
105 | {
106 | title: 'Operations',
107 | dataIndex: '',
108 | width: 200,
109 | key: 'x',
110 | render: (text, record) => (
111 | handleClick(record, e)}>
112 | click {record.a}
113 |
114 | ),
115 | },
116 | ]
117 |
118 | const handleSearchBtnClick = useCallback(() => {
119 | setData(generateData())
120 | refreshFlag.current = true
121 | }, [])
122 |
123 | const vComponent = useMemo(
124 | () =>
125 | VList({
126 | height: 500,
127 | // vid: 'asyncTable',
128 | resetTopWhenDataChange: false,
129 | onListRender,
130 | }),
131 | [onListRender]
132 | )
133 |
134 | return (
135 |
136 |
137 |
async table
138 |
record.a}
142 | pagination={false}
143 | scroll={{ y: 500, x: '100%' }}
144 | components={vComponent}
145 | />
146 |
147 | )
148 | }
149 |
150 | export default AsyncTable
151 |
--------------------------------------------------------------------------------
/docs/examples/DragHandleRow.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useState } from 'react'
2 | import { Table } from 'antd'
3 | import {
4 | sortableContainer,
5 | sortableElement,
6 | sortableHandle,
7 | } from 'react-sortable-hoc'
8 | import { MenuOutlined } from '@ant-design/icons'
9 | import { arrayMoveImmutable } from 'array-move'
10 | import { VList } from '../../src/index'
11 |
12 | const vlistComponents = VList({ height: 500, resetTopWhenDataChange: false })
13 | const VRow = vlistComponents.body.row
14 | const VWrapper = vlistComponents.body.wrapper
15 | const DragHandle = sortableHandle(() => (
16 |
17 | ))
18 |
19 | const SortableItem = sortableElement((props) => )
20 | const SortableContainer = sortableContainer((props) => )
21 |
22 | const columns = [
23 | {
24 | title: 'Sort',
25 | dataIndex: 'sort',
26 | width: 100,
27 | className: 'drag-visible',
28 | render: () => ,
29 | },
30 | {
31 | title: 'Name',
32 | dataIndex: 'name',
33 | className: 'drag-visible',
34 | },
35 | {
36 | title: 'Age',
37 | dataIndex: 'age',
38 | },
39 | {
40 | title: 'Address',
41 | dataIndex: 'address',
42 | },
43 | ]
44 |
45 | const generateData = () => {
46 | const temp = []
47 |
48 | for (let i = 0; i < 100; i += 1) {
49 | temp.push({
50 | key: `${i + 1}`,
51 | name: `John Brown${i}`,
52 | age: 32,
53 | address: 'New York No. 1 Lake Park',
54 | index: i,
55 | })
56 | }
57 |
58 | return temp
59 | }
60 |
61 | const data = generateData()
62 |
63 | class SortableTable extends React.Component {
64 | state = {
65 | dataSource: data,
66 | }
67 |
68 | onSortEnd = ({ oldIndex, newIndex }) => {
69 | const { dataSource } = this.state
70 | if (oldIndex !== newIndex) {
71 | const newData = arrayMoveImmutable(
72 | [].concat(dataSource),
73 | oldIndex,
74 | newIndex
75 | ).filter((el) => !!el)
76 | console.log('Sorted items: ', newData)
77 | this.setState({ dataSource: newData })
78 | }
79 | }
80 |
81 | DraggableContainer = (props) => (
82 |
89 | )
90 |
91 | DraggableBodyRow = ({ className, style, ...restProps }) => {
92 | const { dataSource } = this.state
93 | // function findIndex base on Table rowKey props and should always be a right array index
94 | const index = dataSource.findIndex(
95 | (x) => x.index === restProps['data-row-key']
96 | )
97 | return
98 | }
99 |
100 | render() {
101 | const { dataSource } = this.state
102 |
103 | return (
104 |
119 | )
120 | }
121 | }
122 |
123 | export default SortableTable
124 |
--------------------------------------------------------------------------------
/docs/examples/DragRow.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState,
3 | useMemo,
4 | useCallback,
5 | useEffect,
6 | useRef,
7 | } from 'react';
8 | import { Table } from 'antd';
9 | import { Resizable } from 'react-resizable';
10 | import { DndProvider, useDrag, useDrop } from 'react-dnd';
11 | import { HTML5Backend } from 'react-dnd-html5-backend';
12 | import update from 'immutability-helper';
13 | import { VList } from '../../src/index';
14 | import 'antd/dist/antd.css';
15 |
16 | const type = 'DragableBodyRow';
17 |
18 | const vlistComponent = VList({
19 | height: 500,
20 | resetTopWhenDataChange: false,
21 | });
22 |
23 | const generateData = () => {
24 | const arr = [];
25 |
26 | for (let i = 0; i < 100; i += 1) {
27 | arr.push({
28 | key: i,
29 | date: '天道轮回'.repeat(Math.floor(Math.random() * 10)),
30 | amount: 120,
31 | type: 'income',
32 | note: 'transfer',
33 | });
34 | }
35 |
36 | return arr;
37 | };
38 |
39 | const DragableBodyRow = (props: any) => {
40 | const {
41 | index, moveRow, className, style, ...restProps
42 | } = props;
43 | const ref = useRef();
44 | const [{ isOver, dropClassName }, drop] = useDrop({
45 | accept: type,
46 | collect: (monitor: any) => {
47 | const { index: dragIndex } = monitor.getItem() || {};
48 | if (dragIndex === index) {
49 | return {};
50 | }
51 | return {
52 | isOver: monitor.isOver(),
53 | dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
54 | };
55 | },
56 | drop: (item: any) => {
57 | moveRow(item.index, index);
58 | },
59 | });
60 | const [, drag] = useDrag({
61 | type,
62 | item: { index },
63 | collect: (monitor) => ({
64 | isDragging: monitor.isDragging(),
65 | }),
66 | });
67 |
68 | useEffect(() => {
69 | drop(drag(ref));
70 | }, [drag, drop]);
71 |
72 | const components = useMemo(() => vlistComponent.body.row, []);
73 |
74 | const tempProps = useMemo(() => ({
75 | ref,
76 | className: `${className}${isOver ? dropClassName : ''}`,
77 | style: { cursor: 'move', ...style },
78 | ...restProps,
79 | }), [className, dropClassName, restProps, style, isOver]);
80 |
81 | return (
82 | <>
83 | {' '}
84 | {components(tempProps, ref)}
85 | {' '}
86 | >
87 | );
88 | };
89 |
90 | const ResizableTitle = (props: any) => {
91 | const { onResize, width, ...restProps } = props;
92 |
93 | if (!width) {
94 | return | ;
95 | }
96 |
97 | return (
98 | {
105 | e.stopPropagation();
106 | }}
107 | />
108 | )}
109 | onResize={onResize}
110 | draggableOpts={{ enableUserSelectHack: false }}
111 | >
112 | |
116 |
117 | );
118 | };
119 |
120 | function DragRow() {
121 | const [columns, setColumns] = useState(() => [
122 | {
123 | title: '序号',
124 | key: 'id',
125 | width: 75,
126 | fixed: 'left',
127 | render(text, record, index) {
128 | return index + 1;
129 | },
130 | },
131 | {
132 | title: 'Date',
133 | dataIndex: 'date',
134 | width: 300,
135 | },
136 | {
137 | title: 'Amount',
138 | dataIndex: 'amount',
139 | width: 100,
140 | sorter: (a, b) => a.amount - b.amount,
141 | },
142 | {
143 | title: 'Type',
144 | dataIndex: 'type',
145 | width: 100,
146 | },
147 | {
148 | title: 'Note',
149 | dataIndex: 'note',
150 | width: 100,
151 | },
152 | {
153 | title: 'Note',
154 | width: 200,
155 | render: () => 'aaa',
156 | },
157 | {
158 | title: 'Note',
159 | width: 200,
160 | render: () => 'aaa',
161 | },
162 | {
163 | title: 'Action',
164 | dataIndex: 'action',
165 | width: 200,
166 | fixed: 'right',
167 | render: () => Delete,
168 | },
169 | ].map((col, index) => ({
170 | ...col,
171 | onHeaderCell: (column) => ({
172 | width: column.width,
173 | // eslint-disable-next-line @typescript-eslint/no-use-before-define
174 | onResize: handleResize(index),
175 | }),
176 | })));
177 |
178 | const handleResize = useCallback((index) => (e, { size }) => {
179 | setColumns((pre) => {
180 | const temp = [...pre];
181 | temp[index] = {
182 | ...temp[index],
183 | width: size.width,
184 | };
185 |
186 | return temp;
187 | });
188 | }, []);
189 |
190 | const components = useMemo(() => ({
191 | ...vlistComponent,
192 | body: {
193 | ...vlistComponent.body,
194 | row: DragableBodyRow,
195 | },
196 | header: {
197 | cell: ResizableTitle,
198 | },
199 | }), []);
200 |
201 | const [data, setData] = useState(generateData());
202 |
203 | const moveRow = useCallback(
204 | (dragIndex, hoverIndex) => {
205 | const dragRow = data[dragIndex];
206 | setData(
207 | update(data, {
208 | $splice: [
209 | [dragIndex, 1],
210 | [hoverIndex, 0, dragRow],
211 | ],
212 | }),
213 | );
214 | },
215 | [data],
216 | );
217 |
218 | return (
219 |
220 |
221 | ({
229 | index,
230 | moveRow,
231 | } as any)}
232 | sticky
233 | />
234 |
235 |
236 | );
237 | }
238 |
239 | export default DragRow;
240 |
--------------------------------------------------------------------------------
/docs/examples/EasyTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo } from 'react'
2 | import { Table } from 'antd'
3 | import { VList } from '../../src/index'
4 | import 'antd/dist/antd.css'
5 |
6 | const generateData = () => {
7 | const tempDataSource = []
8 | for (let i = 0; i < 11; i += 1) {
9 | tempDataSource.push({
10 | company_name: `aaa${i} 富士山下的你好美 你知道吗`,
11 | company_name1: `aaa${i} index index index index`,
12 | company_name2: `aaa${i} company index index index`,
13 |
14 | company_name3: `aaa${i} company company index index`,
15 | company_name4: `aaa${i} company company company index`,
16 | company_name5: `aaa${i} company company company company`,
17 | company_name6: `aaa${i} company index index company`,
18 | })
19 | }
20 |
21 | return tempDataSource
22 | }
23 |
24 | function SinglePageLoading() {
25 | const [dataSource] = useState(generateData())
26 |
27 | const columns: any = [
28 | {
29 | title: '序号',
30 | key: 'id',
31 | fixed: 'left',
32 | render(text, record, index) {
33 | return index + 1
34 | },
35 | width: 100,
36 | },
37 | {
38 | title: '公司',
39 | dataIndex: 'company_name',
40 | width: 200,
41 | },
42 | {
43 | title: '公司1',
44 | dataIndex: 'company_name1',
45 | width: 200,
46 | },
47 | {
48 | title: '公司2',
49 | dataIndex: 'company_name2',
50 | width: 200,
51 | },
52 | ]
53 |
54 | const components1 = useMemo(
55 | () =>
56 | VList({
57 | height: 550,
58 | vid: 'first',
59 | }),
60 | []
61 | )
62 |
63 | return (
64 | <>
65 |
73 | >
74 | )
75 | }
76 |
77 | export default SinglePageLoading
78 |
--------------------------------------------------------------------------------
/docs/examples/JumpTreeTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useState } from 'react'
2 | import { Button, Table } from 'antd'
3 | import { VList, scrollTo } from '../../src/index'
4 |
5 | import 'antd/dist/antd.css'
6 |
7 | const generateData = () => {
8 | const temp = []
9 |
10 | for (let i = 0; i < 100; i += 1) {
11 | temp.push({
12 | a: i,
13 | b: 'bbbb'.repeat(Math.floor(Math.random() * 10)),
14 | children: [
15 | {
16 | a: `${i}_${i}`,
17 | b: 'test',
18 | children: [
19 | {
20 | a: `${i}_${i}_${i}`,
21 | b: 'testtest',
22 | },
23 | {
24 | a: `${i}_${i}_${i}_${i}`,
25 | b: 'testtest',
26 | },
27 |
28 | {
29 | a: `nm${i}`,
30 | b: 'testtest',
31 | },
32 | {
33 | a: `ds${i}`,
34 | b: 'testtest',
35 | },
36 | {
37 | a: `${i}sss`,
38 | b: 'testtest',
39 | },
40 | {
41 | a: `llll${i}`,
42 | b: 'testtest',
43 | },
44 | {
45 | a: `lldsll${i}`,
46 | b: 'testtest',
47 | },
48 | {
49 | a: `llall${i}`,
50 | b: 'testtest',
51 | },
52 | {
53 | a: `lllwl${i}`,
54 | b: 'testtest',
55 | },
56 | {
57 | a: `lllql${i}`,
58 | b: 'testtest',
59 | },
60 | {
61 | a: `llewll${i}`,
62 | b: 'testtest',
63 | },
64 | {
65 | a: `lelll${i}`,
66 | b: 'testtest',
67 | },
68 | {
69 | a: `lllfl${i}`,
70 | b: 'testtest',
71 | },
72 | {
73 | a: `lllpl${i}`,
74 | b: 'testtest',
75 | },
76 | {
77 | a: `l]lll${i}`,
78 | b: 'testtest',
79 | },
80 | {
81 | a: `ll2ll${i}`,
82 | b: 'testtest',
83 | },
84 | ],
85 | },
86 | ],
87 | })
88 | }
89 |
90 | return temp
91 | }
92 |
93 | const initData = generateData()
94 |
95 | function TreeTable() {
96 | const [data, setData] = useState(initData)
97 |
98 | const [expandedRowKeys, setExpandedRowKeys] = useState([])
99 |
100 | // eslint-disable-next-line no-unused-vars
101 | const [flag, setFlag] = useState(false)
102 |
103 | const handleClick = (record, e) => {
104 | e.preventDefault()
105 | }
106 |
107 | const handleJumpClick = useCallback(() => {
108 | setExpandedRowKeys([
109 | 0,
110 | 1,
111 | '0_0',
112 | '1_1',
113 | 2,
114 | 3,
115 | 4,
116 | 5,
117 | 6,
118 | 9,
119 | 10,
120 | 12,
121 | 23,
122 | 25,
123 | ])
124 |
125 | setTimeout(() => {
126 | scrollTo({
127 | row: 5,
128 | })
129 | }, 500)
130 | }, [])
131 |
132 | const columns = [
133 | {
134 | title: 'title1',
135 | dataIndex: 'a',
136 | key: 'a',
137 | width: 150,
138 | },
139 | {
140 | title: 'title2',
141 | dataIndex: 'b',
142 | key: 'b',
143 | width: 200,
144 | },
145 | {
146 | title: 'title3',
147 | dataIndex: 'c',
148 | key: 'c',
149 | width: 200,
150 | },
151 | {
152 | title: 'Operations',
153 | dataIndex: '',
154 | width: 200,
155 | key: 'x',
156 | render: (text, record) => (
157 | handleClick(record, e)}>
158 | click {record.a}
159 |
160 | ),
161 | },
162 | ]
163 |
164 | const vComponent = useMemo(
165 | () => VList({ height: 500, resetTopWhenDataChange: false }),
166 | []
167 | )
168 |
169 | return (
170 |
171 |
sub table
172 |
173 |
record.a}
177 | pagination={false}
178 | scroll={{ y: 500, x: '100%' }}
179 | expandable={{
180 | expandedRowKeys,
181 | }}
182 | components={vComponent}
183 | />
184 |
185 | )
186 | }
187 |
188 | export default TreeTable
189 |
--------------------------------------------------------------------------------
/docs/examples/PaginationTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo } from 'react'
2 | import { Table } from 'antd'
3 | import { VList } from 'virtuallist-antd'
4 |
5 | function PaginationTable() {
6 | const [dataSource, setDataSource] = useState([])
7 |
8 | useEffect(() => {
9 | const tempDataSource = []
10 | for (let i = 0; i < 201; i += 1) {
11 | tempDataSource.push({
12 | company_name: `aaa${i}`,
13 | })
14 | }
15 |
16 | setTimeout(() => {
17 | setDataSource(tempDataSource)
18 | }, 2000)
19 | }, [])
20 |
21 | const columns = [
22 | {
23 | title: '序号',
24 | key: 'id',
25 | render(text, record, index) {
26 | return index + 1
27 | },
28 | width: 100,
29 | },
30 | {
31 | title: '公司',
32 | dataIndex: 'company_name',
33 | width: 200,
34 | },
35 | ]
36 |
37 | const pagination = {
38 | defaultPageSize: 50,
39 | defaultCurrent: 1,
40 | showQuickJumper: true,
41 | showTotal(total): any {
42 | return `总共${total}条数据`
43 | },
44 | showSizeChanger: true,
45 | pageSizeOptions: ['10', '50', '100'],
46 | }
47 |
48 | const vc = useMemo(
49 | () =>
50 | VList({
51 | height: 600,
52 | }),
53 | []
54 | )
55 |
56 | return (
57 |
65 | )
66 | }
67 |
68 | export default PaginationTable
69 |
--------------------------------------------------------------------------------
/docs/examples/SinglePageLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, useMemo } from 'react'
2 | import { Button, Table } from 'antd'
3 | import { VList, scrollTo } from '../../src/index'
4 | import 'antd/dist/antd.css'
5 |
6 | const generateData = () => {
7 | const tempDataSource = []
8 | for (let i = 0; i < 50; i += 1) {
9 | tempDataSource.push({
10 | company_name: `aaa${i} 富士山下的你好美 你知道吗`,
11 | company_name1: `aaa${i} index index index index`,
12 | company_name2: `aaa${i} company index index index`,
13 |
14 | company_name3: `aaa${i} company company index index`,
15 | company_name4: `aaa${i} company company company index`,
16 | company_name5: `aaa${i} company company company company`,
17 | company_name6: `aaa${i} company index index company`,
18 | })
19 | }
20 |
21 | return tempDataSource
22 | }
23 |
24 | function SinglePageLoading() {
25 | const [dataSource1, setDataSource1] = useState(generateData())
26 | const [dataSource2, setDataSource2] = useState(generateData())
27 |
28 | const [loading1, setLoading1] = useState(false)
29 | const [loading2, setLoading2] = useState(false)
30 |
31 | const columns: any = [
32 | {
33 | title: '序号',
34 | key: 'id',
35 | fixed: 'left',
36 | render(text, record, index) {
37 | return index + 1
38 | },
39 | width: 100,
40 | },
41 | {
42 | title: '公司',
43 | dataIndex: 'company_name',
44 | width: 200,
45 | },
46 | {
47 | title: '公司1',
48 | dataIndex: 'company_name1',
49 | width: 200,
50 | },
51 | {
52 | title: '公司2',
53 | dataIndex: 'company_name2',
54 | width: 200,
55 | },
56 | {
57 | title: '公司3',
58 | dataIndex: 'company_name3',
59 | width: 200,
60 | },
61 | {
62 | title: '公司4',
63 | dataIndex: 'company_name4',
64 | width: 200,
65 | },
66 | {
67 | title: '公司5',
68 | dataIndex: 'company_name5',
69 | width: 200,
70 | },
71 | {
72 | title: '公司6',
73 | dataIndex: 'company_name6',
74 | width: 200,
75 | },
76 | ]
77 |
78 | const handleReachEnd1 = useCallback(() => {
79 | setLoading1(true)
80 | setDataSource1((pre) => {
81 | const temp = []
82 | return [...pre, ...temp]
83 | })
84 | setTimeout(() => {
85 | setLoading1(false)
86 | }, 1000)
87 | }, [])
88 |
89 | const handleReachEnd2 = useCallback(() => {
90 | setLoading2(true)
91 | setDataSource2((pre) => {
92 | const temp = generateData()
93 | return [...pre, ...temp]
94 | })
95 | setTimeout(() => {
96 | setLoading2(false)
97 | }, 1000)
98 | }, [])
99 |
100 | const components1 = useMemo(
101 | () =>
102 | VList({
103 | height: 500,
104 | onReachEnd: handleReachEnd1,
105 | vid: 'first',
106 | }),
107 | [handleReachEnd1]
108 | )
109 | const components2 = useMemo(
110 | () =>
111 | VList({
112 | height: 500,
113 | onReachEnd: handleReachEnd2,
114 | vid: 'second',
115 | }),
116 | [handleReachEnd2]
117 | )
118 |
119 | return (
120 | <>
121 |
124 |
127 |
136 |
139 |
148 | >
149 | )
150 | }
151 |
152 | export default SinglePageLoading
153 |
--------------------------------------------------------------------------------
/docs/examples/TabsDemo.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo } from 'react';
2 | import { Table, Tabs } from 'antd';
3 | import { VList } from '../../src/index';
4 | import 'antd/dist/antd.css';
5 |
6 | const { TabPane } = Tabs;
7 |
8 | function TabsDemo() {
9 | const [dataSource, setDataSource] = useState([]);
10 | const [activeKey, setActiveKey] = useState('tab1');
11 |
12 | useEffect(() => {
13 | const tempDataSource = [];
14 | for (let i = 0; i < 1000; i += 1) {
15 | tempDataSource.push({
16 | company_name: `aaa${i} 富士山下的你好美 你知道吗 aaa${i} 富士山下的你好美 你知道吗 aaa${i} 富士山下的你好美 你知道吗`,
17 | company_name1: `aaa${i} index index index index`,
18 | company_name2: `aaa${i} company index index index`,
19 |
20 | company_name3: `aaa${i} company company index index`,
21 | company_name4: `aaa${i} company company company index`,
22 | company_name5: `aaa${i} company company company company`,
23 | company_name6: `aaa${i} company index index company`,
24 | });
25 | }
26 |
27 | setDataSource(tempDataSource);
28 | }, []);
29 |
30 | const columns: any[] = [
31 | {
32 | title: '序号',
33 | key: 'id',
34 | fixed: 'left',
35 | render(text, record, index) {
36 | return index + 1;
37 | },
38 | width: 100,
39 | },
40 | {
41 | title: '公司',
42 | dataIndex: 'company_name',
43 | width: 200,
44 | },
45 | {
46 | title: '公司1',
47 | dataIndex: 'company_name1',
48 | width: 200,
49 | },
50 | {
51 | title: '公司2',
52 | dataIndex: 'company_name2',
53 | width: 200,
54 | },
55 | ];
56 |
57 | const vc1 = useMemo(() => VList({
58 | height: 500,
59 | vid: 'first',
60 | }), []);
61 |
62 | const vc2 = useMemo(() => VList({
63 | height: 'calc(20vh)',
64 | vid: 'second',
65 | }), []);
66 |
67 | const vc3 = useMemo(() => VList({
68 | height: 'calc(60vh)',
69 | vid: 'thrid',
70 | }), []);
71 |
72 | return (
73 | <>
74 |
75 |
76 |
85 |
86 |
87 |
88 |
96 |
97 |
98 |
99 |
107 |
108 |
109 | >
110 | );
111 | }
112 |
113 | export default TabsDemo;
114 |
--------------------------------------------------------------------------------
/docs/examples/TreeTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useState } from 'react'
2 | import { Button, Table } from 'antd'
3 | import { VList } from '../../src/index'
4 |
5 | import 'antd/dist/antd.css'
6 |
7 | const generateData = () => {
8 | const temp = []
9 |
10 | for (let i = 0; i < 2; i += 1) {
11 | temp.push({
12 | a: i,
13 | b: 'bbbb'.repeat(Math.floor(Math.random() * 10)),
14 | children: [
15 | {
16 | a: `${i}_${i}`,
17 | b: 'test',
18 | children: [
19 | {
20 | a: `${i}_${i}_${i}`,
21 | b: 'testtest',
22 | },
23 | {
24 | a: `${i}_${i}_${i}_${i}`,
25 | b: 'testtest',
26 | },
27 |
28 | {
29 | a: `nm${i}`,
30 | b: 'testtest',
31 | },
32 | {
33 | a: `ds${i}`,
34 | b: 'testtest',
35 | },
36 | {
37 | a: `${i}sss`,
38 | b: 'testtest',
39 | },
40 | {
41 | a: `llll${i}`,
42 | b: 'testtest',
43 | },
44 | ],
45 | },
46 | ],
47 | })
48 | }
49 |
50 | return temp
51 | }
52 |
53 | const initData = generateData()
54 |
55 | function TreeTable() {
56 | const [data, setData] = useState(initData)
57 |
58 | // eslint-disable-next-line no-unused-vars
59 | const [flag, setFlag] = useState(false)
60 |
61 | const handleClick = (record, e) => {
62 | e.preventDefault()
63 | }
64 |
65 | const handleChangeClick = useCallback(() => {
66 | setFlag((pre) => {
67 | if (!pre) {
68 | setData([])
69 | } else {
70 | setData(generateData())
71 | }
72 |
73 | // setData((d) => [...d]);
74 |
75 | return !pre
76 | })
77 | }, [])
78 |
79 | const columns = [
80 | {
81 | title: 'title1',
82 | dataIndex: 'a',
83 | key: 'a',
84 | width: 150,
85 | },
86 | {
87 | title: 'title2',
88 | dataIndex: 'b',
89 | key: 'b',
90 | width: 200,
91 | },
92 | {
93 | title: 'title3',
94 | dataIndex: 'c',
95 | key: 'c',
96 | width: 200,
97 | },
98 | {
99 | title: 'Operations',
100 | dataIndex: '',
101 | width: 200,
102 | key: 'x',
103 | render: (text, record) => (
104 | handleClick(record, e)}>
105 | click {record.a}
106 |
107 | ),
108 | },
109 | ]
110 |
111 | const vComponent = useMemo(
112 | () => VList({ height: 500, resetTopWhenDataChange: false }),
113 | []
114 | )
115 |
116 | return (
117 |
118 |
sub table
119 |
120 |
record.a}
124 | pagination={false}
125 | scroll={{ y: 500, x: '100%' }}
126 | components={vComponent}
127 | />
128 |
129 | )
130 | }
131 |
132 | export default TreeTable
133 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: virtuallist-antd
3 | ---
4 |
--------------------------------------------------------------------------------
/docs/update.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | verbose: true,
4 | preset: 'ts-jest',
5 | testEnvironment: 'jsdom',
6 | transform: {
7 | '^.+\\.jsx?$': 'babel-jest', // 这个是jest的默认配置
8 | '^.+\\.ts?$': 'ts-jest', // typescript转换
9 | },
10 | transformIgnorePatterns: [
11 | 'node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)',
12 | ],
13 | moduleNameMapper: {
14 | '\\.(css|less)$': '/__mocks__/styleMock.js',
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "virtuallist-antd",
3 | "version": "0.8.0-beta.1",
4 | "description": "virtualList for antd-table, 实现antd-table的虚拟列表, antd-table无限滚动, infinite scrolling for antd-table",
5 | "main": "dist/index.js",
6 | "module": "dist/index.es.js",
7 | "jsnext:main": "dist/index.es.js",
8 | "engines": {
9 | "node": ">=8",
10 | "npm": ">=5"
11 | },
12 | "scripts": {
13 | "start": "dumi dev",
14 | "test": "jest",
15 | "build": "rollup -c"
16 | },
17 | "keywords": [
18 | "antd-table",
19 | "virtualList",
20 | "infinite",
21 | "scrolling",
22 | "antd-table虚拟列表",
23 | "antd-table无限滚动"
24 | ],
25 | "author": "crawler-django",
26 | "license": "MIT",
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/crawler-django/virtuallist-antd"
30 | },
31 | "homepage": "https://github.com/crawler-django/virtuallist-antd",
32 | "peerDependencies": {
33 | "antd": "^4.1.0",
34 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
35 | },
36 | "devDependencies": {
37 | "@babel/core": "^7.9.0",
38 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
39 | "@babel/plugin-proposal-optional-chaining": "^7.9.0",
40 | "@babel/preset-env": "^7.9.0",
41 | "@babel/preset-react": "^7.9.4",
42 | "@babel/preset-typescript": "^7.16.0",
43 | "@svgr/rollup": "^4.3.3",
44 | "@testing-library/jest-dom": "^5.16.1",
45 | "@testing-library/react": "^12.1.2",
46 | "@types/jest": "^27.0.3",
47 | "@types/lodash-es": "^4.17.3",
48 | "@types/react": "^16.9.32",
49 | "@typescript-eslint/eslint-plugin": "^4.31.1",
50 | "@typescript-eslint/parser": "^4.31.1",
51 | "antd": "4.20.2",
52 | "array-move": "^4.0.0",
53 | "babel-loader": "^8.1.0",
54 | "babel-plugin-import": "^1.13.0",
55 | "babel-preset-es2015": "^6.24.1",
56 | "css-loader": "^3.5.0",
57 | "cssnano": "^4.1.10",
58 | "dumi": "^1.1.19",
59 | "eslint": "^7.32.0",
60 | "eslint-config-airbnb": "^18.2.1",
61 | "eslint-plugin-import": "^2.24.2",
62 | "eslint-plugin-jsx-a11y": "^6.4.1",
63 | "eslint-plugin-react": "^7.25.1",
64 | "eslint-plugin-react-hooks": "^4.2.0",
65 | "immutability-helper": "^3.1.1",
66 | "jest": "^27.4.4",
67 | "lodash-es": "^4.17.21",
68 | "minimist": "^1.2.5",
69 | "postcss-cssnext": "^3.1.0",
70 | "postcss-nested": "^4.2.1",
71 | "postcss-simple-vars": "^5.0.2",
72 | "react-dnd": "^14.0.2",
73 | "react-dnd-html5-backend": "^14.0.0",
74 | "react-resizable": "^3.0.2",
75 | "react-sortable-hoc": "2.0.0",
76 | "rollup-plugin-commonjs": "^10.1.0",
77 | "rollup-plugin-node-resolve": "^5.2.0",
78 | "rollup-plugin-peer-deps-external": "^2.2.2",
79 | "rollup-plugin-postcss": "^2.5.0",
80 | "rollup-plugin-typescript": "^1.0.1",
81 | "rollup-plugin-typescript2": "^0.25.3",
82 | "rollup-plugin-uglify-es": "0.0.1",
83 | "rollup-plugin-url": "^3.0.1",
84 | "style-loader": "^1.1.3",
85 | "ts-babel": "^6.1.7",
86 | "ts-jest": "^27.1.1",
87 | "ts-loader": "^6.2.2",
88 | "typescript": "^3.8.3"
89 | },
90 | "files": [
91 | "dist"
92 | ],
93 | "resolutions": {
94 | "minimist": "^1.2.5"
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import external from 'rollup-plugin-peer-deps-external';
4 | // import postcss from 'rollup-plugin-postcss-modules'
5 | import postcss from 'rollup-plugin-postcss';
6 | import resolve from 'rollup-plugin-node-resolve';
7 | import uglify from 'rollup-plugin-uglify-es';
8 | import url from 'rollup-plugin-url';
9 | import svgr from '@svgr/rollup';
10 |
11 | import simplevars from 'postcss-simple-vars';
12 | import nested from 'postcss-nested';
13 | import cssnext from 'postcss-cssnext';
14 | import cssnano from 'cssnano';
15 |
16 | import pkg from './package.json';
17 |
18 | export default {
19 | input: 'src/index.tsx',
20 | output: [
21 | {
22 | file: pkg.main,
23 | format: 'cjs',
24 | },
25 | {
26 | file: pkg.module,
27 | format: 'es',
28 | },
29 | ],
30 | plugins: [
31 | external(),
32 | postcss({
33 | plugins: [
34 | simplevars(),
35 | nested(),
36 | cssnext({ warnForDuplicates: false }),
37 | cssnano(),
38 | ],
39 | extendsions: ['.css'],
40 | }),
41 | uglify(),
42 | url(),
43 | svgr(),
44 | resolve(),
45 | typescript({
46 | rollupCommonJSResolveHack: true,
47 | clean: true,
48 | }),
49 | commonjs({
50 | include: 'node_modules/**',
51 | namedExports: {
52 | 'node_modules/react/index.js': [
53 | 'cloneElement',
54 | 'createContext',
55 | 'Component',
56 | 'createElement',
57 | 'useContext',
58 | 'useRef',
59 | 'useEffect',
60 | 'useState',
61 | 'useReducer',
62 | 'useMemo',
63 | ],
64 | },
65 | }),
66 | ],
67 | };
68 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable arrow-body-style */
2 | import React, {
3 | useRef,
4 | useEffect,
5 | useContext,
6 | createContext,
7 | useReducer,
8 | useState,
9 | useMemo,
10 | } from 'react'
11 | import { throttle, debounce } from 'lodash-es'
12 |
13 | import './style.css'
14 |
15 | // ==============全局常量 ================== //
16 | const DEFAULT_VID = 'vtable'
17 | const vidMap = new Map()
18 | let debounceListRender: any
19 |
20 | // ===============reducer ============== //
21 | const initialState = {
22 | // 行高度
23 | rowHeight: 0,
24 | // 当前的scrollTop
25 | curScrollTop: 0,
26 | // 总行数
27 | totalLen: 0,
28 | }
29 |
30 | function reducer(state, action) {
31 | const { curScrollTop, rowHeight, totalLen, ifScrollTopClear } = action
32 |
33 | let stateScrollTop = state.curScrollTop
34 | switch (action.type) {
35 | // 改变trs 即 改变渲染的列表trs
36 | case 'changeTrs':
37 | return {
38 | ...state,
39 | curScrollTop,
40 | }
41 | // 初始化每行的高度, 表格总高度, 渲染的条数
42 | case 'initHeight':
43 | return {
44 | ...state,
45 | rowHeight,
46 | }
47 | // 更改totalLen
48 | case 'changeTotalLen':
49 | if (totalLen === 0) {
50 | stateScrollTop = 0
51 | }
52 |
53 | return {
54 | ...state,
55 | totalLen,
56 | curScrollTop: stateScrollTop,
57 | }
58 |
59 | case 'reset':
60 | return {
61 | ...state,
62 | curScrollTop: ifScrollTopClear ? 0 : state.curScrollTop,
63 | }
64 | default:
65 | throw new Error()
66 | }
67 | }
68 |
69 | // ===============context ============== //
70 | const ScrollContext = createContext({
71 | dispatch: undefined,
72 | renderLen: 1,
73 | start: 0,
74 | offsetStart: 0,
75 | // =============
76 | rowHeight: initialState.rowHeight,
77 | totalLen: 0,
78 | vid: DEFAULT_VID,
79 | })
80 |
81 | // =============组件 =================== //
82 |
83 | function VCell(props: any): JSX.Element {
84 | const { children, ...restProps } = props
85 |
86 | return (
87 |
88 | {children}
89 | |
90 | )
91 | }
92 |
93 | function VRow(props: any, ref: any): JSX.Element {
94 | const { dispatch, rowHeight, totalLen, vid } = useContext(ScrollContext)
95 |
96 | const { children, style, ...restProps } = props
97 |
98 | const trRef = useRef(null)
99 |
100 | useEffect(() => {
101 | const initHeight = (tempRef) => {
102 | if (tempRef?.current?.offsetHeight && !rowHeight && totalLen) {
103 | const tempRowHeight = tempRef?.current?.offsetHeight ?? 0
104 |
105 | vidMap.set(vid, {
106 | ...vidMap.get(vid),
107 | rowItemHeight: tempRowHeight,
108 | })
109 | dispatch({
110 | type: 'initHeight',
111 | rowHeight: tempRowHeight,
112 | })
113 | }
114 | }
115 |
116 | initHeight(
117 | Object.prototype.hasOwnProperty.call(ref, 'current') ? ref : trRef
118 | )
119 | }, [trRef, dispatch, rowHeight, totalLen, ref, vid])
120 |
121 | return (
122 |
135 | {children}
136 |
137 | )
138 | }
139 |
140 | function VWrapper(props: any): JSX.Element {
141 | const { children, ...restProps } = props
142 |
143 | const { renderLen, start, dispatch, vid, totalLen } = useContext(
144 | ScrollContext
145 | )
146 |
147 | const contents = useMemo(() => {
148 | return children[1]
149 | }, [children])
150 |
151 | const contentsLen = useMemo(() => {
152 | return contents?.length ?? 0
153 | }, [contents])
154 |
155 | useEffect(() => {
156 | if (totalLen !== contentsLen) {
157 | dispatch({
158 | type: 'changeTotalLen',
159 | totalLen: contentsLen ?? 0,
160 | })
161 | }
162 | }, [contentsLen, dispatch, vid, totalLen])
163 |
164 | let tempNode = null
165 | if (Array.isArray(contents) && contents.length) {
166 | tempNode = [
167 | children[0],
168 | contents.slice(start, start + (renderLen ?? 1)).map((item) => {
169 | if (Array.isArray(item)) {
170 | // 兼容antd v4.3.5 --- rc-table 7.8.1及以下
171 | return item[0]
172 | }
173 | // 处理antd ^v4.4.0 --- rc-table ^7.8.2
174 | return item
175 | }),
176 | ]
177 | } else {
178 | tempNode = children
179 | }
180 |
181 | return {tempNode}
182 | }
183 |
184 | function VTable(props: any, otherParams): JSX.Element {
185 | const { style, children, ...rest } = props
186 | const { width, ...rest_style } = style
187 |
188 | const { vid, scrollY, reachEnd, onScroll, resetScrollTopWhenDataChange } =
189 | otherParams ?? {}
190 |
191 | const [state, dispatch] = useReducer(reducer, initialState)
192 |
193 | const wrap_tableRef = useRef(null)
194 | const tableRef = useRef(null)
195 |
196 | // 数据的总条数
197 | const [totalLen, setTotalLen] = useState(
198 | children[1]?.props?.data?.length ?? 0
199 | )
200 |
201 | useEffect(() => {
202 | setTotalLen(state.totalLen)
203 | }, [state.totalLen])
204 |
205 | // 组件卸载的清除操作
206 | useEffect(() => {
207 | return () => {
208 | vidMap.delete(vid)
209 | }
210 | }, [vid])
211 |
212 | // table总高度
213 | const tableHeight = useMemo(() => {
214 | let temp: string | number = 'auto'
215 |
216 | if (state.rowHeight && totalLen) {
217 | temp = state.rowHeight * totalLen
218 | }
219 | return temp
220 | }, [state.rowHeight, totalLen])
221 |
222 | // table的scrollY值
223 | const [tableScrollY, setTableScrollY] = useState(0)
224 |
225 | // tableScrollY 随scrollY / tableHeight 进行变更
226 | useEffect(() => {
227 | let temp = 0
228 |
229 | if (typeof scrollY === 'string') {
230 | temp =
231 | (wrap_tableRef.current?.parentNode as HTMLElement)
232 | ?.offsetHeight ?? 0
233 | } else {
234 | temp = scrollY
235 | }
236 |
237 | // if (isNumber(tableHeight) && tableHeight < temp) {
238 | // temp = tableHeight;
239 | // }
240 |
241 | // 处理tableScrollY <= 0的情况
242 | if (temp <= 0) {
243 | temp = 0
244 | }
245 |
246 | setTableScrollY(temp)
247 | }, [scrollY, tableHeight])
248 |
249 | // 渲染的条数
250 | const renderLen = useMemo(() => {
251 | let temp = 1
252 | if (state.rowHeight && totalLen && tableScrollY) {
253 | if (tableScrollY <= 0) {
254 | temp = 0
255 | } else {
256 | const tempRenderLen =
257 | ((tableScrollY / state.rowHeight) | 0) + 1 + 2
258 | // console.log('tempRenderLen', tempRenderLen)
259 | // temp = tempRenderLen > totalLen ? totalLen : tempRenderLen;
260 | temp = tempRenderLen
261 | }
262 | }
263 |
264 | return temp
265 | }, [state.rowHeight, totalLen, tableScrollY])
266 |
267 | // 渲染中的第一条
268 | let start = state.rowHeight ? (state.curScrollTop / state.rowHeight) | 0 : 0
269 |
270 | // 偏移量
271 | let offsetStart = state.rowHeight ? state.curScrollTop % state.rowHeight : 0
272 |
273 | // 用来优化向上滚动出现的空白
274 | if (
275 | state.curScrollTop &&
276 | state.rowHeight &&
277 | state.curScrollTop > state.rowHeight
278 | ) {
279 | start -= 1
280 | offsetStart += state.rowHeight
281 | } else {
282 | start = 0
283 | }
284 |
285 | // 数据变更 操作scrollTop
286 | useEffect(() => {
287 | const scrollNode = wrap_tableRef.current?.parentNode as HTMLElement
288 |
289 | if (resetScrollTopWhenDataChange) {
290 | // 重置scrollTop
291 | if (scrollNode) {
292 | scrollNode.scrollTop = 0
293 | }
294 |
295 | dispatch({ type: 'reset', ifScrollTopClear: true })
296 | } else {
297 | // 不重置scrollTop 不清空curScrollTop
298 | dispatch({ type: 'reset', ifScrollTopClear: false })
299 | }
300 |
301 | if (vidMap.has(vid)) {
302 | vidMap.set(vid, {
303 | ...vidMap.get(vid),
304 | scrollNode,
305 | })
306 | }
307 | }, [totalLen, resetScrollTopWhenDataChange, vid, children])
308 |
309 | useEffect(() => {
310 | const throttleScroll = throttle((e) => {
311 | const historyScrollHeight = vidMap.get(vid)?.scrollHeight
312 |
313 | const scrollTop: number = e?.target?.scrollTop ?? 0
314 | const scrollHeight: number = e?.target?.scrollHeight ?? 0
315 | const clientHeight: number = e?.target?.clientHeight ?? 0
316 |
317 | // 到底了 没有滚动条就不会触发reachEnd. 建议设置scrolly高度少点或者数据量多点.
318 | if (scrollTop === scrollHeight) {
319 | // reachEnd && reachEnd()
320 | } else if (
321 | scrollTop + clientHeight >= scrollHeight &&
322 | historyScrollHeight !== scrollHeight
323 | ) {
324 | // 相同的tableData情况下, 上次reachEnd执行后, scrollHeight不变, 则不会再次请求reachEnd
325 | vidMap.set(vid, {
326 | ...vidMap.get(vid),
327 | scrollHeight,
328 | })
329 | // 有滚动条的情况
330 | // eslint-disable-next-line no-unused-expressions
331 | reachEnd && reachEnd()
332 | }
333 |
334 | // eslint-disable-next-line no-unused-expressions
335 | onScroll && onScroll()
336 |
337 | // 若renderLen大于totalLen, 置空curScrollTop. => table paddingTop会置空.
338 | dispatch({
339 | type: 'changeTrs',
340 | curScrollTop: renderLen <= totalLen ? scrollTop : 0,
341 | vid,
342 | })
343 | }, 60)
344 |
345 | const ref = wrap_tableRef?.current?.parentNode as HTMLElement
346 |
347 | if (ref) {
348 | ref.addEventListener('scroll', throttleScroll)
349 | }
350 |
351 | return () => {
352 | ref.removeEventListener('scroll', throttleScroll)
353 | }
354 | }, [onScroll, reachEnd, renderLen, totalLen, vid])
355 |
356 | debounceListRender(start, renderLen)
357 |
358 | return (
359 |
395 | )
396 | }
397 |
398 | // ================导出===================
399 | export function VList(props: {
400 | height: number | string
401 | // 到底的回调函数
402 | onReachEnd?: () => void
403 | onScroll?: () => void
404 | // 列表渲染时触发的回调函数(参数可以拿到 start: 渲染开始行, renderLen: 渲染行数)
405 | // listRender: provide info: {start, renderLen} on render list.
406 | // start: start index in render list.
407 | // renderLen: render length in render list.
408 | onListRender?: (listInfo: { start: number; renderLen: number }) => void
409 | // 列表渲染时触发的回调函数防抖毫秒数.
410 | // listRender debounceMs.
411 | debounceListRenderMS?: number
412 | // 唯一标识
413 | vid?: string
414 | // 重置scrollTop 当数据变更的时候. 默认为true
415 | // reset scrollTop when data change
416 | resetTopWhenDataChange?: boolean
417 | }): any {
418 | const {
419 | vid = DEFAULT_VID,
420 | height,
421 | onReachEnd,
422 | onScroll,
423 | onListRender,
424 | debounceListRenderMS,
425 | resetTopWhenDataChange = true,
426 | } = props
427 |
428 | const resetScrollTopWhenDataChange = onReachEnd
429 | ? false
430 | : resetTopWhenDataChange
431 |
432 | if (!vidMap.has(vid)) {
433 | vidMap.set(vid, { _id: vid })
434 | }
435 |
436 | debounceListRender = onListRender
437 | ? debounce((_start, _renderLen) => {
438 | onListRender({ start: _start, renderLen: _renderLen })
439 | }, debounceListRenderMS ?? 300)
440 | : () => {}
441 |
442 | return {
443 | table: (p) =>
444 | VTable(p, {
445 | vid,
446 | scrollY: height,
447 | reachEnd: onReachEnd,
448 | onScroll,
449 | onListRender,
450 | resetScrollTopWhenDataChange,
451 | }),
452 | body: {
453 | wrapper: VWrapper,
454 | row: VRow,
455 | cell: VCell,
456 | },
457 | }
458 | }
459 |
460 | export function scrollTo(option: {
461 | /**
462 | * 行数
463 | */
464 | row?: number
465 | /**
466 | * y的偏移量
467 | */
468 | y?: number
469 | /**
470 | * 同一页面拥有多个虚拟表格的时候的唯一标识.
471 | */
472 | vid?: string
473 | }) {
474 | const { row, y, vid = DEFAULT_VID } = option
475 |
476 | try {
477 | const { scrollNode, rowItemHeight } = vidMap.get(vid)
478 |
479 | if (row) {
480 | if (row - 1 > 0) {
481 | scrollNode.scrollTop = (row - 1) * (rowItemHeight ?? 0)
482 | } else {
483 | scrollNode.scrollTop = 0
484 | }
485 | } else {
486 | scrollNode.scrollTop = y ?? 0
487 | }
488 |
489 | return { vid, rowItemHeight }
490 | } catch (e) {
491 | console.error('dont call scrollTo before init table')
492 |
493 | return { vid, rowItemHeight: -1 }
494 | }
495 | }
496 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | .virtuallist .ant-table-tbody > tr > td > div {
2 | box-sizing: border-box;
3 | white-space: nowrap;
4 | vertical-align: middle;
5 | overflow: hidden;
6 | text-overflow: ellipsis;
7 | width: 100%;
8 | }
9 |
10 | .virtuallist .ant-table-tbody > tr > td.ant-table-row-expand-icon-cell > div {
11 | overflow: inherit;
12 | }
13 |
14 | .ant-table-bordered .virtuallist > table > .ant-table-tbody > tr > td {
15 | border-right: 1px solid #f0f0f0;
16 | }
17 |
--------------------------------------------------------------------------------
/tests/basic.test.js:
--------------------------------------------------------------------------------
1 | // import '@testing-library/jest-dom';
2 | import * as React from 'react';
3 | import {
4 | render, screen, act, fireEvent, waitFor,
5 | } from '@testing-library/react';
6 | import '@testing-library/jest-dom';
7 | import TabsDemo from '../docs/examples/TabsDemo';
8 | import SinglePageLoading from '../docs/examples/SinglePageLoading';
9 |
10 | describe('Test', () => {
11 | beforeAll(() => {
12 | Object.defineProperty(window, 'matchMedia', {
13 | writable: true,
14 | value: jest.fn().mockImplementation((query) => ({
15 | matches: false,
16 | media: query,
17 | onchange: null,
18 | addListener: jest.fn(), // Deprecated
19 | removeListener: jest.fn(), // Deprecated
20 | addEventListener: jest.fn(),
21 | removeEventListener: jest.fn(),
22 | dispatchEvent: jest.fn(),
23 | })),
24 | });
25 | });
26 |
27 | test('TabsDemo render normally', () => {
28 | render();
29 |
30 | // expect(screen.getByText('No Data')).toBeInTheDocument();
31 | expect(screen.getByText('aaa0 index index index index')).toBeInTheDocument();
32 |
33 | // fireEvent.click(screen.getByText('Tab 2'));
34 | });
35 |
36 | test('scrollTo run normally', async () => {
37 | render();
38 |
39 | expect(screen.getAllByText('aaa0 富士山下的你好美 你知道吗').length).toEqual(2);
40 |
41 | fireEvent.click(screen.getByText('跳到1000'));
42 | fireEvent.click(screen.getByText('跳到500'));
43 | // screen.debug();
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
6 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
7 | // "lib": [], /* Specify library files to be included in the compilation. */
8 | "allowJs": true, /* Allow javascript files to be compiled. */
9 | // "checkJs": true, /* Report errors in .js files. */
10 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
11 | "declaration": true, /* Generates corresponding '.d.ts' file. */
12 | "declarationDir": "build",
13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
14 | // "sourceMap": true, /* Generates corresponding '.map' file. */
15 | // "outFile": "./", /* Concatenate and emit output to single file. */
16 | "outDir": "build", /* Redirect output structure to the directory. */
17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
18 | // "composite": true, /* Enable project compilation */
19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
20 | // "removeComments": true, /* Do not emit comments to output. */
21 | // "noEmit": true, /* Do not emit outputs. */
22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
25 |
26 | /* Strict Type-Checking Options */
27 | "strict": true, /* Enable all strict type-checking options. */
28 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
29 | "strictNullChecks": false, /* Enable strict null checks. */
30 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
31 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
32 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
33 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
34 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
35 |
36 | /* Additional Checks */
37 | // "noUnusedLocals": true, /* Report errors on unused locals. */
38 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
39 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
40 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
41 |
42 | /* Module Resolution Options */
43 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
47 | // "typeRoots": [], /* List of folders to include type definitions from. */
48 | // "types": [], /* Type declaration files to be included in compilation. */
49 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
50 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
52 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
53 |
54 | /* Source Map Options */
55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
59 |
60 | /* Experimental Options */
61 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
62 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
63 |
64 | /* Advanced Options */
65 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
66 | },
67 | "include": [
68 | "./src"
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/update.md:
--------------------------------------------------------------------------------
1 | ## changelog
2 |
3 | ## 最新的代码(lastest) v0.8.0-beta.1
4 | 1. patch #103、#97
5 | 2. fix: #102: scrollX cause call onReachEnd
6 |
7 | ## v0.7.6
8 | 1. fix: scrollX problem
9 | 2. open #91
10 | ## v0.7.5
11 | 1. fix: #87. (表格收起时, 高度变化导致的空白问题)
12 | 2. fix: #90. (same as 1)
13 | 3. fix: #91. (firefox兼容问题, 底部有空白且一直回弹)
14 |
15 | ## v0.7.4
16 | 1. feat: onListRender & debounceListRenderMS & demo.
17 | 2. remove console.log
18 | 3. fix #73
19 |
20 | ## v0.7.3
21 | 1. support react18.
22 |
23 | ## v0.7.2
24 | 1. fix: scrollTo row unusual
25 |
26 | ## v0.7.1
27 | 1. fix: scrollTo fault.
28 | 2. Fix the error caused by less data when the tree table is changed
29 |
30 | ## v0.7.0
31 | 1. fix: #51
32 | 2. new option 'resetTopWhenDataChange'
33 |
34 | ## v0.6.5-beta.2
35 | 1. fix: scrollTop is not reset when data change on same data length
36 | 2. optimized relate code on data change
37 |
38 | ## v0.6.5-beta.0
39 | 1. fix: sometimes data is not complete in hot update when debugging
40 |
41 | ## v0.6.4
42 | 1. fix #48
43 | ## v0.6.3
44 | 1. fix: 树形表格看不全数据的问题. (fix tree table bug of incomplete data)
45 | 2. fix: fix offsetStart bug that multiple rows become 0 rows
46 | 3. fix: 处理notRrefresh报错问题.
47 | 4. add eslint
48 |
49 | ## v0.6.2
50 | 1. fix: 树形表格最后行有很多子数据的时候, 滚动会出现问题. (fix tree table bug)
51 | ## v0.6.1
52 | 1. 去掉start的判断. (remove the judgment of the variable start)
53 | ## v0.6.0
54 | 1. fix: 处理存在多个虚拟表格的时候, 全局属性会覆盖的问题. (fix bug that global attr is overridden when exist different virtual tables)
55 | ## v0.5.8
56 | 1. fix: 处理滚动条滚到最后面的时候 会出现抖动的问题 & 删除无用代码. (fix bug that data shake when scroll on final)
57 |
58 | ## v0.5.7
59 | 1. VRow向外暴露ref. (can do this like VRow(props, ref))
60 |
61 | ## v0.5.6
62 | 1. 向外暴露onScroll. (export onscroll)
63 | 2. 去掉多余的10padding. (remove excess 10 padding)
64 |
65 | ## v0.5.5
66 | 1. feat: 支持scrollTo的导出. (support scrollTo)
67 |
68 | ## v0.5.4
69 | 1. feat: 扩展onReachEnd, 支持无限加载. (support load more data on a page.)
70 |
71 | ## v0.5.3
72 | 1. (发布错误)
73 |
74 | ## v0.5.2
75 | 1. 将样式文件的width: inherit 改为 100%.
76 |
77 | ## v0.5.1
78 | 1. update rep info.
79 |
80 | ## v0.5.0
81 | 1. update && adjust npm package.
82 |
83 | ## v0.4.9
84 | 1. 将ISC协议改为MIT.
85 |
86 | ## v0.4.8
87 | 1. fix: 在length为0的时候, 没有更新totalLen的问题. (fix data length is 0, not update totalLen.)
88 |
89 | ## v0.4.8-beta
90 | 1. feat: support #10.
91 |
92 | ## v0.4.7
93 | 1. fix: #8 处理rc-table v7.8.2的改动. 同时兼容上一个版本. (deal rc-table v7.8.2 change)
94 |
95 | ## v0.4.6
96 | 1. fix: #7 处理antd 4.3.5 边框不显示的问题. (fix bug: no border)
97 |
98 | ## v0.4.5
99 | 1. fix: 当scroll值有x: max-content且列数很少的时候, 会出现表头和内容行补齐的样式问题. (fix when scroll={x: 'max-content'} and little columns, content unable to align header.)
100 |
101 | reappear: (https://codesandbox.io/s/festive-worker-wc5wp for v0.4.4)
102 |
103 | already fix: (https://codesandbox.io/s/festive-worker-wc5wp for v0.4.5)
104 |
105 | ## v0.4.4
106 | 1. revert 0.4.3.
107 |
108 | ## v0.4.3 & v0.4.3-beta.0
109 | 1. 内部代码不再将scrollY的值作为全局变量, 考虑到可能有一个页面有多个虚拟列表的情况。
110 |
111 | ## v0.4.2
112 | 1. 还是采用导入的样式文件的方式, 取代掉VCell的内联样式. (performance question)
113 |
114 | ## v0.4.1
115 | 1. fix: 处理因样式问题导致 antd fixed表格 阴影失效的问题. (fix antd box-shadow not work)
116 | 2. 新增了VCell, 会替代components的body cell. 每个td下会有一个div包裹.
117 |
118 | ## v0.4.0
119 | 1. fix: 处理css样式失效的问题. (fix internal css not work)
120 |
121 | ## v0.3.3
122 | 1. revert v0.3.2
123 |
124 | ## v0.3.2
125 | 1. fix: 处理因样式问题导致 antd fixed表格 阴影失效的问题. (fix antd box-shadow not work)
126 |
127 | ## v0.3.1
128 | 1. fix: 处理使用calc()下的高度问题.
129 |
130 | ## v0.3.0
131 | 1. feat: 更新支持antd4的Table.
132 |
133 | ## v0.2.8
134 | 1. 去掉了多余且不灵活的字体设置, 让table字体可自定义.
135 |
136 | ## v0.2.7
137 | 1. 处理tableScrollY <= 0的情况.
138 |
139 | ## v0.2.7beta1
140 | 1. 压缩发布包的代码, 大小变为17k
141 |
142 | ## v0.2.5 & v0.2.6
143 | 1. 去掉一些多余的rollup配置. 文件大小160多k变为50多K.
144 |
145 | ## v0.2.4
146 | 1. 修复在页面空间足够的时候却还出现滚动条的问题.
147 | 2. 清除滚动到最后的空白.
148 |
149 | ## v0.2.3
150 | 1. 修复在tableHeight < tableScrollY的时候 数据会抖动的情况
151 |
152 | ## v0.2.2 (此版本后都支持分页)
153 | 1. 修复tableScrollY变化的时候 会出现表格不全的问题.
154 |
155 | ## v0.2.1
156 | 1. 支持scrollY设置为calc()
157 | 2. 调整节流时间为100ms
158 |
159 | ====================================================
160 |
--------------------------------------------------------------------------------