{"version":3,"file":"useAnimatedHeightBetween.cjs","sources":["../../../../src/hooks/useAnimatedHeight/useAnimatedHeightBetween.ts"],"sourcesContent":["import { type RefObject, useCallback, useEffect, useRef } from \"react\";\nimport tokens from \"../../core/tokens.js\";\nimport { useBrowserPreferences } from \"../useBrowserPreferences/useBrowserPreferences.js\";\nimport { usePreviousValue } from \"../usePreviousValue/usePreviousValue.js\";\nimport type { UseAnimatedHeightOptions } from \"./types.js\";\n\nconst defaultEasing = \"standard\";\nconst defaultTiming = \"productive\";\n\nfunction collapseElement<T extends HTMLElement>(\n    elementRef: RefObject<T | null>,\n    transition: string,\n    raf1: React.MutableRefObject<number | undefined>,\n    raf2: React.MutableRefObject<number | undefined>,\n) {\n    const element = elementRef.current;\n\n    if (!element) return;\n\n    element.removeAttribute(\"style\");\n    const expandedHeight = element.scrollHeight;\n\n    raf1.current = requestAnimationFrame(() => {\n        // Hent kollapset høyde\n        element.style.removeProperty(\"transition\");\n        element.dataset.expanded = \"false\";\n        const collapsedHeight = element.getBoundingClientRect().height;\n        element.dataset.expanded = \"true\";\n\n        // Sett høyde tilbake til utvidet høyde\n        element.style.setProperty(\"height\", `${expandedHeight}px`);\n        element.style.setProperty(\"overflow-y\", \"hidden\");\n\n        raf2.current = requestAnimationFrame(() => {\n            // Sett høyde til kollapset høyde og start transition\n            element.style.setProperty(\"transition\", transition);\n            element.style.setProperty(\"height\", `${collapsedHeight}px`);\n            element.dataset.expanded = \"false\";\n        });\n    });\n}\n\nfunction expandElement<T extends HTMLElement>(\n    elementRef: RefObject<T | null>,\n    transition: string,\n    raf1: React.MutableRefObject<number | undefined>,\n    raf2: React.MutableRefObject<number | undefined>,\n) {\n    const element = elementRef.current;\n\n    if (!element) return;\n\n    element.removeAttribute(\"style\");\n    const expandedHeight = element.scrollHeight;\n\n    raf1.current = requestAnimationFrame(() => {\n        // Hent utvidet høyde\n        element.style.removeProperty(\"transition\");\n        element.dataset.expanded = \"false\";\n        const collapsedHeight = element.getBoundingClientRect().height;\n\n        // Sett høyde tilbake til kollapset høyde\n        element.style.setProperty(\"height\", `${collapsedHeight}px`);\n        element.style.setProperty(\"overflow-y\", \"hidden\");\n\n        raf2.current = requestAnimationFrame(() => {\n            // Sett høyde til utvidet høyde  og start transition\n            element.style.setProperty(\"transition\", transition);\n            element.style.setProperty(\"height\", `${expandedHeight}px`);\n            element.dataset.expanded = \"true\";\n        });\n    });\n}\n\n/**\n * Lar deg enklere animere mellom to tilstander, gitt ved å sette `data-expanded` på et element til `true` eller `false`.\n * Du bestemmer selv hvilke stiler elementet skal ha i de to tilstandene (vha CSS/Sass), og høyden animeres dersom den endrer seg.\n * @param isExpanded indikerer om elementet skal være utvidet eller ikke\n * @param options konfigurasjon for animasjonen, og lyttere for når animasjonen starter og slutter\n * @returns En tuple med referanse til elementet og en funksjon som kan trigge animasjonen\n */\nexport function useAnimatedHeightBetween<T extends HTMLElement>(\n    isExpanded: boolean,\n    options?: Omit<UseAnimatedHeightOptions<T>, \"display\" | \"onFirstVisible\">,\n): [RefObject<T>, () => void] {\n    const wasExpanded = usePreviousValue(isExpanded);\n    const easing = options?.easing || defaultEasing;\n    const timing = options?.timing || defaultTiming;\n    const transition = `${tokens.motion.timing[timing]} height ${tokens.motion.easing[easing]}`;\n\n    const { prefersReducedMotion } = useBrowserPreferences();\n\n    const raf1 = useRef<number>();\n    const raf2 = useRef<number>();\n    const elementRef = useRef<T>(null);\n\n    const handleTransitionEnd = useCallback(\n        (event: TransitionEvent) => {\n            const element = elementRef.current;\n\n            // Ignore bubbling transitions from within container\n            if (element && event.target === element) {\n                element.removeAttribute(\"style\");\n                options?.onTransitionEnd?.(isExpanded, elementRef);\n            }\n        },\n        [options, isExpanded],\n    );\n\n    const runAnimation = useCallback(() => {\n        const element = elementRef.current;\n\n        if (!element) return;\n\n        if (wasExpanded === undefined) {\n            // Første render\n            element.dataset.expanded = isExpanded ? \"true\" : \"false\";\n        }\n\n        if ((!isExpanded && !wasExpanded) || (isExpanded && wasExpanded)) {\n            // Ingen endring\n            return;\n        }\n\n        options?.onTransitionStart?.(isExpanded, elementRef);\n\n        if (prefersReducedMotion) {\n            element.removeAttribute(\"style\");\n            element.dataset.expanded = isExpanded ? \"true\" : \"false\";\n            options?.onTransitionEnd?.(isExpanded, elementRef); // make sure to call callback when animation is off\n            return;\n        }\n\n        if (isExpanded) {\n            expandElement(elementRef, transition, raf1, raf2);\n        } else {\n            collapseElement(elementRef, transition, raf1, raf2);\n        }\n    }, [wasExpanded, isExpanded, options, prefersReducedMotion, transition]);\n\n    // biome-ignore lint/correctness/useExhaustiveDependencies: Vi trigger med isExpanded\n    useEffect(() => {\n        runAnimation();\n    }, [isExpanded, runAnimation]);\n\n    // biome-ignore lint/correctness/useExhaustiveDependencies:\n    useEffect(() => {\n        const element = elementRef.current;\n        if (element) {\n            element.addEventListener(\"transitionend\", handleTransitionEnd);\n        }\n\n        return () => {\n            if (element) {\n                element.removeEventListener(\n                    \"transitionend\",\n                    handleTransitionEnd,\n                );\n            }\n        };\n    }, [isExpanded]);\n\n    useEffect(() => {\n        const r1 = raf1.current;\n        const r2 = raf2.current;\n        return () => {\n            r1 && cancelAnimationFrame(r1);\n            r2 && cancelAnimationFrame(r2);\n        };\n    }, []);\n\n    return [elementRef, runAnimation];\n}\n"],"names":["isExpanded","options","wasExpanded","usePreviousValue","easing","timing","transition","tokens","motion","prefersReducedMotion","useBrowserPreferences","raf1","useRef","raf2","elementRef","handleTransitionEnd","useCallback","event","element","current","target","removeAttribute","onTransitionEnd","runAnimation","dataset","expanded","onTransitionStart","expandedHeight","scrollHeight","requestAnimationFrame","style","removeProperty","collapsedHeight","getBoundingClientRect","height","setProperty","expandElement","collapseElement","useEffect","addEventListener","removeEventListener","r1","r2","cancelAnimationFrame"],"mappings":"mSAiFO,SACHA,EACAC,GAEA,MAAMC,EAAcC,EAAAA,iBAAiBH,GAC/BI,EAASH,GAASG,QAhFN,WAiFZC,EAASJ,GAASI,QAhFN,aAiFZC,EAAa,GAAGC,EAAOC,OAAOH,OAAOA,aAAkBE,EAAOC,OAAOJ,OAAOA,MAE1EK,qBAAAA,GAAyBC,0BAE3BC,EAAOC,EAAAA,SACPC,EAAOD,EAAAA,SACPE,EAAaF,EAAAA,OAAU,MAEvBG,EAAsBC,EAAAA,YACvBC,IACG,MAAMC,EAAUJ,EAAWK,QAGvBD,GAAWD,EAAMG,SAAWF,IAC5BA,EAAQG,gBAAgB,SACxBpB,GAASqB,kBAAkBtB,EAAYc,KAG/C,CAACb,EAASD,IAGRuB,EAAeP,EAAAA,YAAY,KAC7B,MAAME,EAAUJ,EAAWK,QAE3B,GAAKD,SAEe,IAAhBhB,IAEAgB,EAAQM,QAAQC,SAAWzB,EAAa,OAAS,YAG/CA,IAAeE,GAAiBF,GAAcE,IAOpD,CAAA,GAFAD,GAASyB,oBAAoB1B,EAAYc,GAErCL,EAIA,OAHAS,EAAQG,gBAAgB,SACxBH,EAAQM,QAAQC,SAAWzB,EAAa,OAAS,aACjDC,GAASqB,kBAAkBtB,EAAYc,GAIvCd,EA3FZ,SACIc,EACAR,EACAK,EACAE,GAEA,MAAMK,EAAUJ,EAAWK,QAE3B,IAAKD,EAAS,OAEdA,EAAQG,gBAAgB,SACxB,MAAMM,EAAiBT,EAAQU,aAE/BjB,EAAKQ,QAAUU,sBAAsB,KAEjCX,EAAQY,MAAMC,eAAe,cAC7Bb,EAAQM,QAAQC,SAAW,QAC3B,MAAMO,EAAkBd,EAAQe,wBAAwBC,OAGxDhB,EAAQY,MAAMK,YAAY,SAAU,GAAGH,OACvCd,EAAQY,MAAMK,YAAY,aAAc,UAExCtB,EAAKM,QAAUU,sBAAsB,KAEjCX,EAAQY,MAAMK,YAAY,aAAc7B,GACxCY,EAAQY,MAAMK,YAAY,SAAU,GAAGR,OACvCT,EAAQM,QAAQC,SAAW,UAGvC,CA8DYW,CAActB,EAAYR,EAAYK,EAAME,GA7HxD,SACIC,EACAR,EACAK,EACAE,GAEA,MAAMK,EAAUJ,EAAWK,QAE3B,IAAKD,EAAS,OAEdA,EAAQG,gBAAgB,SACxB,MAAMM,EAAiBT,EAAQU,aAE/BjB,EAAKQ,QAAUU,sBAAsB,KAEjCX,EAAQY,MAAMC,eAAe,cAC7Bb,EAAQM,QAAQC,SAAW,QAC3B,MAAMO,EAAkBd,EAAQe,wBAAwBC,OACxDhB,EAAQM,QAAQC,SAAW,OAG3BP,EAAQY,MAAMK,YAAY,SAAU,GAAGR,OACvCT,EAAQY,MAAMK,YAAY,aAAc,UAExCtB,EAAKM,QAAUU,sBAAsB,KAEjCX,EAAQY,MAAMK,YAAY,aAAc7B,GACxCY,EAAQY,MAAMK,YAAY,SAAU,GAAGH,OACvCd,EAAQM,QAAQC,SAAW,WAGvC,CAgGYY,CAAgBvB,EAAYR,EAAYK,EAAME,EAAI,GAEvD,CAACX,EAAaF,EAAYC,EAASQ,EAAsBH,IAG5DgC,OAAAA,EAAAA,UAAU,KACNf,KACD,CAACvB,EAAYuB,IAGhBe,EAAAA,UAAU,KACN,MAAMpB,EAAUJ,EAAWK,QAC3B,OAAID,GACAA,EAAQqB,iBAAiB,gBAAiBxB,GAGvC,KACCG,GACAA,EAAQsB,oBACJ,gBACAzB,KAIb,CAACf,IAEJsC,EAAAA,UAAU,KACN,MAAMG,EAAK9B,EAAKQ,QACVuB,EAAK7B,EAAKM,QAChB,MAAO,KACHsB,GAAME,qBAAqBF,GAC3BC,GAAMC,qBAAqBD,KAEhC,IAEI,CAAC5B,EAAYS,EACxB"}