Skip to content

Commit 94f615c

Browse files
authored
Merge branch 'react-component:master' into patch-1
2 parents 113f8e8 + d2bc792 commit 94f615c

29 files changed

+296
-95
lines changed

.fatherrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
export default {
22
cjs: 'babel',
33
esm: { type: 'babel', importLibToEs: true },
4+
runtimeHelpers: true,
45
preCommit: {
56
eslint: true,
67
prettier: true,
78
},
8-
};
9+
};

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,10 @@ lib/
77
~*
88
yarn.lock
99
package-lock.json
10-
!tests/__mocks__/rc-util/lib
10+
!tests/__mocks__/rc-util/lib
11+
12+
# umi
13+
.umi
14+
.umi-production
15+
.umi-test
16+
.env.local

.umirc.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// more config: https://d.umijs.org/config
2+
import { defineConfig } from 'dumi';
3+
4+
export default defineConfig({
5+
title: 'rc-virtual-list',
6+
favicon:
7+
'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
8+
logo:
9+
'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
10+
outputPath: '.doc',
11+
exportStatic: {},
12+
resolve: {
13+
examples: ['none'],
14+
},
15+
styles: [
16+
`
17+
.markdown table {
18+
width: auto !important;
19+
}
20+
`,
21+
]
22+
});

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
React Virtual List Component which worked with animation.
44

5-
[![NPM version][npm-image]][npm-url]
6-
[![build status][github-actions-image]][github-actions-url]
7-
[![Test coverage][coveralls-image]][coveralls-url]
8-
[![node version][node-image]][node-url]
9-
[![npm download][download-image]][download-url]
5+
[![NPM version][npm-image]][npm-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][coveralls-image]][coveralls-url] [![node version][node-image]][node-url] [![npm download][download-image]][download-url]
106

117
[npm-image]: http://img.shields.io/npm/v/rc-virtual-list.svg?style=flat-square
128
[npm-url]: http://npmjs.org/package/rc-virtual-list
@@ -19,6 +15,10 @@ React Virtual List Component which worked with animation.
1915
[download-image]: https://img.shields.io/npm/dm/rc-virtual-list.svg?style=flat-square
2016
[download-url]: https://npmjs.org/package/rc-virtual-list
2117

18+
## Online Preview
19+
20+
https://virtual-list-react-component.vercel.app/
21+
2222
## Development
2323

2424
```bash

docs/demo/animate.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## animate
2+
3+
<code src="../../examples/animate.tsx">

docs/demo/basic.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## basic
2+
3+
<code src="../../examples/basic.tsx">

docs/demo/height.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## height
2+
3+
<code src="../../examples/height.tsx">

docs/demo/no-virtual.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## no-virtual
2+
3+
<code src="../../examples/no-virtual.tsx">

docs/demo/switch.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## switch
2+
3+
<code src="../../examples/switch.tsx">

docs/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: rc-virtual-list
3+
---
4+
5+
<embed src="../README.md"></embed>

examples/animate.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as React from 'react';
55
import CSSMotion from 'rc-animate/lib/CSSMotion';
66
import classNames from 'classnames';
77
import List, { ListRef } from '../src/List';
8+
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
89
import './animate.less';
910

1011
let uuid = 0;
@@ -58,7 +59,7 @@ const MyItem: React.ForwardRefRenderFunction<any, MyItemProps> = (
5859
ref,
5960
) => {
6061
const motionRef = React.useRef(false);
61-
React.useLayoutEffect(() => {
62+
useLayoutEffect(() => {
6263
return () => {
6364
if (motionRef.current) {
6465
onAppear();

examples/height.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface Item {
66
height: number;
77
}
88

9-
const MyItem: React.FC<Item> = ({ id, height }, ref) => {
9+
const MyItem: React.ForwardRefRenderFunction<HTMLElement, Item> = ({ id, height }, ref) => {
1010
return (
1111
<span
1212
ref={ref}

examples/switch.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
/* eslint-disable jsx-a11y/label-has-associated-control, jsx-a11y/label-has-for */
21
import * as React from 'react';
3-
import List, { ListRef } from '../src/List';
2+
import type { ListRef } from '../src/List';
3+
import List from '../src/List';
44

55
interface Item {
66
id: number;
@@ -37,6 +37,7 @@ function getData(count: number) {
3737
const Demo = () => {
3838
const [height, setHeight] = React.useState(200);
3939
const [data, setData] = React.useState(getData(20));
40+
const [fullHeight, setFullHeight] = React.useState(true);
4041
const listRef = React.useRef<ListRef>();
4142

4243
return (
@@ -98,13 +99,23 @@ const Demo = () => {
9899
200
99100
</label>
100101
</span>
102+
<span>
103+
<button
104+
onClick={() => {
105+
setFullHeight(!fullHeight);
106+
}}
107+
>
108+
Full Height: {String(fullHeight)}
109+
</button>
110+
</span>
101111

102112
<List
103113
ref={listRef}
104114
data={data}
105115
height={height}
106116
itemHeight={10}
107117
itemKey="id"
118+
fullHeight={fullHeight}
108119
style={{
109120
border: '1px solid red',
110121
boxSizing: 'border-box',

now.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@
77
"use": "@now/static-build",
88
"config": { "distDir": ".doc" }
99
}
10+
],
11+
"routes": [
12+
{ "src": "/(.*)", "dest": "/dist/$1" }
1013
]
1114
}

package.json

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rc-virtual-list",
3-
"version": "3.2.3",
3+
"version": "3.4.11",
44
"description": "React Virtual List Component",
55
"engines": {
66
"node": ">=8.x"
@@ -29,8 +29,8 @@
2929
"main": "./lib/index",
3030
"module": "./es/index",
3131
"scripts": {
32-
"start": "cross-env NODE_ENV=development father doc dev --storybook",
33-
"build": "father doc build --storybook",
32+
"start": "dumi dev",
33+
"build": "dumi build",
3434
"compile": "father build",
3535
"prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
3636
"lint": "eslint src/ --ext .tsx,.ts",
@@ -45,24 +45,27 @@
4545
"@types/classnames": "^2.2.10",
4646
"@types/enzyme": "^3.10.5",
4747
"@types/jest": "^25.1.3",
48-
"@types/react": "^16.8.19",
49-
"@types/react-dom": "^16.8.4",
48+
"@types/react": "^18.0.0",
49+
"@types/react-dom": "^18.0.0",
5050
"@types/warning": "^3.0.0",
5151
"cross-env": "^5.2.0",
52+
"dumi": "^1.1.12",
5253
"enzyme": "^3.1.0",
5354
"enzyme-adapter-react-16": "^1.0.2",
5455
"enzyme-to-json": "^3.1.4",
5556
"eslint": "^7.6.0",
5657
"father": "^2.29.10",
58+
"glob": "^7.1.6",
5759
"np": "^5.0.3",
5860
"rc-animate": "^2.9.1",
5961
"react": "^v16.9.0-alpha.0",
6062
"react-dom": "^v16.9.0-alpha.0",
6163
"typescript": "^3.5.2"
6264
},
6365
"dependencies": {
66+
"@babel/runtime": "^7.20.0",
6467
"classnames": "^2.2.6",
65-
"rc-resize-observer": "^0.2.3",
66-
"rc-util": "^5.0.7"
68+
"rc-resize-observer": "^1.0.0",
69+
"rc-util": "^5.15.0"
6770
}
6871
}

src/List.tsx

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { useRef, useState } from 'react';
33
import classNames from 'classnames';
44
import Filler from './Filler';
55
import ScrollBar from './ScrollBar';
6-
import { RenderFunc, SharedConfig, GetKey } from './interface';
6+
import type { RenderFunc, SharedConfig, GetKey } from './interface';
77
import useChildren from './hooks/useChildren';
88
import useHeights from './hooks/useHeights';
99
import useScrollTo from './hooks/useScrollTo';
1010
import useDiffItem from './hooks/useDiffItem';
1111
import useFrameWheel from './hooks/useFrameWheel';
1212
import useMobileTouchMove from './hooks/useMobileTouchMove';
1313
import useOriginScroll from './hooks/useOriginScroll';
14+
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
1415

1516
const EMPTY_DATA = [];
1617

@@ -36,7 +37,7 @@ export type ListRef = {
3637
scrollTo: ScrollTo;
3738
};
3839

39-
export interface ListProps<T> extends React.HTMLAttributes<any> {
40+
export interface ListProps<T> extends Omit<React.HTMLAttributes<any>, 'children'> {
4041
prefixCls?: string;
4142
children: RenderFunc<T>;
4243
data: T[];
@@ -50,6 +51,8 @@ export interface ListProps<T> extends React.HTMLAttributes<any> {
5051
virtual?: boolean;
5152

5253
onScroll?: React.UIEventHandler<HTMLElement>;
54+
/** Trigger when render list item changed */
55+
onVisibleChange?: (visibleList: T[], fullList: T[]) => void;
5356
}
5457

5558
export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
@@ -66,6 +69,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
6669
virtual,
6770
component: Component = 'div',
6871
onScroll,
72+
onVisibleChange,
6973
...restProps
7074
} = props;
7175

@@ -99,7 +103,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
99103

100104
// ================================ Scroll ================================
101105
function syncScrollTop(newTop: number | ((prev: number) => number)) {
102-
setScrollTop(origin => {
106+
setScrollTop((origin) => {
103107
let value: number;
104108
if (typeof newTop === 'function') {
105109
value = newTop(origin);
@@ -177,11 +181,12 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
177181
itemTop = currentItemBottom;
178182
}
179183

180-
// Fallback to normal if not match. This code should never reach
181-
/* istanbul ignore next */
184+
// When scrollTop at the end but data cut to small count will reach this
182185
if (startIndex === undefined) {
183186
startIndex = 0;
184187
startOffset = 0;
188+
189+
endIndex = Math.ceil(height / itemHeight);
185190
}
186191
if (endIndex === undefined) {
187192
endIndex = mergedData.length - 1;
@@ -207,10 +212,11 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
207212
maxScrollHeightRef.current = maxScrollHeight;
208213

209214
function keepInRange(newScrollTop: number) {
210-
let newTop = Math.max(newScrollTop, 0);
215+
let newTop = newScrollTop;
211216
if (!Number.isNaN(maxScrollHeightRef.current)) {
212217
newTop = Math.min(newTop, maxScrollHeightRef.current);
213218
}
219+
newTop = Math.max(newTop, 0);
214220
return newTop;
215221
}
216222

@@ -225,8 +231,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
225231
syncScrollTop(newTop);
226232
}
227233

228-
// This code may only trigger in test case.
229-
// But we still need a sync if some special escape
234+
// When data size reduce. It may trigger native scroll event back to fit scroll position
230235
function onFallbackScroll(e: React.UIEvent<HTMLDivElement>) {
231236
const { scrollTop: newScrollTop } = e.currentTarget;
232237
if (newScrollTop !== scrollTop) {
@@ -242,8 +247,8 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
242247
useVirtual,
243248
isScrollAtTop,
244249
isScrollAtBottom,
245-
offsetY => {
246-
syncScrollTop(top => {
250+
(offsetY) => {
251+
syncScrollTop((top) => {
247252
const newTop = top + offsetY;
248253
return newTop;
249254
});
@@ -260,7 +265,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
260265
return true;
261266
});
262267

263-
React.useLayoutEffect(() => {
268+
useLayoutEffect(() => {
264269
// Firefox only
265270
function onMozMousePixelScroll(e: Event) {
266271
if (useVirtual) {
@@ -273,9 +278,14 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
273278
componentRef.current.addEventListener('MozMousePixelScroll', onMozMousePixelScroll);
274279

275280
return () => {
276-
componentRef.current.removeEventListener('wheel', onRawWheel);
277-
componentRef.current.removeEventListener('DOMMouseScroll', onFireFoxScroll as any);
278-
componentRef.current.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll as any);
281+
if (componentRef.current) {
282+
componentRef.current.removeEventListener('wheel', onRawWheel);
283+
componentRef.current.removeEventListener('DOMMouseScroll', onFireFoxScroll as any);
284+
componentRef.current.removeEventListener(
285+
'MozMousePixelScroll',
286+
onMozMousePixelScroll as any,
287+
);
288+
}
279289
};
280290
}, [useVirtual]);
281291

@@ -297,6 +307,16 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
297307
scrollTo,
298308
}));
299309

310+
// ================================ Effect ================================
311+
/** We need told outside that some list not rendered */
312+
useLayoutEffect(() => {
313+
if (onVisibleChange) {
314+
const renderList = mergedData.slice(start, end + 1);
315+
316+
onVisibleChange(renderList, mergedData);
317+
}
318+
}, [start, end, mergedData]);
319+
300320
// ================================ Render ================================
301321
const listChildren = useChildren(mergedData, start, end, setInstanceRef, children, sharedConfig);
302322

@@ -365,5 +385,5 @@ const List = React.forwardRef<ListRef, ListProps<any>>(RawList);
365385
List.displayName = 'List';
366386

367387
export default List as <Item = any>(
368-
props: React.PropsWithChildren<ListProps<Item>> & { ref?: React.Ref<ListRef> },
388+
props: ListProps<Item> & { ref?: React.Ref<ListRef> },
369389
) => React.ReactElement;

0 commit comments

Comments
 (0)