Skip to content

Commit 81d3b2a

Browse files
authored
refactor: replace scroll logic with inner width (#212)
* chore: fix scroll logic * feat: extraRender support * chore: patch rtl
1 parent db4f5c9 commit 81d3b2a

File tree

6 files changed

+131
-22
lines changed

6 files changed

+131
-22
lines changed

examples/horizontal-scroll.tsx

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,74 @@ interface Item {
66
height: number;
77
}
88

9-
const MyItem: React.ForwardRefRenderFunction<HTMLElement, Item> = ({ id, height }, ref) => {
9+
const Rect = ({ style }: any) => (
10+
<div
11+
style={{
12+
position: 'sticky',
13+
top: 0,
14+
background: 'blue',
15+
flex: 'none',
16+
borderInline: `1px solid red`,
17+
...style,
18+
}}
19+
>
20+
Hello
21+
</div>
22+
);
23+
24+
const MyItem: React.ForwardRefRenderFunction<
25+
HTMLDivElement,
26+
Item & { style?: React.CSSProperties }
27+
> = (props, ref) => {
28+
const { id, height, style } = props;
29+
1030
return (
11-
<span
31+
<div
1232
ref={ref}
1333
style={{
1434
border: '1px solid gray',
15-
padding: '0 16px',
1635
height,
1736
lineHeight: '30px',
1837
boxSizing: 'border-box',
19-
display: 'inline-block',
20-
whiteSpace: 'nowrap',
21-
overflow: 'hidden',
22-
textOverflow: 'ellipsis',
38+
display: 'flex',
39+
// position: 'relative',
40+
alignItems: 'center',
41+
borderInline: 0,
42+
...style,
2343
}}
2444
>
25-
{id} {'longText '.repeat(100)}
26-
</span>
45+
<Rect
46+
style={{
47+
left: 0,
48+
}}
49+
/>
50+
<div
51+
style={{
52+
flex: 'auto',
53+
minWidth: 0,
54+
whiteSpace: 'nowrap',
55+
overflow: 'hidden',
56+
textOverflow: 'ellipsis',
57+
}}
58+
>
59+
{id} {'longText '.repeat(100)}
60+
</div>
61+
<Rect
62+
style={{
63+
right: 0,
64+
}}
65+
/>
66+
</div>
2767
);
2868
};
2969

3070
const ForwardMyItem = React.forwardRef(MyItem);
3171

3272
const data: Item[] = [];
33-
for (let i = 0; i < 100; i += 1) {
73+
for (let i = 0; i < 1000; i += 1) {
3474
data.push({
3575
id: i,
36-
height: 30,
76+
height: 30 + Math.random() * 10,
3777
});
3878
}
3979

@@ -63,11 +103,27 @@ const Demo = () => {
63103
border: '1px solid red',
64104
boxSizing: 'border-box',
65105
}}
106+
extraRender={(info) => {
107+
const { offsetX, rtl: isRTL } = info;
108+
109+
return (
110+
<div
111+
style={{
112+
position: 'absolute',
113+
top: 100,
114+
[isRTL ? 'right' : 'left']: 100 - offsetX,
115+
background: 'rgba(255,0,0,0.1)',
116+
}}
117+
>
118+
Extra
119+
</div>
120+
);
121+
}}
66122
onScroll={(e) => {
67123
// console.log('Scroll:', e);
68124
}}
69125
>
70-
{(item) => <ForwardMyItem {...item} />}
126+
{(item, _, props) => <ForwardMyItem {...item} {...props} />}
71127
</List>
72128
</div>
73129
</div>

src/Filler.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ interface FillerProps {
2121
innerProps?: InnerProps;
2222

2323
rtl: boolean;
24+
25+
extra?: React.ReactNode;
2426
}
2527

2628
/**
@@ -32,12 +34,12 @@ const Filler = React.forwardRef(
3234
height,
3335
offsetY,
3436
offsetX,
35-
scrollWidth,
3637
children,
3738
prefixCls,
3839
onInnerResize,
3940
innerProps,
4041
rtl,
42+
extra,
4143
}: FillerProps,
4244
ref: React.Ref<HTMLDivElement>,
4345
) => {
@@ -49,17 +51,17 @@ const Filler = React.forwardRef(
4951
};
5052

5153
if (offsetY !== undefined) {
54+
// Not set `width` since this will break `sticky: right`
5255
outerStyle = {
5356
height,
54-
width: scrollWidth,
55-
minWidth: '100%',
5657
position: 'relative',
5758
overflow: 'hidden',
5859
};
5960

6061
innerStyle = {
6162
...innerStyle,
62-
transform: `translate(${rtl ? offsetX : -offsetX}px, ${offsetY}px)`,
63+
transform: `translateY(${offsetY}px)`,
64+
[rtl ? 'marginRight' : 'marginLeft']: -offsetX,
6365
position: 'absolute',
6466
left: 0,
6567
right: 0,
@@ -86,6 +88,8 @@ const Filler = React.forwardRef(
8688
>
8789
{children}
8890
</div>
91+
92+
{extra}
8993
</ResizeObserver>
9094
</div>
9195
);

src/List.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Filler from './Filler';
77
import type { InnerProps } from './Filler';
88
import type { ScrollBarDirectionType, ScrollBarRef } from './ScrollBar';
99
import ScrollBar from './ScrollBar';
10-
import type { RenderFunc, SharedConfig, GetKey } from './interface';
10+
import type { RenderFunc, SharedConfig, GetKey, ExtraRenderInfo } from './interface';
1111
import useChildren from './hooks/useChildren';
1212
import useHeights from './hooks/useHeights';
1313
import useScrollTo from './hooks/useScrollTo';
@@ -69,6 +69,9 @@ export interface ListProps<T> extends Omit<React.HTMLAttributes<any>, 'children'
6969

7070
/** Inject to inner container props. Only use when you need pass aria related data */
7171
innerProps?: InnerProps;
72+
73+
/** Render extra content into Filler */
74+
extraRender?: (info: ExtraRenderInfo) => React.ReactNode;
7275
}
7376

7477
export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
@@ -89,6 +92,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
8992
onScroll,
9093
onVisibleChange,
9194
innerProps,
95+
extraRender,
9296
...restProps
9397
} = props;
9498

@@ -386,7 +390,23 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
386390
}, [start, end, mergedData]);
387391

388392
// ================================ Render ================================
389-
const listChildren = useChildren(mergedData, start, end, setInstanceRef, children, sharedConfig);
393+
const listChildren = useChildren(
394+
mergedData,
395+
start,
396+
end,
397+
scrollWidth,
398+
setInstanceRef,
399+
children,
400+
sharedConfig,
401+
);
402+
403+
const extraContent = extraRender?.({
404+
start,
405+
end,
406+
virtual: inVirtual,
407+
offsetX: offsetLeft,
408+
rtl: isRTL,
409+
});
390410

391411
let componentStyle: React.CSSProperties = null;
392412
if (height) {
@@ -438,13 +458,14 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
438458
ref={fillerInnerRef}
439459
innerProps={innerProps}
440460
rtl={isRTL}
461+
extra={extraContent}
441462
>
442463
{listChildren}
443464
</Filler>
444465
</Component>
445466
</ResizeObserver>
446467

447-
{useVirtual && scrollHeight > height && (
468+
{inVirtual && scrollHeight > height && (
448469
<ScrollBar
449470
ref={verticalScrollBarRef}
450471
prefixCls={prefixCls}
@@ -459,7 +480,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
459480
/>
460481
)}
461482

462-
{useVirtual && scrollWidth && (
483+
{inVirtual && scrollWidth && (
463484
<ScrollBar
464485
ref={horizontalScrollBarRef}
465486
prefixCls={prefixCls}

src/hooks/useChildren.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ export default function useChildren<T>(
66
list: T[],
77
startIndex: number,
88
endIndex: number,
9+
scrollWidth: number,
910
setNodeRef: (item: T, element: HTMLElement) => void,
1011
renderFunc: RenderFunc<T>,
1112
{ getKey }: SharedConfig<T>,
1213
) {
1314
return list.slice(startIndex, endIndex + 1).map((item, index) => {
1415
const eleIndex = startIndex + index;
1516
const node = renderFunc(item, eleIndex, {
16-
// style: status === 'MEASURE_START' ? { visibility: 'hidden' } : {},
17+
style: {
18+
width: scrollWidth,
19+
},
1720
}) as React.ReactElement;
1821

1922
const key = getKey(item);
2023
return (
21-
<Item key={key} setRef={ele => setNodeRef(item, ele)}>
24+
<Item key={key} setRef={(ele) => setNodeRef(item, ele)}>
2225
{node}
2326
</Item>
2427
);

src/interface.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,16 @@ export interface SharedConfig<T> {
99
}
1010

1111
export type GetKey<T> = (item: T) => React.Key;
12+
13+
export interface ExtraRenderInfo {
14+
/** Virtual list start line */
15+
start: number;
16+
/** Virtual list end line */
17+
end: number;
18+
/** Is current in virtual render */
19+
virtual: boolean;
20+
/** Used for `scrollWidth` tell the horizontal offset */
21+
offsetX: number;
22+
23+
rtl: boolean;
24+
}

tests/scrollWidth.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,16 @@ describe('List.scrollWidth', () => {
109109
width: '20px',
110110
});
111111
});
112+
113+
it('support extraRender', () => {
114+
const { container } = genList({
115+
itemHeight: 20,
116+
height: 100,
117+
data: genData(100),
118+
scrollWidth: 1000,
119+
extraRender: () => <div className="bamboo" />,
120+
});
121+
122+
expect(container.querySelector('.bamboo')).toBeTruthy();
123+
});
112124
});

0 commit comments

Comments
 (0)