StackedRouter: fixing browser backwards

* Supports hot reload
This commit is contained in:
thislight 2024-11-25 17:02:02 +08:00
parent 62a80ddce2
commit fe39675d0c
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E

View file

@ -10,6 +10,7 @@ import {
Show, Show,
untrack, untrack,
useContext, useContext,
onCleanup,
type Accessor, type Accessor,
} from "solid-js"; } from "solid-js";
import { createStore, unwrap } from "solid-js/store"; import { createStore, unwrap } from "solid-js/store";
@ -374,6 +375,30 @@ function createManagedSwipeToBack(
}; };
} }
function animateUntil(
stepfn: (onCreated: (animation: Animation) => void) => void,
) {
const execStep = () => {
requestAnimationFrame(() => {
stepfn((step) => {
step.addEventListener("finish", () => {
execStep();
});
});
});
};
execStep();
}
/**
* The cache key of saved stack for hot reload.
*
* We could not use symbols because every time the hot reload the `Symbol()`
* call creates a new symbol.
*/
const $StackedRouterSavedStack = "$StackedRouterSavedStack";
/** /**
* The router that stacks the pages. * The router that stacks the pages.
* *
@ -417,6 +442,29 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" }); const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" });
const windowSize = useWindowSize(); const windowSize = useWindowSize();
if (import.meta.hot) {
const saveStack = () => {
import.meta.hot!.data[$StackedRouterSavedStack] = unwrap(stack);
console.debug("stack saved");
};
import.meta.hot.on("vite:beforeUpdate", saveStack);
onCleanup(() => import.meta.hot!.off("vite:beforeUpdate", saveStack));
const loadStack = () => {
const savedStack = import.meta.hot!.data[$StackedRouterSavedStack];
if (savedStack) {
mutStack(savedStack);
console.debug("stack loaded");
}
delete import.meta.hot!.data[$StackedRouterSavedStack];
};
createRenderEffect(() => {
loadStack()
});
}
const pushFrame = (path: string, opts?: Readonly<NewFrameOptions<any>>) => const pushFrame = (path: string, opts?: Readonly<NewFrameOptions<any>>) =>
untrack(() => { untrack(() => {
const frame = { const frame = {
@ -444,11 +492,35 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
return frame; return frame;
}); });
const onlyPopFrame = (depth: number) => { const onlyPopFrameOnStack = (depth: number) => {
mutStack((o) => o.toSpliced(o.length - depth, depth)); mutStack((o) => o.toSpliced(o.length - depth, depth));
};
const onlyPopFrame = (depth: number) => {
onlyPopFrameOnStack(depth);
window.history.go(-depth); window.history.go(-depth);
}; };
const animatePopOneFrame = (onCreated: (animation: Animation) => void) => {
const lastFrame = stack[stack.length - 1];
const element = document.getElementById(
lastFrame.rootId,
)! as HTMLDialogElement;
const createAnimation = lastFrame.animateClose ?? animateClose;
element.classList.add("animating");
const onNavAnimEnd = () => {
element.classList.remove("animating");
};
requestAnimationFrame(() => {
const animation = createAnimation(element);
animation.addEventListener("finish", onNavAnimEnd);
animation.addEventListener("cancel", onNavAnimEnd);
onCreated(animation);
});
};
const popFrame = (depth: number = 1) => const popFrame = (depth: number = 1) =>
untrack(() => { untrack(() => {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
@ -456,30 +528,27 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
console.warn("the depth to pop should not < 0, now is", depth); console.warn("the depth to pop should not < 0, now is", depth);
} }
} }
if (stack.length > 1) { if (stack.length > 1) {
const lastFrame = stack[stack.length - 1]; let count = depth;
const element = document.getElementById( animateUntil((created) => {
lastFrame.rootId, if (count > 0) {
)! as HTMLDialogElement; animatePopOneFrame((a) => {
const createAnimation = lastFrame.animateClose ?? animateClose; a.addEventListener("finish", () => onlyPopFrame(1));
requestAnimationFrame(() => { created(a);
element.classList.add("animating"); });
const animation = createAnimation(element); }
animation.addEventListener("finish", () => { count--;
element.classList.remove("animating");
onlyPopFrame(depth);
});
}); });
} else { } else {
onlyPopFrame(depth); onlyPopFrame(1);
} }
}); });
createRenderEffect(() => { createRenderEffect(() => {
if (stack.length === 0) { if (stack.length === 0) {
mutStack(0, { pushFrame(window.location.pathname, {
path: window.location.pathname, replace: "all",
rootId: createUniqueId(),
}); });
} }
}); });
@ -488,10 +557,23 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
makeEventListener(window, "popstate", (event) => { makeEventListener(window, "popstate", (event) => {
if (!event.state) return; if (!event.state) return;
// TODO: verify the stack in state and handling forwards
if (stack.length === 0) { if (stack.length === 0) {
mutStack(event.state); mutStack(event.state || []);
} else if (stack.length > event.state.length) { } else if (stack.length > event.state.length) {
popFrame(stack.length - event.state.length); let count = stack.length - event.state.length;
animateUntil((created) => {
if (count > 0) {
animatePopOneFrame((a) => {
a.addEventListener("finish", () => {
onlyPopFrameOnStack(1);
created(a);
});
});
}
count--;
});
} }
}); });
}); });