StackedRouter: new router simulates app behaviour #45
2 changed files with 61 additions and 5 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue