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>();
 | 
					const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get the possible navigator of the {@link StackedRouter}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see useNavigator for the navigator usage.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
export function useMaybeNavigator() {
 | 
					export function useMaybeNavigator() {
 | 
				
			||||||
  return useContext(NavigatorContext);
 | 
					  return useContext(NavigatorContext);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -91,6 +96,8 @@ export function useMaybeNavigator() {
 | 
				
			||||||
 * path and its state. If you need push guide, you may want to
 | 
					 * path and its state. If you need push guide, you may want to
 | 
				
			||||||
 * define your own function (like `useAppNavigator`) and cast the
 | 
					 * define your own function (like `useAppNavigator`) and cast the
 | 
				
			||||||
 * navigator to the type you need.
 | 
					 * navigator to the type you need.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see {@link useMaybeNavigator} if you are not sure you are under a {@link StackedRouter}.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function useNavigator() {
 | 
					export function useNavigator() {
 | 
				
			||||||
  const navigator = useMaybeNavigator();
 | 
					  const navigator = useMaybeNavigator();
 | 
				
			||||||
| 
						 | 
					@ -110,10 +117,20 @@ export type CurrentFrame = {
 | 
				
			||||||
const CurrentFrameContext =
 | 
					const CurrentFrameContext =
 | 
				
			||||||
  /* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
 | 
					  /* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Return the current, if possible.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see {@link useCurrentFrame} asserts the frame exists
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
export function useMaybeCurrentFrame() {
 | 
					export function useMaybeCurrentFrame() {
 | 
				
			||||||
  return useContext(CurrentFrameContext);
 | 
					  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() {
 | 
					export function useCurrentFrame() {
 | 
				
			||||||
  const frame = useMaybeCurrentFrame();
 | 
					  const frame = useMaybeCurrentFrame();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,8 +147,11 @@ export function useCurrentFrame() {
 | 
				
			||||||
 * A suspended frame is the one not on the top. "Suspended"
 | 
					 * A suspended frame is the one not on the top. "Suspended"
 | 
				
			||||||
 * is the description of a certain situtation, not in the life cycle
 | 
					 * is the description of a certain situtation, not in the life cycle
 | 
				
			||||||
 * of a frame.
 | 
					 * 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() || {};
 | 
					  const { frames } = useMaybeNavigator() || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (typeof frames === "undefined") {
 | 
					  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.
 | 
					 * 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`.
 | 
					 * You can seamlessly use the `<Route />` from `@solidjs/router`.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Be advised that this component is not a drop-in replacement of that 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
 | 
					 * Navigation animations (even the custom ones) will be played during
 | 
				
			||||||
 * swipe to back, please keep in mind when designing animations.
 | 
					 * 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 StackedRouter: Component<StackedRouterProps> = (oprops) => {
 | 
				
			||||||
  const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" });
 | 
					  const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" });
 | 
				
			||||||
| 
						 | 
					@ -388,11 +429,7 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
 | 
				
			||||||
    origFigX = fig0.clientX;
 | 
					    origFigX = fig0.clientX;
 | 
				
			||||||
    origFigY = fig0.clientY;
 | 
					    origFigY = fig0.clientY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isNotInSwipeToBackArea =
 | 
					    if (isNotInIOSSwipeToBackArea(fig0.clientX)) {
 | 
				
			||||||
      (fig0.clientX > 22 && fig0.clientX < window.innerWidth - 22) ||
 | 
					 | 
				
			||||||
      (fig0.clientX < -22 && fig0.clientX > window.innerWidth + 22);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isNotInSwipeToBackArea) {
 | 
					 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // Prevent the default swipe to back/forward on iOS
 | 
					    // Prevent the default swipe to back/forward on iOS
 | 
				
			||||||
| 
						 | 
					@ -400,6 +437,21 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
 | 
				
			||||||
    event.preventDefault();
 | 
					    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 = (
 | 
					  const onDialogTouchMove = (
 | 
				
			||||||
    event: TouchEvent & { currentTarget: HTMLDialogElement },
 | 
					    event: TouchEvent & { currentTarget: HTMLDialogElement },
 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
| 
						 | 
					@ -429,13 +481,13 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
 | 
				
			||||||
    event.preventDefault();
 | 
					    event.preventDefault();
 | 
				
			||||||
    event.stopPropagation();
 | 
					    event.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const pc = ofsX / origWidth / window.devicePixelRatio;
 | 
					    nextAnimationProgress = ofsX / origWidth / window.devicePixelRatio;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { activeDuration, delay } =
 | 
					    if (!animationProgressUpdateRequested) {
 | 
				
			||||||
      reenterableAnimation.effect!.getComputedTiming();
 | 
					      animationProgressUpdateRequested = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const totalTime = (delay || 0) + Number(activeDuration);
 | 
					      requestAnimationFrame(updateAnimationProgress)
 | 
				
			||||||
    reenterableAnimation.currentTime = totalTime * pc;
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onDialogTouchEnd = (event: TouchEvent) => {
 | 
					  const onDialogTouchEnd = (event: TouchEvent) => {
 | 
				
			||||||
| 
						 | 
					@ -492,6 +544,7 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
 | 
				
			||||||
                    class="StackedPage"
 | 
					                    class="StackedPage"
 | 
				
			||||||
                    id={frame().rootId}
 | 
					                    id={frame().rootId}
 | 
				
			||||||
                    role="presentation"
 | 
					                    role="presentation"
 | 
				
			||||||
 | 
					                    on:touchstart={onEntryTouchStart}
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    <StaticRouter url={frame().path} {...oprops} />
 | 
					                    <StaticRouter url={frame().path} {...oprops} />
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ import { Refresh as RefreshIcon } from "@suid/icons-material";
 | 
				
			||||||
import { CircularProgress } from "@suid/material";
 | 
					import { CircularProgress } from "@suid/material";
 | 
				
			||||||
import { makeEventListener } from "@solid-primitives/event-listener";
 | 
					import { makeEventListener } from "@solid-primitives/event-listener";
 | 
				
			||||||
import { createVisibilityObserver } from "@solid-primitives/intersection-observer";
 | 
					import { createVisibilityObserver } from "@solid-primitives/intersection-observer";
 | 
				
			||||||
import { useMaybeIsFrameSuspended } from "../platform/StackedRouter";
 | 
					import { useIsFrameSuspended } from "../platform/StackedRouter";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const PullDownToRefresh: Component<{
 | 
					const PullDownToRefresh: Component<{
 | 
				
			||||||
  loading?: boolean;
 | 
					  loading?: boolean;
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@ const PullDownToRefresh: Component<{
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const rootVisible = obvx(() => rootElement);
 | 
					  const rootVisible = obvx(() => rootElement);
 | 
				
			||||||
  const isFrameSuspended = useMaybeIsFrameSuspended()
 | 
					  const isFrameSuspended = useIsFrameSuspended()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createEffect(() => {
 | 
					  createEffect(() => {
 | 
				
			||||||
    if (!rootVisible()) setPullDown(0);
 | 
					    if (!rootVisible()) setPullDown(0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue