@@ -233,6 +233,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
233
233
const objectPageContentRef = useRef < HTMLDivElement > ( null ) ;
234
234
const selectionScrollTimeout = useRef ( null ) ;
235
235
const isToggledRef = useRef ( false ) ;
236
+ const isInitial = useRef ( true ) ;
236
237
const [ headerCollapsedInternal , setHeaderCollapsedInternal ] = useState < undefined | boolean > ( undefined ) ;
237
238
const [ scrolledHeaderExpanded , setScrolledHeaderExpanded ] = useState ( false ) ;
238
239
const scrollTimeout = useRef ( 0 ) ;
@@ -366,7 +367,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
366
367
} ;
367
368
368
369
// section was selected by clicking on the tab bar buttons
369
- const handleOnSectionSelected = ( targetEvent , newSelectionSectionId , index , section ) => {
370
+ const handleOnSectionSelected = ( targetEvent , newSelectionSectionId , index : number | string , section ) => {
370
371
isProgrammaticallyScrolled . current = true ;
371
372
debouncedOnSectionChange . cancel ( ) ;
372
373
setSelectedSubSectionId ( undefined ) ;
@@ -458,42 +459,67 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
458
459
} , [ props . selectedSubSectionId , isMounted ] ) ;
459
460
460
461
const tabContainerContainerRef = useRef ( null ) ;
462
+ const isHeaderPinnedAndExpanded = headerPinned && ! headerCollapsed ;
461
463
useEffect ( ( ) => {
462
464
const objectPage = objectPageRef . current ;
463
- const sectionNodes = objectPage . querySelectorAll < HTMLDivElement > ( '[id^="ObjectPageSection"]' ) ;
464
- const lastSectionNode = sectionNodes [ sectionNodes . length - 1 ] ;
465
465
const tabContainerContainer = tabContainerContainerRef . current ;
466
466
467
- const observer = new ResizeObserver ( ( [ sectionElement ] ) => {
467
+ if ( ! objectPage || ! tabContainerContainer ) {
468
+ return ;
469
+ }
470
+
471
+ const footerElement = objectPage . querySelector < HTMLDivElement > ( '[data-component-name="ObjectPageFooter"]' ) ;
472
+ const topHeaderElement = objectPage . querySelector ( '[data-component-name="ObjectPageTopHeader"]' ) ;
473
+
474
+ const calculateSpacer = ( [ lastSectionNodeEntry ] : ResizeObserverEntry [ ] ) => {
475
+ const lastSectionNode = lastSectionNodeEntry ?. target ;
476
+
477
+ if ( ! lastSectionNode ) {
478
+ setSectionSpacer ( 0 ) ;
479
+ return ;
480
+ }
481
+
468
482
const subSections = lastSectionNode . querySelectorAll < HTMLDivElement > ( '[id^="ObjectPageSubSection"]' ) ;
469
483
const lastSubSection = subSections [ subSections . length - 1 ] ;
470
- const lastSubSectionOrSection = lastSubSection ?? sectionElement . target ;
484
+ const lastSubSectionOrSection = lastSubSection ?? lastSectionNode ;
485
+
471
486
if ( ( currentTabModeSection && ! lastSubSection ) || ( sectionNodes . length === 1 && ! lastSubSection ) ) {
472
487
setSectionSpacer ( 0 ) ;
473
- } else if ( tabContainerContainer ) {
474
- const footerHeight =
475
- objectPage . querySelector < HTMLDivElement | undefined > ( '[data-component-name="ObjectPageFooter"]' )
476
- ?. offsetHeight ?? 0 ;
477
-
478
- setSectionSpacer (
479
- objectPage . getBoundingClientRect ( ) . bottom -
480
- tabContainerContainer . getBoundingClientRect ( ) . bottom -
481
- lastSubSectionOrSection . getBoundingClientRect ( ) . height -
482
- footerHeight -
483
- // section padding
484
- 8
485
- ) ;
488
+ return ;
486
489
}
487
- } ) ;
488
490
489
- if ( objectPage && lastSectionNode ) {
491
+ // batching DOM-reads together minimizes reflow
492
+ const footerHeight = footerElement ?. offsetHeight ?? 0 ;
493
+ const objectPageRect = objectPage . getBoundingClientRect ( ) ;
494
+ const tabContainerContainerRect = tabContainerContainer . getBoundingClientRect ( ) ;
495
+ const lastSubSectionOrSectionRect = lastSubSectionOrSection . getBoundingClientRect ( ) ;
496
+
497
+ let stickyHeaderBottom = 0 ;
498
+ if ( ! isHeaderPinnedAndExpanded ) {
499
+ const topHeaderBottom = topHeaderElement ?. getBoundingClientRect ( ) . bottom ?? 0 ;
500
+ stickyHeaderBottom = topHeaderBottom + tabContainerContainerRect . height ;
501
+ } else {
502
+ stickyHeaderBottom = tabContainerContainerRect . bottom ;
503
+ }
504
+
505
+ const spacer = Math . ceil (
506
+ objectPageRect . bottom - stickyHeaderBottom - lastSubSectionOrSectionRect . height - footerHeight // section padding (8px) not included, so that the intersection observer is triggered correctly
507
+ ) ;
508
+ setSectionSpacer ( Math . max ( spacer , 0 ) ) ;
509
+ } ;
510
+
511
+ const observer = new ResizeObserver ( calculateSpacer ) ;
512
+ const sectionNodes = objectPage . querySelectorAll < HTMLDivElement > ( '[id^="ObjectPageSection"]' ) ;
513
+ const lastSectionNode = sectionNodes [ sectionNodes . length - 1 ] ;
514
+
515
+ if ( lastSectionNode ) {
490
516
observer . observe ( lastSectionNode , { box : 'border-box' } ) ;
491
517
}
492
518
493
519
return ( ) => {
494
520
observer . disconnect ( ) ;
495
521
} ;
496
- } , [ headerCollapsed , topHeaderHeight , headerContentHeight , currentTabModeSection , children , mode ] ) ;
522
+ } , [ topHeaderHeight , headerContentHeight , currentTabModeSection , children , mode , isHeaderPinnedAndExpanded ] ) ;
497
523
498
524
const onToggleHeaderContentVisibility = useCallback ( ( e ) => {
499
525
isToggledRef . current = true ;
@@ -592,7 +618,6 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
592
618
593
619
const snappedHeaderInObjPage = titleArea && titleArea . props . snappedContent && headerCollapsed === true && ! ! image ;
594
620
595
- const isInitial = useRef ( true ) ;
596
621
useEffect ( ( ) => {
597
622
if ( ! isInitial . current ) {
598
623
scrollTimeout . current = performance . now ( ) + 200 ;
@@ -646,6 +671,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
646
671
}
647
672
event . preventDefault ( ) ;
648
673
const { sectionId, index, isSubTab, parentId } = event . detail . tab . dataset ;
674
+
649
675
if ( isSubTab !== undefined ) {
650
676
handleOnSubSectionSelected ( enrichEventWithDetails ( event , { sectionId : parentId , subSectionId : sectionId } ) ) ;
651
677
} else {
0 commit comments