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,
|
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--;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue