StackedRouter: fixing browser backwards
* Supports hot reload
This commit is contained in:
parent
62a80ddce2
commit
fe39675d0c
1 changed files with 101 additions and 19 deletions
|
@ -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--;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue