@virtualstate/app-history
Version:
Native JavaScript [app-history](https://github.com/WICG/app-history) implementation
169 lines • 5.47 kB
JavaScript
import { AppHistoryTransitionCommittedDeferred } from "./app-history-transition.js";
export const AppLocationCheckChange = Symbol.for("@virtualstate/app-history/location/checkChange");
export const AppLocationAwaitFinished = Symbol.for("@virtualstate/app-history/location/awaitFinished");
export const AppLocationTransitionURL = Symbol.for("@virtualstate/app-history/location/transitionURL");
const baseUrl = "https://html.spec.whatwg.org/";
/**
* @experimental
*/
export class AppHistoryLocation {
#options;
#appHistory;
constructor(options) {
this.#options = options;
this.#appHistory = options.appHistory;
const reset = () => {
this.#transitioningURL = undefined;
this.#initialURL = undefined;
};
this.#appHistory.addEventListener("navigate", () => {
const transition = this.#appHistory.transition;
if (transition && isCommittedAvailable(transition)) {
transition[AppHistoryTransitionCommittedDeferred]
.promise
.then(reset, reset);
}
function isCommittedAvailable(transition) {
return AppHistoryTransitionCommittedDeferred in transition;
}
});
this.#appHistory.addEventListener("currentchange", reset);
}
ancestorOrigins;
#urls = new WeakMap();
#transitioningURL;
#initialURL;
get #url() {
if (this.#transitioningURL) {
return this.#transitioningURL;
}
const { current } = this.#appHistory;
if (!current) {
const initialUrl = this.#options.initialUrl ?? "/";
this.#initialURL = typeof initialUrl === "string" ? new URL(initialUrl, baseUrl) : initialUrl;
return this.#initialURL;
}
const existing = this.#urls.get(current);
if (existing)
return existing;
const next = new URL(current.url, baseUrl);
this.#urls.set(current, next);
return next;
}
get hash() {
return this.#url.hash;
}
set hash(value) {
this.#setUrlValue("hash", value);
}
get host() {
return this.#url.host;
}
set host(value) {
this.#setUrlValue("host", value);
}
get hostname() {
return this.#url.hostname;
}
set hostname(value) {
this.#setUrlValue("hostname", value);
}
get href() {
return this.#url.href;
}
set href(value) {
this.#setUrlValue("href", value);
}
get origin() {
return this.#url.origin;
}
get pathname() {
return this.#url.pathname;
}
set pathname(value) {
this.#setUrlValue("pathname", value);
}
get port() {
return this.#url.port;
}
set port(value) {
this.#setUrlValue("port", value);
}
get protocol() {
return this.#url.protocol;
}
set protocol(value) {
this.#setUrlValue("protocol", value);
}
get search() {
return this.#url.search;
}
set search(value) {
this.#setUrlValue("search", value);
}
#setUrlValue = (key, value) => {
const currentUrlString = this.#url.toString();
const nextUrl = new URL(currentUrlString);
nextUrl[key] = value;
const nextUrlString = nextUrl.toString();
if (currentUrlString === nextUrlString) {
return;
}
void this.#transitionURL(nextUrl, () => this.#appHistory.navigate(nextUrlString));
};
async replace(url) {
return this.#transitionURL(url, (url) => this.#appHistory.navigate(url.toString(), {
replace: true
}));
}
async reload() {
return this.#awaitFinished(this.#appHistory.reload());
}
async assign(url) {
await this.#transitionURL(url, (url) => this.#appHistory.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.#url.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.#initialURL = undefined;
if (!result)
return;
const { committed, finished } = result;
await Promise.all([
committed || Promise.resolve(undefined),
finished || Promise.resolve(undefined)
]);
};
#triggerIfUrlChanged = () => {
const current = this.#url;
const currentUrl = current.toString();
const expectedUrl = this.#appHistory.current.url;
if (currentUrl !== expectedUrl) {
return this.#transitionURL(current, () => this.#appHistory.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