@virtualstate/navigation
Version:
Native JavaScript [navigation](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API) implementation
176 lines • 5.82 kB
JavaScript
import { NavigationTransitionCommittedDeferred } from "./navigation-transition.js";
import { getBaseURL } from "./base-url.js";
export const AppLocationCheckChange = Symbol.for("@virtualstate/navigation/location/checkChange");
export const AppLocationAwaitFinished = Symbol.for("@virtualstate/navigation/location/awaitFinished");
export const AppLocationTransitionURL = Symbol.for("@virtualstate/navigation/location/transitionURL");
export const AppLocationUrl = Symbol.for("@virtualstate/navigation/location/url");
export const NAVIGATION_LOCATION_DEFAULT_URL = "https://html.spec.whatwg.org/";
/**
* @experimental
*/
export class NavigationLocation {
#options;
#navigation;
constructor(options) {
this.#options = options;
this.#navigation = options.navigation;
const reset = () => {
this.#transitioningURL = undefined;
this.#baseURL = undefined;
};
this.#navigation.addEventListener("navigate", () => {
const transition = this.#navigation.transition;
if (transition && isCommittedAvailable(transition)) {
transition[NavigationTransitionCommittedDeferred].promise.then(reset, reset);
}
function isCommittedAvailable(transition) {
return NavigationTransitionCommittedDeferred in transition;
}
});
this.#navigation.addEventListener("currententrychange", reset);
}
#urls = new WeakMap();
#transitioningURL;
#baseURL;
get [AppLocationUrl]() {
if (this.#transitioningURL) {
return this.#transitioningURL;
}
const { currentEntry } = this.#navigation;
if (!currentEntry) {
this.#baseURL = getBaseURL(this.#options.baseURL);
return this.#baseURL;
}
const existing = this.#urls.get(currentEntry);
if (existing)
return existing;
const next = new URL(currentEntry.url ?? NAVIGATION_LOCATION_DEFAULT_URL);
this.#urls.set(currentEntry, next);
return next;
}
get hash() {
return this[AppLocationUrl].hash;
}
set hash(value) {
this.#setUrlValue("hash", value);
}
get host() {
return this[AppLocationUrl].host;
}
set host(value) {
this.#setUrlValue("host", value);
}
get hostname() {
return this[AppLocationUrl].hostname;
}
set hostname(value) {
this.#setUrlValue("hostname", value);
}
get href() {
return this[AppLocationUrl].href;
}
set href(value) {
this.#setUrlValue("href", value);
}
get origin() {
return this[AppLocationUrl].origin;
}
get pathname() {
return this[AppLocationUrl].pathname;
}
set pathname(value) {
this.#setUrlValue("pathname", value);
}
get port() {
return this[AppLocationUrl].port;
}
set port(value) {
this.#setUrlValue("port", value);
}
get protocol() {
return this[AppLocationUrl].protocol;
}
set protocol(value) {
this.#setUrlValue("protocol", value);
}
get search() {
return this[AppLocationUrl].search;
}
set search(value) {
this.#setUrlValue("search", value);
}
#setUrlValue = (key, value) => {
const currentUrlString = this[AppLocationUrl].toString();
let nextUrl;
if (key === "href") {
nextUrl = new URL(value, currentUrlString);
}
else {
nextUrl = new URL(currentUrlString);
nextUrl[key] = value;
}
const nextUrlString = nextUrl.toString();
if (currentUrlString === nextUrlString) {
return;
}
void this.#transitionURL(nextUrl, () => this.#navigation.navigate(nextUrlString));
};
replace(url) {
return this.#transitionURL(url, (url) => this.#navigation.navigate(url.toString(), {
history: "replace",
}));
}
reload() {
return this.#awaitFinished(this.#navigation.reload());
}
assign(url) {
return this.#transitionURL(url, (url) => this.#navigation.navigate(url.toString()));
}
[AppLocationTransitionURL](url, fn) {
return this.#transitionURL(url, fn);
}
#transitionURL = async (url, fn) => {
const instance = (this.#transitioningURL =
typeof url === "string"
? new URL(url, this[AppLocationUrl].toString())
: url);
try {
await this.#awaitFinished(fn(instance));
}
finally {
if (this.#transitioningURL === instance) {
this.#transitioningURL = undefined;
}
}
};
[AppLocationAwaitFinished](result) {
return this.#awaitFinished(result);
}
#awaitFinished = async (result) => {
this.#baseURL = undefined;
if (!result)
return;
const { committed, finished } = result;
await Promise.all([
committed || Promise.resolve(undefined),
finished || Promise.resolve(undefined),
]);
};
#triggerIfUrlChanged = () => {
const current = this[AppLocationUrl];
const currentUrl = current.toString();
const expectedUrl = this.#navigation.currentEntry?.url;
if (currentUrl !== expectedUrl) {
return this.#transitionURL(current, () => this.#navigation.navigate(currentUrl));
}
};
/**
* This is needed if you have changed searchParams using its mutating methods
*
* TODO replace get searchParams with an observable change to auto trigger this function
*/
[AppLocationCheckChange]() {
return this.#triggerIfUrlChanged();
}
}
//# sourceMappingURL=location.js.map