├── .eslintignore ├── .eslintrc.json ├── .fatherrc.ts ├── .gitignore ├── CHANGE_LOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── index.tsx └── vt.tsx ├── test ├── CustomRows Hooks.jsx ├── Drag soring.css ├── Drag soring.jsx ├── Editable Rows.css ├── Editable Rows.tsx ├── expandable.tsx ├── fetch.tsx ├── indez.jsx ├── reload.tsx ├── scroll-to.tsx ├── table ajax.tsx ├── table#100.tsx └── table1.tsx ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | **/lib/* 3 | **/es/* 4 | 5 | dist/* 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/eslint-recommended", 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "sourceType": "module" 12 | }, 13 | "env": { 14 | "browser": true, 15 | "es6": true, 16 | "worker": true, 17 | "node": true, 18 | "shared-node-browser": true, 19 | "commonjs": true 20 | }, 21 | "rules": { 22 | "camelcase": "off", 23 | "semi": ["error", "never"], 24 | "@typescript-eslint/camelcase": "off", 25 | "no-console": ["error", { "allow": ["debug", "warn", "assert", "info"] }], 26 | "@typescript-eslint/class-name-casing": "off", 27 | "no-constant-condition": "off", 28 | "@typescript-eslint/explicit-function-return-type": "off", 29 | "@typescript-eslint/no-explicit-any": "off", 30 | "@typescript-eslint/no-unused-vars": ["warn", { 31 | "vars": "all", 32 | "args": "none", 33 | "ignoreRestSiblings": true 34 | }], 35 | "@typescript-eslint/no-non-null-assertion": "off", 36 | "@typescript-eslint/no-empty-function": "off" 37 | }, 38 | "root": true 39 | } -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'father' 2 | export default defineConfig({ 3 | esm: { 4 | input: 'src', 5 | }, 6 | cjs: { 7 | input: 'src', 8 | }, 9 | // umd: { 10 | // output: 'dist', 11 | // } 12 | }); 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | *~ 4 | *.log* 5 | package-lock.json 6 | dist 7 | .npmignore 8 | .vscode 9 | .idea 10 | 11 | lib 12 | es -------------------------------------------------------------------------------- /CHANGE_LOG.md: -------------------------------------------------------------------------------- 1 | # 1.3.1 2 | 1. Fixed [#141](https://github.com/wubostc/virtualized-table-for-antd/issues/141). 3 | 2. Removed an unnecessary print `console.log`. 4 | 5 | 6 | 7 | 8 | # 1.3.0 9 | 1. Fixed a bug on safari. 10 | 1. Improved the performance. 11 | 1. Fixed [#136](https://github.com/wubostc/virtualized-table-for-antd/issues/136). 12 | 13 | 14 | # 1.2.2 15 | 1. Supported immutable records([#120](https://github.com/wubostc/virtualized-table-for-antd/issues/120))([#132](https://github.com/wubostc/virtualized-table-for-antd/issues/120)). 16 | 2. Fixed the return type(ts) of the `useVT`. 17 | 3. Supported antd-v5. 18 | 4. Improved the function `helper_diagnosis`. 19 | 20 | # 1.2.1 21 | 1. fixed [#126](https://github.com/wubostc/virtualized-table-for-antd/issues/126). 22 | 2. fixed [#125](https://github.com/wubostc/virtualized-table-for-antd/issues/125). 23 | 3. fixed [#122](https://github.com/wubostc/virtualized-table-for-antd/issues/122). 24 | 4. fixed a [bug](https://github.com/wubostc/virtualized-table-for-antd/commit/81b91bb68c2ba0bd3196487b708a5b59ec02f6b8). 25 | 26 | ## 1.2.0 27 | 1. Updated the building tools. 28 | 2. Supported the React18(#123). 29 | 30 | 31 | ## 1.1.6 32 | 1. Supported tree-structure. 33 | 34 | ## 1.1.5(zh) 35 | 1. 添加支持 `expandable`,(#106) 36 | [例子](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/expandable.tsx). 37 | 2. 表样式增加 `minWidth: 100%`(#114). 38 | 3. 修复`useVT`的`scroll`类型问题(#116). 39 | ## 1.1.5(en) 40 | 1. Added support for `expandable`,(#106) 41 | [refer](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/expandable.tsx). 42 | 2. Added table style `minWidth: 100%`(#114). 43 | 3. Fixed the `scroll` type problem of `useVT`(#116). 44 | 45 | 46 | ## 1.1.4(zh) 47 | 1. feat: `useVT` 返回 ref. 48 | 2. fix: 改进了 `scrollTo`,可以精确地滚动到指定位置. 49 | 3. feat: 新增 `scrollToIndex` 方法(#94),[例子](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/scroll-to.tsx#L74) 50 | ## 1.1.4(en) 51 | 1. feat: `useVT` can returns `ref` instead of the previous method. 52 | 2. fix: `scrollTo` has been improved, that can now scroll exactly to the specified position. 53 | 3. feat: added function `scrollToIndex`(#94), [refer](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/scroll-to.tsx#L74) 54 | 55 | 56 | ## 1.1.3(zh) 57 | 1. fix: 删除多余的导出 useOnce. 58 | 2. fix: 一个bug(#100). 59 | ## 1.1.3(en) 60 | 1. fix: `useOnce` of the exports has been deleted. 61 | 2. fix: a bug(#100). 62 | 63 | 64 | 65 | ## 1.1.2(zh) 66 | 1. fix: 初始化时空数据的崩溃问题(#84). 67 | ## 1.1.2(en) 68 | 1. fix: crashing problem by the empty data, it happened when the VT was initialized(#84). 69 | 70 | 71 | ## 1.1.1(zh) 72 | 1. fix: 空数据的渲染问题. 73 | ## 1.1.1(en) 74 | 1. fix: rendering of empty data. 75 | 76 | 77 | ## 1.1.0(zh) 78 | 1. feat: scrollTo方法,支持指定位置滚动(#75). 79 | 2. fix: 支持React17(#77). 80 | 3. fix: 修复onScroll参数在高DPI下isEnd无法为true的问题(#74). 81 | 82 | 83 | ## 1.0.0(zh) 84 | 1. 改进的虚拟化算法并全面支持antd4. 85 | 86 | 87 | ## 1.0.0(en) 88 | 1. the improved virtualization algorithm, and full supports antd4. 89 | 90 | 91 | ## 0.7.8(zh) 92 | 1. fix: 删除被标记为`deprecated`的接口和参数。 93 | + 接口:`getVTComponents` 94 | + 接口:`getVTContext` 95 | + 参数:`vt_opts.reflection` 96 | 1. refacotry: 一些细小重构。 97 | 98 | 99 | ## 0.7.8(en) 100 | 1. fix: delete the APIs and the params marked as `deprecated`. 101 | + API:`getVTComponents` 102 | + API:`getVTContext` 103 | + param:`vt_opts.reflection` 104 | 1. refacotry: some minor refactorings. 105 | 106 | 107 | ## 0.7.7 108 | 1. fix: `Cannot redefine property: __DIAGNOSIS__`(#55). 109 | 110 | ## 0.7.6 111 | 1. fix: ctx._React_ptr.forceUpdate is not a function.(#46) 112 | 113 | 114 | ## 0.7.5 115 | 1. improved diff algorithm, fix some bugs. 116 | 117 | 118 | ## 0.7.4 119 | 1. improved diff algorithm. 120 | 121 | 122 | ## 0.7.3 123 | 1. fix a bug.(#42) 124 | 125 | 126 | ## 0.7.2 127 | 1. fix some bugs, thanks @liubinis86.(#35 #34). 128 | 129 | 130 | ## 0.7.1 131 | 1. improve consistency to avoid incorrect rendering. 132 | 133 | 134 | ## 0.7.0 135 | 1. add a new Hooks API, `useVT`. 136 | 1. fix a bug that free the same index repeatedly.(#21) 137 | 138 | 139 | ## 0.6.3~0.6.4 140 | 1. fix some bugs.(#21) 141 | 142 | 143 | 144 | ## 0.6.2 145 | 1. an unmounted component will not update style. 146 | 1. fix a bug.(#25) 147 | 1. `getComponent` has been deprecated, use `setComponent` instead(#26). 148 | 1. adjusted log format. 149 | 150 | 151 | ## 0.6.1 152 | 1. removed debug info `console.log` (sorry, guys~). 153 | 154 | 155 | ## 0.6.0 156 | ### this is first stable version, and the main changes are: 157 | 1. removed `VTRefresh`. 158 | 1. removed `height`, now it depends entirely on `scroll.y`. 159 | 1. redesigned interface `VTScroll`. 160 | 1. much bugs was fixed. 161 | 1. fast, fast and more faster, with my best trying that all operations costs time about O(1). 162 | 1. browers required support `requestAnimationFrame`. 163 | > I suggest you to test this library carefully after installing it. 164 | 165 | 166 | ## 0.5.5 167 | 1. improving compatibility. 168 | 169 | ## 0.5.4 170 | 1. fix some bugs. 171 | 172 | ## 0.5.3 173 | 1. fix `debug` bug when the param `e` is `null`. 174 | 175 | ## 0.5.2 176 | 1. refactory `scrollHook`. 177 | 178 | ## 0.5.1 179 | 1. refactory `scrollHook` to optimize performance. 180 | 1. update README.md. 181 | 182 | ## 0.5.0 183 | 1. fix the definition of `vt_ctx`. 184 | 1. remove `VTWrapperRender` option. 185 | 1. remove `changedBits` option. 186 | 1. more friendly reading format for this file. 187 | 1. `debug` can shows `scrollTop`. 188 | 189 | 190 | ## 0.4.0 191 | 1. { debug: true, ... } to see more debugging details. 192 | 2. fix VTScroll bug. 193 | 3. using render-lock, VT can now renders stably. 194 | 4. improved throttling. 195 | 5. fix some problems in TS 3.5. 196 | 6. the default value of vt_opts.overscanRowCount is now 5. 197 | 198 | 199 | ## 0.4.0-beta.2 200 | 1. show the warning when you don't have 'height' as a field in the vt_opts. 201 | 2. add throttling to optimize scrolling. 202 | 3. change the styles ([#9](https://github.com/wubostc/virtualized-table-for-antd/issues/9 "Style Error")) 203 | 204 | 205 | ## 0.4.0-beta.1 206 | 1. support for the opt ColumnProps.fixed ([#5](https://github.com/wubostc/virtualized-table-for-antd/issues/5 "不支持 fixed")) 207 | 2. support for the fixed lists. 208 | 3. compatible with ie9-11. 209 | 210 | 211 | ## 0.3.4 212 | 1. fix VTScroll bug. 213 | 214 | 215 | ## 0.3.1 216 | 1. fix minor style bug that using offsetHeight instead of clientHeight ([#2](https://github.com/wubostc/virtualized-table-for-antd/issues/2 "offsetHeight instead of clientHeight")) 217 | 218 | 219 | ## 0.3.0 220 | 1. optimize the program logic 221 | 2. add debug feature 222 | 223 | 224 | ## 0.2.1 (ignored) 225 | 226 | 227 | ## 0.2.0 228 | 1. removed two interfaces in vt_opts ( VTScroll and VTRefresh) 229 | 230 | 231 | ## 0.1.0 232 | 1. by default, CACHE is enable, , set the prop destory to control whether the component is destroyed when it is uninstalled 233 | 234 | 235 | ## 0.0.9 236 | 1. rewrite const enum to enum 237 | 238 | 239 | ## 0.0.8 240 | 1. the interface vt_opts no longer requires too many params 241 | 242 | 243 | ## 0.0.7 244 | 1. now, the func VTScroll can correct restores last scroll state of antd table 245 | 246 | 247 | ## 0.0.6 248 | 1. add new API VTScroll (overload+2) 249 | ```typescript 250 | 251 | class MyComponent extends React.Component { 252 | ... 253 | render() { 254 | 259 | } 260 | 261 | componentDitMount() { 262 | VTScroll(1000, { top: 200 }) // to set 263 | const { top, left } = VTScroll(1000); // to get 264 | } 265 | } 266 | 267 | ``` 268 | 2. add new API onScroll of the scroll event 269 | ```typescript 270 |
console.log(left, top) })} 275 | /> 276 | ``` 277 | 278 | 279 | ## 0.0.5 280 | 1. add new API VTRefresh 281 | ```typescript 282 | export declare function VTRefresh(id: number): void; 283 | ``` 284 | 2. remove the func shouldComponentUpdate of VTWrapper 285 | 3. remove the func shouldComponentUpdate of VTRow 286 | 287 | 288 | ## 0.0.4 289 | 1. Solve the initial rendering bug. ([#1](https://github.com/wubostc/virtualized-table-for-antd/issues/1 "能有个完整的demo吗")) 290 | 2. Update the README.md 291 | 292 | 293 | ## 0.0.3 294 | 1. Added missing type. 295 | ```typescript 296 | function VTComponents(vt_opts: vt_opts): TableComponents 297 | ``` 298 | 2. Some bugs fixed. 299 | 300 | 301 | ## 0.0.1 ~ 0.0.2 302 | 1. init ver. 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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 | # The virtualized table component for AntD4,fast, restorable and smallest size for gzip! 2 | 3 | 4 | ![npm](https://img.shields.io/npm/v/virtualizedtableforantd4) 5 | ![dm](https://img.shields.io/npm/dm/virtualizedtableforantd4) 6 | ![license](https://img.shields.io/npm/l/virtualizedtableforantd4) 7 | 8 | 9 | [![NPM](https://nodei.co/npm/virtualizedtableforantd4.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/virtualizedtableforantd4/) 10 | 11 | + Install 12 | 13 | ```shell 14 | npm i --save virtualizedtableforantd4 15 | ``` 16 | 17 | + the opts of `useVT`([examples](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test)) 18 | ```typescript 19 | interface VtOpts { 20 | id?: number | string; 21 | /** 22 | * @default 5 23 | */ 24 | overscanRowCount?: number; 25 | 26 | /** 27 | * this only needs the scroll.y 28 | */ 29 | scroll: { 30 | y: number | string; 31 | }; 32 | 33 | /** 34 | * wheel event(only works on native events). 35 | */ 36 | onScroll?: ({ left, top, isEnd, }: 37 | { top: number; left: number; isEnd: boolean }) => void; 38 | 39 | initTop?: number; 40 | 41 | /** 42 | * Offset of the table when isEnd becomes true. 43 | * @default 0 44 | */ 45 | offset?: number; 46 | 47 | /** 48 | * @default false 49 | */ 50 | debug?: boolean; 51 | 52 | 53 | // pass -1 means scroll to the bottom of the table 54 | ref?: React.MutableRefObject<{ 55 | scrollTo: (y: number) => void; 56 | scrollToIndex: (idx: number) => void; 57 | }> 58 | } 59 | ``` 60 | 61 | 62 | + Quick start 63 | > You need to change your style like following if your Table.size is not default. 64 | 65 | ```less 66 | // size={'small'} 67 | ant-table [vt] > table > .ant-table-tbody > tr > td { 68 | padding: 8px; 69 | } 70 | ``` 71 | ```typescript 72 | import React from 'react'; 73 | import { Table } from 'antd'; 74 | import { useVT } from 'virtualizedtableforantd4'; 75 | 76 | const [ vt, set_components ] = useVT(() => ({ scroll: { y: 600 } }), []); 77 | 78 |
84 | ``` 85 | 86 | + Scroll to 87 | - [scroll-to](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/scroll-to.tsx) 88 | 89 | 90 | + Restoring last state 91 | 92 | - [reload](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/reload.tsx) 93 | 94 | 95 | + Editable Table 96 | 97 | - [CustomRows Hooks](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/CustomRows%20Hooks.jsx) 98 | - [Editable Rows](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/Editable%20Rows.jsx) 99 | 100 | + Drag soring 101 | 102 | - [Drag soring](https://github.com/wubostc/virtualized-table-for-antd/blob/master/test/Drag%20soring.jsx) 103 | 104 | + expanded rows & tree-structure 105 | has been well supported! 106 | 107 | ## License 108 | 109 | [MIT](LICENSE) 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "virtualizedtableforantd4", 3 | "version": "1.3.1", 4 | "description": "The virtualized table component for ant design4/5, using typescript.", 5 | "keywords": [ 6 | "antd", 7 | "virtualized table", 8 | "react", 9 | "react virtualized", 10 | "antd virtualized table", 11 | "infinity", 12 | "scroll", 13 | "virtual", 14 | "无限滚动表格", 15 | "虚拟表格", 16 | "无限滚动", 17 | "table" 18 | ], 19 | "main": "dist/cjs/index.js", 20 | "module": "dist/esm/index.js", 21 | "files": [ 22 | "dist" 23 | ], 24 | "scripts": { 25 | "clean": "rimraf dist", 26 | "compile": "father build", 27 | "lint": "eslint src/ --ext .ts,.tsx", 28 | "build": "npm run lint && npm run compile", 29 | "doctor": "father doctor", 30 | "prepublishOnly": "father doctor && npm run build" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/wubostc/virtualized-table-for-antd" 35 | }, 36 | "homepage": "https://github.com/wubostc/virtualized-table-for-antd", 37 | "author": { 38 | "name": "wubooo", 39 | "email": "913721086@qq.com" 40 | }, 41 | "private": false, 42 | "license": "MIT", 43 | "devDependencies": { 44 | "@types/react": "^18.0.18", 45 | "@typescript-eslint/eslint-plugin": "^5.36.1", 46 | "@typescript-eslint/parser": "^5.36.1", 47 | "cross-env": "^7.0.3", 48 | "eslint": "^8.23.0", 49 | "eslint-loader": "^4.0.2", 50 | "father": "^4.0.3", 51 | "rimraf": "^3.0.2", 52 | "typescript": "^4.8.2" 53 | }, 54 | "peerDependencies": { 55 | "antd": "^4.0.0 || ^5.0.0", 56 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0", 57 | "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" 58 | }, 59 | "browserslist": [ 60 | "> 0.5%", 61 | "last 2 versions", 62 | "Firefox ESR", 63 | "not dead" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2019 https://github.com/wubostc/ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | */ 11 | 12 | /* eslint-disable no-console */ 13 | 14 | import { TableComponents, _set_components, VtOpts, init } from './vt' 15 | 16 | const _brower = 1 17 | const _node = 2 18 | 19 | ;(function () { 20 | const env = typeof window === 'object' && window instanceof Window ? _brower : _node 21 | if (env & _brower) { 22 | if (!Object.hasOwnProperty.call(window, 'requestAnimationFrame') && !window.requestAnimationFrame) 23 | throw new Error('Please using the modern browers or appropriate polyfill!') 24 | } 25 | })() 26 | 27 | 28 | 29 | /** 30 | * @example 31 | * 32 | * function MyTableComponent() { 33 | * 34 | * // ... your code 35 | * 36 | * 37 | * const y = 600; 38 | * const [ vt, setComponents, vtRef ] = useVT(() => ({ 39 | * scroll: { 40 | * y 41 | * } 42 | * }), 43 | * [y]); 44 | * 45 | * // useEffect(() => { 46 | * // setComponents({ 47 | * // body: { 48 | * // cell: MyCell, 49 | * // } 50 | * // }) 51 | * // }); 52 | * 53 | * // useEffect(() => vtRef.current.toScroll(100), []); 54 | * 55 | * return ( 56 | *
62 | * ); 63 | * } 64 | */ 65 | function useVT(fnOpts: () => VtOpts, deps: React.DependencyList): 66 | [ 67 | TableComponents, 68 | (components: TableComponents) => void, 69 | Required['ref'] 70 | ] 71 | { 72 | const ctx = init(fnOpts, deps || []) 73 | 74 | 75 | return [ctx._vtcomponents, (components: TableComponents) => _set_components(ctx, components), ctx.ref] 76 | } 77 | 78 | 79 | export { useVT } 80 | -------------------------------------------------------------------------------- /src/vt.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2019 https://github.com/wubostc/ 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | */ 12 | 13 | 14 | import { useRef, useState, useCallback, useContext, useEffect, useMemo, useImperativeHandle, useLayoutEffect } from 'react' 15 | import * as React from 'react' 16 | 17 | 18 | type CustomizeComponent = React.FC; 19 | 20 | export 21 | interface TableComponents { 22 | table?: CustomizeComponent; 23 | header?: { 24 | wrapper?: CustomizeComponent; 25 | row?: CustomizeComponent; 26 | cell?: CustomizeComponent; 27 | }; 28 | body?: { 29 | wrapper?: CustomizeComponent; 30 | row?: CustomizeComponent; 31 | cell?: CustomizeComponent; 32 | }; 33 | } 34 | 35 | 36 | 37 | /** 38 | * THE EVENTS OF SCROLLING. 39 | */ 40 | const SCROLLEVT_NULL = (0<<0) 41 | const SCROLLEVT_INIT = (1<<0) 42 | const SCROLLEVT_RECOMPUTE = (1<<1) 43 | const SCROLLEVT_NATIVE = (1<<3) 44 | const SCROLLEVT_BY_HOOK = (1<<6) 45 | 46 | 47 | // any events will be `SCROLLEVT_BY_HOOK` if the `ctx.f_top === TOP_CONTINUE`. 48 | const TOP_CONTINUE = 0 49 | const TOP_DONE = 1 50 | 51 | 52 | 53 | interface RefObject { 54 | scrollTo: (y: number) => void; 55 | scrollToIndex: (idx: number) => void; 56 | } 57 | 58 | export 59 | interface VtOpts { 60 | id?: number | string; 61 | /** 62 | * @default 5 63 | */ 64 | overscanRowCount?: number; 65 | 66 | /** 67 | * this only needs the scroll.y 68 | */ 69 | scroll: { 70 | y: number | string; 71 | }; 72 | 73 | /** 74 | * wheel event(only works on native events). 75 | */ 76 | onScroll?: ({ left, top, isEnd, }: 77 | { top: number; left: number; isEnd: boolean }) => void; 78 | 79 | initTop?: number; 80 | 81 | /** 82 | * Offset of the table when isEnd becomes true. 83 | * @default 0 84 | */ 85 | offset?: number; 86 | 87 | /** 88 | * @default false 89 | */ 90 | debug?: boolean; 91 | 92 | 93 | // pass -1 means scroll to the bottom of the table 94 | ref?: React.MutableRefObject; 95 | } 96 | 97 | /** 98 | * `INIT` -> `LOADED` -> `RUNNING` 99 | */ 100 | enum e_VT_STATE { 101 | INIT = 1, 102 | LOADED = 2, 103 | RUNNING = 4, 104 | } 105 | 106 | 107 | 108 | type SimEvent = { 109 | target: { scrollTop: number; scrollLeft: number }; 110 | flag: number; 111 | end?: boolean; 112 | }; 113 | 114 | 115 | 116 | interface VT_CONTEXT extends VtOpts { 117 | _y: number; // an actual height of the HTML element '.ant-table-body'. 118 | _scroll_y: number | string; // this is the same as the `Table.scroll.y`. 119 | 120 | _vtcomponents: TableComponents; // virtual layer. 121 | components: TableComponents; // implementation layer. 122 | vt_state: e_VT_STATE; 123 | possible_hight_per_tr: number; 124 | 125 | /* 0: needn't to recalculate, > 0: to add, < 0 to subtract */ 126 | re_computed: number; 127 | row_height: number[]; 128 | row_count: number; 129 | prev_row_count: number; 130 | wrap_inst: React.RefObject; 131 | 132 | // return the last state. 133 | VTScroll?: (param?: { top: number; left: number }) => { top: number; left: number }; 134 | 135 | 136 | computed_h: number; // a cache for the WH. 137 | WH: number; // Wrapped Height. 138 | // it's the newest value of `wrap_inst`'s height to update. 139 | 140 | HND_PAINT: number; // a handle for Batch Repainting. 141 | HND_RAF: number; // a handler of requestAnimationFrame 142 | 143 | 144 | // stores the variables for the offset following. 145 | // | 146 | // | 147 | // top 148 | // children[index] - head 149 | // . 150 | // . 151 | // . 152 | // . 153 | // children[index] - tail <= children.len 154 | // | 155 | _offset_top: number/* int */; 156 | _offset_head: number/* int */; 157 | _offset_tail: number/* int */; 158 | 159 | top: number; 160 | left: number; 161 | evt: number; 162 | end: boolean; 163 | 164 | final_top: number; 165 | f_final_top: number; 166 | 167 | update_count: number; 168 | 169 | on_update_wrap_style: () => void; /* it will be called when the `_y` is 0. */ 170 | 171 | indexMap: WeakMap; 172 | 173 | // computing queue. 174 | cq: { index: number; func: () => void }[]; 175 | 176 | retry_count: number; 177 | } 178 | 179 | function default_context(): VT_CONTEXT { 180 | return { 181 | vt_state: e_VT_STATE.INIT, 182 | possible_hight_per_tr: -1, 183 | computed_h: 0, 184 | re_computed: 0, 185 | row_height: [], 186 | row_count: 0, 187 | prev_row_count: 0, 188 | _offset_top: 0 | 0, 189 | _offset_head: 0 | 0, 190 | _offset_tail: 0 | 1, 191 | WH: 0, // the wrapper's height 192 | top: 0, 193 | left: 0, 194 | evt: SCROLLEVT_NULL, 195 | end: false, 196 | final_top: 0, 197 | f_final_top: TOP_DONE, 198 | update_count: 0, 199 | indexMap: new WeakMap(), 200 | HND_PAINT: 0, 201 | retry_count: 5, 202 | } as VT_CONTEXT 203 | } 204 | 205 | 206 | 207 | /* overload __DIAGNOSIS__. */ 208 | function helper_diagnosis(ctx: VT_CONTEXT): void { 209 | if (Object.prototype.hasOwnProperty.call(ctx, 'CLICK~__DIAGNOSIS__')) return 210 | function diagnosis(flag: boolean) { 211 | let expect_height = 0 212 | for (let i = 0; i < ctx.row_count; ++i) { 213 | expect_height += ctx.row_height[i] 214 | } 215 | let color: string, explain: string 216 | if (expect_height > ctx.computed_h) { 217 | color = 'color:rgb(15, 179, 9)' // green 218 | explain = 'lower than expected' 219 | } else if (expect_height < ctx.computed_h) { 220 | color = 'color:rgb(202, 61, 81)' // red 221 | explain = 'higher than expected' 222 | } else { 223 | color = 'color:rgba(0, 0, 0, 0.85)' 224 | explain = 'normal' 225 | } 226 | const ret = ctx.computed_h - expect_height 227 | if (flag) { 228 | console.debug('OoOoOoO DIAGNOSIS OoOoOoO') 229 | console.debug(`%c%d(%d)(${explain})`, color, expect_height, ctx.computed_h - expect_height) 230 | console.debug('OoOoOoOoOoOoOOoOoOoOoOoOo') 231 | } 232 | return ret 233 | } 234 | Object.defineProperty(ctx, 'CLICK~__DIAGNOSIS__', { 235 | get() { 236 | diagnosis(true) 237 | }, 238 | configurable: false, 239 | enumerable: false, 240 | }) 241 | function cb() { 242 | if (diagnosis(false) !== 0) { 243 | window.alert('vt: an error occurred!') 244 | return 245 | } 246 | window.requestIdleCallback ? window.requestIdleCallback(cb) : window.setTimeout(cb) 247 | } 248 | ctx.debug && cb() 249 | } 250 | 251 | 252 | 253 | function log_debug(ctx: VT_CONTEXT, msg: string): void { 254 | if (ctx.debug) { 255 | if (msg[0] === '+') { 256 | return console.debug(msg.slice(1)) 257 | } 258 | const d = new Date() 259 | const tid = `${d.toLocaleTimeString()}.${d.getMilliseconds()}` 260 | console.debug(`%c[${ctx.id}][${tid}][${msg}]`, 'color:#a00', ctx) 261 | } 262 | } 263 | 264 | 265 | 266 | 267 | /** 268 | * Default Implementation Layer. 269 | */ 270 | /** AntD.TableComponent.table */ 271 | const TableImpl = React.forwardRef(function TableImpl(props, ref) { 272 | return
273 | }) 274 | /** AntD.TableComponent.body.wrapper */ 275 | function WrapperImpl(props: any): JSX.Element { 276 | return 277 | } 278 | /** AntD.TableComponent.body.row */ 279 | const RowImpl = React.forwardRef(function RowImpl(props, ref) { 280 | return 281 | }) 282 | 283 | 284 | /** 285 | * O(n) 286 | * returns offset: [head, tail, top] 287 | */ 288 | function scroll_with_offset(ctx: VT_CONTEXT, top: number): [number, number, number] { 289 | 290 | const { 291 | row_height, 292 | row_count, 293 | overscanRowCount, 294 | } = ctx 295 | 296 | ctx._scroll_y = ctx.scroll.y 297 | 298 | if (typeof ctx._scroll_y === 'number') { 299 | ctx._y = ctx._scroll_y 300 | } else if (typeof ctx._scroll_y === 'string') { 301 | /* a string, like "calc(100vh - 300px)" */ 302 | // this offsetHeight may be 0! 303 | ctx._y = ctx.wrap_inst.current.parentElement.offsetHeight 304 | } else { 305 | console.assert(false, 'VT: did you forget to set `scroll.y`?') 306 | ctx._y = ctx.wrap_inst.current.parentElement.offsetHeight 307 | } 308 | 309 | console.assert(ctx._y >= 0) 310 | // to calc `_top` with `row_height` and `overscan`. 311 | let _top = 0, i = 0, j = 0 312 | // the height to render. 313 | let torender_h = 0 314 | 315 | // scroll to the bottom of the table. 316 | if (top === -1 && row_count > 0) { 317 | i = row_count 318 | while (i > 0 && torender_h < ctx._y) { 319 | torender_h += row_height[--i] 320 | } 321 | 322 | return [0 | i, 0 | row_count, 0 | ctx.computed_h - torender_h] 323 | } 324 | 325 | for (; i < row_count && _top < top; ++i) { 326 | _top += row_height[i] 327 | } 328 | 329 | // start j from the visible area 330 | j = i 331 | for (; j < row_count && torender_h < ctx._y; ++j) { 332 | torender_h += row_height[j] 333 | } 334 | 335 | // keep offset row on top and bottom 336 | let overscan = overscanRowCount! < 0 ? 0 : overscanRowCount! 337 | while (i > 0 && overscan--) { 338 | _top -= row_height[--i] 339 | } 340 | j += overscanRowCount! 341 | 342 | if (j > row_count) j = row_count 343 | // returns [head, tail, top]. 344 | return [0 | i, 0 | j, 0 | _top] 345 | } 346 | 347 | 348 | // set the variables for offset top/head/tail. 349 | function set_offset( 350 | ctx: VT_CONTEXT, top: number, head: number, tail: number): void 351 | { 352 | ctx._offset_top = 0 | top 353 | ctx._offset_head = 0 | head 354 | ctx._offset_tail = 0 | tail 355 | } 356 | 357 | 358 | function set_scroll(ctx: VT_CONTEXT, 359 | top: number, left: number, evt: number, end: boolean): void 360 | { 361 | ctx.top = top 362 | ctx.left = left 363 | ctx.evt = evt 364 | ctx.end = end 365 | } 366 | 367 | 368 | 369 | // scrolls the parent element to specified location. 370 | function scroll_to(ctx: VT_CONTEXT, top: number, left: number): void { 371 | if (!ctx.wrap_inst.current) return 372 | const ele = ctx.wrap_inst.current.parentElement! 373 | /** ie */ 374 | ele.scrollTop = top 375 | ele.scrollLeft = Math.max(left, ele.scrollLeft) 376 | } 377 | 378 | 379 | 380 | function repainting(ctx: VT_CONTEXT): number { 381 | if (ctx.HND_PAINT) return 382 | 383 | const { cq, wrap_inst } = ctx 384 | 385 | const fn = (): void => { 386 | ctx.HND_PAINT = 0 387 | 388 | // BATCH PROCESS in once time... 389 | for (let i = 0; i < cq.length; ++i) { 390 | if (cq[i].index >= ctx._offset_head && cq[i].index < ctx._offset_tail) { 391 | cq[i].func() 392 | } 393 | } 394 | 395 | if (ctx.vt_state !== e_VT_STATE.RUNNING || !wrap_inst.current) return 396 | 397 | const h = ctx.computed_h 398 | 399 | if (ctx.WH === h) return 400 | 401 | ctx.WH = h 402 | const s = wrap_inst.current.style 403 | s.height = h ? 404 | (s.maxHeight = h + 'px', s.maxHeight) : 405 | (s.maxHeight = 'unset', s.maxHeight) 406 | 407 | ctx.on_update_wrap_style() 408 | } 409 | 410 | 411 | ctx.HND_PAINT = ctx.evt === SCROLLEVT_NATIVE ? window.requestAnimationFrame(fn) 412 | : window.setTimeout(fn) 413 | } 414 | 415 | 416 | function srs_expand(ctx: VT_CONTEXT, len: number, prev_len: number, fill_value: number): void { 417 | const slen = len - prev_len 418 | const shadow_rows = new Array(slen).fill(fill_value) 419 | ctx.row_height = ctx.row_height.concat(shadow_rows) 420 | ctx.computed_h += slen * fill_value 421 | } 422 | 423 | 424 | function srs_shrink(ctx: VT_CONTEXT, len: number, prev_len: number): void { 425 | if (len === 0) { 426 | ctx.computed_h = 0 427 | ctx.row_height.length = 0 428 | ctx.top = 0 429 | return 430 | } 431 | const rows = ctx.row_height 432 | let h2shrink = 0 433 | for (let i = len; i < prev_len; ++i) { 434 | h2shrink += rows[i] 435 | } 436 | ctx.computed_h -= h2shrink 437 | } 438 | 439 | 440 | function set_tr_cnt(ctx: VT_CONTEXT, n: number): void { 441 | ctx.re_computed = n - ctx.row_count 442 | ctx.prev_row_count = ctx.row_count 443 | ctx.row_count = n 444 | } 445 | 446 | 447 | interface VTableProps { 448 | style: React.CSSProperties; 449 | context: React.Context; 450 | children: React.ReactNode; 451 | } 452 | 453 | 454 | const VTable: React.ForwardRefRenderFunction = (props, ref) => { 455 | const { style, context, ...rest } = props 456 | 457 | 458 | // force update this vt. 459 | const force = useState(0) 460 | 461 | const ref_func = useRef<() => void>(() => {}) 462 | 463 | // eslint-disable-next-line prefer-const 464 | let scroll_hook: (e?: SimEvent | Event) => void 465 | 466 | /*********** DOM ************/ 467 | const wrap_inst = useMemo(() => React.createRef(), []) 468 | 469 | /*********** context ************/ 470 | const ctx = useContext(context) 471 | useMemo(() => { 472 | Object.assign(ctx, default_context()) 473 | if (ctx.wrap_inst && ctx.wrap_inst.current) { 474 | ctx.wrap_inst.current.parentElement.removeEventListener('scroll', scroll_hook as any) 475 | } 476 | ctx.wrap_inst = wrap_inst 477 | ctx.top = ctx.initTop 478 | ctx.on_update_wrap_style = () => { 479 | if (ctx._y === 0 && `${ctx._scroll_y}`.length) { 480 | scroll_hook({ 481 | flag: SCROLLEVT_BY_HOOK, 482 | target: { 483 | scrollTop: ctx.top, 484 | scrollLeft: ctx.left, 485 | } 486 | }) 487 | } 488 | } 489 | 490 | if (process.env.NODE_ENV !== 'production') 491 | helper_diagnosis(ctx) 492 | 493 | ctx.cq = [] 494 | let pfirst = 0 495 | // let plast = 0 496 | let circleBufferSize = 0 497 | ctx.cq.push = (item) => { 498 | if (ctx.vt_state !== e_VT_STATE.RUNNING) return 499 | 500 | const size = ctx._offset_tail - ctx._offset_head + ctx.overscanRowCount * 2 + 10 501 | circleBufferSize = Math.max(circleBufferSize, size) 502 | 503 | if (pfirst > circleBufferSize) { 504 | pfirst = 0 505 | } 506 | 507 | ctx.cq[pfirst++] = item 508 | 509 | return 0 510 | } 511 | }, []) 512 | 513 | 514 | /*********** scroll event ************/ 515 | const event_queue = useRef([]).current 516 | 517 | 518 | /* eslint-disable prefer-const */ 519 | let RAF_update_self: FrameRequestCallback 520 | 521 | /*********** scroll hook ************/ 522 | scroll_hook = useCallback((ev?: SimEvent | Event) => { 523 | if (ctx.vt_state !== e_VT_STATE.RUNNING) return 524 | 525 | const t0 = performance.now() 526 | 527 | if (ev) { 528 | if ('flag' in ev) { 529 | event_queue.push(ev) 530 | } else { 531 | const target = ev.target as any 532 | const top = Math.max(target.scrollTop, 0) 533 | 534 | event_queue.push({ 535 | target: { 536 | scrollTop: top, 537 | scrollLeft: target.scrollLeft, 538 | }, 539 | end: Math.abs(target.scrollHeight - target.clientHeight - Math.round(top)) <= (ctx.offset || 0), 540 | flag: SCROLLEVT_NATIVE, 541 | }) 542 | } 543 | 544 | 545 | if (ctx.f_final_top === TOP_CONTINUE) { 546 | return RAF_update_self(t0) 547 | } 548 | } 549 | 550 | if (ctx.HND_RAF) return 551 | ctx.HND_RAF = window.setTimeout(() => Promise.resolve().then(() => RAF_update_self(t0))) 552 | }, []) 553 | 554 | 555 | /* requestAnimationFrame callback */ 556 | RAF_update_self = useCallback((time: number) => { 557 | ctx.HND_RAF = 0 558 | 559 | const t1 = performance.now() 560 | if (t1 - time > 10 && ctx.retry_count-- > 0) { 561 | scroll_hook() 562 | return 563 | } 564 | 565 | ctx.retry_count = 5 566 | 567 | if (ctx.vt_state !== e_VT_STATE.RUNNING) return 568 | 569 | const evq = event_queue 570 | 571 | let e: SimEvent 572 | if (!evq.length) { 573 | return 574 | } 575 | 576 | // this always consumes the last element of the event queue. 577 | e = evq.pop() 578 | evq.length = 0 579 | 580 | let etop = e.target.scrollTop 581 | let eleft = e.target.scrollLeft 582 | const flag = e.flag 583 | 584 | log_debug(ctx, `top: ${etop}, left: ${eleft}`) 585 | 586 | // checks every tr's height, which will take some time... 587 | const offset = scroll_with_offset( 588 | ctx, 589 | ctx.f_final_top === TOP_CONTINUE ? ctx.final_top : etop) 590 | 591 | const head = offset[0] 592 | const tail = offset[1] 593 | const top = offset[2] 594 | 595 | const prev_head = ctx._offset_head 596 | const prev_tail = ctx._offset_tail 597 | const prev_top = ctx._offset_top 598 | 599 | let end = false 600 | 601 | switch (flag) { 602 | case SCROLLEVT_INIT: 603 | log_debug(ctx, 'SCROLLEVT_INIT') 604 | break 605 | 606 | case SCROLLEVT_BY_HOOK: 607 | log_debug(ctx, 'SCROLLEVT_BY_HOOK') 608 | 609 | if (head === prev_head && tail === prev_tail && top === prev_top) { 610 | ctx.f_final_top = TOP_DONE 611 | if (ctx.final_top === -1) etop = ctx.computed_h - ctx._y 612 | end = true 613 | } else { 614 | if (ctx.final_top === -1) etop = top 615 | } 616 | 617 | break 618 | 619 | case SCROLLEVT_RECOMPUTE: 620 | if (head === prev_head && tail === prev_tail && top === prev_top) { 621 | return 622 | } 623 | 624 | log_debug(ctx, 'SCROLLEVT_RECOMPUTE') 625 | break 626 | 627 | 628 | case SCROLLEVT_NATIVE: 629 | if (head === prev_head && tail === prev_tail && top === prev_top) { 630 | return 631 | } 632 | 633 | log_debug(ctx, 'SCROLLEVT_NATIVE') 634 | 635 | if (ctx.onScroll) { 636 | ctx.onScroll({ 637 | top: etop, 638 | left: eleft, 639 | isEnd: e.end, 640 | }) 641 | } 642 | 643 | end = e.end 644 | break 645 | } 646 | 647 | set_offset(ctx, top, head, tail) 648 | set_scroll(ctx, etop, eleft, flag, end) 649 | force[1](++ctx.update_count) 650 | }, []) 651 | 652 | 653 | // expose to the parent components you are using. 654 | useImperativeHandle(ref, () => { 655 | // `y === -1` indicates you need to scroll to the bottom of the table. 656 | const scrollTo = (y: number) => { 657 | ctx.f_final_top = TOP_CONTINUE 658 | ctx.final_top = y 659 | scroll_hook({ 660 | target: { scrollTop: y, scrollLeft: -1 }, 661 | flag: SCROLLEVT_BY_HOOK, 662 | }) 663 | } 664 | return { 665 | scrollTo: (y) => { 666 | ref_func.current = () => scrollTo(y) 667 | ref_func.current() 668 | }, 669 | scrollToIndex: (idx) => { 670 | ref_func.current = () => { 671 | if (idx > ctx.row_count - 1) idx = ctx.row_count - 1 672 | if (idx < 0) idx = 0 673 | let y = 0 674 | for (let i = 0; i < idx; ++i) { 675 | y += ctx.row_height[i] 676 | } 677 | scrollTo(y) 678 | } 679 | ref_func.current() 680 | } 681 | } 682 | }, []) 683 | 684 | 685 | useEffect(() => { 686 | const el = wrap_inst.current.parentElement 687 | try { 688 | el.addEventListener('scroll', scroll_hook as any, { 689 | passive: true, 690 | }) 691 | } catch { 692 | el.addEventListener('scroll', scroll_hook as any, false) 693 | } 694 | }, [wrap_inst.current]) 695 | 696 | 697 | 698 | useEffect(() => { 699 | scroll_hook({ 700 | flag: SCROLLEVT_BY_HOOK, 701 | target: { 702 | scrollLeft: ctx.left, 703 | scrollTop: ctx.top, 704 | } 705 | }) 706 | }, [ctx.scroll.y]) 707 | 708 | 709 | // update DOM style. 710 | useEffect(() => { 711 | switch (ctx.evt) { 712 | case SCROLLEVT_BY_HOOK: 713 | if (ctx.f_final_top === TOP_CONTINUE) { 714 | ref_func.current() 715 | } else { 716 | scroll_to(ctx, ctx.top, ctx.left) 717 | } 718 | break 719 | case SCROLLEVT_INIT: 720 | case SCROLLEVT_RECOMPUTE: 721 | scroll_to(ctx, ctx.top, ctx.left) 722 | break 723 | } 724 | }, [force[0]/* for performance. */]) 725 | 726 | 727 | useEffect(() => { 728 | switch (ctx.vt_state) { 729 | case e_VT_STATE.LOADED: // changed by VTRow only. 730 | ctx.vt_state = e_VT_STATE.RUNNING 731 | 732 | // force update. 733 | scroll_hook({ 734 | target: { scrollTop: ctx.top, scrollLeft: 0 }, 735 | flag: SCROLLEVT_BY_HOOK, 736 | }) 737 | break 738 | 739 | case e_VT_STATE.RUNNING: 740 | if (ctx.re_computed !== 0) { // rerender 741 | ctx.re_computed = 0 742 | scroll_hook({ 743 | target: { scrollTop: ctx.top, scrollLeft: ctx.left }, 744 | flag: SCROLLEVT_RECOMPUTE, 745 | }) 746 | } 747 | break 748 | } 749 | }) 750 | 751 | const wrapStyle = useMemo( 752 | () => ({ 753 | width: style.width, 754 | minWidth: '100%', 755 | position: 'relative', 756 | transform: 'translate(0)', 757 | }), 758 | [style.width] 759 | ) 760 | 761 | const tableStyle = useMemo(() => ( 762 | { 763 | ...style, 764 | width: void 0, 765 | position: 'relative', 766 | top: ctx._offset_top, 767 | transform: 'translate(0)', 768 | }), 769 | [ctx._offset_top] 770 | ) 771 | 772 | const internalCtx = useMemo(() => ({ ...ctx }), [ctx.update_count]) 773 | 774 | const Table = ctx.components.table 775 | 776 | return ( 777 |
781 | 782 |
783 | 784 | 785 | ) 786 | 787 | } 788 | 789 | 790 | interface VWrapperProps { 791 | ctx: VT_CONTEXT; 792 | className: string; 793 | children: React.ReactNode; 794 | } 795 | 796 | const VWrapper: React.FC = (props) => { 797 | const { children, className, ctx } = props 798 | const measureRow = children[0] 799 | const rows = children[1] 800 | 801 | // reference https://github.com/react-component/table/blob/master/src/Body/index.tsx#L6 802 | let len = Array.isArray(rows) ? rows.length : 0 803 | 804 | let { _offset_head: head, _offset_tail: tail } = ctx 805 | 806 | type RowType = React.ReactElement<{ 807 | indent: number; 808 | record: object; 809 | }> 810 | 811 | let trs: RowType[] = [] 812 | 813 | switch (ctx.vt_state) { 814 | case e_VT_STATE.INIT: 815 | if (len >= 0) { 816 | console.assert(head === 0) 817 | console.assert(tail === 1) 818 | if (Array.isArray(rows)) { 819 | trs = rows.slice(head, tail) 820 | ctx.indexMap.set(trs[0].props.record, 0) 821 | } else { 822 | trs = rows 823 | } 824 | ctx.re_computed = len 825 | ctx.prev_row_count = len 826 | ctx.row_count = len 827 | } 828 | break 829 | 830 | case e_VT_STATE.RUNNING: { 831 | if (tail > len) { 832 | const offset = tail - len 833 | tail -= offset 834 | head -= offset 835 | if (head < 0) head = 0 836 | if (tail < 0) tail = 0 837 | // update the `head` and `tail`. 838 | set_offset(ctx, 839 | ctx._offset_top/* NOTE: invalided param, just to fill for this param */, 840 | head, tail) 841 | } 842 | 843 | if (ctx.row_count !== len) { 844 | set_tr_cnt(ctx, len) 845 | } 846 | 847 | len = ctx.row_count 848 | const prev_len = ctx.prev_row_count 849 | 850 | /* shadow-rows rendering phase. */ 851 | if (len < prev_len) { 852 | srs_shrink(ctx, len, prev_len) 853 | } else if (len > prev_len) { 854 | const row_h = ctx.row_height 855 | if ((len - row_h.length) > 0) { 856 | srs_expand(ctx, len, prev_len, ctx.possible_hight_per_tr) 857 | } else { 858 | // using an existing array. 859 | row_h.fill(ctx.possible_hight_per_tr, prev_len, len) 860 | ctx.computed_h += ctx.possible_hight_per_tr * (len - prev_len) 861 | } 862 | } 863 | 864 | /** 865 | * tree-structure if indent is not 0 866 | * | idx 867 | * | 0 || 0a 0 || 0a 868 | * | 1 || 0b --collapse occurred-- 1 || 0b 869 | * | 2 || - 1 5->2 || 0c 870 | * head | 3 || - 1 6->3 || 0d 871 | * | 4 || - 2 7->4 || 0e 872 | * | 5 || 0c 8->5 || - 1 873 | * | 6 || 0d 9->6 || - 2 874 | * | 7 || 0e 10->7 || - 3 875 | * tail | 8 || - 1 11->8 || 0f 876 | * | 9 || - 2 877 | * | 10 || - 3 878 | * | 11 || 0f 879 | * | 12 || 880 | */ 881 | if (len) { 882 | let idx = head 883 | trs = rows.slice(idx, tail) 884 | trs.forEach(el => ctx.indexMap.set(el.props.record, idx++)) 885 | } else { 886 | trs = rows 887 | } 888 | 889 | ctx.prev_row_count = ctx.row_count 890 | } 891 | break 892 | 893 | case e_VT_STATE.LOADED: 894 | console.assert(false) 895 | break 896 | } 897 | 898 | const Wrapper = ctx.components.body.wrapper 899 | 900 | return ( 901 | 902 | {measureRow} 903 | {trs} 904 | 905 | ) 906 | } 907 | 908 | interface VRowProps { 909 | style: React.CSSProperties; 910 | ctx: VT_CONTEXT; 911 | children: React.ReactNode; 912 | } 913 | 914 | 915 | const VTRow: React.FC = (props) => { 916 | 917 | const ref = React.createRef() 918 | 919 | const { ctx, ...rest } = props 920 | 921 | const children = props.children 922 | 923 | const Row = ctx.components.body.row 924 | 925 | if (!Array.isArray(children)) { 926 | // https://github.com/react-component/table/blob/master/src/Body/BodyRow.tsx#L211 927 | // https://github.com/react-component/table/blob/master/src/Body/index.tsx#L105 928 | // only empty or expanded row... 929 | return {children} 930 | } 931 | 932 | const row_props = children[0].props 933 | const index: number = ctx.indexMap.get(row_props.record) 934 | const last_index = useRef(index) 935 | 936 | const expanded_cls = useMemo(() => `.${row_props.prefixCls}-expanded-row`, [row_props.prefixCls]) 937 | 938 | const t0 = performance.now() 939 | 940 | useLayoutEffect(() => { 941 | const t1 = performance.now() 942 | log_debug(ctx, `+idx ${index} tooks ${t1 - t0} ms`) 943 | 944 | if (ctx.vt_state === e_VT_STATE.RUNNING) { 945 | repainting(ctx) 946 | } else { 947 | ctx.possible_hight_per_tr = ref.current.offsetHeight 948 | srs_expand(ctx, ctx.row_count, 0, ctx.possible_hight_per_tr) 949 | repainting(ctx) 950 | ctx.vt_state = e_VT_STATE.LOADED 951 | } 952 | 953 | return () => { 954 | repainting(ctx) 955 | } 956 | }, []) 957 | 958 | 959 | useEffect(() => { 960 | ctx.cq.push({ 961 | index: index, 962 | func: () => { 963 | const rowElm = ref.current 964 | 965 | if (!rowElm) return 966 | 967 | let h = rowElm.offsetHeight 968 | let sibling = rowElm.nextSibling as HTMLTableRowElement 969 | // https://github.com/react-component/table/blob/master/src/Body/BodyRow.tsx#L212 970 | // include heights of all expanded rows, in parent rows 971 | while (sibling && sibling.matches(expanded_cls)) { 972 | h += sibling.offsetHeight 973 | sibling = sibling.nextSibling as HTMLTableRowElement 974 | } 975 | 976 | const curr_h = ctx.row_height[index] 977 | const last_h = ctx.row_height[last_index.current] 978 | 979 | ctx.computed_h -= curr_h 980 | ctx.computed_h += last_h 981 | ctx.computed_h += h - last_h 982 | ctx.row_height[index] = h 983 | } 984 | }) 985 | 986 | repainting(ctx) 987 | }) 988 | 989 | return 990 | } 991 | 992 | 993 | 994 | 995 | export 996 | function _set_components(ctx: VT_CONTEXT, components: TableComponents): void { 997 | const { table, body, header } = components 998 | ctx.components.body = { ...ctx.components.body, ...body } 999 | if (body && body.cell) { 1000 | ctx._vtcomponents.body.cell = body.cell 1001 | } 1002 | if (header) { 1003 | ctx.components.header = header 1004 | ctx._vtcomponents.header = header 1005 | } 1006 | if (table) { 1007 | ctx.components.table = table 1008 | } 1009 | } 1010 | 1011 | export 1012 | function init(fnOpts: () => VtOpts, deps: React.DependencyList): VT_CONTEXT { 1013 | const ctx = useRef(React.createContext({ } as VT_CONTEXT)).current 1014 | const ctx_value = useContext(ctx) 1015 | const default_ref: VtOpts['ref'] = useRef({ 1016 | scrollTo: (y: number) => {}, 1017 | scrollToIndex: (idx: number) => {}, 1018 | }) 1019 | useMemo(() => { 1020 | return Object.assign( 1021 | ctx_value, 1022 | { 1023 | id: (+new Date()).toString(36).slice(4), 1024 | initTop: 0, 1025 | overscanRowCount: 5, 1026 | debug: false, 1027 | ref: default_ref, 1028 | }, 1029 | fnOpts()) 1030 | }, deps) 1031 | 1032 | useMemo(() => { 1033 | const VT = React.forwardRef(VTable) 1034 | 1035 | // set the virtual layer. 1036 | ctx_value._vtcomponents = { 1037 | table: props => , 1038 | body: { 1039 | // https://github.com/react-component/table/blob/master/src/Body/index.tsx#L114 1040 | wrapper: props => { 1041 | return ( 1042 | 1043 | {(/* value */) => { 1044 | return ( 1045 | 1046 | ) 1047 | }} 1048 | 1049 | ) 1050 | }, 1051 | row: props => , 1052 | } 1053 | } 1054 | // set the default implementation layer. 1055 | ctx_value.components = {} 1056 | _set_components(ctx_value, { 1057 | table: TableImpl, 1058 | body: { 1059 | wrapper: WrapperImpl, 1060 | row: RowImpl, 1061 | } 1062 | }) 1063 | // start -> `INIT` 1064 | ctx_value.vt_state = e_VT_STATE.INIT 1065 | }, []) 1066 | 1067 | return ctx_value 1068 | } 1069 | 1070 | -------------------------------------------------------------------------------- /test/CustomRows Hooks.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable object-curly-newline */ 3 | /* eslint-disable react/prop-types */ 4 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 5 | /* eslint-disable jsx-a11y/anchor-is-valid */ 6 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 7 | /* eslint-disable arrow-parens */ 8 | /* eslint-disable react/react-in-jsx-scope */ 9 | /** 10 | * copy this file to your working directory. 11 | */ 12 | import React, { useRef, useEffect, useMemo } from 'react'; 13 | import { Table } from 'antd'; 14 | 15 | import { useVT } from 'virtualizedtableforantd'; 16 | 17 | const data = []; 18 | for (let i = 0; i < 100; i++) { 19 | data.push({ 20 | key: i.toString(), 21 | name: `Edrward ${i}`, 22 | age: 32, 23 | address: `London Park no. ${i}`, 24 | }); 25 | } 26 | 27 | 28 | /** 29 | * must use `React.forwardRef` to create your custom component, and don't forget to use `ref`. 30 | */ 31 | const MyRow = React.forwardRef((props, ref) => { 32 | const { children, ...rest } = props; 33 | return window.alert('1')}>{children}; 34 | }); 35 | 36 | 37 | function CustomRowsHooks() { 38 | const columns = useRef([ 39 | { 40 | title: 'name', 41 | dataIndex: 'name', 42 | width: '25%', 43 | editable: true, 44 | }, 45 | { 46 | title: 'age', 47 | dataIndex: 'age', 48 | width: '15%', 49 | editable: true, 50 | }, 51 | { 52 | title: 'address', 53 | dataIndex: 'address', 54 | // width: '40%', 55 | editable: true, 56 | }, 57 | ]); 58 | 59 | const [VT, setVT] = useVT(() => ({ scroll: { y: 500 }, debug: true })); 60 | 61 | useMemo(() => setVT({ body: { row: MyRow } }), [setVT]); 62 | 63 | return ( 64 |
72 | ); 73 | } 74 | 75 | export default CustomRowsHooks; 76 | -------------------------------------------------------------------------------- /test/Drag soring.css: -------------------------------------------------------------------------------- 1 | #components-table-demo-drag-sorting tr.drop-over-downward td { 2 | border-bottom: 2px dashed #1890ff; 3 | } 4 | 5 | #components-table-demo-drag-sorting tr.drop-over-upward td { 6 | border-top: 2px dashed #1890ff; 7 | } -------------------------------------------------------------------------------- /test/Drag soring.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; 2 | import { Table } from 'antd'; 3 | import { DndProvider, useDrag, useDrop, createDndContext } from 'react-dnd'; 4 | import HTML5Backend from 'react-dnd-html5-backend'; 5 | import update from 'immutability-helper'; 6 | import { useVT } from 'virtualizedtableforantd'; 7 | 8 | 9 | const RNDContext = createDndContext(HTML5Backend); 10 | 11 | const type = 'DragableBodyRow'; 12 | 13 | 14 | /** 15 | * must use `React.forwardRef` to create your custom component, and don't forget to use `ref`. 16 | */ 17 | const DragableBodyRow = React.forwardRef((props, ref) => { 18 | 19 | const { index, moveRow, className, style, ...restProps } = props as any; 20 | 21 | 22 | const [{ isOver, dropClassName }, drop] = useDrop({ 23 | accept: type, 24 | collect: monitor => { 25 | const { index: dragIndex } = monitor.getItem() || {}; 26 | if (dragIndex === index) { 27 | return {}; 28 | } 29 | return { 30 | isOver: monitor.isOver(), 31 | dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward', 32 | }; 33 | }, 34 | drop: item => { 35 | moveRow((item as any).index, index); 36 | }, 37 | }); 38 | 39 | const [, drag] = useDrag({ 40 | item: { type, index }, 41 | collect: monitor => ({ 42 | isDragging: monitor.isDragging(), 43 | }), 44 | }); 45 | 46 | useEffect(() => { 47 | drop(drag((ref as any).current)); 48 | }, []); 49 | 50 | 51 | return ( 52 | 58 | ); 59 | 60 | }); 61 | 62 | const columns = [ 63 | { 64 | title: 'Name', 65 | dataIndex: 'name', 66 | key: 'name', 67 | }, 68 | { 69 | title: 'Age', 70 | dataIndex: 'age', 71 | key: 'age', 72 | }, 73 | { 74 | title: 'Address', 75 | dataIndex: 'address', 76 | key: 'address', 77 | }, 78 | ]; 79 | 80 | const DragSortingTable: React.FC = () => { 81 | const [data, setData] = useState([ 82 | { 83 | key: '1', 84 | name: 'John Brown', 85 | age: 32, 86 | address: 'New York No. 1 Lake Park', 87 | }, 88 | { 89 | key: '2', 90 | name: 'Jim Green', 91 | age: 42, 92 | address: 'London No. 1 Lake Park', 93 | }, 94 | { 95 | key: '3', 96 | name: 'Joe Black', 97 | age: 32, 98 | address: 'Sidney No. 1 Lake Park', 99 | }, 100 | ]); 101 | 102 | 103 | const moveRow = useCallback( 104 | (dragIndex, hoverIndex) => { 105 | const dragRow = data[dragIndex]; 106 | setData( 107 | update(data, { 108 | $splice: [ 109 | [dragIndex, 1], 110 | [hoverIndex, 0, dragRow], 111 | ], 112 | }), 113 | ); 114 | }, 115 | [data], 116 | ); 117 | 118 | const manager = useRef(RNDContext); 119 | 120 | /************************/ 121 | const [vt, setVT] = useVT(() => { 122 | return { 123 | scroll: { y: 500 }, 124 | } 125 | }); 126 | 127 | useMemo(() => setVT({ 128 | body: { 129 | row: DragableBodyRow, 130 | }, 131 | }), []); 132 | /************************/ 133 | 134 | return ( 135 | 136 |
({ 142 | index, 143 | moveRow, 144 | })} 145 | /> 146 | 147 | ); 148 | }; 149 | 150 | export default DragSortingTable; 151 | -------------------------------------------------------------------------------- /test/Editable Rows.css: -------------------------------------------------------------------------------- 1 | .editable-row .ant-form-explain { 2 | position: absolute; 3 | font-size: 12px; 4 | margin-top: -4px; 5 | } -------------------------------------------------------------------------------- /test/Editable Rows.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { useState } from 'react'; 5 | import { Table, Input, InputNumber, Popconfirm, Form } from 'antd'; 6 | import { useVT } from 'virtualizedtableforantd'; 7 | 8 | // import style from './Editable Rows.css'; 9 | 10 | const data = []; 11 | for (let i = 0; i < 100; i++) { 12 | data.push({ 13 | key: i.toString(), 14 | name: `Edrward ${i}`, 15 | age: 32, 16 | address: `London Park no. ${i}`, 17 | }); 18 | } 19 | 20 | 21 | 22 | 23 | interface Item { 24 | key: string; 25 | name: string; 26 | age: number; 27 | address: string; 28 | } 29 | 30 | const originData: Item[] = []; 31 | for (let i = 0; i < 100; i++) { 32 | originData.push({ 33 | key: i.toString(), 34 | name: `Edrward ${i}`, 35 | age: 32, 36 | address: `London Park no. ${i}`, 37 | }); 38 | } 39 | interface EditableCellProps extends React.HTMLAttributes { 40 | editing: boolean; 41 | dataIndex: string; 42 | title: any; 43 | inputType: 'number' | 'text'; 44 | record: Item; 45 | index: number; 46 | children: React.ReactNode; 47 | } 48 | 49 | const EditableCell: React.FC = ({ 50 | editing, 51 | dataIndex, 52 | title, 53 | inputType, 54 | record, 55 | index, 56 | children, 57 | ...restProps 58 | }) => { 59 | const inputNode = inputType === 'number' ? : ; 60 | 61 | return ( 62 | 80 | ); 81 | }; 82 | 83 | const EditableTable = () => { 84 | const [form] = Form.useForm(); 85 | const [data, setData] = useState(originData); 86 | const [editingKey, setEditingKey] = useState(''); 87 | 88 | const isEditing = (record: Item) => record.key === editingKey; 89 | 90 | const edit = (record: Item) => { 91 | form.setFieldsValue({ name: '', age: '', address: '', ...record }); 92 | setEditingKey(record.key); 93 | }; 94 | 95 | const cancel = () => { 96 | setEditingKey(''); 97 | }; 98 | 99 | const save = async (key: React.Key) => { 100 | try { 101 | const row = (await form.validateFields()) as Item; 102 | 103 | const newData = [...data]; 104 | const index = newData.findIndex(item => key === item.key); 105 | if (index > -1) { 106 | const item = newData[index]; 107 | newData.splice(index, 1, { 108 | ...item, 109 | ...row, 110 | }); 111 | setData(newData); 112 | setEditingKey(''); 113 | } else { 114 | newData.push(row); 115 | setData(newData); 116 | setEditingKey(''); 117 | } 118 | } catch (errInfo) { 119 | console.log('Validate Failed:', errInfo); 120 | } 121 | }; 122 | 123 | const columns = [ 124 | { 125 | title: 'name', 126 | dataIndex: 'name', 127 | width: '25%', 128 | editable: true, 129 | }, 130 | { 131 | title: 'age', 132 | dataIndex: 'age', 133 | width: '15%', 134 | editable: true, 135 | }, 136 | { 137 | title: 'address', 138 | dataIndex: 'address', 139 | width: '40%', 140 | editable: true, 141 | }, 142 | { 143 | title: 'operation', 144 | dataIndex: 'operation', 145 | render: (_: any, record: Item) => { 146 | const editable = isEditing(record); 147 | return editable ? ( 148 | 149 | save(record.key)} style={{ marginRight: 8 }}> 150 | Save 151 | 152 | 153 | Cancel 154 | 155 | 156 | ) : ( 157 | edit(record)}> 158 | Edit 159 | 160 | ); 161 | }, 162 | }, 163 | ]; 164 | 165 | const mergedColumns = columns.map(col => { 166 | if (!col.editable) { 167 | return col; 168 | } 169 | return { 170 | ...col, 171 | onCell: (record: Item) => ({ 172 | record, 173 | inputType: col.dataIndex === 'age' ? 'number' : 'text', 174 | dataIndex: col.dataIndex, 175 | title: col.title, 176 | editing: isEditing(record), 177 | }), 178 | }; 179 | }); 180 | 181 | 182 | 183 | 184 | 185 | /*******************************/ 186 | const y = 500; 187 | const [vt, setComponent] = useVT(() => ({ scroll: { y }, debug: true } )); 188 | setComponent({ 189 | body: { 190 | cell: EditableCell, 191 | }, 192 | }); 193 | /*******************************/ 194 | 195 | 196 | return ( 197 |
198 |
63 | {editing ? ( 64 | 74 | {inputNode} 75 | 76 | ) : ( 77 | children 78 | )} 79 |
214 | 215 | ); 216 | }; 217 | 218 | export default EditableTable; 219 | -------------------------------------------------------------------------------- /test/expandable.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { FC, useState } from 'react'; 5 | import { useVT } from 'virtualizedtableforantd4'; 6 | import { Table } from 'antd'; 7 | 8 | 9 | function rndstr(str: string) { 10 | const n = 0 | (Math.random() * 10) + 1; 11 | let s = ''; 12 | for (let i = 0; i < n; ++i) { 13 | s += str; 14 | } 15 | return s; 16 | } 17 | 18 | const _data: any[] = []; 19 | 20 | for (let i = 0; i < 1000; i++) { 21 | _data.push({ 22 | index: i, 23 | name: `Edrward ${i}`, 24 | expandable: ((Math.random() * 10) | 0) > 4, 25 | age: 32, 26 | address: rndstr(`London Park no. ${i}`), 27 | }); 28 | } 29 | 30 | const TableScrollTo: FC = () => { 31 | const [expandedRowKeys, setExpandedRowKeys] = useState([]); 32 | 33 | const [VT] = useVT(() => { 34 | return { 35 | // ref: ref, // deprecated 36 | initTop: 1000, 37 | debug: true, 38 | scroll: { 39 | y: 600, 40 | } 41 | } 42 | }, []); 43 | 44 | 45 | return ( 46 | <> 47 |
{ 64 | if (expanded) { 65 | setExpandedRowKeys([...expandedRowKeys, record.index]); 66 | } else { 67 | const keys = expandedRowKeys.filter((k) => k !== record.index); 68 | setExpandedRowKeys(keys); 69 | } 70 | }, 71 | expandedRowRender: record =>

my name is {record.name}

, 72 | rowExpandable: record => record.expandable, 73 | }} 74 | /> 75 | 76 | ) 77 | } 78 | 79 | export default TableScrollTo; 80 | -------------------------------------------------------------------------------- /test/fetch.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { useState, useEffect } from 'react'; 5 | import { Table } from 'antd'; 6 | import { useVT } from 'virtualizedtableforantd'; 7 | 8 | export default function TableFetch() { 9 | const [dataSource, setDataSource] = useState([]); 10 | const y = 500; 11 | const [vt] = useVT(() => { 12 | return { 13 | scroll: { y }, 14 | debug: true, 15 | }; 16 | }); 17 | 18 | let tempDataSource: any[] = []; 19 | for (let i = 0; i < 100; i++) { 20 | tempDataSource.push({ 21 | company_name: `aaa${i}`, 22 | key: i, 23 | }); 24 | } 25 | 26 | useEffect(() => { 27 | setTimeout(() => { 28 | setDataSource(tempDataSource); 29 | }, 5000); 30 | }, []); 31 | 32 | 33 | const columns = [ 34 | { 35 | title: "No.", 36 | key: "id", 37 | render(text: any, record: any, index: any) { 38 | return index + 1; 39 | }, 40 | width: 100 41 | }, 42 | { 43 | title: "company name", 44 | dataIndex: "company_name", 45 | width: 200 46 | } 47 | ]; 48 | 49 | return ( 50 |
57 | ); 58 | } -------------------------------------------------------------------------------- /test/indez.jsx: -------------------------------------------------------------------------------- 1 | import { useVT } from '../src/index' 2 | 3 | 4 | const EditableCell = (props) => { 5 | 6 | return , 29 | } 30 | }) 31 | 32 | 33 | return ( 34 | 35 | ) 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/reload.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { useState, useMemo, useCallback, useRef, useContext, useEffect, } from 'react'; 5 | import { Table, Button } from 'antd'; 6 | import { useVT } from 'virtualizedtableforantd'; 7 | import { Switch as RouteSwitch, Route, NavLink } from 'react-router-dom'; 8 | 9 | 10 | 11 | 12 | function Table1({ ctx }: { ctx: any} ) { 13 | 14 | const _data = useMemo(() => { 15 | const data: any[] = []; 16 | for (let i = 0; i < 1000; i++) { 17 | data.push({ 18 | key: i, 19 | name: `Edrward ${i}`, 20 | age: 32, 21 | address: (`London Park no. ${i}`), 22 | }); 23 | } 24 | return data; 25 | }, []); 26 | 27 | 28 | const [data, setData] = useState([]); 29 | 30 | 31 | // Column name age 1 2 3 4 5 6 7 8 operation 32 | const _columns: any = useMemo(() => [ 33 | { 34 | title: 'Full Name', 35 | width: 150, 36 | dataIndex: 'name', 37 | key: 'name', 38 | render: (text: any) => { 39 | return {text} 40 | }, 41 | fixed: 'left', 42 | }, 43 | { 44 | title: 'Age', 45 | width: 100, 46 | dataIndex: 'age', 47 | key: 'age', 48 | }, 49 | { 50 | title: 'Column 1', 51 | dataIndex: 'address', 52 | key: '1', 53 | width: 150, 54 | }, 55 | { 56 | title: 'Column 2', 57 | dataIndex: 'address', 58 | key: '2', 59 | width: 150, 60 | }, 61 | { 62 | title: 'Column 3', 63 | dataIndex: 'address', 64 | key: '3', 65 | width: 170, 66 | }, 67 | { 68 | title: 'Column 4', 69 | dataIndex: 'address', 70 | key: '4', 71 | width: 180, 72 | }, 73 | { 74 | title: 'Column 5', 75 | dataIndex: 'address', 76 | key: '5', 77 | width: 190, 78 | }, 79 | { 80 | title: 'Column 6', 81 | dataIndex: 'address', 82 | key: '6', 83 | width: 150, 84 | }, 85 | { 86 | title: 'Column 7', 87 | dataIndex: 'address', 88 | key: '7', 89 | 90 | }, 91 | { title: 'Column 8', dataIndex: 'address', key: '8' }, 92 | 93 | ], []); 94 | 95 | 96 | const [vt] = useVT(() => { 97 | return { 98 | initTop: ctx.top, 99 | onScroll: ({ top }) => ctx.top = top, 100 | scroll: { y: 500 }, 101 | debug: true, 102 | } 103 | }, [ctx.top]); 104 | 105 | useEffect(() => { 106 | setTimeout(() => { 107 | window.alert('fetching the data...'); 108 | setData(_data); 109 | }, 50); 110 | }, []); 111 | 112 | 113 | return ( 114 | <> 115 | 116 | 117 | 118 | 119 |
120 |
121 | 122 | 123 |
7 | } 8 | 9 | const App = () => { 10 | const [vt, setVt] = useVT(() => { 11 | return { 12 | scroll: { 13 | y: 500 14 | } 15 | } 16 | }, []) 17 | 18 | const convertedLegacyValues = [] 19 | 20 | setVt({ 21 | body: { 22 | cell: (props) => ( 23 | 27 | ), 28 | row: (...args) =>
131 |
132 | 133 | ); 134 | } 135 | 136 | 137 | export default function Reload() { 138 | 139 | const ctx = useRef({ top: 0 }); 140 | 141 | return ( 142 | 143 | { 144 | return ( 145 | <> 146 |

go home

147 |
Mr. Huggins
148 | 149 | ) 150 | }}>
151 | }> 152 |
153 | ) 154 | } -------------------------------------------------------------------------------- /test/scroll-to.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState, useRef } from 'react'; 2 | import { useVT } from 'virtualizedtableforantd4'; 3 | import { Table, Input, Button } from 'antd'; 4 | 5 | 6 | function rndstr(str: string) { 7 | const n = 0 | (Math.random() * 10) + 1; 8 | let s = ''; 9 | for (let i = 0; i < n; ++i) { 10 | s += str; 11 | } 12 | return s; 13 | } 14 | 15 | const _data: any[] = []; 16 | 17 | for (let i = 0; i < 1000; i++) { 18 | _data.push({ 19 | index: i, 20 | name: `Edrward ${i}`, 21 | age: 32, 22 | address: rndstr(`London Park no. ${i}`), 23 | }); 24 | } 25 | 26 | const TableScrollTo: FC = () => { 27 | const [top, setTop] = useState(-1); 28 | const [idx, setIdx] = useState(0); 29 | // const ref = useRef(); 30 | 31 | const [VT, _, ref/* new */] = useVT(() => { 32 | return { 33 | // ref: ref, // deprecated 34 | initTop: 1000, 35 | debug: true, 36 | scroll: { 37 | y: 600, 38 | } 39 | } 40 | }, []); 41 | 42 | 43 | return ( 44 | <> 45 | scrollTo: 46 | { 49 | const top = (+e.target.value); 50 | setTop(Number.isNaN(top) ? 0 : top); 51 | }} 52 | /> 53 | 61 | 62 |
63 | scrollToIdx: 64 | { 68 | const idx = (+e.target.value); 69 | setIdx(Number.isNaN(idx) ? 0 : idx); 70 | }} 71 | /> 72 | 79 | 80 | 95 | 96 | ) 97 | } 98 | 99 | export default TableScrollTo; 100 | -------------------------------------------------------------------------------- /test/table ajax.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { useState, useMemo, useCallback, useRef, useContext, useEffect, } from 'react'; 5 | import { Table, Button } from 'antd'; 6 | import { useVT } from 'virtualizedtableforantd'; 7 | 8 | 9 | 10 | 11 | function rndstr(str: string) { 12 | const n = 0 | (Math.random() * 4) + 1; 13 | let s = ''; 14 | for (let i = 0; i < n; ++i) { 15 | s += str; 16 | } 17 | return s; 18 | } 19 | 20 | 21 | export default function Table1() { 22 | 23 | 24 | const [chunkid, setChunkID] = useState(1); 25 | const [data, setData] = useState([]); 26 | 27 | const myajax = useCallback((chunkid: number) => { 28 | const data: any[] = []; 29 | if (chunkid === 1) { 30 | for (let i = 0; i < 1000; i++) { 31 | const n = 0 | Math.random() * 3000 + 1000; 32 | data.push({ 33 | key: `${chunkid}-${i}`, 34 | name: `Edrward ${chunkid}-${i}`, 35 | age: 0 | Math.random() * 88 + 12, 36 | address: rndstr(`London Park no. ${n}`), 37 | }); 38 | } 39 | } else { 40 | /** chunk */ 41 | for (let i = 0; i < 200; i++) { 42 | const n = 0 | Math.random() * 3000 + 1000; 43 | data.push({ 44 | key: `${chunkid}-${i}`, 45 | name: `Edrward ${chunkid}-${i}`, 46 | age: 0 | Math.random() * 88 + 12, 47 | address: rndstr(`London Park no. ${n}`), 48 | }); 49 | } 50 | } 51 | 52 | 53 | return new Promise((resolve, reject) => { 54 | setTimeout(() => { 55 | // returns chunkid from your server. 56 | resolve([chunkid + 1, data]); 57 | }, Math.random() * 200 + 100); 58 | }); 59 | }, []); 60 | 61 | // Pagination 62 | const [showPagination, setPagination] = useState(false); 63 | 64 | // Column name age 1 2 3 4 5 6 7 8 operation 65 | const _columns: any = useMemo(() => [ 66 | { 67 | title: 'Full Name', 68 | width: 150, 69 | dataIndex: 'name', 70 | key: 'name', 71 | 72 | fixed: 'left', 73 | }, 74 | { 75 | title: 'Age', 76 | width: 100, 77 | dataIndex: 'age', 78 | key: 'age', 79 | }, 80 | { 81 | title: 'Column 1', 82 | dataIndex: 'address', 83 | key: '1', 84 | width: 150, 85 | }, 86 | { 87 | title: 'Column 2', 88 | dataIndex: 'address', 89 | key: '2', 90 | width: 150, 91 | }, 92 | { 93 | title: 'Column 3', 94 | dataIndex: 'address', 95 | key: '3', 96 | width: 170, 97 | }, 98 | { 99 | title: 'Column 4', 100 | dataIndex: 'address', 101 | key: '4', 102 | width: 180, 103 | }, 104 | { 105 | title: 'Column 5', 106 | dataIndex: 'address', 107 | key: '5', 108 | width: 190, 109 | }, 110 | { 111 | title: 'Column 6', 112 | dataIndex: 'address', 113 | key: '6', 114 | width: 150, 115 | }, 116 | { 117 | title: 'Column 7', 118 | dataIndex: 'address', 119 | key: '7', 120 | 121 | }, 122 | { title: 'Column 8', dataIndex: 'address', key: '8' }, 123 | 124 | ], []); 125 | 126 | 127 | const loadDataByChunk = useCallback(async (chunkid, data) => { 128 | const [ _newchunkid, _newdata ] = await myajax(chunkid); 129 | setChunkID(_newchunkid); 130 | setData([...data, ..._newdata]); 131 | }, [setData, setChunkID]); 132 | 133 | const y = 500; 134 | 135 | const [vt] = useVT(() => ({ 136 | onScroll: ({ isEnd }) => { 137 | if (isEnd) { 138 | console.log("loadDataByChunk"); 139 | loadDataByChunk(chunkid, data); 140 | } 141 | }, 142 | scroll: { y }, 143 | debug: true, 144 | }), [chunkid, data]); 145 | 146 | return ( 147 | <> 148 | 151 |
152 |
153 | 154 | ---- pagination ---- 155 |
156 | 163 | 164 |
174 |
175 | 176 | ); 177 | } 178 | 179 | -------------------------------------------------------------------------------- /test/table#100.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { useState} from 'react'; 5 | import { Table } from 'antd'; 6 | import { useVT } from 'virtualizedtableforantd4'; 7 | 8 | 9 | 10 | 11 | export default function Table1() { 12 | const initialItems = [ 13 | { name: "Item1", description: "Description1", key: 1 }, 14 | { name: "Item2", description: "Description2", key: 2 }, 15 | { name: "Item3", description: "Description3", key: 3 }, 16 | { name: "Item4", description: "Description4", key: 4 } 17 | ]; 18 | const columns = [ 19 | { title: "Name", dataIndex: "name" }, 20 | { title: "Description", dataIndex: "description" } 21 | ]; 22 | 23 | const [items, setItems] = useState([]); 24 | 25 | const [vt] = useVT(() => ({ debug:true, scroll: { y: 500 } }), []); 26 | 27 | const addFirstTwo = () => { 28 | const firstTwo = [initialItems[0], initialItems[1]]; 29 | setItems(firstTwo); 30 | }; 31 | 32 | const removeAll = () => { 33 | setItems([]); 34 | }; 35 | 36 | const addFirstFour = () => { 37 | const firstFour = [ 38 | initialItems[0], 39 | initialItems[1], 40 | initialItems[2], 41 | initialItems[3] 42 | ]; 43 | 44 | setItems(firstFour); 45 | }; 46 | 47 | return ( 48 |
49 | 50 | 51 | 52 | 53 | 61 | 62 | ); 63 | } 64 | 65 | -------------------------------------------------------------------------------- /test/table1.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * copy this file to your working directory. 3 | */ 4 | import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react'; 5 | import { Table, Button, Input, Switch, Modal, Checkbox } from 'antd'; 6 | import { useVT } from 'virtualizedtableforantd4'; 7 | import { isNumber } from 'util'; 8 | 9 | 10 | function rndstr(str: string) { 11 | const n = 0 | (Math.random() * 10) + 1; 12 | let s = ''; 13 | for (let i = 0; i < n; ++i) { 14 | s += str; 15 | } 16 | return s; 17 | } 18 | 19 | 20 | const myajax = (() => { 21 | const data: any[] = []; 22 | const len = 0 | (Math.random() * 2000); 23 | for (let i = 0; i < len; i++) { 24 | const n = 0 | Math.random() * 3000 + 1000; 25 | data.push({ 26 | key: i, 27 | name: `Edrward ${n}`, 28 | age: 0 | Math.random() * 88 + 12, 29 | address: rndstr(`London Park no. ${n}`), 30 | }); 31 | } 32 | 33 | return new Promise((resolve, reject) => { 34 | setTimeout(() => { 35 | resolve(data); 36 | }, Math.random() * 200 + 100); 37 | }); 38 | }); 39 | 40 | const _data: any[] = []; 41 | for (let i = 0; i < 1000; i++) { 42 | _data.push({ 43 | key: i, 44 | name: `Edrward ${i}`, 45 | age: 32, 46 | address: rndstr(`London Park no. ${i}`), 47 | }); 48 | } 49 | 50 | export default function Table1() { 51 | const [data, setData] = useState(_data); 52 | const [rowKeys, setSelectedRowKeys] = useState([]); 53 | const [showPagination, setPagination] = useState(false); 54 | const [pageSize, setPageSize] = useState(100); 55 | const [showSelection, setSelection] = useState(false); 56 | const [visibleOfUpdateModal, setVisibleOfUpdateModal] = useState(false); 57 | 58 | const [text, setText] = useState(""); 59 | 60 | 61 | // add record 62 | const [name, setFullname] = useState(""); 63 | const [age, setAge] = useState(); 64 | const [key, setKey] = useState(); 65 | 66 | const aaa = data; 67 | 68 | const showDeleteConfirm = (record: any) => { 69 | Modal.confirm({ 70 | title: 'Are you sure delete this record?', 71 | content: 'Some descriptions', 72 | okText: 'Yes', 73 | okType: 'danger', 74 | cancelText: 'No', 75 | onOk: () => { 76 | const arr = aaa.filter((v) => { 77 | return v.key !== record.key; 78 | }); 79 | setData([...arr]); 80 | }, 81 | onCancel() { 82 | }, 83 | }); 84 | }; 85 | 86 | 87 | const record = useRef(); 88 | const showUpdateDialog = useCallback((_record: any) => { 89 | setVisibleOfUpdateModal(true); 90 | setText(_record.address); 91 | record.current = _record; 92 | }, [setVisibleOfUpdateModal, setText]); 93 | 94 | // Column name age 1 2 3 4 5 6 7 8 operation 95 | const _columns = [ 96 | { 97 | title: 'Full Name', 98 | width: 150, 99 | dataIndex: 'name', 100 | key: 'name', 101 | }, 102 | { 103 | title: 'Age', 104 | width: 100, 105 | dataIndex: 'age', 106 | key: 'age', 107 | }, 108 | { 109 | title: 'Column 1', 110 | dataIndex: 'address', 111 | key: '1', 112 | width: 200, 113 | }, 114 | { 115 | title: 'Column 2', 116 | dataIndex: 'address', 117 | key: '2', 118 | width: 200, 119 | }, 120 | { 121 | title: 'Column 3', 122 | dataIndex: 'address', 123 | key: '3', 124 | width: 200, 125 | }, 126 | { 127 | title: 'Column 4', 128 | dataIndex: 'address', 129 | key: '4', 130 | width: 200, 131 | }, 132 | { 133 | title: 'Column 5', 134 | dataIndex: 'address', 135 | key: '5', 136 | width: 200, 137 | }, 138 | { 139 | title: 'Column 6', 140 | dataIndex: 'address', 141 | key: '6', 142 | width: 200, 143 | }, 144 | { 145 | title: 'Column 7', 146 | dataIndex: 'address', 147 | key: '7', 148 | width: 200, 149 | 150 | }, 151 | { title: 'Column 8', dataIndex: 'address', key: '8' }, 152 | { 153 | title: 'Action', 154 | key: 'operation', 155 | width: 180, 156 | render: (text: any, record: any, index: any) => { 157 | return ( 158 | <> 159 | showUpdateDialog(record)}>update 160 | | 161 | showDeleteConfirm(record)}>delete 162 | 163 | ); 164 | }, 165 | }, 166 | ]; 167 | 168 | // columns 169 | const [checkedColumns, setCheckedColumns] = useState(["name", "age", "1", "2", "3", "4", "operation"]); 170 | const [columns, setColumns] = useState(checkedColumns.map(v => _columns.find(column => column.key === v))); 171 | 172 | 173 | const [checkedColumnsFixed, setCheckedColumnsFixed] = useState(); 174 | 175 | 176 | 177 | // VTComponents 178 | const [height, setHeight] = useState(500); 179 | const [overscanRowCount, setOverscanRowCount] = useState(5); 180 | const [destroy, setDestroy] = useState(false); 181 | 182 | const [vt] = useVT(() => Object.assign({ 183 | scroll: { y: height }, 184 | debug: true,/* onScroll: ({ left, top }) => console.log(top, left) */ 185 | }, 186 | { 187 | onScroll: ({top, left, isEnd}) => { 188 | console.log('isEnd', isEnd); 189 | if (isEnd) { 190 | window.alert('isEnd'); 191 | } 192 | } 193 | }, 194 | overscanRowCount ? { overscanRowCount } : null) as any, []); 195 | 196 | return ( 197 | <> 198 | 199 | 200 | 201 | 202 |
203 |
204 | ---- pagination ---- 205 |
206 | 213 | 214 | 215 |
216 |
217 | ---- add record ---- 218 |
219 | key: { 220 | setKey(ev.target.value); 221 | }}> 222 | Full Name: { 223 | setFullname(ev.target.value); 224 | }}> 225 | Age: { 226 | setAge(isNaN(+ev.target.value) ? 0 : +ev.target.value); 227 | }}> 228 | 242 | 256 | 257 |
258 |
259 | 260 | 261 | ----rowSelection---- 262 |
263 | setSelection(checked)} /> 264 |
265 | 266 | 276 | 277 |
278 |
279 | 280 | 281 | ----Columns---- 282 |
283 | 284 | { 321 | setCheckedColumnsFixed(checkedValue as any); 322 | const fixed: any = { 323 | "name": "left", 324 | "age": "left", 325 | "1": "left", 326 | "2": "left", 327 | "3": "left", 328 | 329 | "6": "right", 330 | "7": "right", 331 | "8": "right", 332 | "operation": "right", 333 | }; 334 | columns.forEach((column) => { 335 | const c = checkedValue.find(v => v === column.key); 336 | if (c) { 337 | (column as any).fixed = fixed[c]; 338 | } else { 339 | try { 340 | delete (column as any).fixed; 341 | } catch {} 342 | } 343 | }); 344 | }} 345 | /> 346 | 347 | { 384 | setCheckedColumns(checkedValue); 385 | setColumns(checkedValue.map(v => _columns.find(column => column.key === v))); 386 | }} 387 | /> 388 | 389 | 390 |
391 |
392 | 393 | 394 | ----VTComponents---- 395 |
396 | height: 397 | { 398 | setHeight(Number.isNaN(+e.target.value) ? e.target.value : +e.target.value); 399 | }}>
400 | overscanRowCount: 401 | { 403 | isNumber(+e.target.value) ? 404 | setOverscanRowCount(+e.target.value) : setOverscanRowCount(5); 405 | }}>
406 | destroy: setDestroy(e.target.checked)}>
407 |
408 |
409 |
410 | 411 | 412 |
{ 427 | setPageSize(pageSize); 428 | 429 | // if (_page !== page) { 430 | // setPage(_page); 431 | // VTScroll(1, { top: 0, left: 0 }); 432 | // } 433 | }, 434 | onShowSizeChange(current, size) { 435 | setPageSize(size); 436 | 437 | } 438 | } : false 439 | } 440 | rowSelection={ 441 | showSelection ? 442 | { 443 | selectedRowKeys: rowKeys, 444 | onChange: (selectedRowKeys, selectedRows) => { 445 | setSelectedRowKeys(selectedRowKeys); 446 | } 447 | } : null 448 | } 449 | > 450 |
451 | 452 | { 456 | const k = record.current.key; 457 | data.find((v) => v.key === k).address = text; 458 | setData([...data]); 459 | setVisibleOfUpdateModal(false); 460 | }} 461 | onCancel={() => { 462 | setVisibleOfUpdateModal(false); 463 | }} 464 | > 465 | Column 1: 466 | { 467 | setText(ev.target.value); 468 | }}> 469 | 470 | 471 | ); 472 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "strict": false, 5 | "declaration": true, 6 | "skipLibCheck": true, 7 | "baseUrl": "./", 8 | "esModuleInterop": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------