Skip to content

Commit 757490f

Browse files
authored
feat: Table scrollTo support offset (#1301)
* feat: support offset * test: add test case * chore: align with virtual
1 parent 7bb3620 commit 757490f

File tree

7 files changed

+159
-4
lines changed

7 files changed

+159
-4
lines changed

docs/examples/scrollY.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import '../../assets/index.less';
44

55
const data = [];
6-
for (let i = 0; i < 10; i += 1) {
6+
for (let i = 0; i < 20; i += 1) {
77
data.push({
88
key: i,
99
a: `a${i}`,
@@ -68,6 +68,26 @@ const Test = () => {
6868
>
6969
Scroll To top
7070
</button>
71+
<button
72+
onClick={() => {
73+
tblRef.current?.scrollTo({
74+
index: 5,
75+
offset: -10,
76+
});
77+
}}
78+
>
79+
Scroll To Index 5 + Offset -10
80+
</button>
81+
<button
82+
onClick={() => {
83+
tblRef.current?.scrollTo({
84+
key: 6,
85+
offset: -10,
86+
});
87+
}}
88+
>
89+
Scroll To Key 6 + Offset -10
90+
</button>
7191
<Table
7292
ref={tblRef}
7393
columns={columns}

docs/examples/virtual.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ const Demo: React.FC = () => {
199199
<button onClick={() => tableRef.current?.scrollTo({ index: data.length - 1 })}>
200200
Scroll To Key
201201
</button>
202+
<button onClick={() => tableRef.current?.scrollTo({ index: 10, offset: -10 })}>
203+
Scroll To Index 10 + Offset -10
204+
</button>
205+
<button onClick={() => tableRef.current?.scrollTo({ key: '50', offset: -10 })}>
206+
Scroll To Key 50 + Offset -10
207+
</button>
202208
<VirtualTable
203209
style={{ marginTop: 16 }}
204210
ref={tableRef}

src/Table.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,26 @@ function Table<RecordType extends DefaultRecordType>(
342342
scrollTo: config => {
343343
if (scrollBodyRef.current instanceof HTMLElement) {
344344
// Native scroll
345-
const { index, top, key } = config;
345+
const { index, top, key, offset } = config;
346+
346347
if (validNumberValue(top)) {
348+
// In top mode, offset is ignored
347349
scrollBodyRef.current?.scrollTo({ top });
348350
} else {
349351
const mergedKey = key ?? getRowKey(mergedData[index]);
350-
scrollBodyRef.current.querySelector(`[data-row-key="${mergedKey}"]`)?.scrollIntoView();
352+
const targetElement = scrollBodyRef.current.querySelector(
353+
`[data-row-key="${mergedKey}"]`,
354+
);
355+
if (targetElement) {
356+
if (!offset) {
357+
// No offset, use scrollIntoView for default behavior
358+
targetElement.scrollIntoView();
359+
} else {
360+
// With offset, use element's offsetTop + offset
361+
const elementTop = (targetElement as HTMLElement).offsetTop;
362+
scrollBodyRef.current.scrollTo({ top: elementTop + offset });
363+
}
364+
}
351365
}
352366
} else if ((scrollBodyRef.current as any)?.scrollTo) {
353367
// Pass to proxy

src/VirtualTable/BodyGrid.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,18 @@ const Grid = React.forwardRef<GridRef, GridProps>((props, ref) => {
7878
React.useImperativeHandle(ref, () => {
7979
const obj = {
8080
scrollTo: (config: ScrollConfig) => {
81-
listRef.current?.scrollTo(config);
81+
const { offset, ...restConfig } = config;
82+
83+
// If offset is provided, force align to 'top' for consistent behavior
84+
if (offset) {
85+
listRef.current?.scrollTo({
86+
...restConfig,
87+
offset,
88+
align: 'top',
89+
});
90+
} else {
91+
listRef.current?.scrollTo(config);
92+
}
8293
},
8394
nativeElement: listRef.current?.nativeElement,
8495
} as unknown as GridRef;

src/interface.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,19 @@ export type DefaultRecordType = Record<string, any>;
3030
export type TableLayout = 'auto' | 'fixed';
3131

3232
export type ScrollConfig = {
33+
/** The index of the row to scroll to */
3334
index?: number;
35+
/** The key of the row to scroll to */
3436
key?: Key;
37+
/** The absolute scroll position from top */
3538
top?: number;
39+
/**
40+
* Additional offset in pixels to apply to the scroll position.
41+
* Only effective when using `key` or `index` mode.
42+
* Ignored when using `top` mode.
43+
* When offset is set, the target element will always be aligned to the top of the container.
44+
*/
45+
offset?: number;
3646
};
3747

3848
export type Reference = {

tests/Virtual.spec.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,53 @@ describe('Table.Virtual', () => {
368368
});
369369
});
370370

371+
it('scrollTo with offset should pass', async () => {
372+
const tblRef = React.createRef<Reference>();
373+
getTable({ ref: tblRef });
374+
375+
// Test index with offset
376+
tblRef.current.scrollTo({
377+
index: 50,
378+
offset: 20,
379+
});
380+
381+
await waitFakeTimer();
382+
383+
expect(global.scrollToConfig).toEqual({
384+
index: 50,
385+
offset: 20,
386+
align: 'top',
387+
});
388+
389+
// Test key with offset
390+
tblRef.current.scrollTo({
391+
key: '25',
392+
offset: -10,
393+
});
394+
395+
await waitFakeTimer();
396+
397+
expect(global.scrollToConfig).toEqual({
398+
key: '25',
399+
offset: -10,
400+
align: 'top',
401+
});
402+
403+
// Test top with offset (offset should be passed to virtual list)
404+
tblRef.current.scrollTo({
405+
top: 100,
406+
offset: 30,
407+
});
408+
409+
await waitFakeTimer();
410+
411+
expect(global.scrollToConfig).toEqual({
412+
top: 100,
413+
offset: 30,
414+
align: 'top',
415+
});
416+
});
417+
371418
describe('auto width', () => {
372419
async function prepareTable(columns: any[]) {
373420
const { container } = getTable({

tests/refs.spec.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,51 @@ describe('Table.Ref', () => {
6262
});
6363
expect(scrollIntoViewElement.textContent).toEqual('bamboo');
6464
});
65+
66+
it('support scrollTo with offset', () => {
67+
const ref = React.createRef<Reference>();
68+
69+
render(
70+
<Table
71+
data={[{ key: 'light' }, { key: 'bamboo' }]}
72+
columns={[
73+
{
74+
dataIndex: 'key',
75+
},
76+
]}
77+
ref={ref}
78+
scroll={{
79+
y: 10,
80+
}}
81+
/>,
82+
);
83+
84+
// Scroll To top with offset should ignore offset
85+
ref.current.scrollTo({
86+
top: 100,
87+
offset: 50,
88+
});
89+
expect(scrollParam.top).toEqual(100); // offset ignored
90+
91+
// Scroll index with offset
92+
ref.current.scrollTo({
93+
index: 0,
94+
offset: 30,
95+
});
96+
expect(scrollParam.top).toEqual(30); // offsetTop (0) + offset (30)
97+
98+
// Scroll key with offset
99+
ref.current.scrollTo({
100+
key: 'bamboo',
101+
offset: 20,
102+
});
103+
expect(scrollParam.top).toEqual(20); // offsetTop (0) + offset (20)
104+
105+
// Scroll index without offset should use scrollIntoView
106+
scrollIntoViewElement = null;
107+
ref.current.scrollTo({
108+
index: 0,
109+
});
110+
expect(scrollIntoViewElement.textContent).toEqual('light');
111+
});
65112
});

0 commit comments

Comments
 (0)