Skip to content

Commit 2b658d8

Browse files
authored
fix(ObjectPage): fix scroll-to/selection behavior (#6879)
The `height` for the `spacer` is now calculated initially and is only adjusted if one of the relevant elements or properties are changed. This way the initial content height is not updated when the header is collapsed, leading to a smoother scroll experience and preventing wrong scroll position. Fixes #6798
1 parent 35e1420 commit 2b658d8

File tree

1 file changed

+48
-22
lines changed
  • packages/main/src/components/ObjectPage

1 file changed

+48
-22
lines changed

packages/main/src/components/ObjectPage/index.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
233233
const objectPageContentRef = useRef<HTMLDivElement>(null);
234234
const selectionScrollTimeout = useRef(null);
235235
const isToggledRef = useRef(false);
236+
const isInitial = useRef(true);
236237
const [headerCollapsedInternal, setHeaderCollapsedInternal] = useState<undefined | boolean>(undefined);
237238
const [scrolledHeaderExpanded, setScrolledHeaderExpanded] = useState(false);
238239
const scrollTimeout = useRef(0);
@@ -366,7 +367,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
366367
};
367368

368369
// 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) => {
370371
isProgrammaticallyScrolled.current = true;
371372
debouncedOnSectionChange.cancel();
372373
setSelectedSubSectionId(undefined);
@@ -458,42 +459,67 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
458459
}, [props.selectedSubSectionId, isMounted]);
459460

460461
const tabContainerContainerRef = useRef(null);
462+
const isHeaderPinnedAndExpanded = headerPinned && !headerCollapsed;
461463
useEffect(() => {
462464
const objectPage = objectPageRef.current;
463-
const sectionNodes = objectPage.querySelectorAll<HTMLDivElement>('[id^="ObjectPageSection"]');
464-
const lastSectionNode = sectionNodes[sectionNodes.length - 1];
465465
const tabContainerContainer = tabContainerContainerRef.current;
466466

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+
468482
const subSections = lastSectionNode.querySelectorAll<HTMLDivElement>('[id^="ObjectPageSubSection"]');
469483
const lastSubSection = subSections[subSections.length - 1];
470-
const lastSubSectionOrSection = lastSubSection ?? sectionElement.target;
484+
const lastSubSectionOrSection = lastSubSection ?? lastSectionNode;
485+
471486
if ((currentTabModeSection && !lastSubSection) || (sectionNodes.length === 1 && !lastSubSection)) {
472487
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;
486489
}
487-
});
488490

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) {
490516
observer.observe(lastSectionNode, { box: 'border-box' });
491517
}
492518

493519
return () => {
494520
observer.disconnect();
495521
};
496-
}, [headerCollapsed, topHeaderHeight, headerContentHeight, currentTabModeSection, children, mode]);
522+
}, [topHeaderHeight, headerContentHeight, currentTabModeSection, children, mode, isHeaderPinnedAndExpanded]);
497523

498524
const onToggleHeaderContentVisibility = useCallback((e) => {
499525
isToggledRef.current = true;
@@ -592,7 +618,6 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
592618

593619
const snappedHeaderInObjPage = titleArea && titleArea.props.snappedContent && headerCollapsed === true && !!image;
594620

595-
const isInitial = useRef(true);
596621
useEffect(() => {
597622
if (!isInitial.current) {
598623
scrollTimeout.current = performance.now() + 200;
@@ -646,6 +671,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
646671
}
647672
event.preventDefault();
648673
const { sectionId, index, isSubTab, parentId } = event.detail.tab.dataset;
674+
649675
if (isSubTab !== undefined) {
650676
handleOnSubSectionSelected(enrichEventWithDetails(event, { sectionId: parentId, subSectionId: sectionId }));
651677
} else {

0 commit comments

Comments
 (0)