diff --git a/src/platform/StackedRouter.tsx b/src/platform/StackedRouter.tsx index 5bc6d8c..372c851 100644 --- a/src/platform/StackedRouter.tsx +++ b/src/platform/StackedRouter.tsx @@ -15,7 +15,10 @@ import { createStore, unwrap } from "solid-js/store"; import "./StackedRouter.css"; import { animateSlideInFromRight, animateSlideOutToRight } from "./anim"; import { ANIM_CURVE_DECELERATION, ANIM_CURVE_STD } from "../material/theme"; -import { makeEventListener } from "@solid-primitives/event-listener"; +import { + eventListener, + makeEventListener, +} from "@solid-primitives/event-listener"; export type StackedRouterProps = Omit; @@ -289,6 +292,93 @@ const StackedRouter: Component = (oprops) => { }); }; + let reenterableAnimation: Animation | undefined; + let origX = 0, + origWidth = 0; + + const resetAnimation = () => { + reenterableAnimation = undefined; + }; + + const onDialogTouchStart = ( + event: TouchEvent & { currentTarget: HTMLDialogElement }, + ) => { + if (event.touches.length !== 1) { + return; + } + const [fig0] = event.touches; + const { x, width } = event.currentTarget.getBoundingClientRect(); + if (fig0.clientX < x - 22 || fig0.clientX > x + 22) { + return; + } + origX = x; + origWidth = width; + + const lastFr = stack[stack.length - 1]; + const createAnimation = lastFr.animateClose ?? animateClose; + reenterableAnimation = createAnimation(event.currentTarget); + reenterableAnimation.pause(); + reenterableAnimation.addEventListener("finish", resetAnimation); + reenterableAnimation.addEventListener("cancel", resetAnimation); + }; + + const onDialogTouchMove = ( + event: TouchEvent & { currentTarget: HTMLDialogElement }, + ) => { + if (event.touches.length !== 1) { + if (reenterableAnimation) { + reenterableAnimation.reverse(); + reenterableAnimation.play(); + } + } + + if (!reenterableAnimation) return; + + event.preventDefault(); + event.stopPropagation(); + + const [fig0] = event.touches; + const ofsX = fig0.clientX - origX; + const pc = ofsX / origWidth / window.devicePixelRatio; + + const { activeDuration, delay } = + reenterableAnimation.effect!.getComputedTiming(); + + const totalTime = (delay || 0) + Number(activeDuration); + reenterableAnimation.currentTime = totalTime * pc; + }; + + const onDialogTouchEnd = (event: TouchEvent) => { + if (!reenterableAnimation) return; + + event.preventDefault(); + event.stopPropagation(); + + const { activeDuration, delay } = + reenterableAnimation.effect!.getComputedTiming(); + const totalTime = (delay || 0) + Number(activeDuration); + + if ( + Number(reenterableAnimation.currentTime) / totalTime > + 0.1 + ) { + reenterableAnimation.addEventListener("finish", () => { + onlyPopFrame(1); + }); + reenterableAnimation.play(); + } else { + reenterableAnimation.cancel(); + } + }; + + const onDialogTouchCancel = (event: TouchEvent) => { + if (!reenterableAnimation) return; + + event.preventDefault(); + event.stopPropagation(); + reenterableAnimation.cancel(); + }; + return ( = (oprops) => { class="StackedPage" onCancel={[popFrame, 1]} onClick={[onDialogClick, popFrame]} + onTouchStart={onDialogTouchStart} + onTouchMove={onDialogTouchMove} + onTouchEnd={onDialogTouchEnd} + onTouchCancel={onDialogTouchCancel} id={frame().rootId} >