@virtualstate/app-history

Native JavaScript app-history implementation

Usage no npm install needed!

<script type="module">
  import virtualstateAppHistory from 'https://cdn.skypack.dev/@virtualstate/app-history';
</script>

README

@virtualstate/app-history

Native JavaScript app-history implementation

Support

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

Test Coverage

Web Platform Tests 115/158 93.54%25 lines covered 93.54%25 statements covered 85.14%25 functions covered 83.88%25 branches covered

Install

Skypack

const { AppHistory } = await import("https://cdn.skypack.dev/@virtualstate/app-history");

Or

import { AppHistory } from "https://cdn.skypack.dev/@virtualstate/app-history";

npm / yarn / GitHub

npm i --save @virtualstate/app-history

Or

yarn add @virtualstate/app-history

Then

import { AppHistory } from "@virtualstate/app-history";

Navigation

import { AppHistory } from "@virtualstate/app-history";

const appHistory = new AppHistory();

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

appHistory.navigate("/skipped");

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

Waiting for events

import { AppHistory } from "@virtualstate/app-history";

const appHistory = new AppHistory();

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

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

Transitions

import { AppHistory } from "@virtualstate/app-history";
import { loadPhotoIntoCache } from "./cache";

const appHistory = new AppHistory();

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

URLPattern

You can match destination.url using URLPattern

import {AppHistory} from "@virtualstate/app-history";
import {URLPattern} from "urlpattern-polyfill";

const appHistory = new AppHistory();

appHistory.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)
    }
});

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

State


import { AppHistory } from "@virtualstate/app-history";

const appHistory = new AppHistory();

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

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

await appHistory.updateCurrent({
    state: {
        ...appHistory.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 { AppHistory } from "@virtualstate/app-history";

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

appHistory.addEventListener("currentchange", () => {
    const { current } = appHistory;
    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)
    }
})