Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import useHeights from './hooks/useHeights';
import useMobileTouchMove from './hooks/useMobileTouchMove';
import useOriginScroll from './hooks/useOriginScroll';
import useScrollDrag from './hooks/useScrollDrag';
import type { ScrollPos, ScrollTarget } from './hooks/useScrollTo';
import type {
ScrollOffset,
ScrollOffsetInfo,
ScrollPos,
ScrollTarget,
} from './hooks/useScrollTo';
import useScrollTo from './hooks/useScrollTo';
import type { ExtraRenderInfo, GetKey, RenderFunc, SharedConfig } from './interface';
import type { ScrollBarDirectionType, ScrollBarRef } from './ScrollBar';
Expand All @@ -38,6 +43,8 @@ export type ScrollConfig = ScrollTarget | ScrollPos;

export type ScrollTo = (arg?: number | ScrollConfig | null) => void;

export type { ScrollOffset, ScrollOffsetInfo };

export type ListRef = {
nativeElement: HTMLDivElement;
scrollTo: ScrollTo;
Expand Down Expand Up @@ -506,12 +513,15 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
horizontalScrollBarRef.current?.delayHidden();
};

const getSize = useGetSize(mergedData, getKey, heights, itemHeight);

const scrollTo = useScrollTo<T>(
componentRef,
mergedData,
heights,
itemHeight,
getKey,
getSize,
() => collectHeight(true),
syncScrollTop,
delayHideScrollBar,
Expand Down Expand Up @@ -550,8 +560,6 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
}, [start, end, mergedData]);

// ================================ Extra =================================
const getSize = useGetSize(mergedData, getKey, heights, itemHeight);

const extraContent = extraRender?.({
start,
end,
Expand Down
32 changes: 25 additions & 7 deletions src/hooks/useScrollTo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */
import * as React from 'react';
import { raf, useLayoutEffect, warning } from '@rc-component/util';
import type { GetKey } from '../interface';
import type { GetKey, GetSize } from '../interface';
import type CacheMap from '../utils/CacheMap';

const MAX_TIMES = 10;
Expand All @@ -13,24 +13,41 @@ export type ScrollPos = {
top?: number;
};

export interface ScrollOffsetInfo {
/**
* Get item size range by key.
* 通过 key 获取元素在虚拟列表中的尺寸范围。
*/
getSize: GetSize;
}

export type ScrollOffset = number | ((info: ScrollOffsetInfo) => number);

export type ScrollTarget =
| {
index: number;
align?: ScrollAlign;
offset?: number;
offset?: ScrollOffset;
}
| {
key: React.Key;
align?: ScrollAlign;
offset?: number;
offset?: ScrollOffset;
};

function getOffset(rawOffset: ScrollOffset, info: ScrollOffsetInfo) {
const resolvedOffset = typeof rawOffset === 'function' ? rawOffset(info) : rawOffset;

return Number.isFinite(resolvedOffset) ? resolvedOffset : 0;
}

export default function useScrollTo<T>(
containerRef: React.RefObject<HTMLDivElement>,
data: T[],
heights: CacheMap,
itemHeight: number,
getKey: GetKey<T>,
getSize: GetSize,
collectHeight: () => void,
syncScrollTop: (newTop: number) => void,
triggerFlash: () => void,
Expand All @@ -40,7 +57,7 @@ export default function useScrollTo<T>(
const [syncState, setSyncState] = React.useState<{
times: number;
index: number;
offset: number;
offset: ScrollOffset;
originAlign: ScrollAlign;
targetAlign?: 'top' | 'bottom';
lastTop?: number;
Expand All @@ -57,7 +74,8 @@ export default function useScrollTo<T>(

collectHeight();

const { targetAlign, originAlign, index, offset } = syncState;
const { targetAlign, originAlign, index, offset: rawOffset } = syncState;
const offset = getOffset(rawOffset, { getSize });

const height = containerRef.current.clientHeight;
let needCollectHeight = false;
Expand Down Expand Up @@ -171,12 +189,12 @@ export default function useScrollTo<T>(
index = data.findIndex((item) => getKey(item) === arg.key);
}

const { offset = 0 } = arg;
const { offset: rawOffset = 0 } = arg;

setSyncState({
times: 0,
index,
offset,
offset: rawOffset,
originAlign: align,
});
}
Expand Down
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import List from './List';

export type { ListRef, ListProps, ScrollConfig, ScrollTo } from './List';
export type {
ListRef,
ListProps,
ScrollConfig,
ScrollOffset,
ScrollOffsetInfo,
ScrollTo,
} from './List';
export { default as MockList } from './mock';

export default List;
22 changes: 22 additions & 0 deletions tests/scroll.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,28 @@ describe('List.Scroll', () => {
expect(container.querySelector('ul').scrollTop).toEqual(520);
});

it('supports function offset with getSize info', () => {
const { scrollTo, container } = presetList();
const offset = jest.fn(({ getSize }) => getSize('2').bottom);

scrollTo({ key: '30', align: 'top', offset });

expect(offset).toHaveBeenCalledWith({
getSize: expect.any(Function),
});
expect(offset).toHaveBeenCalledTimes(2);
expect(container.querySelector('ul').scrollTop).toEqual(540);
});

it('fallbacks invalid function offset to zero', () => {
const { scrollTo, container } = presetList();
const offset = jest.fn(() => NaN);

scrollTo({ key: '30', align: 'top', offset });

expect(container.querySelector('ul').scrollTop).toEqual(600);
});

it('smart', () => {
const { scrollTo, container } = presetList();
scrollTo(0);
Expand Down
Loading