Skip to content

Commit 167e60c

Browse files
authored
feat: expose resolved align to scrollTo offset callback (#373)
The `offset` callback receives `ScrollOffsetInfo`, which now includes the resolved `align` direction. For `align: 'auto'` it reads `'auto'` on the first measure pass and settles to `'top'`/`'bottom'` on the pass that positions the item. This lets consumers compute direction-dependent offsets (e.g. compensating for a sticky header only when an item is pinned to the top) without having to reverse-engineer the direction from `getScrollInfo()` and item position.
1 parent 43ef236 commit 167e60c

2 files changed

Lines changed: 54 additions & 13 deletions

File tree

src/hooks/useScrollTo.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ export interface ScrollOffsetInfo {
1919
* 通过 key 获取元素在虚拟列表中的尺寸范围。
2020
*/
2121
getSize: GetSize;
22+
/**
23+
* Resolved align direction. For `auto` this reads `'auto'` on the first
24+
* measure pass (before the direction is decided) and settles to
25+
* `'top'`/`'bottom'` on the pass that actually positions the item.
26+
*
27+
* 已解析的对齐方向。auto 在首帧测量时仍是 'auto',定向后变 'top'/'bottom'。
28+
*/
29+
align: ScrollAlign;
2230
}
2331

2432
export type ScrollOffset = number | ((info: ScrollOffsetInfo) => number);
@@ -75,7 +83,8 @@ export default function useScrollTo<T>(
7583
collectHeight();
7684

7785
const { targetAlign, originAlign, index, offset: rawOffset } = syncState;
78-
const offset = getOffset(rawOffset, { getSize });
86+
const mergedAlign = targetAlign || originAlign;
87+
const offset = getOffset(rawOffset, { getSize, align: mergedAlign });
7988

8089
const height = containerRef.current.clientHeight;
8190
let needCollectHeight = false;
@@ -84,8 +93,6 @@ export default function useScrollTo<T>(
8493

8594
// Go to next frame if height not exist
8695
if (height) {
87-
const mergedAlign = targetAlign || originAlign;
88-
8996
// Get top & bottom
9097
let stackTop = 0;
9198
let itemTop = 0;

tests/scroll.test.js

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,12 @@ describe('List.Scroll', () => {
9999
it('scrollTo null will show the scrollbar', () => {
100100
jest.useFakeTimers();
101101
const listRef = React.createRef();
102-
const { container } = genList({ itemHeight: 20, height: 100, data: genData(100), ref: listRef });
102+
const { container } = genList({
103+
itemHeight: 20,
104+
height: 100,
105+
data: genData(100),
106+
ref: listRef,
107+
});
103108
act(() => {
104109
jest.runAllTimers();
105110
listRef.current.scrollTo(null);
@@ -211,11 +216,42 @@ describe('List.Scroll', () => {
211216

212217
expect(offset).toHaveBeenCalledWith({
213218
getSize: expect.any(Function),
219+
align: 'top',
214220
});
215221
expect(offset).toHaveBeenCalledTimes(2);
216222
expect(container.querySelector('ul').scrollTop).toEqual(540);
217223
});
218224

225+
it('auto align exposes resolved direction to offset callback', () => {
226+
const { scrollTo, container } = presetList();
227+
228+
// Target below viewport → auto pins to bottom
229+
scrollTo(0);
230+
const belowAligns = [];
231+
scrollTo({
232+
index: 30,
233+
offset: ({ align }) => {
234+
belowAligns.push(align);
235+
return 0;
236+
},
237+
});
238+
expect(belowAligns).toContain('bottom');
239+
expect(container.querySelector('ul').scrollTop).toEqual(520);
240+
241+
// Target above viewport → auto pins to top
242+
scrollTo(800);
243+
const aboveAligns = [];
244+
scrollTo({
245+
index: 30,
246+
offset: ({ align }) => {
247+
aboveAligns.push(align);
248+
return 0;
249+
},
250+
});
251+
expect(aboveAligns).toContain('top');
252+
expect(container.querySelector('ul').scrollTop).toEqual(600);
253+
});
254+
219255
it('fallbacks invalid function offset to zero', () => {
220256
const { scrollTo, container } = presetList();
221257
const offset = jest.fn(() => NaN);
@@ -328,15 +364,13 @@ describe('List.Scroll', () => {
328364
it('should show scrollbar when element has showScrollBar prop set to true', () => {
329365
jest.useFakeTimers();
330366
const listRef = React.createRef();
331-
const { container } = genList(
332-
{
333-
itemHeight: 20,
334-
height: 100,
335-
data: genData(100),
336-
ref: listRef,
337-
showScrollBar: true,
338-
}
339-
);
367+
const { container } = genList({
368+
itemHeight: 20,
369+
height: 100,
370+
data: genData(100),
371+
ref: listRef,
372+
showScrollBar: true,
373+
});
340374
act(() => {
341375
jest.runAllTimers();
342376
});

0 commit comments

Comments
 (0)