import { untrack } from "solid-js"; // Stage 04 โ€” observability spike. // // `snapshot(c)` is the one-shot, untracked, plain-object representation of // a compound's state. Used by: // // - Devtools dumps. // - `JSON.stringify(snapshot(c))` for persistence / SSR payloads. // - Test assertions: `expect(snapshot(c)).toEqual({...})`. // // **Design choice โ€” explicit opt-in over auto-iteration.** A compound's // API object mixes state accessors (`c.open()`), slot getters // (`c.trigger(props?)`), setters (`c.setOpen`), methods (`c.toggle`), // directives (`c.triggerRef`), and a Provider component. Auto-iterating // own enumerable properties and "calling every function" would invoke // slot getters (allocating per-call) and the Provider component. Wrong. // // Instead: each compound declares its observable surface via the // `SNAPSHOT_KEYS` symbol, OR ships a `snapshot()` method (escape hatch). // The library's helper is a tiny dispatcher; the contract lives at the // compound's own definition site. // // Companion doc: work-left/primitive-template.md ยง2.5. export const SNAPSHOT_KEYS = Symbol.for("base-ui.snapshot-keys"); export interface Snapshottable { /** Per-compound declared observable surface. */ [SNAPSHOT_KEYS]?: readonly string[]; /** * Optional override that takes precedence over `SNAPSHOT_KEYS`. Use * when the snapshot shape is computed (e.g. async-shaped compounds * that want to project a stable `{ status, items }` payload regardless * of internal accessor names). */ snapshot?: () => Record; } /** * Read the declared state surface of a compound API object as a plain * object. Reads are untracked โ€” calling `snapshot(c)` does NOT subscribe * the caller to future state changes; it returns a frozen-in-time view. * * If neither `snapshot()` nor `SNAPSHOT_KEYS` is declared, returns `{}`. * The library does not auto-iterate own properties (see file header). */ export function snapshot(c: C): Record { return untrack(() => { const explicit = (c as Snapshottable).snapshot; if (typeof explicit === "function") { return explicit.call(c); } const keys = (c as Snapshottable)[SNAPSHOT_KEYS]; if (Array.isArray(keys)) { const out: Record = {}; for (const key of keys) { const v = (c as Record)[key]; out[key] = typeof v === "function" ? (v as () => unknown).call(c) : v; } return out; } return {}; }); } /** * Convenience for declaring `SNAPSHOT_KEYS` with type-safe key names. * Returns the key tuple; use as * * return { ...api, [SNAPSHOT_KEYS]: snapshotKeys('open', 'disabled') }; * * Compile-time check that every passed key exists on the API type. */ export function snapshotKeys( ...keys: ReadonlyArray ): readonly string[] { return keys; }