From ecd424e80c27839d1ee2c5150c17d02437041c9a Mon Sep 17 00:00:00 2001 From: Sebastiaan Besselsen Date: Thu, 30 Mar 2023 17:03:25 +0200 Subject: [PATCH 1/4] fix: attempt to fix viewport dimensions calc glitch before first scroll or resize --- src/components.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components.ts b/src/components.ts index 3ed32cc..ee3adf6 100644 --- a/src/components.ts +++ b/src/components.ts @@ -15,6 +15,7 @@ import { useMemo, useRef, } from "react"; +import { useIsomorphicLayoutEffect } from "./util"; import { elementRootOffset, ICssStyleData, @@ -114,6 +115,7 @@ export const Sticky: FC> = memo( }) => { const { baseZIndex } = useContext(StickyConfigContext); const behaviorState = useRef({}); + const didApplyBehaviorRef = useRef(false); const placeholderRef = useRef(); let ref: RefObject; const handle: IStickyHandle = { @@ -143,6 +145,7 @@ export const Sticky: FC> = memo( } placeholder.style.height = wrapper.offsetHeight + "px"; wrapper.style.width = placeholder.offsetWidth + "px"; + didApplyBehaviorRef.current = true; }, }; @@ -153,6 +156,16 @@ export const Sticky: FC> = memo( // We are not running in a scroll container. Just show the content. return createElement(Fragment, {}, children); } + // eslint-disable-next-line react-hooks/rules-of-hooks + useIsomorphicLayoutEffect(() => { + // Set wrapper style in a layout effect for compatibility with SSR. But only if the true behavior hasn't been applied yet. + if (ref.current && !didApplyBehaviorRef.current) { + const element = ref.current; + Object.entries(wrapperStyle).forEach(([k, v]) => { + element.style.setProperty(k, v); + }); + } + }, [didApplyBehaviorRef, ref]); return createElement( Fragment, From f34a618b681fd0da16c613182afb57bbd5c69beb Mon Sep 17 00:00:00 2001 From: Daphne Smit Date: Fri, 31 Mar 2023 18:13:20 +0200 Subject: [PATCH 2/4] repro for broken sdu titan portal --- src/Sticky.stories.tsx | 137 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/src/Sticky.stories.tsx b/src/Sticky.stories.tsx index a8c520c..5159c53 100644 --- a/src/Sticky.stories.tsx +++ b/src/Sticky.stories.tsx @@ -1,5 +1,6 @@ import { action } from "@storybook/addon-actions"; import { Meta, Story } from "@storybook/react"; +import { createPortal } from "react-dom"; import React, { ComponentPropsWithoutRef, CSSProperties, @@ -7,6 +8,7 @@ import React, { forwardRef, PropsWithChildren, useCallback, + useEffect, useRef, useState, } from "react"; @@ -527,3 +529,138 @@ PositionAbsoluteContainer.argTypes = { type: "boolean", }, }; + +function Drawer({ behavior1, onClose, children, right, left }: any) { + return ( +
+
} + > + +

Title

+ +
+
+

{children}

+
    +
  • + Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, + euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras + consequat. +
  • +
  • + Praesent dapibus, neque id cursus faucibus, tortor neque egestas + augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam + dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, + metus. +
  • +
  • + Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec + consectetuer ligula vulputate sem tristique cursus. Nam nulla + quam, gravida non, commodo a, sodales sit amet, nisi. +
  • +
  • + Pellentesque fermentum dolor. Aliquam quam lectus, facilisis + auctor, ultrices ut, elementum vulputate, nunc. +
  • +
+
+ + + ); +} + +interface IPortal { + /** Custom DOM node to render the portal in */ + node?: HTMLDivElement; + /** Set a custom id for the portal node */ + id?: string; + children?: React.ReactNode; +} + +const Portal: React.FC = ({ children }) => { + const [defaultNode, setDefaultNode] = useState(); + const portalId = "portal0"; + + useEffect(() => { + const portalDiv = + typeof window === "undefined" ? undefined : document.createElement("div"); + + if (portalDiv) portalDiv.id = portalId; + + setDefaultNode(portalDiv); + }, [portalId]); + + useEffect(() => { + if (!defaultNode) return; + + document.body.appendChild(defaultNode); + return () => { + /** Query the element to remove, in case it was modified externally. */ + const portal = document.getElementById(portalId); + if (portal) { + document.body.removeChild(portal); + } + }; + }, [defaultNode, portalId]); + + if (!defaultNode) { + return null; + } + + return createPortal(children, defaultNode); +}; + +function InPortal({ id, children }: any) { + const [hasMounted, setHasMounted] = React.useState(false); + React.useEffect(() => { + setHasMounted(true); + }, []); + if (!hasMounted) { + return null; + } + return createPortal(children, document.querySelector(`#${id}`)!); +} +export const MountingAndUnmountingDrawer: Story = ({ + behavior1, +}) => { + const [isOpen, setOpen] = useState(false); + + return ( +
+ +
+ {isOpen && ( + + setOpen(false)} behavior1={behavior1}> + WORKS + + + )} + {isOpen && ( + + setOpen(false)} + behavior1={behavior1} + BROKEN + > + + )} +
+ ); +}; + +MountingAndUnmountingDrawer.argTypes = { + behavior1: behaviorControl("Behavior 1"), +}; From 4ac20b54bfb977a9c2616eabc3926b5366fa5937 Mon Sep 17 00:00:00 2001 From: Daphne Smit Date: Fri, 31 Mar 2023 18:16:27 +0200 Subject: [PATCH 3/4] the real effect --- src/Sticky.stories.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sticky.stories.tsx b/src/Sticky.stories.tsx index 5159c53..ed16f6c 100644 --- a/src/Sticky.stories.tsx +++ b/src/Sticky.stories.tsx @@ -29,6 +29,7 @@ import { useStickyOffsetCalculator, } from "./index"; import { ScrollContext, useScrollElement } from "./scroll"; +import { useIsomorphicLayoutEffect } from "util"; // tslint:disable-next-line:no-object-literal-type-assertion export default { @@ -601,7 +602,7 @@ const Portal: React.FC = ({ children }) => { setDefaultNode(portalDiv); }, [portalId]); - useEffect(() => { + useIsomorphicLayoutEffect(() => { if (!defaultNode) return; document.body.appendChild(defaultNode); From 0655ebff9f59560f6ee8bb153c81fd2ef6a616c9 Mon Sep 17 00:00:00 2001 From: Daphne Smit Date: Mon, 3 Apr 2023 09:26:57 +0200 Subject: [PATCH 4/4] util --- src/Sticky.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sticky.stories.tsx b/src/Sticky.stories.tsx index ed16f6c..5f09740 100644 --- a/src/Sticky.stories.tsx +++ b/src/Sticky.stories.tsx @@ -29,7 +29,7 @@ import { useStickyOffsetCalculator, } from "./index"; import { ScrollContext, useScrollElement } from "./scroll"; -import { useIsomorphicLayoutEffect } from "util"; +import { useIsomorphicLayoutEffect } from "./util"; // tslint:disable-next-line:no-object-literal-type-assertion export default {