@virtualstate/navigation
Version:
Native JavaScript [navigation](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API) implementation
99 lines • 3.63 kB
JavaScript
import { getNavigation } from "../get-navigation.js";
import { Push } from "@virtualstate/promise";
import { isPromise, ok } from "../is.js";
export function setState(state, navigation = getNavigation()) {
const currentEntryChangePromise = new Promise(resolve => {
navigation.addEventListener("currententrychange", resolve, { once: true });
});
const returned = navigation.updateCurrentEntry({
state
});
const promises = [currentEntryChangePromise];
if (isPromise(returned)) {
promises.push(returned);
}
return Promise.all(promises);
}
export function getState(navigation = getNavigation()) {
return navigation.currentEntry.getState();
}
const states = new WeakMap();
export function state(navigation = getNavigation()) {
const existing = states.get(navigation);
// Using a stable object for a navigation instance
// allows for this state object to be used in memo'd functions
if (isExisting(existing)) {
return existing;
}
// Returning an object like this allows callers to bind
// a navigation to the stateGenerator function, each time
// a new asyncIterator is created, the transition or currentEntry
// at that point will be used, meaning that this object
// too can be freely used as a static source across
// an application.
const result = {
[Symbol.asyncIterator]() {
return stateGenerator(navigation);
}
};
states.set(navigation, result);
return result;
function isExisting(existing) {
return !!existing;
}
}
export async function* stateGenerator(navigation = getNavigation()) {
let lastState = undefined, wasState = false;
let currentEntry = navigation.currentEntry;
if (navigation.transition) {
currentEntry = await navigation.transition?.finished;
}
const push = new Push();
ok(currentEntry, "Expected a currentEntry");
pushState();
navigation.addEventListener("navigate", onNavigate);
navigation.addEventListener("navigatesuccess", onNavigateSuccess);
navigation.addEventListener("navigateerror", onNavigateError, { once: true });
navigation.addEventListener("currententrychange", pushState);
currentEntry.addEventListener("dispose", close, { once: true });
yield* push;
function pushState() {
if (navigation.currentEntry.id !== currentEntry.id) {
return close();
}
const state = currentEntry.getState();
if (wasState || typeof state !== "undefined") {
if (lastState === state) {
return;
}
push.push(state);
lastState = state;
wasState = true;
}
}
function onNavigate(event) {
// Indicate that we have intercepted navigation
// and are using it as a state tracker
event.intercept?.({
handler: () => Promise.resolve()
});
}
function onNavigateSuccess() {
if (navigation.currentEntry.id !== currentEntry.id) {
close();
}
}
function onNavigateError(event) {
const { error } = event;
push.throw(error);
}
function close() {
navigation.removeEventListener("navigate", onNavigate);
navigation.removeEventListener("navigatesuccess", onNavigateSuccess);
navigation.removeEventListener("navigateerror", onNavigateError);
navigation.removeEventListener("currententrychange", pushState);
currentEntry.removeEventListener("dispose", close);
push.close();
}
}
//# sourceMappingURL=state.js.map