Repository navigation
fix(virtual-core): read the scroll element's size with each scroll event #1378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@tanstack/virtual-core': patch | ||
| --- | ||
|
|
||
| Read the scroll element's size along with each scroll event. After a resize the browser fires the scroll event (from the clamped position) before the ResizeObserver runs, so an update could pair the new scroll position with the old size. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,11 +69,18 @@ export interface Rect { | |
|
|
||
| // | ||
|
|
||
| // How the default rect observers measure the scroll element. The scroll | ||
| // handler measures with the same functions (`_readDefaultRect`). | ||
| const getRect = (element: HTMLElement): Rect => { | ||
| const { clientWidth, clientHeight } = element | ||
| return { width: clientWidth, height: clientHeight } | ||
| return { width: Math.round(clientWidth), height: Math.round(clientHeight) } | ||
| } | ||
|
|
||
| const getWindowRect = (win: Window): Rect => ({ | ||
| width: win.innerWidth, | ||
| height: win.innerHeight, | ||
| }) | ||
|
|
||
| // Animation frames for `useAnimationFrameWithResizeObserver`, requested from | ||
| // the scroll element's own window rather than the global one. | ||
| const createFrameQueue = (targetWindow: Window) => { | ||
|
|
@@ -126,8 +133,7 @@ export const observeElementRect = <T extends Element>( | |
| } | ||
|
|
||
| const handler = (rect: Rect) => { | ||
| const { width, height } = rect | ||
| cb({ width: Math.round(width), height: Math.round(height) }) | ||
| cb(rect) | ||
| } | ||
|
|
||
| handler(getRect(element as unknown as HTMLElement)) | ||
|
|
@@ -169,7 +175,7 @@ export const observeWindowRect = ( | |
| } | ||
|
|
||
| const handler = () => { | ||
| cb({ width: element.innerWidth, height: element.innerHeight }) | ||
| cb(getWindowRect(element)) | ||
| } | ||
| handler() | ||
|
|
||
|
|
@@ -1118,7 +1124,7 @@ export class Virtualizer< | |
|
|
||
| this.unsubs.push( | ||
| this.options.observeElementRect(this, (rect) => { | ||
| this.scrollRect = rect | ||
| this._setScrollRect(rect) | ||
| this.maybeNotify() | ||
| }), | ||
| ) | ||
|
|
@@ -1250,6 +1256,14 @@ export class Virtualizer< | |
| this.scrollState = null | ||
| } | ||
|
|
||
| // The element may have changed size since its ResizeObserver last | ||
| // reported it: a resize fires its scroll event (from the clamped | ||
| // position), and the isScrolling reset timer, before the observer | ||
| // runs. Read the size with the offset, so a notify never pairs a | ||
| // new position with an old size. | ||
| const liveRect = this._readDefaultRect() | ||
| if (liveRect !== null) this._setScrollRect(liveRect) | ||
|
Comment on lines
+1264
to
+1265
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| // A held correction is written once a write is possible. | ||
| this._flushHoldIfReady(true) | ||
|
|
||
|
|
@@ -2355,6 +2369,32 @@ export class Virtualizer< | |
| return target > maxAtWrite + 0.5 ? { target, maxAtWrite } : null | ||
| } | ||
|
|
||
| // The scroll element's current size, measured as the default rect observer | ||
| // measures it. Null when the consumer supplied their own rect observer: only | ||
| // a size produced the same way can be refreshed, and their size is theirs. | ||
| private _readDefaultRect = (): Rect | null => { | ||
| const el = this.scrollElement as unknown | ||
| const observe = this.options.observeElementRect as unknown | ||
| if (el == null) return null | ||
| if (observe === observeElementRect) return getRect(el as HTMLElement) | ||
| if (observe === observeWindowRect) return getWindowRect(el as Window) | ||
| return null | ||
| } | ||
|
|
||
| // Take a new size for the scroll element, from its rect observer or read | ||
| // with a scroll event. | ||
| private _setScrollRect = (rect: Rect) => { | ||
| const prev = this.scrollRect | ||
| if ( | ||
| prev !== null && | ||
| prev.width === rect.width && | ||
| prev.height === rect.height | ||
| ) { | ||
| return | ||
| } | ||
| this.scrollRect = rect | ||
| } | ||
|
|
||
| private getVirtualDistanceFromEnd = () => { | ||
| // `_getLayoutTotalSize()` measures the sizer, which starts `scrollMargin` | ||
| // into the scroll content, while `getScrollOffset()` is measured from the | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: TanStack/virtual
Length of output: 15414
Script executed:
Repository: TanStack/virtual
Length of output: 20072
Limit the release note to built-in rect observers.
Custom rect observers do not receive this refresh. A consumer using one can still receive a scroll notification with the observer’s last reported size. Clarify the exception in the release note.
Suggested edit
Committable suggestion