StackedRouter: new router simulates app behaviour #45
2 changed files with 54 additions and 18 deletions
|
@ -24,12 +24,8 @@ dialog.StackedPage {
|
|||
background: var(--tutu-color-surface);
|
||||
box-shadow: var(--tutu-shadow-e16);
|
||||
|
||||
@media (min-width: 560px) {
|
||||
& {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@media (max-width: 560px) {
|
||||
& {
|
||||
|
@ -50,4 +46,12 @@ dialog.StackedPage {
|
|||
&::backdrop {
|
||||
background: none;
|
||||
}
|
||||
|
||||
&.animating {
|
||||
overflow: hidden;
|
||||
|
||||
* {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import {
|
|||
createRenderEffect,
|
||||
createUniqueId,
|
||||
Index,
|
||||
onMount,
|
||||
Show,
|
||||
untrack,
|
||||
useContext,
|
||||
|
@ -15,9 +16,7 @@ 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 { makeEventListener } from "@solid-primitives/event-listener";
|
||||
|
||||
export type StackedRouterProps = Omit<RouterProps, "url">;
|
||||
|
||||
|
@ -25,6 +24,9 @@ export type StackFrame = {
|
|||
path: string;
|
||||
rootId: string;
|
||||
state: unknown;
|
||||
|
||||
animateOpen?: (element: HTMLElement) => Animation;
|
||||
animateClose?: (element: HTMLElement) => Animation;
|
||||
};
|
||||
|
||||
export type NewFrameOptions<T> = (T extends undefined
|
||||
|
@ -33,6 +35,8 @@ export type NewFrameOptions<T> = (T extends undefined
|
|||
}
|
||||
: { state: T }) & {
|
||||
replace?: boolean;
|
||||
animateOpen?: StackFrame["animateOpen"];
|
||||
animateClose?: StackFrame["animateClose"];
|
||||
};
|
||||
|
||||
export type FramePusher<T, K extends keyof T = keyof T> = T[K] extends
|
||||
|
@ -117,9 +121,11 @@ function animateClose(element: HTMLElement) {
|
|||
|
||||
function animateOpen(element: HTMLElement) {
|
||||
if (window.innerWidth <= 560) {
|
||||
animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION });
|
||||
return animateSlideInFromRight(element, {
|
||||
easing: ANIM_CURVE_DECELERATION,
|
||||
});
|
||||
} else {
|
||||
element.animate(
|
||||
return element.animate(
|
||||
{
|
||||
opacity: [0.5, 1],
|
||||
},
|
||||
|
@ -128,6 +134,17 @@ function animateOpen(element: HTMLElement) {
|
|||
}
|
||||
}
|
||||
|
||||
function serializableStack(stack: readonly StackFrame[]) {
|
||||
const frames = unwrap(stack);
|
||||
return frames.map((fr) => {
|
||||
return {
|
||||
path: fr.path,
|
||||
rootId: fr.rootId,
|
||||
state: fr.state,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The router that stacks the pages.
|
||||
*/
|
||||
|
@ -140,13 +157,15 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
path,
|
||||
state: opts?.state,
|
||||
rootId: createUniqueId(),
|
||||
animateOpen: opts?.animateOpen,
|
||||
animateClose: opts?.animateClose,
|
||||
};
|
||||
|
||||
mutStack(opts?.replace ? stack.length - 1 : stack.length, frame);
|
||||
if (opts?.replace) {
|
||||
window.history.replaceState(unwrap(stack), "", path);
|
||||
window.history.replaceState(serializableStack(stack), "", path);
|
||||
} else {
|
||||
window.history.pushState(unwrap(stack), "", path);
|
||||
window.history.pushState(serializableStack(stack), "", path);
|
||||
}
|
||||
return frame;
|
||||
});
|
||||
|
@ -165,10 +184,17 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
}
|
||||
if (stack.length > 1) {
|
||||
const lastFrame = stack[stack.length - 1];
|
||||
const element = document.getElementById(lastFrame.rootId)!;
|
||||
const element = document.getElementById(
|
||||
lastFrame.rootId,
|
||||
)! as HTMLDialogElement;
|
||||
const createAnimation = lastFrame.animateClose ?? animateClose;
|
||||
requestAnimationFrame(() => {
|
||||
const animation = animateClose(element);
|
||||
animation.addEventListener("finish", () => onlyPopFrame(depth));
|
||||
element.classList.add("animating")
|
||||
const animation = createAnimation(element);
|
||||
animation.addEventListener("finish", () => {
|
||||
element.classList.remove("animating")
|
||||
onlyPopFrame(depth);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
onlyPopFrame(depth);
|
||||
|
@ -195,10 +221,16 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
|||
});
|
||||
|
||||
const onBeforeDialogMount = (element: HTMLDialogElement) => {
|
||||
createEffect(() => {
|
||||
onMount(() => {
|
||||
const lastFr = untrack(() => stack[stack.length - 1]);
|
||||
const createAnimation = lastFr.animateOpen ?? animateOpen;
|
||||
requestAnimationFrame(() => {
|
||||
element.showModal();
|
||||
animateOpen(element);
|
||||
element.classList.add("animating");
|
||||
const animation = createAnimation(element);
|
||||
animation.addEventListener("finish", () =>
|
||||
element.classList.remove("animating"),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue