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,
untrack,
useContext,
onCleanup,
type Accessor,
} from "solid-js";
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.
*
@ -417,6 +442,29 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
const [stack, mutStack] = createStore([] as StackFrame[], { name: "stack" });
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>>) =>
untrack(() => {
const frame = {
@ -444,11 +492,35 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
return frame;
});
const onlyPopFrame = (depth: number) => {
const onlyPopFrameOnStack = (depth: number) => {
mutStack((o) => o.toSpliced(o.length - depth, depth));
};
const onlyPopFrame = (depth: number) => {
onlyPopFrameOnStack(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) =>
untrack(() => {
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);
}
}
if (stack.length > 1) {
const lastFrame = stack[stack.length - 1];
const element = document.getElementById(
lastFrame.rootId,
)! as HTMLDialogElement;
const createAnimation = lastFrame.animateClose ?? animateClose;
requestAnimationFrame(() => {
element.classList.add("animating");
const animation = createAnimation(element);
animation.addEventListener("finish", () => {
element.classList.remove("animating");
onlyPopFrame(depth);
});
let count = depth;
animateUntil((created) => {
if (count > 0) {
animatePopOneFrame((a) => {
a.addEventListener("finish", () => onlyPopFrame(1));
created(a);
});
}
count--;
});
} else {
onlyPopFrame(depth);
onlyPopFrame(1);
}
});
createRenderEffect(() => {
if (stack.length === 0) {
mutStack(0, {
path: window.location.pathname,
rootId: createUniqueId(),
pushFrame(window.location.pathname, {
replace: "all",
});
}
});
@ -488,10 +557,23 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
makeEventListener(window, "popstate", (event) => {
if (!event.state) return;
// TODO: verify the stack in state and handling forwards
if (stack.length === 0) {
mutStack(event.state);
mutStack(event.state || []);
} 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--;
});
}
});
});