import { createEffect, createRenderEffect, onCleanup, onMount, type ParentComponent, } from "solid-js"; import styles from "./BottomSheet.module.css"; import { useHeroSignal } from "../platform/anim"; export type BottomSheetProps = { open?: boolean; }; export const HERO = Symbol("BottomSheet Hero Symbol"); function composeAnimationFrame({ top, left, height, width, }: Record<"top" | "left" | "height" | "width", number>) { return { top: `${top}px`, left: `${left}px`, height: `${height}px`, width: `${width}px`, }; } const MOVE_SPEED = 1400; // 1400px/s, bottom sheet is big and a bit heavier than small papers const BottomSheet: ParentComponent = (props) => { let element: HTMLDialogElement; let animation: Animation | undefined; const hero = useHeroSignal(HERO); createEffect(() => { if (props.open) { if (!element.open) { element.showModal(); animateOpen(); } } else { if (element.open) { if (animation) { animation.cancel(); } element.close(); } } }); const animateOpen = () => { // Do hero animation const startRect = hero(); console.debug("capture hero source", startRect); if (!startRect) return; const endRect = element.getBoundingClientRect(); const easing = "ease-in-out"; console.debug("easing", easing); element.classList.add(styles.animated); const distance = Math.sqrt( Math.pow(Math.abs(startRect.top - endRect.top), 2) + Math.pow(Math.abs(startRect.left - startRect.top), 2), ); const duration = (distance / MOVE_SPEED) * 1000; animation = element.animate( [composeAnimationFrame(startRect), composeAnimationFrame(endRect)], { easing, duration }, ); const onAnimationEnd = () => { element.classList.remove(styles.animated); animation = undefined; }; animation.addEventListener("finish", onAnimationEnd); animation.addEventListener("cancel", onAnimationEnd); }; onCleanup(() => { if (animation) { animation.cancel(); } }); return ( {props.children} ); }; export default BottomSheet;