From b4bc3f75aa75573bc1197872efe82ef9f06a4d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Tue, 31 Mar 2026 09:55:37 +0800 Subject: [PATCH 1/7] feat: scrollTo add align --- README.md | 19 +++ docs/examples/scrollY.tsx | 30 ++++ src/Table.tsx | 25 +++- src/interface.ts | 2 + tests/__snapshots__/ExpandRow.spec.jsx.snap | 42 +++--- tests/__snapshots__/FixedColumn.spec.tsx.snap | 128 +++++++++--------- tests/__snapshots__/Summary.spec.tsx.snap | 2 +- tests/__snapshots__/Table.spec.jsx.snap | 8 +- tests/refs.spec.tsx | 75 ++++++++++ 9 files changed, 234 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 01f59467b..0ce04a915 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,25 @@ React.render(, mountNode); | summary | (data: readonly RecordType[]) => React.ReactNode | - | `summary` attribute in `table` component is used to define the summary row. | | rowHoverable | boolean | true | Table hover interaction | +### Methods + +#### scrollTo + +Table 组件暴露 `scrollTo` 方法用于滚动到指定位置: + +```js +const tblRef = useRef(); +tblRef.current?.scrollTo({ key: 'rowKey', align: 'start' }); +``` + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| index | number | - | Row index to scroll to | +| top | number | - | Scroll to specific top position (in px) | +| key | string | - | Scroll to row by row key | +| offset | number | - | Additional offset from target position | +| align | `start` \| `center` \| `end` \| `nearest` | `nearest` | Alignment of the target element within the scroll container. `start` aligns to top, `center` to middle, `end` to bottom, `nearest` automatically chooses the closest alignment | + ## Column Props | Name | Type | Default | Description | diff --git a/docs/examples/scrollY.tsx b/docs/examples/scrollY.tsx index 4146498a5..84559fd3f 100644 --- a/docs/examples/scrollY.tsx +++ b/docs/examples/scrollY.tsx @@ -88,6 +88,36 @@ const Test = () => { > Scroll To Key 6 + Offset -10 + + +
- extends Omit, 'showExpandColumn'> { +export interface TableProps extends Omit< + LegacyExpandableProps, + 'showExpandColumn' +> { prefixCls?: string; className?: string; style?: React.CSSProperties; @@ -349,7 +351,7 @@ const Table = ( scrollTo: config => { if (scrollBodyRef.current instanceof HTMLElement) { // Native scroll - const { index, top, key, offset } = config; + const { index, top, key, offset, align = 'nearest' } = config; if (validNumberValue(top)) { // In top mode, offset is ignored @@ -361,12 +363,21 @@ const Table = ( ); if (targetElement) { if (!offset) { - // No offset, use scrollIntoView for default behavior - targetElement.scrollIntoView(); + targetElement.scrollIntoView({ block: align }); } else { - // With offset, use element's offsetTop + offset + const container = scrollBodyRef.current; const elementTop = (targetElement as HTMLElement).offsetTop; - scrollBodyRef.current.scrollTo({ top: elementTop + offset }); + const elementHeight = (targetElement as HTMLElement).offsetHeight; + const containerHeight = container.clientHeight; + + const alignMap: Record = { + start: elementTop, + end: elementTop + elementHeight - containerHeight, + center: elementTop + (elementHeight - containerHeight) / 2, + nearest: elementTop, + }; + const targetTop = alignMap[align] ?? elementTop; + container.scrollTo({ top: targetTop + offset }); } } } diff --git a/src/interface.ts b/src/interface.ts index 2c2d67801..552acf974 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -43,6 +43,8 @@ export type ScrollConfig = { * When offset is set, the target element will always be aligned to the top of the container. */ offset?: number; + + align?: ScrollLogicalPosition; }; export type Reference = { diff --git a/tests/__snapshots__/ExpandRow.spec.jsx.snap b/tests/__snapshots__/ExpandRow.spec.jsx.snap index 9531f6414..e589629f2 100644 --- a/tests/__snapshots__/ExpandRow.spec.jsx.snap +++ b/tests/__snapshots__/ExpandRow.spec.jsx.snap @@ -129,7 +129,7 @@ exports[`Table.Expand > does not crash if scroll is not set 1`] = ` @@ -543,7 +543,7 @@ exports[`Table.Expand > renders fixed column correctly > work 1`] = ` @@ -600,7 +600,7 @@ exports[`Table.Expand > renders fixed column correctly > work 1`] = ` > @@ -619,7 +619,7 @@ exports[`Table.Expand > renders fixed column correctly > work 1`] = ` @@ -647,7 +647,7 @@ exports[`Table.Expand > renders fixed column correctly > work 1`] = ` > @@ -666,7 +666,7 @@ exports[`Table.Expand > renders fixed column correctly > work 1`] = ` @@ -991,7 +991,7 @@ exports[`Table.Expand > work in expandable fix 1`] = ` @@ -1237,7 +1237,7 @@ exports[`Table.Expand > work in expandable fix 2`] = ` @@ -114,7 +114,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` @@ -243,7 +243,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` > @@ -305,7 +305,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` @@ -316,7 +316,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` > @@ -378,7 +378,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` @@ -389,7 +389,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` > @@ -437,7 +437,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> fixed column renders correctly RTL 1`] = ` > @@ -494,7 +494,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> fixed column renders correctly RTL 1`] = ` > @@ -551,7 +551,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> fixed column renders correctly RTL 1`] = ` > @@ -608,7 +608,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> fixed column renders correctly RTL 1`] = ` > @@ -665,7 +665,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> fixed column renders correctly RTL 1`] = ` > @@ -722,7 +722,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> fixed column renders correctly RTL 1`] = ` > @@ -779,7 +779,7 @@ exports[`Table.FixedColumn > fixed column renders correctly RTL 1`] = ` /> @@ -832,7 +832,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` @@ -905,7 +905,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` @@ -1034,7 +1034,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` > @@ -1096,7 +1096,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` @@ -1107,7 +1107,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` > @@ -1169,7 +1169,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` @@ -1180,7 +1180,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` > @@ -1228,7 +1228,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> renders correctly > scrollX - with data 1`] = ` > @@ -1285,7 +1285,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> renders correctly > scrollX - with data 1`] = ` > @@ -1342,7 +1342,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> renders correctly > scrollX - with data 1`] = ` > @@ -1399,7 +1399,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> renders correctly > scrollX - with data 1`] = ` > @@ -1456,7 +1456,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> renders correctly > scrollX - with data 1`] = ` > @@ -1513,7 +1513,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> renders correctly > scrollX - with data 1`] = ` > @@ -1570,7 +1570,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - with data 1`] = ` /> @@ -1621,7 +1621,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - without data 1`] = ` @@ -1694,7 +1694,7 @@ exports[`Table.FixedColumn > renders correctly > scrollX - without data 1`] = ` @@ -1901,7 +1901,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` @@ -1980,7 +1980,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` @@ -2136,7 +2136,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` > @@ -2198,7 +2198,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` @@ -2209,7 +2209,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` > @@ -2271,7 +2271,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` @@ -2282,7 +2282,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` > @@ -2330,7 +2330,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> renders correctly > scrollXY - with data 1`] = ` > @@ -2387,7 +2387,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> renders correctly > scrollXY - with data 1`] = ` > @@ -2444,7 +2444,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> renders correctly > scrollXY - with data 1`] = ` > @@ -2501,7 +2501,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> renders correctly > scrollXY - with data 1`] = ` > @@ -2558,7 +2558,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> renders correctly > scrollXY - with data 1`] = ` > @@ -2615,7 +2615,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> renders correctly > scrollXY - with data 1`] = ` > @@ -2672,7 +2672,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - with data 1`] = ` /> @@ -2723,7 +2723,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - without data 1`] = ` @@ -2802,7 +2802,7 @@ exports[`Table.FixedColumn > renders correctly > scrollXY - without data 1`] = ` diff --git a/tests/__snapshots__/Summary.spec.tsx.snap b/tests/__snapshots__/Summary.spec.tsx.snap index c71599c83..6904cccea 100644 --- a/tests/__snapshots__/Summary.spec.tsx.snap +++ b/tests/__snapshots__/Summary.spec.tsx.snap @@ -8,7 +8,7 @@ exports[`Table.Summary > support data type 1`] = ` diff --git a/tests/__snapshots__/Table.spec.jsx.snap b/tests/__snapshots__/Table.spec.jsx.snap index 90936f847..dca0d4471 100644 --- a/tests/__snapshots__/Table.spec.jsx.snap +++ b/tests/__snapshots__/Table.spec.jsx.snap @@ -98,7 +98,7 @@ exports[`Table.Basic > custom components > renders fixed column and header corre class="rc-table-cell rc-table-cell-fix rc-table-cell-fix-start rc-table-cell-fix-start-shadow" name="my-header-cell" scope="col" - style="inset-inline-start: 0; --z-offset: 8; --z-offset-reverse: 4;" + style="inset-inline-start: 0px; --z-offset: 8; --z-offset-reverse: 4;" > Name @@ -120,7 +120,7 @@ exports[`Table.Basic > custom components > renders fixed column and header corre @@ -179,7 +179,7 @@ exports[`Table.Basic > custom components > renders fixed column and header corre @@ -192,7 +192,7 @@ exports[`Table.Basic > custom components > renders fixed column and header corre diff --git a/tests/refs.spec.tsx b/tests/refs.spec.tsx index a53b03cb7..b2d022fe9 100644 --- a/tests/refs.spec.tsx +++ b/tests/refs.spec.tsx @@ -21,6 +21,7 @@ describe('Table.Ref', () => { beforeEach(() => { scrollParam = null; + scrollIntoViewElement = null; }); it('support reference', () => { @@ -109,4 +110,78 @@ describe('Table.Ref', () => { }); expect(scrollIntoViewElement.textContent).toEqual('light'); }); + + it('support scrollTo with align', () => { + const ref = React.createRef(); + + render( +
does not crash if scroll is not set 1`] = ` > does not crash if scroll is not set 1`] = ` > does not crash if scroll is not set 2`] = `
does not crash if scroll is not set 2`] = ` > does not crash if scroll is not set 2`] = ` > renders fixed column correctly > work 1`] = `
Name Gender renders fixed column correctly > work 1`] = ` Lucy F renders fixed column correctly > work 1`] = ` Jack M
work in expandable fix 1`] = ` > work in expandable fix 1`] = ` > work in expandable fix 2`] = `
work in expandable fix 2`] = ` fixed column renders correctly RTL 1`] = ` title1 title12 123 xxxxxxxx cdd edd12221 133
133
133
133
133
133
133
title1 title12 123 xxxxxxxx cdd edd12221 133
133
133
133
133
133
133
title1 title12 title1
123 xxxxxxxx cdd edd12221 133
133
133
133
133
133
133
title1
Light
Lucy F
, + ); + + // Default behavior: uses scrollIntoView (not scrollTo) + ref.current.scrollTo({ index: 0 }); + expect(scrollIntoViewElement).not.toBeNull(); + expect(scrollIntoViewElement.textContent).toEqual('light'); + + // Align start - should use scrollIntoView + scrollIntoViewElement = null; + ref.current.scrollTo({ index: 0, align: 'start' }); + expect(scrollIntoViewElement.textContent).toEqual('light'); + + // Align center - should use scrollIntoView + ref.current.scrollTo({ index: 1, align: 'center' }); + expect(scrollIntoViewElement.textContent).toEqual('bamboo'); + + // Align end - should use scrollIntoView + scrollIntoViewElement = null; + ref.current.scrollTo({ key: 'bamboo', align: 'end' }); + expect(scrollIntoViewElement.textContent).toEqual('bamboo'); + }); + + it('support scrollTo with align and offset', () => { + const ref = React.createRef(); + + render( +
, + ); + + // align start + offset 20 = 0 + 20 = 20 + ref.current.scrollTo({ index: 0, align: 'start', offset: 20 }); + expect(scrollIntoViewElement).toBeNull(); + expect(scrollParam.top).toEqual(20); + + // align center + offset 30 = 0 + 30 = 30 + ref.current.scrollTo({ index: 1, align: 'center', offset: 30 }); + expect(scrollParam.top).toEqual(30); + + // align end + offset 10 = 0 + 10 = 10 + ref.current.scrollTo({ key: 'bamboo', align: 'end', offset: 10 }); + expect(scrollParam.top).toEqual(10); + + // align nearest + offset 50 = 0 + 50 = 50 + ref.current.scrollTo({ index: 0, align: 'nearest', offset: 50 }); + expect(scrollParam.top).toEqual(50); + }); }); From 65f7b76082451ef457555cf0d249b62154fb2f18 Mon Sep 17 00:00:00 2001 From: EmilyyyLiu <100924403+EmilyyyLiu@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:29:36 +0800 Subject: [PATCH 2/7] Update README.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ce04a915..dbe625eb7 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ React.render(
, mountNode); #### scrollTo -Table 组件暴露 `scrollTo` 方法用于滚动到指定位置: +Table component exposes `scrollTo` method to scroll to a specific position: ```js const tblRef = useRef(); From 74f51a1e1445a269c71dd896f7bd94369eb86282 Mon Sep 17 00:00:00 2001 From: EmilyyyLiu <100924403+EmilyyyLiu@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:29:49 +0800 Subject: [PATCH 3/7] Update src/Table.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/Table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Table.tsx b/src/Table.tsx index e74c81a9c..ef9db729a 100644 --- a/src/Table.tsx +++ b/src/Table.tsx @@ -370,7 +370,7 @@ const Table = ( const elementHeight = (targetElement as HTMLElement).offsetHeight; const containerHeight = container.clientHeight; - const alignMap: Record = { + const alignMap: Record = { start: elementTop, end: elementTop + elementHeight - containerHeight, center: elementTop + (elementHeight - containerHeight) / 2, From 1027f1793f81081465728a044dafbb9efb3ad640 Mon Sep 17 00:00:00 2001 From: EmilyyyLiu <100924403+EmilyyyLiu@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:30:03 +0800 Subject: [PATCH 4/7] Update src/interface.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface.ts b/src/interface.ts index 552acf974..d55a9744c 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -40,7 +40,7 @@ export type ScrollConfig = { * Additional offset in pixels to apply to the scroll position. * Only effective when using `key` or `index` mode. * Ignored when using `top` mode. - * When offset is set, the target element will always be aligned to the top of the container. + * When offset is set, the target element will be aligned according to the `align` parameter. */ offset?: number; From 833f5ced1dc6a54f476324d2c22fcac73d8fbb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Wed, 1 Apr 2026 15:59:32 +0800 Subject: [PATCH 5/7] fix: virtual table scrollTo align support - Add VirtualScrollConfig type with Exclude - Implement align mapping for virtual table (start->top, end->bottom, nearest->auto) - Add default align 'auto' when neither align nor offset provided - Add backward compatibility: default to 'top' when offset provided but align not - Update tests to cover align parameter scenarios - Fix offset JSDoc comment Co-Authored-By: Claude Opus 4.6 --- src/VirtualTable/BodyGrid.tsx | 32 ++++++++++++++++++++++---------- src/interface.ts | 6 +++++- tests/Virtual.spec.tsx | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/VirtualTable/BodyGrid.tsx b/src/VirtualTable/BodyGrid.tsx index a4db84ce4..7a7df4ebf 100644 --- a/src/VirtualTable/BodyGrid.tsx +++ b/src/VirtualTable/BodyGrid.tsx @@ -3,7 +3,12 @@ import VirtualList, { type ListProps, type ListRef } from '@rc-component/virtual import * as React from 'react'; import TableContext, { responseImmutable } from '../context/TableContext'; import useFlattenRecords, { type FlattenData } from '../hooks/useFlattenRecords'; -import type { ColumnType, OnCustomizeScroll, ScrollConfig } from '../interface'; +import type { + ColumnType, + OnCustomizeScroll, + ScrollConfig, + VirtualScrollConfig, +} from '../interface'; import BodyLine from './BodyLine'; import { GridContext, StaticContext } from './context'; @@ -79,15 +84,22 @@ const Grid = React.forwardRef((props, ref) => { // =========================== Ref ============================ React.useImperativeHandle(ref, () => { const obj = { - scrollTo: (config: ScrollConfig) => { - const { offset, ...restConfig } = config; - - // If offset is provided, force align to 'top' for consistent behavior - if (offset) { - listRef.current?.scrollTo({ ...restConfig, offset, align: 'top' }); - } else { - listRef.current?.scrollTo(config); - } + scrollTo: (config: VirtualScrollConfig) => { + const { align, offset, ...restConfig } = config; + + const alignMap: Record = { + start: 'top', + end: 'bottom', + nearest: 'auto', + }; + + const virtualAlign = align ? alignMap[align] : offset ? 'top' : 'auto'; + + listRef.current?.scrollTo({ + ...restConfig, + offset, + align: virtualAlign, + }); }, nativeElement: listRef.current?.nativeElement, } as unknown as GridRef; diff --git a/src/interface.ts b/src/interface.ts index d55a9744c..d5a8aee03 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -40,13 +40,17 @@ export type ScrollConfig = { * Additional offset in pixels to apply to the scroll position. * Only effective when using `key` or `index` mode. * Ignored when using `top` mode. - * When offset is set, the target element will be aligned according to the `align` parameter. + * In `key` / `index` mode, `offset` is added to the position resolved by `align`. */ offset?: number; align?: ScrollLogicalPosition; }; +export type VirtualScrollConfig = ScrollConfig & { + align?: Exclude; +}; + export type Reference = { nativeElement: HTMLDivElement; scrollTo: (config: ScrollConfig) => void; diff --git a/tests/Virtual.spec.tsx b/tests/Virtual.spec.tsx index 81aff6aba..dad854b21 100644 --- a/tests/Virtual.spec.tsx +++ b/tests/Virtual.spec.tsx @@ -373,6 +373,7 @@ describe('Table.Virtual', () => { expect(global.scrollToConfig).toEqual({ index: 99, + align: 'auto', }); }); @@ -423,6 +424,31 @@ describe('Table.Virtual', () => { }); }); + it('scrollTo with align should pass', async () => { + const tblRef = React.createRef(); + getTable({ ref: tblRef }); + + // align start -> top + tblRef.current.scrollTo({ index: 50, align: 'start' }); + await waitFakeTimer(); + expect(global.scrollToConfig).toEqual({ index: 50, align: 'top' }); + + // align end -> bottom + tblRef.current.scrollTo({ index: 50, align: 'end' }); + await waitFakeTimer(); + expect(global.scrollToConfig).toEqual({ index: 50, align: 'bottom' }); + + // align nearest -> auto + tblRef.current.scrollTo({ index: 50, align: 'nearest' }); + await waitFakeTimer(); + expect(global.scrollToConfig).toEqual({ index: 50, align: 'auto' }); + + // offset + align + tblRef.current.scrollTo({ index: 50, offset: 20, align: 'end' }); + await waitFakeTimer(); + expect(global.scrollToConfig).toEqual({ index: 50, offset: 20, align: 'bottom' }); + }); + describe('auto width', () => { async function prepareTable(columns: any[]) { const { container } = getTable({ From c71b494e68310f34b1b42769e652596d37a1dfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Wed, 1 Apr 2026 16:53:53 +0800 Subject: [PATCH 6/7] fix: implement real nearest behavior with offset in Table scrollTo - Fix nearest align to compute position after offset, then determine if scrolling is needed - Add align test buttons to scrollY and virtual demo files - Update center calculation formula Co-Authored-By: Claude Opus 4.6 --- docs/examples/scrollY.tsx | 21 +++++++++++++++++++++ docs/examples/virtual.tsx | 15 +++++++++++++++ src/Table.tsx | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/docs/examples/scrollY.tsx b/docs/examples/scrollY.tsx index 84559fd3f..39d525dd7 100644 --- a/docs/examples/scrollY.tsx +++ b/docs/examples/scrollY.tsx @@ -118,6 +118,27 @@ const Test = () => { > Scroll To key 9 (align: end) + +
{ + + + + + ( scrollTo: config => { if (scrollBodyRef.current instanceof HTMLElement) { // Native scroll - const { index, top, key, offset, align = 'nearest' } = config; + const { index, top, key, offset, align } = config; if (validNumberValue(top)) { // In top mode, offset is ignored @@ -363,21 +363,38 @@ const Table = ( ); if (targetElement) { if (!offset) { - targetElement.scrollIntoView({ block: align }); + targetElement.scrollIntoView({ block: align ?? 'nearest' }); } else { const container = scrollBodyRef.current; const elementTop = (targetElement as HTMLElement).offsetTop; const elementHeight = (targetElement as HTMLElement).offsetHeight; const containerHeight = container.clientHeight; - - const alignMap: Record = { - start: elementTop, - end: elementTop + elementHeight - containerHeight, - center: elementTop + (elementHeight - containerHeight) / 2, - nearest: elementTop, - }; - const targetTop = alignMap[align] ?? elementTop; - container.scrollTo({ top: targetTop + offset }); + const currentTop = container.scrollTop; + const elementBottom = elementTop + elementHeight; + const viewportBottom = currentTop + containerHeight; + let targetTop: number; + + if (align === 'nearest') { + const targetWithOffset = elementTop + offset; + const targetBottomWithOffset = elementBottom + offset; + + if (targetWithOffset < currentTop) { + targetTop = targetWithOffset; + } else if (targetBottomWithOffset > viewportBottom) { + targetTop = targetBottomWithOffset - containerHeight; + } else { + targetTop = currentTop; + } + } else { + const alignMap: Record = { + start: elementTop, + end: elementBottom - containerHeight, + center: elementTop - (containerHeight - elementHeight) / 2, + }; + targetTop = alignMap[align ?? 'start'] + offset; + } + + container.scrollTo({ top: targetTop }); } } } From ec47eb8e2be8cb5bf1d8bd5c67a159adcf913294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Wed, 1 Apr 2026 17:03:08 +0800 Subject: [PATCH 7/7] fix: improve virtual table align handling Use nullish coalescing for safer align fallback Co-Authored-By: Claude Opus 4.6 --- src/VirtualTable/BodyGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VirtualTable/BodyGrid.tsx b/src/VirtualTable/BodyGrid.tsx index 7a7df4ebf..a360033a1 100644 --- a/src/VirtualTable/BodyGrid.tsx +++ b/src/VirtualTable/BodyGrid.tsx @@ -93,7 +93,7 @@ const Grid = React.forwardRef((props, ref) => { nearest: 'auto', }; - const virtualAlign = align ? alignMap[align] : offset ? 'top' : 'auto'; + const virtualAlign = alignMap[align] ?? (offset ? 'top' : 'auto'); listRef.current?.scrollTo({ ...restConfig,