From f50ed8d907c53fed4f7341269556d5c3365a2b93 Mon Sep 17 00:00:00 2001 From: thislight Date: Sat, 23 Nov 2024 20:19:43 +0800 Subject: [PATCH] BottomSheet: stop clicks propagation --- src/material/BottomSheet.tsx | 103 ++++++++++++++++------------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index b9a9f38..73fd734 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -1,19 +1,14 @@ import { children, createEffect, - createSignal, onCleanup, useTransition, type JSX, type ParentComponent, - type ResolvedChildren, } from "solid-js"; import "./BottomSheet.css"; import material from "./material.module.css"; -import { - ANIM_CURVE_ACELERATION, - ANIM_CURVE_DECELERATION, -} from "./theme"; +import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme"; import { animateSlideInFromRight, animateSlideOutToRight, @@ -28,11 +23,36 @@ export type BottomSheetProps = { const MOVE_SPEED = 1600; +function animateSlideInFromBottom(element: HTMLElement, reverse?: boolean) { + const rect = element.getBoundingClientRect(); + const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; + element.classList.add("animated"); + const oldOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + const distance = Math.abs(rect.top - window.innerHeight); + const duration = (distance / MOVE_SPEED) * 1000; + + const animation = element.animate( + { + top: reverse + ? [`${rect.top}px`, `${window.innerHeight}px`] + : [`${window.innerHeight}px`, `${rect.top}px`], + }, + { easing, duration }, + ); + const onAnimationEnd = () => { + element.classList.remove("animated"); + document.body.style.overflow = oldOverflow; + }; + animation.addEventListener("cancel", onAnimationEnd); + animation.addEventListener("finish", onAnimationEnd); + return animation; +} + const BottomSheet: ParentComponent = (props) => { let element: HTMLDialogElement; let animation: Animation | undefined; - const [cache, setCache] = createSignal(); - const ochildren = children(() => props.children); + const child = children(() => props.children); const [pending] = useTransition(); @@ -40,12 +60,10 @@ const BottomSheet: ParentComponent = (props) => { if (props.open) { if (!element.open && !pending()) { requestAnimationFrame(animatedOpen); - setCache(ochildren()); } } else { if (element.open) { animatedClose(); - setCache(undefined); } } }); @@ -55,22 +73,21 @@ const BottomSheet: ParentComponent = (props) => { }; const animatedClose = () => { - - if (window.innerWidth > 560 && !props.bottomUp) { - onClose(); - return; - } - const onAnimationEnd = () => { - element.classList.remove("animated"); - onClose(); - }; - element.classList.add("animated"); - animation = props.bottomUp - ? animateSlideInFromBottom(element, true) - : animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION }); - animation.addEventListener("finish", onAnimationEnd); - animation.addEventListener("cancel", onAnimationEnd); - + if (window.innerWidth > 560 && !props.bottomUp) { + onClose(); + return; + } + const onAnimationEnd = () => { + element.classList.remove("animated"); + animation = undefined; + onClose(); + }; + element.classList.add("animated"); + animation = props.bottomUp + ? animateSlideInFromBottom(element, true) + : animateSlideOutToRight(element, { easing: ANIM_CURVE_ACELERATION }); + animation.addEventListener("finish", onAnimationEnd); + animation.addEventListener("cancel", onAnimationEnd); }; const animatedOpen = () => { @@ -81,6 +98,7 @@ const BottomSheet: ParentComponent = (props) => { element.classList.add("animated"); const onAnimationEnd = () => { element.classList.remove("animated"); + animation = undefined; }; animation = animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION, @@ -90,36 +108,6 @@ const BottomSheet: ParentComponent = (props) => { } }; - const animateSlideInFromBottom = ( - element: HTMLElement, - reserve?: boolean, - ) => { - const rect = element.getBoundingClientRect(); - const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; - element.classList.add("animated"); - const oldOverflow = document.body.style.overflow; - document.body.style.overflow = "hidden"; - const distance = Math.abs(rect.top - window.innerHeight); - const duration = (distance / MOVE_SPEED) * 1000; - - animation = element.animate( - { - top: reserve - ? [`${rect.top}px`, `${window.innerHeight}px`] - : [`${window.innerHeight}px`, `${rect.top}px`], - }, - { easing, duration }, - ); - const onAnimationEnd = () => { - element.classList.remove("animated"); - document.body.style.overflow = oldOverflow; - animation = undefined; - }; - animation.addEventListener("cancel", onAnimationEnd); - animation.addEventListener("finish", onAnimationEnd); - return animation; - }; - onCleanup(() => { if (animation) { animation.cancel(); @@ -129,6 +117,7 @@ const BottomSheet: ParentComponent = (props) => { const onDialogClick = ( event: MouseEvent & { currentTarget: HTMLDialogElement }, ) => { + event.stopPropagation(); if (event.target !== event.currentTarget) return; const rect = event.currentTarget.getBoundingClientRect(); const isNotInDialog = @@ -159,7 +148,7 @@ const BottomSheet: ParentComponent = (props) => { tabIndex={-1} role="presentation" > - {ochildren() ?? cache()} + {child()} ); };