Back to Black Site Map Editor
SOURCE / PINNED RELEASE

Made of little things.

Black Site Map Editor

Release
32a57733b7e9…
Author-recorded commit
3f54890f542f…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/black-site-map-editor

Archive hash verified: 7a4b1abbac7db5b0…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/state/store.ts
import type { ReactiveController, ReactiveControllerHost } from 'lit';

type Listener<S> = (state: S) => void;

/** A minimal observable state container; `set` merges a patch and notifies subscribers. */
export class Store<S extends object> {
    #state: S;
    #listeners = new Set<Listener<S>>();
    constructor(initial: S) {
        this.#state = initial;
    }
    get state(): S {
        return this.#state;
    }
    set(patch: Partial<S>): void {
        this.#state = { ...this.#state, ...patch };
        for (const listener of this.#listeners) listener(this.#state);
    }
    subscribe(listener: Listener<S>): () => void {
        this.#listeners.add(listener);
        return () => this.#listeners.delete(listener);
    }
}

function sameValues(a: unknown, b: unknown): boolean {
    if (Object.is(a, b)) return true;
    if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
    return a.every((value, index) => Object.is(value, b[index]));
}

/**
 * Re-render a Lit host when its selected slice of the store changes.
 * Selectors may return a single value or an array of values compared item by item.
 */
export class StoreController<S extends object, T> implements ReactiveController {
    value: T;
    #unsubscribe?: () => void;
    constructor(
        private host: ReactiveControllerHost,
        private store: Store<S>,
        private select: (state: S) => T,
    ) {
        this.value = select(store.state);
        host.addController(this);
    }
    hostConnected(): void {
        this.value = this.select(this.store.state);
        this.#unsubscribe = this.store.subscribe((state) => {
            const next = this.select(state);
            if (sameValues(next, this.value)) return;
            this.value = next;
            this.host.requestUpdate();
        });
    }
    hostDisconnected(): void {
        this.#unsubscribe?.();
    }
}