StackedRouter: blocks the ios swipe gesture
- added more docs
This commit is contained in:
parent
6726ffe664
commit
76f7e08e78
2 changed files with 68 additions and 15 deletions
|
@ -79,6 +79,11 @@ export type Navigator<PushGuide = Record<string, any>> = {
|
|||
|
||||
const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
|
||||
|
||||
/**
|
||||
* Get the possible navigator of the {@link StackedRouter}.
|
||||
*
|
||||
* @see useNavigator for the navigator usage.
|
||||
*/
|
||||
export function useMaybeNavigator() {
|
||||
return useContext(NavigatorContext);
|
||||
}
|
||||
|
@ -91,6 +96,8 @@ export function useMaybeNavigator() {
|
|||
* path and its state. If you need push guide, you may want to
|
||||
* define your own function (like `useAppNavigator`) and cast the
|
||||
* navigator to the type you need.
|
||||
*
|
||||
* @see {@link useMaybeNavigator} if you are not sure you are under a {@link StackedRouter}.
|
||||
*/
|
||||
export function useNavigator() {
|
||||
const navigator = useMaybeNavigator();
|
||||
|
@ -110,10 +117,20 @@ export type CurrentFrame = {
|
|||
const CurrentFrameContext =
|
||||
/* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
|
||||
|
||||
/**
|
||||
* Return the current, if possible.
|
||||
*
|
||||
* @see {@link useCurrentFrame} asserts the frame exists
|
||||
*/
|
||||
export function useMaybeCurrentFrame() {
|
||||
return useContext(CurrentFrameContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current frame, assert the frame exists.
|
||||
*
|
||||
* @see {@link useMaybeCurrentFrame} if you are not sure you are under a {@link StackedRouter}.
|
||||
*/
|
||||
export function useCurrentFrame() {
|
||||
const frame = useMaybeCurrentFrame();
|
||||
|
||||
|
@ -130,8 +147,11 @@ export function useCurrentFrame() {
|
|||
* A suspended frame is the one not on the top. "Suspended"
|
||||
* is the description of a certain situtation, not in the life cycle
|
||||
* of a frame.
|
||||
*
|
||||
* If this is not called under a {@link StackedRouter}, it always
|
||||
* returns `false`.
|
||||
*/
|
||||
export function useMaybeIsFrameSuspended() {
|
||||
export function useIsFrameSuspended() {
|
||||
const { frames } = useMaybeNavigator() || {};
|
||||
|
||||
if (typeof frames === "undefined") {
|
||||
|
@ -203,10 +223,31 @@ function serializableStack(stack: readonly StackFrame[]) {
|
|||
});
|
||||
}
|
||||
|
||||
function isNotInIOSSwipeToBackArea(x: number) {
|
||||
return (
|
||||
(x > 22 && x < window.innerWidth - 22) ||
|
||||
(x < -22 && x > window.innerWidth + 22)
|
||||
);
|
||||
}
|
||||
|
||||
function onEntryTouchStart(event: TouchEvent) {
|
||||
if (event.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [fig0] = event.touches;
|
||||
|
||||
if (isNotInIOSSwipeToBackArea(fig0.clientX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* The router that stacks the pages.
|
||||
*
|
||||
* **Routes** The router accepts the {@link RouteProps} excluding the "url" field.
|
||||
* **Routes** The router accepts the {@link RouterProps} excluding the "url" field.
|
||||
* You can seamlessly use the `<Route />` from `@solidjs/router`.
|
||||
*
|
||||
* Be advised that this component is not a drop-in replacement of that router.
|
||||
|
@ -240,7 +281,7 @@ function serializableStack(stack: readonly StackFrame[]) {
|
|||
* Navigation animations (even the custom ones) will be played during
|
||||
* swipe to back, please keep in mind when designing animations.
|
||||
*
|
||||
* The iOS default gesture is blocked on those pages.
|
||||
* The iOS default gesture is blocked on all pages.
|
||||
*/
|
||||
const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
||||
const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" });
|
||||
|
@ -388,11 +429,7 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
origFigX = fig0.clientX;
|
||||
origFigY = fig0.clientY;
|
||||
|
||||
const isNotInSwipeToBackArea =
|
||||
(fig0.clientX > 22 && fig0.clientX < window.innerWidth - 22) ||
|
||||
(fig0.clientX < -22 && fig0.clientX > window.innerWidth + 22);
|
||||
|
||||
if (isNotInSwipeToBackArea) {
|
||||
if (isNotInIOSSwipeToBackArea(fig0.clientX)) {
|
||||
return;
|
||||
}
|
||||
// Prevent the default swipe to back/forward on iOS
|
||||
|
@ -400,6 +437,21 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
event.preventDefault();
|
||||
};
|
||||
|
||||
let animationProgressUpdateRequested = false
|
||||
let nextAnimationProgress = 0
|
||||
|
||||
const updateAnimationProgress = () => {
|
||||
if (!reenterableAnimation) return;
|
||||
|
||||
const { activeDuration, delay } =
|
||||
reenterableAnimation.effect!.getComputedTiming();
|
||||
|
||||
const totalTime = (delay || 0) + Number(activeDuration);
|
||||
reenterableAnimation.currentTime = totalTime * nextAnimationProgress;
|
||||
|
||||
animationProgressUpdateRequested = false
|
||||
}
|
||||
|
||||
const onDialogTouchMove = (
|
||||
event: TouchEvent & { currentTarget: HTMLDialogElement },
|
||||
) => {
|
||||
|
@ -429,13 +481,13 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const pc = ofsX / origWidth / window.devicePixelRatio;
|
||||
nextAnimationProgress = ofsX / origWidth / window.devicePixelRatio;
|
||||
|
||||
const { activeDuration, delay } =
|
||||
reenterableAnimation.effect!.getComputedTiming();
|
||||
if (!animationProgressUpdateRequested) {
|
||||
animationProgressUpdateRequested = true;
|
||||
|
||||
const totalTime = (delay || 0) + Number(activeDuration);
|
||||
reenterableAnimation.currentTime = totalTime * pc;
|
||||
requestAnimationFrame(updateAnimationProgress)
|
||||
}
|
||||
};
|
||||
|
||||
const onDialogTouchEnd = (event: TouchEvent) => {
|
||||
|
@ -492,6 +544,7 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
class="StackedPage"
|
||||
id={frame().rootId}
|
||||
role="presentation"
|
||||
on:touchstart={onEntryTouchStart}
|
||||
>
|
||||
<StaticRouter url={frame().path} {...oprops} />
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Refresh as RefreshIcon } from "@suid/icons-material";
|
|||
import { CircularProgress } from "@suid/material";
|
||||
import { makeEventListener } from "@solid-primitives/event-listener";
|
||||
import { createVisibilityObserver } from "@solid-primitives/intersection-observer";
|
||||
import { useMaybeIsFrameSuspended } from "../platform/StackedRouter";
|
||||
import { useIsFrameSuspended } from "../platform/StackedRouter";
|
||||
|
||||
const PullDownToRefresh: Component<{
|
||||
loading?: boolean;
|
||||
|
@ -34,7 +34,7 @@ const PullDownToRefresh: Component<{
|
|||
});
|
||||
|
||||
const rootVisible = obvx(() => rootElement);
|
||||
const isFrameSuspended = useMaybeIsFrameSuspended()
|
||||
const isFrameSuspended = useIsFrameSuspended()
|
||||
|
||||
createEffect(() => {
|
||||
if (!rootVisible()) setPullDown(0);
|
||||
|
|
Loading…
Reference in a new issue