@@ -102,7 +102,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
102102 const isProgrammaticallyScrolled = useRef ( false ) ;
103103 const [ componentRef , objectPageRef ] = useSyncRef ( ref ) ;
104104 const topHeaderRef = useRef < HTMLDivElement > ( null ) ;
105- const prevTopHeaderHeight = useRef ( 0 ) ;
105+ const pendingScrollTargetRef = useRef < number | null > ( null ) ;
106106 // @ts -expect-error: useSyncRef will create a ref if not present
107107 const [ componentRefHeaderContent , headerContentRef ] = useSyncRef ( headerArea ?. ref ) ;
108108 const scrollEvent = useRef ( undefined ) ;
@@ -278,7 +278,8 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
278278 return ;
279279 }
280280
281- const safeTopHeaderHeight = topHeaderHeight || prevTopHeaderHeight . current ;
281+ // header collapses in this commit but topHeaderHeight state lags a tick, so measure live
282+ const safeTopHeaderHeight = topHeaderRef . current ?. getBoundingClientRect ( ) . height || topHeaderHeight ;
282283
283284 const scrollMargin =
284285 - 1 /* reduce margin-block so that intersection observer detects correct section*/ +
@@ -295,10 +296,14 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
295296 const objectPageRect = objectPageElement . getBoundingClientRect ( ) ;
296297
297298 // Calculate the top position of the section relative to the container
298- objectPageElement . scrollTop =
299- sectionRect . top - objectPageRect . top + objectPageElement . scrollTop - scrollMargin ;
299+ const targetScrollTop = sectionRect . top - objectPageRect . top + objectPageElement . scrollTop - scrollMargin ;
300+ objectPageElement . scrollTop = targetScrollTop ;
300301
301302 section . style . scrollMarginBlockStart = '' ;
303+
304+ // remember a target the browser clamped because the bottom spacer hasn't grown yet
305+ pendingScrollTargetRef . current =
306+ targetScrollTop > objectPageElement . scrollHeight - objectPageElement . clientHeight ? targetScrollTop : null ;
302307 }
303308 } ;
304309 // In TabBar mode the section is only rendered when selected: delay scroll for subsection
@@ -311,6 +316,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
311316 [
312317 mode ,
313318 objectPageRef ,
319+ topHeaderRef ,
314320 topHeaderHeight ,
315321 tabContainerHeaderHeight ,
316322 headerPinned ,
@@ -407,6 +413,20 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
407413 }
408414 } , [ selectedSubSectionId , sectionSpacer , scrollToSectionById ] ) ;
409415
416+ // re-apply the clamped target once sectionSpacer has grown enough
417+ useEffect ( ( ) => {
418+ const target = pendingScrollTargetRef . current ;
419+ if ( target == null ) {
420+ return ;
421+ }
422+ pendingScrollTargetRef . current = null ;
423+ const objectPage = objectPageRef . current ;
424+ if ( objectPage && objectPage . scrollHeight - objectPage . clientHeight + 1 >= target ) {
425+ objectPage . scrollTop = target ;
426+ }
427+ // eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable; only sectionSpacer should re-trigger
428+ } , [ sectionSpacer ] ) ;
429+
410430 useEffect ( ( ) => {
411431 if ( headerPinnedProp !== undefined ) {
412432 setHeaderPinned ( headerPinnedProp ) ;
0 commit comments