Something went wrong. Try again.
Rewild Your Web
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677// SPDX-License-Identifier: AGPL-3.0-or-later
/** * Tiny state-machine helper. Mediacenter's AMBIENT / DOCK / BROWSER / * PLAYING flow is the first user; mobile's pebble (AMBIENT / GLANCE / * COMMAND) is the natural second. * * The class deliberately only tracks current + previous state. Per-state * entry/exit *behavior* lives at the call site since keeping it here would * tie this class to specific shell concerns (DOM mutations, dpad * contexts, etc.). * * Transition guards are optional: pass `transitions` in the config to * restrict which states can flow to which others. Without them, any * state can transition to any other, including itself. * * @example * const sm = new StateMachine({ * initial: "ambient", * transitions: { * ambient: ["dock"], * dock: ["ambient", "browser", "playing"], * browser: ["dock", "browser", "playing"], * playing: ["dock", "browser", "playing"], * }, * }); * sm.transition("dock"); // ok: ambient → dock allowed * sm.transition("playing"); // ok: dock → playing allowed * sm.transition("ambient"); // throws: playing → ambient not allowed * sm.previous; // "dock" */export class StateMachine { /** * @param {object} config * @param {string} config.initial Starting state. * @param {Object<string, string[]>} [config.transitions] * Optional adjacency map. When omitted, all transitions are allowed. * When provided, `transition(next)` throws if `next` isn't in * `transitions[current]`. */ constructor(config = {}) { if (config.initial === undefined) { throw new Error("StateMachine requires config.initial"); } /** @type {string} */ this.current = config.initial; /** @type {string|null} */ this.previous = null; /** @type {Object<string, string[]>|null} */ this._allowed = config.transitions ?? null; }
/** * Transition to `next`. Throws when transition guards are configured * and `next` isn't reachable from `current`. * * @param {string} next */ transition(next) { if (this._allowed) { const valid = this._allowed[this.current] ?? []; if (!valid.includes(next)) { throw new Error( `StateMachine: invalid transition ${this.current} → ${next}`, ); } } this.previous = this.current; this.current = next; }
/** Convenience: is the machine currently in `state`? */ is(state) { return this.current === state; }}