/** * Reads the resolved variables for the current composition. * * Resolves to declared defaults from `` * merged with `window.__hfVariables` (set at render time by the engine when * the user passes `hyperframes render --variables ''`). * * Returns `Partial` because not every declared variable is guaranteed to * have a default, and not every key in `__hfVariables` is guaranteed to be * declared. Callers are expected to destructure with their own fallbacks * where strictness matters: * * const { title = "Untitled", theme = "light" } = getVariables(); */ export function getVariables< T extends Record = Record, >(): Partial { if (typeof document === "undefined") return {} as Partial; const declaredDefaults = readDeclaredDefaults(document.documentElement); const overrides = readOverrides(); return { ...declaredDefaults, ...overrides } as Partial; } function readDeclaredDefaults(root: Element | null): Record { if (!root) return {}; const raw = root.getAttribute("data-composition-variables"); if (!raw) return {}; let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return {}; } if (!Array.isArray(parsed)) return {}; const out: Record = {}; for (const entry of parsed) { if ( entry && typeof entry === "object" && typeof (entry as { id?: unknown }).id === "string" && "default" in entry ) { out[(entry as { id: string }).id] = (entry as { default: unknown }).default; } } return out; } function readOverrides(): Record { if (typeof window === "undefined") return {}; const raw = (window as Window & { __hfVariables?: unknown }).__hfVariables; if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {}; return raw as Record; }