diff --git a/cypress.config.ts b/cypress.config.ts index 269725cf424..57bd1c50878 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'cypress'; import codeCoverageTask from '@cypress/code-coverage/task'; +import { defineConfig } from 'cypress'; export default defineConfig({ component: { @@ -16,5 +16,6 @@ export default defineConfig({ viewportWidth: 1920, viewportHeight: 1080, video: false, - screenshotOnRunFailure: false + screenshotOnRunFailure: false, + scrollBehavior: false }); diff --git a/package.json b/package.json index 297e7c04f14..126cb85ea9e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "Apache-2.0", "scripts": { "start": "lerna run build:i18n && start-storybook -p 6006", - "build:prepare": "lerna run build:i18n && node scripts/build-wrappers.js", + "build:prepare": "lerna run build:i18n && node scripts/build-wrappers.js && rimraf node_modules/@types/mocha", "build:cleanup": "rimraf packages/main/tmp", "build": "yarn build:prepare && tsc --build && yarn build:cleanup", "build:storybook": "lerna run build:i18n && build-storybook -o .out", diff --git a/packages/main/src/components/DynamicPage/DynamicPage.cy.tsx b/packages/main/src/components/DynamicPage/DynamicPage.cy.tsx index f44e95705bb..116371964d7 100644 --- a/packages/main/src/components/DynamicPage/DynamicPage.cy.tsx +++ b/packages/main/src/components/DynamicPage/DynamicPage.cy.tsx @@ -1,4 +1,5 @@ -import { DynamicPage, DynamicPageHeader, DynamicPageTitle } from '../..'; +import { useState } from 'react'; +import { Button, DynamicPage, DynamicPageHeader, DynamicPagePropTypes, DynamicPageTitle } from '../..'; describe('DynamicPage', () => { it('toggle header', () => { @@ -35,4 +36,164 @@ describe('DynamicPage', () => { cy.get('@toggleSpy').should('have.been.calledWith', true); cy.get('@toggleSpy').should('have.callCount', 4); }); + + it('pin header', () => { + const pin = cy.spy().as('onPinSpy'); + cy.mount( + } + headerContent={DynamicPageHeader} + headerContentPinnable + showHideHeaderButton + onPinnedStateChange={pin} + data-testid="op" + > +
+ + ); + cy.wait(50); + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + + cy.findByTestId('op').scrollTo('top'); + + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click(); + cy.get('@onPinSpy').should('have.been.calledOnce'); + cy.get('@onPinSpy').should('have.been.calledWith', true); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click(); + cy.get('@onPinSpy').should('have.been.calledTwice'); + cy.get('@onPinSpy').should('have.been.calledWith', false); + cy.findByTestId('op').scrollTo(0, 501); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + }); + + it('programmatically pin header (`alwaysShowContentHeader`)', () => { + const TestComp = ({ onPinnedStateChange }: DynamicPagePropTypes) => { + const [pinned, setPinned] = useState(false); + const handlePinChange = (pinned) => { + onPinnedStateChange(pinned); + setPinned(pinned); + }; + return ( + <> + + } + headerContent={DynamicPageHeader} + headerContentPinnable + showHideHeaderButton + alwaysShowContentHeader={pinned} + onPinnedStateChange={handlePinChange} + data-testid="op" + > +
+ + + ); + }; + const pin = cy.spy().as('onPinSpy'); + cy.mount(); + cy.wait(50); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + + cy.findByTestId('btn').click(); + cy.get('@onPinSpy').should('have.been.calledOnce'); + cy.get('@onPinSpy').should('have.been.calledWith', true); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 0); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 800); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.findByTestId('btn').click(); + cy.get('@onPinSpy').should('have.been.calledTwice'); + cy.get('@onPinSpy').should('have.been.calledWith', false); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 801); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + + cy.findByTestId('btn').click(); + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.findByTestId('btn').click(); + cy.findByTestId('op').scrollTo(0, 501); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + + cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click(); + cy.findByText('DynamicPageHeader').should('be.visible'); + + // wait for timeout of expand click + cy.wait(500); + + cy.findByTestId('op').scrollTo(0, 502); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + + cy.findByTestId('op').scrollTo(0, 30); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click(); + cy.get('@onPinSpy').should('have.callCount', 5); + cy.findByTestId('btn').should('have.text', 'toggle false'); + cy.findByText('DynamicPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByTestId('btn').click(); + cy.findByTestId('op').scrollTo(0, 501); + cy.findByText('DynamicPageHeader').should('not.be.visible'); + + cy.findByTestId('btn').click(); + cy.findByText('DynamicPageHeader').should('be.visible'); + cy.get('@onPinSpy').should('have.callCount', 7); + }); + + it('collapse header when partially visible', () => { + cy.viewport(1440, 1080); + cy.mount( + } + headerContent={ + +
DynamicPageHeader
+
+ } + headerContentPinnable + showHideHeaderButton + data-testid="op" + > +
+ + ); + cy.wait(50); + + cy.findByTestId('op').scrollTo(0, 400); + cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click(); + // wait for timeout of expand click + cy.wait(500); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist'); + + cy.findByTestId('op').scrollTo(0, 1); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist'); + cy.wait(50); + cy.findByTestId('op').scrollTo(0, 0); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('be.visible'); + cy.findByText('DynamicPageHeader').should('be.visible'); + }); }); diff --git a/packages/main/src/components/DynamicPage/__snapshots__/DynamicPage.test.tsx.snap b/packages/main/src/components/DynamicPage/__snapshots__/DynamicPage.test.tsx.snap index 547723e533b..400b10a243a 100644 --- a/packages/main/src/components/DynamicPage/__snapshots__/DynamicPage.test.tsx.snap +++ b/packages/main/src/components/DynamicPage/__snapshots__/DynamicPage.test.tsx.snap @@ -264,6 +264,7 @@ exports[`DynamicPage always show content header 1`] = ` { * Fired when the `headerContent` is expanded or collapsed. */ onToggleHeaderContent?: (visible: boolean) => void; + /** + * Fired when the `headerContent` changes its `pinned` state. + */ + onPinnedStateChange?: (pinned: boolean) => void; } /** @@ -107,6 +111,7 @@ const DynamicPage = forwardRef((props, ref footer, a11yConfig, onToggleHeaderContent, + onPinnedStateChange, ...rest } = props; const { onScroll: _1, ...propsWithoutOmitted } = rest; @@ -117,6 +122,7 @@ const DynamicPage = forwardRef((props, ref const [componentRefTopHeader, topHeaderRef] = useSyncRef((headerTitle as any)?.ref); const [componentRefHeaderContent, headerContentRef] = useSyncRef((headerContent as any)?.ref); + const scrollTimeout = useRef(0); const [headerState, setHeaderState] = useState( alwaysShowContentHeader ? HEADER_STATES.VISIBLE_PINNED : HEADER_STATES.AUTO @@ -134,7 +140,8 @@ const DynamicPage = forwardRef((props, ref [headerCollapsedInternal, setHeaderCollapsedInternal], { noHeader: false, - fixedHeader: headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.HIDDEN_PINNED + fixedHeader: headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.HIDDEN_PINNED, + scrollTimeout } ); @@ -168,19 +175,21 @@ const DynamicPage = forwardRef((props, ref }, []); useEffect(() => { + const dynamicPage = dynamicPageRef.current; const oneTimeScrollHandler = () => { setHeaderState(HEADER_STATES.AUTO); setHeaderCollapsedInternal(true); }; if (headerState === HEADER_STATES.VISIBLE || headerState === HEADER_STATES.HIDDEN) { - dynamicPageRef.current?.addEventListener('scroll', oneTimeScrollHandler, { once: true }); + dynamicPage?.addEventListener('scroll', oneTimeScrollHandler, { once: true }); } return () => { - dynamicPageRef.current?.removeEventListener('scroll', oneTimeScrollHandler); + dynamicPage?.removeEventListener('scroll', oneTimeScrollHandler); }; }, [dynamicPageRef, headerState]); const onToggleHeaderContentVisibility = (e) => { + scrollTimeout.current = performance.now() + 500; const shouldHideHeader = !e.detail.visible; setHeaderState((oldState) => { if (oldState === HEADER_STATES.VISIBLE_PINNED || oldState === HEADER_STATES.HIDDEN_PINNED) { @@ -222,10 +231,14 @@ const DynamicPage = forwardRef((props, ref }; useEffect(() => { - if (alwaysShowContentHeader) { - setHeaderState(HEADER_STATES.VISIBLE_PINNED); + if (alwaysShowContentHeader !== undefined) { + if (alwaysShowContentHeader) { + setHeaderState(HEADER_STATES.VISIBLE_PINNED); + } else { + setHeaderState(HEADER_STATES.VISIBLE); + } } - }, [alwaysShowContentHeader, setHeaderState]); + }, [alwaysShowContentHeader]); const responsivePaddingClass = useResponsiveContentPadding(dynamicPageRef.current); @@ -297,11 +310,12 @@ const DynamicPage = forwardRef((props, ref headerContentPinnable={headerContentPinnable} showHideHeaderButton={showHideHeaderButton} headerContentVisible={headerContent && headerCollapsed !== true} - onToggleHeaderContentVisibility={onToggleHeaderContentInternal} - setHeaderPinned={handleHeaderPinnedChange} headerPinned={headerState === HEADER_STATES.VISIBLE_PINNED || headerState === HEADER_STATES.HIDDEN_PINNED} - onHoverToggleButton={onHoverToggleButton} a11yConfig={a11yConfig} + onHoverToggleButton={onHoverToggleButton} + onToggleHeaderContentVisibility={onToggleHeaderContentInternal} + onPinnedStateChange={onPinnedStateChange} + setHeaderPinned={handleHeaderPinnedChange} />
void; } /** @@ -118,11 +122,12 @@ const DynamicPageAnchorBar = forwardRef { + if (!isInitial.current && typeof onPinnedStateChange === 'function') { + onPinnedStateChange(headerPinned); + } + if (isInitial.current) { + isInitial.current = false; + } + }, [headerPinned]); + const anchorBarActionButtonClasses = clsx(classes.anchorBarActionButton, isRTL && classes.anchorBarActionButtonRtl); const bothActionClasses = clsx( @@ -172,6 +187,7 @@ const DynamicPageAnchorBar = forwardRef )} {shouldRenderHeaderPinnableButton && ( @@ -187,6 +203,7 @@ const DynamicPageAnchorBar = forwardRef )} diff --git a/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx b/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx index 86a972c926e..3b5299309f7 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx +++ b/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx @@ -1,4 +1,5 @@ -import { ObjectPage, DynamicPageHeader, DynamicPageTitle, ObjectPageSection } from '../..'; +import { useState } from 'react'; +import { Button, DynamicPageHeader, DynamicPageTitle, ObjectPage, ObjectPagePropTypes, ObjectPageSection } from '../..'; describe('ObjectPage', () => { it('toggle header', () => { @@ -38,4 +39,167 @@ describe('ObjectPage', () => { cy.get('@toggleSpy').should('have.been.calledWith', true); cy.get('@toggleSpy').should('have.callCount', 4); }); + + it('pin header', () => { + const pin = cy.spy().as('onPinSpy'); + cy.mount( + } + headerContent={ObjectPageHeader} + headerContentPinnable + showHideHeaderButton + onPinnedStateChange={pin} + data-testid="op" + > + +
+ + + ); + cy.wait(50); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + cy.findByTestId('op').scrollTo('top'); + + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click(); + cy.get('@onPinSpy').should('have.been.calledOnce'); + cy.get('@onPinSpy').should('have.been.calledWith', true); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('ObjectPageHeader').should('be.visible'); + + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click(); + cy.get('@onPinSpy').should('have.been.calledTwice'); + cy.get('@onPinSpy').should('have.been.calledWith', false); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + }); + + it('programmatically pin header (`alwaysShowContentHeader`)', () => { + const TestComp = ({ onPinnedStateChange }: ObjectPagePropTypes) => { + const [pinned, setPinned] = useState(false); + const handlePinChange = (pinned) => { + onPinnedStateChange(pinned); + setPinned(pinned); + }; + return ( + <> + + } + headerContent={ObjectPageHeader} + headerContentPinnable + showHideHeaderButton + alwaysShowContentHeader={pinned} + onPinnedStateChange={handlePinChange} + data-testid="op" + > + +
+ + + + ); + }; + const pin = cy.spy().as('onPinSpy'); + cy.mount(); + cy.wait(50); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + cy.findByTestId('btn').click(); + cy.get('@onPinSpy').should('have.been.calledOnce'); + cy.get('@onPinSpy').should('have.been.calledWith', true); + cy.findByText('ObjectPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 0); + cy.findByText('ObjectPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 800); + cy.findByText('ObjectPageHeader').should('be.visible'); + + cy.findByTestId('btn').click(); + cy.get('@onPinSpy').should('have.been.calledTwice'); + cy.get('@onPinSpy').should('have.been.calledWith', false); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + cy.findByTestId('btn').click(); + cy.findByTestId('op').scrollTo(0, 500); + cy.findByText('ObjectPageHeader').should('be.visible'); + + cy.findByTestId('btn').click(); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click(); + cy.findByText('ObjectPageHeader').should('be.visible'); + + // wait for timeout of expand click + cy.wait(500); + + cy.findByTestId('op').scrollTo(0, 501); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + cy.findByTestId('op').scrollTo(0, 30); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').click(); + cy.get('@onPinSpy').should('have.callCount', 5); + cy.findByTestId('btn').should('have.text', 'toggle false'); + cy.findByText('ObjectPageHeader').should('be.visible'); + + cy.findByTestId('op').scrollTo(0, 500); + cy.findByTestId('btn').click(); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + cy.findByTestId('btn').click(); + cy.findByText('ObjectPageHeader').should('be.visible'); + cy.get('@onPinSpy').should('have.callCount', 7); + }); + + it('collapse header when partially visible', () => { + cy.viewport(1440, 1080); + cy.mount( + } + headerContent={ + +
ObjectPageHeader
+
+ } + headerContentPinnable + showHideHeaderButton + data-testid="op" + > + +
+ + + ); + cy.wait(50); + + cy.findByTestId('op').scrollTo(0, 400); + cy.get('[data-component-name="DynamicPageAnchorBarExpandBtn"]').click(); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist'); + cy.findByText('ObjectPageHeader').should('not.be.visible'); + + // wait for timeout of expand click + cy.wait(500); + + cy.findByTestId('op').scrollTo(0, 1); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('not.exist'); + cy.wait(50); + cy.findByTestId('op').scrollTo(0, 0); + cy.get('[data-component-name="DynamicPageAnchorBarPinBtn"]').should('be.visible'); + cy.findByText('ObjectPageHeader').should('be.visible'); + }); }); diff --git a/packages/main/src/components/ObjectPage/__snapshots__/ObjectPage.test.tsx.snap b/packages/main/src/components/ObjectPage/__snapshots__/ObjectPage.test.tsx.snap index 5e9e084eaca..91bfd00e78b 100644 --- a/packages/main/src/components/ObjectPage/__snapshots__/ObjectPage.test.tsx.snap +++ b/packages/main/src/components/ObjectPage/__snapshots__/ObjectPage.test.tsx.snap @@ -1213,6 +1213,7 @@ exports[`ObjectPage with anchor-bar 1`] = ` { * Defines the ID of the currently `ObjectPageSubSection` section. */ selectedSubSectionId?: string; - /** - * Fired when the selected section changes. - */ - onSelectedSectionChange?: ( - event: CustomEvent<{ selectedSectionIndex: number; selectedSectionId: string; section: HTMLDivElement }> - ) => void; - /** - * Fired when the `headerContent` is expanded or collapsed. - */ - onToggleHeaderContent?: (visible: boolean) => void; - - // appearance /** * Defines whether the `headerContent` is hidden by scrolling down. */ @@ -131,6 +119,20 @@ export interface ObjectPagePropTypes extends Omit { * __Note:__ Although this prop accepts all HTML Elements, it is strongly recommended that you only use placeholder components like the `IllustratedMessage` or custom skeletons pages in order to preserve the intended design. */ placeholder?: ReactNode; + /** + * Fired when the selected section changes. + */ + onSelectedSectionChange?: ( + event: CustomEvent<{ selectedSectionIndex: number; selectedSectionId: string; section: HTMLDivElement }> + ) => void; + /** + * Fired when the `headerContent` is expanded or collapsed. + */ + onToggleHeaderContent?: (visible: boolean) => void; + /** + * Fired when the `headerContent` changes its pinned state. + */ + onPinnedStateChange?: (pinned: boolean) => void; } const useStyles = createUseStyles(styles, { name: 'ObjectPage' }); @@ -161,6 +163,7 @@ const ObjectPage = forwardRef((props, ref) placeholder, onSelectedSectionChange, onToggleHeaderContent, + onPinnedStateChange, ...rest } = props; @@ -180,9 +183,14 @@ const ObjectPage = forwardRef((props, ref) //@ts-ignore const [componentRefHeaderContent, headerContentRef] = useSyncRef(headerContent?.ref); const anchorBarRef = useRef(null); - const scrollTimeout = useRef(null); + const selectionScrollTimeout = useRef(null); const [isAfterScroll, setIsAfterScroll] = useState(false); const isToggledRef = useRef(false); + const isRTL = useIsRTL(objectPageRef); + const [responsivePaddingClass, responsiveRange] = useResponsiveContentPadding(objectPageRef.current, true); + const [headerCollapsedInternal, setHeaderCollapsedInternal] = useState(undefined); + const [scrolledHeaderExpanded, setScrolledHeaderExpanded] = useState(false); + const scrollTimeout = useRef(0); const prevInternalSelectedSectionId = useRef(internalSelectedSectionId); const fireOnSelectedChangedEvent = (targetEvent, index, id, section) => { @@ -198,18 +206,13 @@ const ObjectPage = forwardRef((props, ref) } }; const debouncedOnSectionChange = useRef(debounce(fireOnSelectedChangedEvent, 500)).current; - useEffect(() => { return () => { debouncedOnSectionChange.cancel(); - clearTimeout(scrollTimeout.current); + clearTimeout(selectionScrollTimeout.current); }; }, []); - const isRTL = useIsRTL(objectPageRef); - const [responsivePaddingClass, responsiveRange] = useResponsiveContentPadding(objectPageRef.current, true); - - const [headerCollapsedInternal, setHeaderCollapsedInternal] = useState(undefined); // observe heights of header parts const { topHeaderHeight, headerContentHeight, anchorBarHeight, totalHeaderHeight, headerCollapsed } = useObserveHeights( @@ -220,7 +223,8 @@ const ObjectPage = forwardRef((props, ref) [headerCollapsedInternal, setHeaderCollapsedInternal], { noHeader: !headerTitle && !headerContent, - fixedHeader: headerPinned + fixedHeader: headerPinned, + scrollTimeout } ); @@ -387,8 +391,24 @@ const ObjectPage = forwardRef((props, ref) ]); useEffect(() => { - setHeaderPinned(alwaysShowContentHeader); - }, [setHeaderPinned, alwaysShowContentHeader]); + if (alwaysShowContentHeader !== undefined) { + setHeaderPinned(alwaysShowContentHeader); + } + if (alwaysShowContentHeader) { + onToggleHeaderContentVisibility({ detail: { visible: true } }); + } + }, [alwaysShowContentHeader]); + + const prevHeaderPinned = useRef(headerPinned); + useEffect(() => { + if (prevHeaderPinned.current && !headerPinned && objectPageRef.current.scrollTop > topHeaderHeight) { + onToggleHeaderContentVisibility({ detail: { visible: false } }); + prevHeaderPinned.current = false; + } + if (!prevHeaderPinned.current && headerPinned) { + prevHeaderPinned.current = true; + } + }, [headerPinned, topHeaderHeight]); useEffect(() => { setSelectedSubSectionId(props.selectedSubSectionId); @@ -453,6 +473,19 @@ const ObjectPage = forwardRef((props, ref) }; }, [totalHeaderHeight, objectPageRef, children, mode, footer]); + const onToggleHeaderContentVisibility = useCallback((e) => { + isToggledRef.current = true; + scrollTimeout.current = performance.now() + 500; + if (!e.detail.visible) { + setHeaderCollapsedInternal(true); + objectPageRef.current?.classList.add(classes.headerCollapsed); + } else { + setHeaderCollapsedInternal(false); + setScrolledHeaderExpanded(true); + objectPageRef.current?.classList.remove(classes.headerCollapsed); + } + }, []); + const handleOnSubSectionSelected = useCallback( (e) => { isProgrammaticallyScrolled.current = true; @@ -471,20 +504,6 @@ const ObjectPage = forwardRef((props, ref) }, [mode, setInternalSelectedSectionId, setSelectedSubSectionId, isProgrammaticallyScrolled, children] ); - const [scrolledHeaderExpanded, setScrolledHeaderExpanded] = useState(false); - const scrollTimout = useRef(0); - const onToggleHeaderContentVisibility = useCallback((e) => { - isToggledRef.current = true; - scrollTimout.current = performance.now() + 500; - if (!e.detail.visible) { - setHeaderCollapsedInternal(true); - objectPageRef.current?.classList.add(classes.headerCollapsed); - } else { - setHeaderCollapsedInternal(false); - setScrolledHeaderExpanded(true); - objectPageRef.current?.classList.remove(classes.headerCollapsed); - } - }, []); const objectPageClasses = clsx( classes.objectPage, @@ -659,7 +678,7 @@ const ObjectPage = forwardRef((props, ref) if (!isToggledRef.current) { isToggledRef.current = true; } - if (scrollTimout.current >= performance.now()) { + if (scrollTimeout.current >= performance.now()) { return; } scrollEvent.current = e; @@ -669,10 +688,10 @@ const ObjectPage = forwardRef((props, ref) if (selectedSubSectionId) { setSelectedSubSectionId(undefined); } - if (scrollTimeout.current) { - clearTimeout(scrollTimeout.current); + if (selectionScrollTimeout.current) { + clearTimeout(selectionScrollTimeout.current); } - scrollTimeout.current = setTimeout(() => { + selectionScrollTimeout.current = setTimeout(() => { setIsAfterScroll(true); }, 100); if (!headerPinned || e.target.scrollTop === 0) { @@ -757,11 +776,12 @@ const ObjectPage = forwardRef((props, ref) headerContentVisible={headerContent && headerCollapsed !== true} headerContentPinnable={headerContentPinnable} showHideHeaderButton={showHideHeaderButton} + headerPinned={headerPinned} + a11yConfig={a11yConfig} onToggleHeaderContentVisibility={onToggleHeaderContentVisibility} setHeaderPinned={setHeaderPinned} - headerPinned={headerPinned} onHoverToggleButton={onHoverToggleButton} - a11yConfig={a11yConfig} + onPinnedStateChange={onPinnedStateChange} />
)} diff --git a/packages/main/src/internal/useObserveHeights.ts b/packages/main/src/internal/useObserveHeights.ts index 4f1410b7c0b..8d5f084c89b 100644 --- a/packages/main/src/internal/useObserveHeights.ts +++ b/packages/main/src/internal/useObserveHeights.ts @@ -8,7 +8,11 @@ export const useObserveHeights = ( headerContentRef, anchorBarRef, [headerCollapsed, setHeaderCollapsed]: [boolean, React.Dispatch>], - { noHeader, fixedHeader = false }: { noHeader: boolean; fixedHeader?: boolean } + { + noHeader, + fixedHeader = false, + scrollTimeout = { current: 0 } + }: { noHeader: boolean; fixedHeader?: boolean; scrollTimeout?: React.MutableRefObject } ) => { const [topHeaderHeight, setTopHeaderHeight] = useState(0); const [headerContentHeight, setHeaderContentHeight] = useState(0); @@ -19,6 +23,11 @@ export const useObserveHeights = ( (e) => { const scrollDown = prevScrollTop.current <= e.target.scrollTop; prevScrollTop.current = e.target.scrollTop; + + if (scrollTimeout.current >= performance.now()) { + return; + } + if (scrollDown && e.target.scrollTop >= headerContentHeight && !headerCollapsed) { setIsIntersecting(false); setHeaderCollapsed(true); @@ -31,11 +40,18 @@ export const useObserveHeights = ( ); useEffect(() => { + if (headerContentRef.current && headerCollapsed !== undefined) { + setHeaderContentHeight(headerContentRef.current.getBoundingClientRect().height); + } + }, [headerCollapsed]); + + useEffect(() => { + const page = pageRef.current; if (!fixedHeader) { - pageRef.current.addEventListener('scroll', onScroll); + page.addEventListener('scroll', onScroll); } return () => { - pageRef.current?.removeEventListener('scroll', onScroll); + page.removeEventListener('scroll', onScroll); }; }, [onScroll, fixedHeader]); diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 32394567d51..dc58d15912f 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -6,5 +6,5 @@ "moduleResolution": "Node", "jsx": "react-jsx" }, - "include": ["cypress/**/*.ts", "cypress/**/*.tsx", "**/*.cy.ts", "**/*.cy.tsx"] + "include": ["cypress/**/*.ts", "cypress/**/*.tsx", "**/*.cy.ts", "**/*.cy.tsx", "cypress.config.ts"] }