Initial commit: HMDesignSystem baseline
This commit is contained in:
commit
b6a0a61103
486 files changed
+68432
No files matched your search
@@ -0,0 +1,56 @@
|
||||
import { runtimeThemeContract, runtimeGradientGroups, type RuntimeThemeToken } from "./contract"
|
||||
import { invalidateThemeRequest } from "./requests"
|
||||
|
||||
type SavedProperty = { property: string; value: string; priority: string }
|
||||
const applied = new WeakMap<HTMLElement, { properties: SavedProperty[]; scope: string | null }>()
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
|
||||
/** Restore the host's previous inline values and scope. */
|
||||
export function resetRuntimeTheme(root: HTMLElement) {
|
||||
invalidateThemeRequest(root)
|
||||
const previous = applied.get(root)
|
||||
if (!previous) return
|
||||
for (const { property, value, priority } of previous.properties) {
|
||||
if (value) root.style.setProperty(property, value, priority)
|
||||
else root.style.removeProperty(property)
|
||||
}
|
||||
if (previous.scope === null) root.removeAttribute("data-hm-theme")
|
||||
else root.setAttribute("data-hm-theme", previous.scope)
|
||||
applied.delete(root)
|
||||
}
|
||||
|
||||
/** Validate without touching the DOM (editors/loaders can preserve their last valid value). */
|
||||
export function isRuntimeTheme(theme: unknown): theme is import("./contract").RuntimeTheme {
|
||||
if (!isRecord(theme) || theme.schemaVersion !== 1 || !isRecord(theme.tokens)) return false
|
||||
const tokens = theme.tokens
|
||||
for (const group of runtimeGradientGroups) {
|
||||
const count = group.filter(key => Object.hasOwn(tokens, key)).length
|
||||
if (count !== 0 && count !== group.length) return false
|
||||
}
|
||||
for (const [key, value] of Object.entries(tokens)) {
|
||||
if (!Object.hasOwn(runtimeThemeContract, key) || typeof value !== "string") return false
|
||||
const definition = runtimeThemeContract[key as RuntimeThemeToken]
|
||||
const valid = definition.type === "radius"
|
||||
? /^(?:[2-9]|1[0-2])px$/.test(value)
|
||||
: /^#[0-9a-fA-F]{6}$/.test(value)
|
||||
if (!valid) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Atomic validation. Invalid documents release the previous override to host/default. */
|
||||
export function applyRuntimeTheme(root: HTMLElement, theme: unknown): boolean {
|
||||
if (!isRuntimeTheme(theme)) { resetRuntimeTheme(root); return false }
|
||||
const values = Object.entries(theme.tokens).map(([key, value]) =>
|
||||
[runtimeThemeContract[key as RuntimeThemeToken].property, value!] as const)
|
||||
resetRuntimeTheme(root)
|
||||
const scope = root.getAttribute("data-hm-theme")
|
||||
const properties = values.map(([property]) => ({
|
||||
property, value: root.style.getPropertyValue(property), priority: root.style.getPropertyPriority(property),
|
||||
}))
|
||||
root.setAttribute("data-hm-theme", "runtime")
|
||||
for (const [property, value] of values) root.style.setProperty(property, value)
|
||||
applied.set(root, { properties, scope })
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user