StackedRouter: new router simulates app behaviour #45

Merged
Rubicon merged 18 commits from stacky into master 2024-11-18 10:35:30 +00:00
2 changed files with 61 additions and 5 deletions
Showing only changes of commit 169aa91e73 - Show all commits

View file

@ -3,6 +3,8 @@
display: contents; display: contents;
max-width: 100vw; max-width: 100vw;
max-width: 100dvw; max-width: 100dvw;
contain: layout;
} }
dialog.StackedPage { dialog.StackedPage {

View file

@ -2,7 +2,6 @@ import { StaticRouter, type RouterProps } from "@solidjs/router";
import { import {
Component, Component,
createContext, createContext,
createEffect,
createRenderEffect, createRenderEffect,
createUniqueId, createUniqueId,
Index, Index,
@ -34,8 +33,33 @@ export type NewFrameOptions<T> = (T extends undefined
state?: T; state?: T;
} }
: { state: T }) & { : { state: T }) & {
/**
* The new frame should replace the current frame.
*/
replace?: boolean; replace?: boolean;
/**
* The animatedOpen phase of the life cycle.
*
* You can use this hook to animate the opening
* of the frame. In this phase, the frame content is created
* and is mounted to the document.
*
* You must return an {@link Animation}. This function must be
* without side effects. This phase is ended after the {@link Animation}
* finished.
*/
animateOpen?: StackFrame["animateOpen"]; animateOpen?: StackFrame["animateOpen"];
/**
* The animatedClose phase of the life cycle.
*
* You can use this hook to animate the closing of the frame.
* In this phase, the frame content is still mounted in the
* document and will be unmounted after this phase.
*
* You must return an {@link Animation}. This function must be
* without side effects. This phase is ended after the
* {@link Animation} finished.
*/
animateClose?: StackFrame["animateClose"]; animateClose?: StackFrame["animateClose"];
}; };
@ -53,6 +77,10 @@ export type Navigator<PushGuide = Record<string, any>> = {
const NavigatorContext = /* @__PURE__ */ createContext<Navigator>(); const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
export function useMaybeNavigator() {
return useContext(NavigatorContext);
}
/** /**
* Get the navigator of the {@link StackedRouter}. * Get the navigator of the {@link StackedRouter}.
* *
@ -63,7 +91,7 @@ const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
* navigator to the type you need. * navigator to the type you need.
*/ */
export function useNavigator() { export function useNavigator() {
const navigator = useContext(NavigatorContext); const navigator = useMaybeNavigator();
if (!navigator) { if (!navigator) {
throw new TypeError("not in available scope of StackedRouter"); throw new TypeError("not in available scope of StackedRouter");
@ -80,8 +108,12 @@ export type CurrentFrame = {
const CurrentFrameContext = const CurrentFrameContext =
/* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>(); /* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
export function useMaybeCurrentFrame() {
return useContext(CurrentFrameContext);
}
export function useCurrentFrame() { export function useCurrentFrame() {
const frame = useContext(CurrentFrameContext); const frame = useMaybeCurrentFrame();
if (!frame) { if (!frame) {
throw new TypeError("not in available scope of StackedRouter"); throw new TypeError("not in available scope of StackedRouter");
@ -90,6 +122,28 @@ export function useCurrentFrame() {
return frame; return frame;
} }
/**
* Return an accessor of is current frame is suspended.
*
* 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.
*/
export function useMaybeIsFrameSuspended() {
const { frames } = useMaybeNavigator() || {};
if (typeof frames === "undefined") {
return () => false;
}
const thisFrame = useCurrentFrame();
return () => {
const idx = thisFrame().index;
return frames.length - 1 > idx;
};
}
function onDialogClick( function onDialogClick(
onClose: () => void, onClose: () => void,
event: MouseEvent & { currentTarget: HTMLDialogElement }, event: MouseEvent & { currentTarget: HTMLDialogElement },
@ -189,10 +243,10 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
)! as HTMLDialogElement; )! as HTMLDialogElement;
const createAnimation = lastFrame.animateClose ?? animateClose; const createAnimation = lastFrame.animateClose ?? animateClose;
requestAnimationFrame(() => { requestAnimationFrame(() => {
element.classList.add("animating") element.classList.add("animating");
const animation = createAnimation(element); const animation = createAnimation(element);
animation.addEventListener("finish", () => { animation.addEventListener("finish", () => {
element.classList.remove("animating") element.classList.remove("animating");
onlyPopFrame(depth); onlyPopFrame(depth);
}); });
}); });