Skip to content

Commit 8d14571

Browse files
authored
fix(ObjectPage): scroll to section when programmatically selected (#7002)
Fixes #6765
1 parent d7b03b7 commit 8d14571

File tree

2 files changed

+124
-38
lines changed

2 files changed

+124
-38
lines changed

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

+81-4
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,75 @@ describe('ObjectPage', () => {
905905
cy.get('@cb').should('not.been.called');
906906
});
907907

908+
it('programmatic prop selection', () => {
909+
const TestComp = (props: ObjectPagePropTypes) => {
910+
const [selectedSection, setSelectedSection] = useState(props.selectedSectionId);
911+
const [selectedSubSection, setSelectedSubSection] = useState(props.selectedSubSectionId);
912+
913+
return (
914+
<>
915+
<button
916+
onClick={() => {
917+
setSelectedSection('goals');
918+
}}
919+
>
920+
Select Goals
921+
</button>
922+
<button
923+
onClick={() => {
924+
setSelectedSubSection('personal-payment-information');
925+
}}
926+
>
927+
Select Payment Information
928+
</button>
929+
<ObjectPage
930+
{...props}
931+
selectedSubSectionId={selectedSubSection}
932+
selectedSectionId={selectedSection}
933+
style={{ height: '1000px', scrollBehavior: 'auto' }}
934+
>
935+
{[
936+
...OPContent.slice(0, 1),
937+
<ObjectPageSection key="0.5" titleText="Test2" id="test2" aria-label="Test2">
938+
<div data-testid="section 1.5" style={{ height: '1200px', width: '100%', background: 'lightyellow' }}>
939+
<span data-testid="test-content">test-content</span>
940+
</div>
941+
</ObjectPageSection>,
942+
...OPContent.slice(1)
943+
]}
944+
</ObjectPage>
945+
</>
946+
);
947+
};
948+
949+
[
950+
{ headerTitle: DPTitle, headerContent: DPContent },
951+
{ headerTitle: DPTitle },
952+
{ headerContent: DPContent },
953+
{}
954+
].forEach((props: ObjectPagePropTypes) => {
955+
cy.mount(<TestComp {...props} selectedSubSectionId={`employment-job-relationship`} />);
956+
cy.findByText('employment-job-relationship-content').should('be.visible');
957+
cy.findByText('Job Information').should('not.be.visible');
958+
cy.get('[ui5-tabcontainer]').findUi5TabByText('Employment').should('have.attr', 'aria-selected', 'true');
959+
960+
cy.mount(<TestComp {...props} selectedSectionId={`personal`} />);
961+
cy.findByText('personal-connect-content').should('be.visible');
962+
cy.findByText('test-content').should('not.be.visible');
963+
cy.get('[ui5-tabcontainer]').findUi5TabByText('Personal').should('have.attr', 'aria-selected', 'true');
964+
965+
cy.findByText('Select Goals').click();
966+
cy.findByText('goals-content').should('be.visible');
967+
cy.findByText('personal-connect-content').should('not.be.visible');
968+
cy.get('[ui5-tabcontainer]').findUi5TabByText('Goals').should('have.attr', 'aria-selected', 'true');
969+
970+
cy.findByText('Select Payment Information').click();
971+
cy.findByText('personal-payment-information-content').should('be.visible');
972+
cy.findByText('personal-connect-content').should('not.be.visible');
973+
cy.get('[ui5-tabcontainer]').findUi5TabByText('Personal').should('have.attr', 'aria-selected', 'true');
974+
});
975+
});
976+
908977
cypressPassThroughTestsFactory(ObjectPage);
909978
});
910979

@@ -952,7 +1021,9 @@ const DPContent = (
9521021

9531022
const OPContent = [
9541023
<ObjectPageSection key="0" titleText="Goals" id="goals" aria-label="Goals">
955-
<div data-testid="section 1" style={{ height: '400px', width: '100%', background: 'lightblue' }} />
1024+
<div data-testid="section 1" style={{ height: '400px', width: '100%', background: 'lightblue' }}>
1025+
<span>goals-content</span>
1026+
</div>
9561027
</ObjectPageSection>,
9571028
<ObjectPageSection key="1" titleText="Test" id="test" aria-label="Test">
9581029
<div data-testid="section 2" style={{ height: '1200px', width: '100%', background: 'lightyellow' }}></div>
@@ -972,14 +1043,18 @@ const OPContent = [
9721043
</>
9731044
}
9741045
>
975-
<div style={{ height: '400px', width: '100%', background: 'black' }} />
1046+
<div style={{ height: '400px', width: '100%', background: 'black' }}>
1047+
<span>personal-connect-content</span>
1048+
</div>
9761049
</ObjectPageSubSection>
9771050
<ObjectPageSubSection
9781051
titleText="Payment Information"
9791052
id="personal-payment-information"
9801053
aria-label="Payment Information"
9811054
>
982-
<div style={{ height: '400px', width: '100%', background: 'blue' }} />
1055+
<div style={{ height: '400px', width: '100%', background: 'blue' }}>
1056+
<span>personal-payment-information-content</span>
1057+
</div>
9831058
</ObjectPageSubSection>
9841059
</ObjectPageSection>,
9851060
<ObjectPageSection key="3" titleText="Employment" id={`~\`!1@#$%^&*()-_+={}[]:;"'z,<.>/?|♥`} aria-label="Employment">
@@ -990,7 +1065,9 @@ const OPContent = [
9901065
<div style={{ height: '100px', width: '100%', background: 'cadetblue' }}></div>
9911066
</ObjectPageSubSection>
9921067
<ObjectPageSubSection titleText="Job Relationship" id="employment-job-relationship" aria-label="Job Relationship">
993-
<div style={{ height: '100px', width: '100%', background: 'lightgrey' }}></div>
1068+
<div style={{ height: '100px', width: '100%', background: 'lightgrey' }}>
1069+
<span>employment-job-relationship-content</span>
1070+
</div>
9941071
</ObjectPageSubSection>
9951072
</ObjectPageSection>
9961073
];

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

+43-34
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
224224
const [sectionSpacer, setSectionSpacer] = useState(0);
225225
const [currentTabModeSection, setCurrentTabModeSection] = useState(null);
226226
const sections = mode === ObjectPageMode.IconTabBar ? currentTabModeSection : children;
227+
const isScrolling = useRef<ReturnType<typeof setTimeout> | null>(null);
227228

228229
const deprecationNoticeDisplayed = useRef(false);
229230
useEffect(() => {
@@ -336,7 +337,6 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
336337
} else {
337338
scrollToSectionById(sectionId);
338339
}
339-
isProgrammaticallyScrolled.current = false;
340340
};
341341

342342
const programmaticallySetSection = () => {
@@ -388,6 +388,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
388388
}
389389
return newSelectionSectionId;
390390
});
391+
prevSelectedSectionId.current = newSelectionSectionId;
391392
scrollEvent.current = targetEvent;
392393
fireOnSelectedChangedEvent(targetEvent, index, newSelectionSectionId, section);
393394
};
@@ -448,6 +449,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
448449
});
449450
if (sectionId) {
450451
setInternalSelectedSectionId(sectionId);
452+
prevSelectedSectionId.current = sectionId;
451453
}
452454
}
453455
}
@@ -504,6 +506,7 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
504506
if (mode === ObjectPageMode.IconTabBar) {
505507
const sectionId = e.detail.sectionId;
506508
setInternalSelectedSectionId(sectionId);
509+
prevSelectedSectionId.current = sectionId;
507510
const sectionNodes = objectPageRef.current?.querySelectorAll(
508511
'section[data-component-name="ObjectPageSection"]'
509512
);
@@ -552,11 +555,11 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
552555
}
553556
return visibleSectionIds.current.has(section.id);
554557
});
555-
556-
if (sortedVisibleSections.length > 0) {
558+
if (sortedVisibleSections.length > 0 && !isProgrammaticallyScrolled.current) {
557559
const section = sortedVisibleSections[0];
558560
const id = sortedVisibleSections[0].id.slice(18);
559561
setInternalSelectedSectionId(id);
562+
prevSelectedSectionId.current = id;
560563
debouncedOnSectionChange(scrollEvent.current, currentIndex, id, section);
561564
}
562565
});
@@ -707,40 +710,46 @@ const ObjectPage = forwardRef<HTMLDivElement, ObjectPagePropTypes>((props, ref)
707710
};
708711

709712
const prevScrollTop = useRef();
710-
const onObjectPageScroll = useCallback(
711-
(e) => {
712-
if (!isToggledRef.current) {
713-
isToggledRef.current = true;
714-
}
715-
if (scrollTimeout.current >= performance.now()) {
713+
const onObjectPageScroll = (e) => {
714+
if (!isToggledRef.current) {
715+
isToggledRef.current = true;
716+
}
717+
if (isScrolling.current) {
718+
clearTimeout(isScrolling.current);
719+
}
720+
721+
isScrolling.current = setTimeout(() => {
722+
console.log('end scroll');
723+
isProgrammaticallyScrolled.current = false;
724+
}, 300);
725+
726+
if (scrollTimeout.current >= performance.now()) {
727+
return;
728+
}
729+
scrollEvent.current = e;
730+
if (typeof props.onScroll === 'function') {
731+
props.onScroll(e);
732+
}
733+
if (selectedSubSectionId) {
734+
setSelectedSubSectionId(undefined);
735+
}
736+
if (selectionScrollTimeout.current) {
737+
clearTimeout(selectionScrollTimeout.current);
738+
}
739+
if (!headerPinned || e.target.scrollTop === 0) {
740+
objectPageRef.current?.classList.remove(classNames.headerCollapsed);
741+
}
742+
if (scrolledHeaderExpanded && e.target.scrollTop !== prevScrollTop.current) {
743+
if (e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight) {
716744
return;
717745
}
718-
scrollEvent.current = e;
719-
if (typeof props.onScroll === 'function') {
720-
props.onScroll(e);
721-
}
722-
if (selectedSubSectionId) {
723-
setSelectedSubSectionId(undefined);
746+
prevScrollTop.current = e.target.scrollTop;
747+
if (!headerPinned) {
748+
setHeaderCollapsedInternal(true);
724749
}
725-
if (selectionScrollTimeout.current) {
726-
clearTimeout(selectionScrollTimeout.current);
727-
}
728-
if (!headerPinned || e.target.scrollTop === 0) {
729-
objectPageRef.current?.classList.remove(classNames.headerCollapsed);
730-
}
731-
if (scrolledHeaderExpanded && e.target.scrollTop !== prevScrollTop.current) {
732-
if (e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight) {
733-
return;
734-
}
735-
prevScrollTop.current = e.target.scrollTop;
736-
if (!headerPinned) {
737-
setHeaderCollapsedInternal(true);
738-
}
739-
setScrolledHeaderExpanded(false);
740-
}
741-
},
742-
[topHeaderHeight, headerPinned, props.onScroll, scrolledHeaderExpanded, selectedSubSectionId]
743-
);
750+
setScrolledHeaderExpanded(false);
751+
}
752+
};
744753

745754
const onHoverToggleButton = useCallback(
746755
(e) => {

0 commit comments

Comments
 (0)