UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

202 lines (201 loc) 5.7 kB
"use client"; import { useCallback, useMemo, useRef, useState } from 'react'; import HeightAnimationInstance from "./HeightAnimationInstance.js"; import { useIsomorphicLayoutEffect as useLayoutEffect } from "../../shared/helpers/useIsomorphicLayoutEffect.js"; export function useHeightAnimation(targetRef, { open = true, animate = true, children = null, compensateForGap, onInit = null, onOpen = null, onAnimationStart = null, onAnimationEnd = null } = {}) { const instRef = useRef(null); const isInitialRenderRef = useRef(typeof globalThis !== 'undefined' ? globalThis.readjustTime !== -1 : true); const [isOpen, setIsOpen] = useState(open); const [isVisible, setIsVisible] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const [isVisibleParallax, setParallax] = useState(open); const eventsRef = useRef({ onInit, onAnimationEnd, onAnimationStart, onOpen }); eventsRef.current.onInit = onInit; eventsRef.current.onAnimationEnd = onAnimationEnd; eventsRef.current.onAnimationStart = onAnimationStart; eventsRef.current.onOpen = onOpen; useLayoutEffect(() => { instRef.current = new HeightAnimationInstance(); eventsRef.current.onInit?.(instRef.current); return () => { instRef.current?.remove(); instRef.current = null; }; }, []); useLayoutEffect(() => { instRef.current.setOptions({ animate }); if (typeof global !== 'undefined' && globalThis.IS_TEST) { instRef.current.setOptions({ animate: false }); } }, [animate]); const handleCompensateForGap = useCallback(() => { if (compensateForGap) { let gap = compensateForGap; const { elem } = instRef.current; if (compensateForGap === 'auto') { gap = window.getComputedStyle(elem.parentElement).getPropertyValue('row-gap'); } elem.style.marginTop = `calc(${gap} * -1)`; const inner = elem.querySelector('.compensateForGap'); inner.style.marginTop = gap; } }, [compensateForGap]); useLayoutEffect(() => { instRef.current.onStart(state => { switch (state) { case 'opening': handleCompensateForGap(); setIsVisible(true); setParallax(true); setIsAnimating(true); break; case 'closing': setParallax(false); setIsAnimating(true); break; case 'adjusting': setIsAnimating(true); break; } if (!isInitialRenderRef.current) { eventsRef.current.onAnimationStart?.(state); } }); instRef.current.onEnd(state => { switch (state) { case 'opened': setIsVisible(true); setIsOpen(true); setIsAnimating(false); eventsRef.current.onOpen?.(true); break; case 'closed': setIsVisible(false); setIsOpen(false); setParallax(false); setIsAnimating(false); eventsRef.current.onOpen?.(false); break; case 'adjusted': setIsAnimating(false); break; } if (!isInitialRenderRef.current) { eventsRef.current.onAnimationEnd?.(state); } }); }, [compensateForGap, handleCompensateForGap]); useOpenClose({ open, instRef, isInitialRenderRef, targetRef }); useAdjust({ children, instRef, isInitialRenderRef, targetRef }); const firstPaintStyle = open && !isVisible && !isAnimating && instRef.current?.firstPaintStyle || {}; const isInDOM = open || isVisible; return { open, isOpen, isInDOM, isVisible, isVisibleParallax, isAnimating, firstPaintStyle }; } function useOpenClose({ open, instRef, isInitialRenderRef, targetRef }) { const isTest = typeof process !== 'undefined' && process.env.NODE_ENV === 'test' && typeof globalThis.IS_TEST === 'undefined'; useLayoutEffect(() => { instRef.current.setElement(targetRef.current); if (!targetRef.current || instRef.current.state === 'init' && isInitialRenderRef.current) { if (open) { instRef.current.setAsOpen(); } else { instRef.current.setAsClosed(); } } else { if (open) { instRef.current.open(); } else { instRef.current.close(); } } if (isTest) { const event = new CustomEvent('transitionend'); targetRef.current?.dispatchEvent(event); } }, [open, targetRef, instRef, isInitialRenderRef, isTest]); useLayoutEffect(() => { const run = () => { isInitialRenderRef.current = false; }; if (globalThis.bypassTime === -1 || isTest) { run(); } else { window.requestAnimationFrame?.(run); } }, [isInitialRenderRef, isTest]); } function useAdjust({ children, instRef, isInitialRenderRef, targetRef }) { const fromHeight = useRef(0); const [timer] = useState(() => Date.now()); const shouldAdjust = () => { switch (instRef.current?.state) { case 'opened': case 'adjusted': case 'adjusting': return !isInitialRenderRef.current && Date.now() - timer > (globalThis.readjustTime || 100); } return false; }; useMemo(() => { if (shouldAdjust()) { fromHeight.current = instRef.current?.getHeight(); } }, [children]); useLayoutEffect(() => { if (shouldAdjust()) { instRef.current.setElement(targetRef.current); instRef.current.elem.style.height = ''; const toHeight = instRef.current.getHeight(); instRef.current.adjustTo(fromHeight.current, toHeight); } }, [children]); } //# sourceMappingURL=useHeightAnimation.js.map