Skip to main content
Deno 2 is finally here 🎉️
Learn more

@virtualstate/navigation

Native JavaScript navigation implementation

Support

Node.js supported Deno supported Chromium supported Webkit supported Firefox supported

Test Coverage

Web Platform Tests 115/158 93.59%25 lines covered 93.59%25 statements covered 85.14%25 functions covered 83.62%25 branches covered

Install

Skypack

const { Navigation } = await import("https://cdn.skypack.dev/@virtualstate/navigation");

Or

import { Navigation } from "https://cdn.skypack.dev/@virtualstate/navigation";

npm / yarn / GitHub

npm i --save @virtualstate/navigation

Or

yarn add @virtualstate/navigation

Then

import { Navigation } from "@virtualstate/navigation";
import { Navigation } from "@virtualstate/navigation";

const navigation = new Navigation();

// Set initial url
navigation.navigate("/");

navigation.navigate("/skipped");

// Use .finished to wait for the transition to complete
await navigation.navigate("/awaited").finished;

Waiting for events

import { Navigation } from "@virtualstate/navigation";

const navigation = new Navigation();

navigation.addEventListener("navigate", async ({ destination }) => {
    if (destination.url === "/disallow") {
        throw new Error("No!");
    }
});

await navigation.navigate("/allowed").finished; // Resolves
await navigation.navigate("/disallow").finished; // Rejects

Transitions

import { Navigation } from "@virtualstate/navigation";
import { loadPhotoIntoCache } from "./cache";

const navigation = new Navigation();

navigation.addEventListener("navigate", async ({ destination, transitionWhile }) => {
    transitionWhile(loadPhotoIntoCache(destination.url));
});

URLPattern

You can match destination.url using URLPattern

import {Navigation} from "@virtualstate/navigation";
import {URLPattern} from "urlpattern-polyfill";

const navigation = new Navigation();

navigation.addEventListener("navigate", async ({destination, transitionWhile}) => {
    const pattern = new URLPattern({ pathname: "/books/:id" });
    const match = pattern.exec(destination.url);
    if (match) {
        transitionWhile(transition());
    }

    async function transition() {
        console.log("load book", match.pathname.groups.id)
    }
});

navigation.navigate("/book/1");

State

import { Navigation } from "@virtualstate/navigation";

const navigation = new Navigation();

navigation.addEventListener("currentchange", () => {
    console.log({ updatedState: navigation.current?.getState() });
});

await navigation.updateCurrent({
    state: {
        items: [
            "first",
            "second"
        ],
        index: 0
    }
}).finished;

await navigation.updateCurrent({
    state: {
        ...navigation.current.getState(),
        index: 1
    }
}).finished;

Updating browser url

This is a pending development task. The below code will help visually update the window

This can be achieved various ways, but if your application completely utilises the app history interface, then you can directly use pushState to immediately update the window’s url.

This does not take into account the browser’s native back/forward functionality, which would need to be investigated further.

import { Navigation } from "@virtualstate/navigation";

const navigation = new Navigation();
const origin = typeof location === "undefined" ? "https://example.com" : location.origin;

navigation.addEventListener("currentchange", () => {
    const { current } = navigation;
    if (!current || !current.sameDocument) return;
    const state = current.getState() ?? {};
    const { pathname } = new URL(current.url, origin);
    if (typeof window !== "undefined" && window.history) {
        window.history.pushState(state, state.title, origin)
    }
})