Skip to content

Commit be7444c

Browse files
authored
fix(ObjectPage): improve selection & scroll behavior (#6492) (#6507)
Fixes #6478
1 parent 44ecee8 commit be7444c

File tree

2 files changed

+33
-62
lines changed

2 files changed

+33
-62
lines changed

packages/main/src/components/ObjectPage/ObjectPageUtils.ts

-5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,3 @@ export const getSectionById = (sections, id) => {
66
return isValidElement(objectPageSection) && objectPageSection.props?.id === id;
77
});
88
};
9-
10-
export const extractSectionIdFromHtmlId = (id: string) => {
11-
if (!id) return null;
12-
return id.replace(/^ObjectPageSection-/, '');
13-
};

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

+33-57
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { DynamicPageHeader } from '../DynamicPageHeader/index.js';
2525
import type { ObjectPageSectionPropTypes } from '../ObjectPageSection/index.js';
2626
import { CollapsedAvatar } from './CollapsedAvatar.js';
2727
import { classNames, styleData } from './ObjectPage.module.css.js';
28-
import { extractSectionIdFromHtmlId, getSectionById } from './ObjectPageUtils.js';
28+
import { getSectionById } from './ObjectPageUtils.js';
2929

3030
addCustomCSSWithScoping(
3131
'ui5-tabcontainer',
@@ -216,7 +216,6 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
216216
const anchorBarRef = useRef<HTMLDivElement>(null);
217217
const objectPageContentRef = useRef<HTMLDivElement>(null);
218218
const selectionScrollTimeout = useRef(null);
219-
const [isAfterScroll, setIsAfterScroll] = useState(false);
220219
const isToggledRef = useRef(false);
221220
const [headerCollapsedInternal, setHeaderCollapsedInternal] = useState<undefined | boolean>(undefined);
222221
const [scrolledHeaderExpanded, setScrolledHeaderExpanded] = useState(false);
@@ -245,7 +244,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
245244

246245
const prevInternalSelectedSectionId = useRef(internalSelectedSectionId);
247246
const fireOnSelectedChangedEvent = (targetEvent, index, id, section) => {
248-
if (typeof onSelectedSectionChange === 'function' && prevInternalSelectedSectionId.current !== id) {
247+
if (typeof onSelectedSectionChange === 'function' && targetEvent && prevInternalSelectedSectionId.current !== id) {
249248
onSelectedSectionChange(
250249
enrichEventWithDetails(targetEvent, {
251250
selectedSectionIndex: parseInt(index, 10),
@@ -318,7 +317,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
318317
safeTopHeaderHeight +
319318
anchorBarHeight +
320319
TAB_CONTAINER_HEADER_HEIGHT +
321-
(headerPinned ? headerContentHeight : 0) +
320+
(headerPinned && !headerCollapsed ? headerContentHeight : 0) +
322321
'px';
323322
section.focus();
324323
section.scrollIntoView({ behavior: 'smooth' });
@@ -527,73 +526,53 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
527526

528527
const { onScroll: _0, selectedSubSectionId: _1, ...propsWithoutOmitted } = rest;
529528

529+
const visibleSectionIds = useRef<Set<string>>(new Set());
530530
useEffect(() => {
531531
const sectionNodes = objectPageRef.current?.querySelectorAll('section[data-component-name="ObjectPageSection"]');
532-
const objectPageHeight = objectPageRef.current?.clientHeight ?? 1000;
533-
const marginBottom = objectPageHeight - totalHeaderHeight - /*TabContainer*/ TAB_CONTAINER_HEADER_HEIGHT;
534-
const rootMargin = `-${totalHeaderHeight}px 0px -${marginBottom < 0 ? 0 : marginBottom}px 0px`;
532+
// only the sticky part of the header must be added as margin
533+
const rootMargin = `-${(headerPinned && !headerCollapsed ? totalHeaderHeight : topHeaderHeight) + TAB_CONTAINER_HEADER_HEIGHT}px 0px 0px 0px`;
534+
535535
const observer = new IntersectionObserver(
536-
([section]) => {
537-
if (section.isIntersecting && isProgrammaticallyScrolled.current === false) {
538-
if (
539-
objectPageRef.current.getBoundingClientRect().top + totalHeaderHeight + TAB_CONTAINER_HEADER_HEIGHT <=
540-
section.target.getBoundingClientRect().bottom
541-
) {
542-
const currentId = extractSectionIdFromHtmlId(section.target.id);
543-
setInternalSelectedSectionId(currentId);
544-
const currentIndex = safeGetChildrenArray(children).findIndex((objectPageSection) => {
545-
return isValidElement(objectPageSection) && objectPageSection.props?.id === currentId;
546-
});
547-
debouncedOnSectionChange(scrollEvent.current, currentIndex, currentId, section.target);
536+
(entries) => {
537+
entries.forEach((entry) => {
538+
const sectionId = entry.target.id;
539+
if (entry.isIntersecting) {
540+
visibleSectionIds.current.add(sectionId);
541+
} else {
542+
visibleSectionIds.current.delete(sectionId);
548543
}
549-
}
544+
545+
let currentIndex: undefined | number;
546+
const sortedVisibleSections = Array.from(sectionNodes).filter((section, index) => {
547+
const isVisibleSection = visibleSectionIds.current.has(section.id);
548+
if (currentIndex === undefined && isVisibleSection) {
549+
currentIndex = index;
550+
}
551+
return visibleSectionIds.current.has(section.id);
552+
});
553+
554+
if (sortedVisibleSections.length > 0) {
555+
const section = sortedVisibleSections[0];
556+
const id = sortedVisibleSections[0].id.slice(18);
557+
setInternalSelectedSectionId(id);
558+
debouncedOnSectionChange(scrollEvent.current, currentIndex, id, section);
559+
}
560+
});
550561
},
551562
{
552563
root: objectPageRef.current,
553564
rootMargin,
554565
threshold: [0]
555566
}
556567
);
557-
558568
sectionNodes.forEach((el) => {
559569
observer.observe(el);
560570
});
561571

562572
return () => {
563573
observer.disconnect();
564574
};
565-
}, [children, totalHeaderHeight, setInternalSelectedSectionId, isProgrammaticallyScrolled]);
566-
567-
// Fallback when scrolling faster than the IntersectionObserver can observe (in most cases faster than 60fps)
568-
useEffect(() => {
569-
const sectionNodes = objectPageRef.current?.querySelectorAll('section[data-component-name="ObjectPageSection"]');
570-
if (isAfterScroll) {
571-
let currentSection = sectionNodes[sectionNodes.length - 1];
572-
let currentIndex: number;
573-
for (let i = 0; i <= sectionNodes.length - 1; i++) {
574-
const sectionNode = sectionNodes[i];
575-
if (
576-
objectPageRef.current.getBoundingClientRect().top + totalHeaderHeight + TAB_CONTAINER_HEADER_HEIGHT <=
577-
sectionNode.getBoundingClientRect().bottom
578-
) {
579-
currentSection = sectionNode;
580-
currentIndex = i;
581-
break;
582-
}
583-
}
584-
const currentSectionId = extractSectionIdFromHtmlId(currentSection?.id);
585-
if (currentSectionId !== internalSelectedSectionId) {
586-
setInternalSelectedSectionId(currentSectionId);
587-
debouncedOnSectionChange(
588-
scrollEvent.current,
589-
currentIndex ?? sectionNodes.length - 1,
590-
currentSectionId,
591-
currentSection
592-
);
593-
}
594-
setIsAfterScroll(false);
595-
}
596-
}, [isAfterScroll]);
575+
}, [children, totalHeaderHeight, setInternalSelectedSectionId, headerPinned, debouncedOnSectionChange]);
597576

598577
const titleHeaderNotClickable =
599578
(alwaysShowContentHeader && !headerContentPinnable) ||
@@ -744,9 +723,6 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
744723
if (selectionScrollTimeout.current) {
745724
clearTimeout(selectionScrollTimeout.current);
746725
}
747-
selectionScrollTimeout.current = setTimeout(() => {
748-
setIsAfterScroll(true);
749-
}, 100);
750726
if (!headerPinned || e.target.scrollTop === 0) {
751727
objectPageRef.current?.classList.remove(classNames.headerCollapsed);
752728
}
@@ -907,7 +883,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
907883
</div>
908884
)}
909885
<div data-component-name="ObjectPageContent" className={classNames.content} ref={objectPageContentRef}>
910-
<div style={{ height: headerCollapsed ? `${headerContentHeight}px` : 0 }} aria-hidden />
886+
<div style={{ height: headerCollapsed && !headerPinned ? `${headerContentHeight}px` : 0 }} aria-hidden />
911887
{placeholder ? placeholder : sections}
912888
<div style={{ height: `${sectionSpacer}px` }} aria-hidden />
913889
</div>

0 commit comments

Comments
 (0)