Files
HMDesign/packages/ui/src/theme/loader.ts
T

37 lines
1.9 KiB
TypeScript

import { defaultRuntimeTheme, type RuntimeTheme } from "./contract"
import { applyRuntimeTheme, isRuntimeTheme, resetRuntimeTheme } from "./runtime"
import { beginThemeRequest, isCurrentThemeRequest } from "./requests"
/** Fetch/validate only. No DOM access, fixed URL, storage or framework dependency. */
export async function fetchRuntimeTheme(url: string | URL, request?: RequestInit): Promise<RuntimeTheme> {
const response = await fetch(url, request)
if (!response.ok) throw new Error(`Theme request failed: HTTP ${response.status}`)
const theme: unknown = await response.json()
if (!isRuntimeTheme(theme)) throw new Error("Invalid Runtime Theme v1 document")
return { schemaVersion: 1, tokens: { ...theme.tokens } }
}
/** Resolve a supplied default + partial override using the existing v1 contract. */
export function resolveRuntimeTheme(theme: unknown, defaults: unknown = defaultRuntimeTheme): RuntimeTheme {
if (!isRuntimeTheme(defaults) || !isRuntimeTheme(theme)) throw new Error("Invalid Runtime Theme v1 document")
return { schemaVersion: 1, tokens: { ...defaultRuntimeTheme.tokens, ...defaults.tokens, ...theme.tokens } }
}
export type RuntimeThemeLoadOptions = { request?: RequestInit; defaults?: RuntimeTheme }
/** Convenience path: CDN/settings URL -> validate -> optional merge -> apply.
* Failure releases this API's override to the host/HM CSS fallback.
* Superseded requests never apply or reset the newer owner's theme.
*/
export async function loadRuntimeTheme(root: HTMLElement, url: string | URL, options: RuntimeThemeLoadOptions = {}): Promise<boolean> {
const request = beginThemeRequest(root)
try {
const theme = await fetchRuntimeTheme(url, options.request)
if (!isCurrentThemeRequest(root, request)) return false
return applyRuntimeTheme(root, options.defaults ? resolveRuntimeTheme(theme, options.defaults) : theme)
} catch {
if (isCurrentThemeRequest(root, request)) resetRuntimeTheme(root)
return false
}
}