import { createContext, createRenderEffect, createSignal, untrack, useContext, type Accessor, type Signal, } from "solid-js"; export type HeroSource = { [key: string | symbol | number]: HTMLElement | undefined; }; const HeroSourceContext = createContext>( /* __@PURE__ */ undefined, ); export const HeroSourceProvider = HeroSourceContext.Provider; export function useHeroSource() { return useContext(HeroSourceContext); } /** * Use hero value for the {@link key}. * * Note: the setter here won't set the value of the hero source. * To set hero source, use {@link useHeroSource} and set the corresponding key. */ export function useHeroSignal( key: string | symbol | number, ): Signal { const source = useHeroSource(); if (source) { const [get, set] = createSignal(); createRenderEffect(() => { const value = source[0](); if (value[key]) { set(value[key]); source[1]((x) => { const cpy = Object.assign({}, x); delete cpy[key]; return cpy; }); } }); return [get, set]; } else { console.debug("no hero source") return [() => undefined, () => undefined]; } } export function animateRollOutFromTop( root: HTMLElement, options?: Omit, ) { const overflow = root.style.overflow; root.style.overflow = "hidden"; const { height } = root.getBoundingClientRect(); const opts = Object.assign( { duration: Math.floor((height / 1600) * 1000), }, options, ); const animation = root.animate( { height: ["0px", `${height}px`], }, opts, ); const restore = () => (root.style.overflow = overflow); animation.addEventListener("finish", restore); animation.addEventListener("cancel", restore); return animation; } export function animateRollInFromBottom( root: HTMLElement, options?: Omit, ) { const overflow = root.style.overflow; root.style.overflow = "hidden"; const { height } = root.getBoundingClientRect(); const opts = Object.assign( { duration: Math.floor((height / 1600) * 1000), }, options, ); const animation = root.animate( { height: [`${height}px`, "0px"], }, opts, ); const restore = () => (root.style.overflow = overflow); animation.addEventListener("finish", restore); animation.addEventListener("cancel", restore); return animation; } export function animateGrowFromTopRight( root: HTMLElement, options?: KeyframeAnimationOptions, ) { const transformOrigin = root.style.transformOrigin; root.style.transformOrigin = "top right"; const { width, height } = root.getBoundingClientRect(); const durationX = Math.floor((height / 1600) * 1000); const durationY = Math.floor((width / 1600) * 1000); // finds the offset for the center frame, // it will stops at the (minDuration / maxDuration)% const minDuration = Math.min(durationX, durationY); const maxDuration = Math.max(durationX, durationY); const centerOffset = minDuration / maxDuration; const keyframes = [ { transform: "scaleX(0)", opacity: 0, height: "0px", offset: 0 }, { transform: `scaleX(${minDuration === durationX ? "1" : centerOffset})`, height: `${(minDuration === durationY ? 1 : centerOffset) * height}px`, offset: centerOffset, opacity: 1, }, { transform: "scaleX(1)", height: `${height}px`, offset: 1 }, ]; const animation = root.animate( keyframes, { ...options, duration: maxDuration }, ); const restore = () => { root.style.transformOrigin = transformOrigin; }; animation.addEventListener("cancel", restore); animation.addEventListener("finish", restore); return animation; } export function animateShrinkToTopRight( root: HTMLElement, options?: KeyframeAnimationOptions, ) { const overflow = root.style.overflow; root.style.overflow = "hidden"; const transformOrigin = root.style.transformOrigin; root.style.transformOrigin = "top right"; const { width, height } = root.getBoundingClientRect(); const duration = Math.floor( Math.max((width / 1600) * 1000, (height / 1600) * 1000), ); const animation = root.animate( { transform: ["scale(1)", "scale(0.5)"], opacity: [1, 0], }, { ...options, duration }, ); const restore = () => { root.style.overflow = overflow; root.style.transformOrigin = transformOrigin; }; animation.addEventListener("cancel", restore); animation.addEventListener("finish", restore); return animation; }