Skip to content

Commit

Permalink
refactor: replace scroll logic with inner width (#212)
Browse files Browse the repository at this point in the history
* chore: fix scroll logic

* feat: extraRender support

* chore: patch rtl
  • Loading branch information
zombieJ committed Aug 17, 2023
1 parent db4f5c9 commit 81d3b2a
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 22 deletions.
80 changes: 68 additions & 12 deletions examples/horizontal-scroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,74 @@ interface Item {
height: number;
}

const MyItem: React.ForwardRefRenderFunction<HTMLElement, Item> = ({ id, height }, ref) => {
const Rect = ({ style }: any) => (
<div
style={{
position: 'sticky',
top: 0,
background: 'blue',
flex: 'none',
borderInline: `1px solid red`,
...style,
}}
>
Hello
</div>
);

const MyItem: React.ForwardRefRenderFunction<
HTMLDivElement,
Item & { style?: React.CSSProperties }
> = (props, ref) => {
const { id, height, style } = props;

return (
<span
<div
ref={ref}
style={{
border: '1px solid gray',
padding: '0 16px',
height,
lineHeight: '30px',
boxSizing: 'border-box',
display: 'inline-block',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'flex',
// position: 'relative',
alignItems: 'center',
borderInline: 0,
...style,
}}
>
{id} {'longText '.repeat(100)}
</span>
<Rect
style={{
left: 0,
}}
/>
<div
style={{
flex: 'auto',
minWidth: 0,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{id} {'longText '.repeat(100)}
</div>
<Rect
style={{
right: 0,
}}
/>
</div>
);
};

const ForwardMyItem = React.forwardRef(MyItem);

const data: Item[] = [];
for (let i = 0; i < 100; i += 1) {
for (let i = 0; i < 1000; i += 1) {
data.push({
id: i,
height: 30,
height: 30 + Math.random() * 10,
});
}

Expand Down Expand Up @@ -63,11 +103,27 @@ const Demo = () => {
border: '1px solid red',
boxSizing: 'border-box',
}}
extraRender={(info) => {
const { offsetX, rtl: isRTL } = info;

return (
<div
style={{
position: 'absolute',
top: 100,
[isRTL ? 'right' : 'left']: 100 - offsetX,
background: 'rgba(255,0,0,0.1)',
}}
>
Extra
</div>
);
}}
onScroll={(e) => {
// console.log('Scroll:', e);
}}
>
{(item) => <ForwardMyItem {...item} />}
{(item, _, props) => <ForwardMyItem {...item} {...props} />}
</List>
</div>
</div>
Expand Down
12 changes: 8 additions & 4 deletions src/Filler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface FillerProps {
innerProps?: InnerProps;

rtl: boolean;

extra?: React.ReactNode;
}

/**
Expand All @@ -32,12 +34,12 @@ const Filler = React.forwardRef(
height,
offsetY,
offsetX,
scrollWidth,
children,
prefixCls,
onInnerResize,
innerProps,
rtl,
extra,
}: FillerProps,
ref: React.Ref<HTMLDivElement>,
) => {
Expand All @@ -49,17 +51,17 @@ const Filler = React.forwardRef(
};

if (offsetY !== undefined) {
// Not set `width` since this will break `sticky: right`
outerStyle = {
height,
width: scrollWidth,
minWidth: '100%',
position: 'relative',
overflow: 'hidden',
};

innerStyle = {
...innerStyle,
transform: `translate(${rtl ? offsetX : -offsetX}px, ${offsetY}px)`,
transform: `translateY(${offsetY}px)`,
[rtl ? 'marginRight' : 'marginLeft']: -offsetX,
position: 'absolute',
left: 0,
right: 0,
Expand All @@ -86,6 +88,8 @@ const Filler = React.forwardRef(
>
{children}
</div>

{extra}
</ResizeObserver>
</div>
);
Expand Down
29 changes: 25 additions & 4 deletions src/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Filler from './Filler';
import type { InnerProps } from './Filler';
import type { ScrollBarDirectionType, ScrollBarRef } from './ScrollBar';
import ScrollBar from './ScrollBar';
import type { RenderFunc, SharedConfig, GetKey } from './interface';
import type { RenderFunc, SharedConfig, GetKey, ExtraRenderInfo } from './interface';
import useChildren from './hooks/useChildren';
import useHeights from './hooks/useHeights';
import useScrollTo from './hooks/useScrollTo';
Expand Down Expand Up @@ -69,6 +69,9 @@ export interface ListProps<T> extends Omit<React.HTMLAttributes<any>, 'children'

/** Inject to inner container props. Only use when you need pass aria related data */
innerProps?: InnerProps;

/** Render extra content into Filler */
extraRender?: (info: ExtraRenderInfo) => React.ReactNode;
}

export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
Expand All @@ -89,6 +92,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
onScroll,
onVisibleChange,
innerProps,
extraRender,
...restProps
} = props;

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

// ================================ Render ================================
const listChildren = useChildren(mergedData, start, end, setInstanceRef, children, sharedConfig);
const listChildren = useChildren(
mergedData,
start,
end,
scrollWidth,
setInstanceRef,
children,
sharedConfig,
);

const extraContent = extraRender?.({
start,
end,
virtual: inVirtual,
offsetX: offsetLeft,
rtl: isRTL,
});

let componentStyle: React.CSSProperties = null;
if (height) {
Expand Down Expand Up @@ -438,13 +458,14 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
ref={fillerInnerRef}
innerProps={innerProps}
rtl={isRTL}
extra={extraContent}
>
{listChildren}
</Filler>
</Component>
</ResizeObserver>

{useVirtual && scrollHeight > height && (
{inVirtual && scrollHeight > height && (
<ScrollBar
ref={verticalScrollBarRef}
prefixCls={prefixCls}
Expand All @@ -459,7 +480,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
/>
)}

{useVirtual && scrollWidth && (
{inVirtual && scrollWidth && (
<ScrollBar
ref={horizontalScrollBarRef}
prefixCls={prefixCls}
Expand Down
7 changes: 5 additions & 2 deletions src/hooks/useChildren.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ export default function useChildren<T>(
list: T[],
startIndex: number,
endIndex: number,
scrollWidth: number,
setNodeRef: (item: T, element: HTMLElement) => void,
renderFunc: RenderFunc<T>,
{ getKey }: SharedConfig<T>,
) {
return list.slice(startIndex, endIndex + 1).map((item, index) => {
const eleIndex = startIndex + index;
const node = renderFunc(item, eleIndex, {
// style: status === 'MEASURE_START' ? { visibility: 'hidden' } : {},
style: {
width: scrollWidth,
},
}) as React.ReactElement;

const key = getKey(item);
return (
<Item key={key} setRef={ele => setNodeRef(item, ele)}>
<Item key={key} setRef={(ele) => setNodeRef(item, ele)}>
{node}
</Item>
);
Expand Down
13 changes: 13 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,16 @@ export interface SharedConfig<T> {
}

export type GetKey<T> = (item: T) => React.Key;

export interface ExtraRenderInfo {
/** Virtual list start line */
start: number;
/** Virtual list end line */
end: number;
/** Is current in virtual render */
virtual: boolean;
/** Used for `scrollWidth` tell the horizontal offset */
offsetX: number;

rtl: boolean;
}
12 changes: 12 additions & 0 deletions tests/scrollWidth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,16 @@ describe('List.scrollWidth', () => {
width: '20px',
});
});

it('support extraRender', () => {
const { container } = genList({
itemHeight: 20,
height: 100,
data: genData(100),
scrollWidth: 1000,
extraRender: () => <div className="bamboo" />,
});

expect(container.querySelector('.bamboo')).toBeTruthy();
});
});

0 comments on commit 81d3b2a

Please sign in to comment.