import { ThemeCoats, ThemeObject, ThemeValueLike } from '@o/css'
import { isPlainObj } from '../helpers/helpers'
import { ThemeValue } from './ThemeValue'
export type CompiledTheme = any> = {
[key in keyof A]: A[key] extends ThemeCoats | string | number ? A[key] : ThemeValueLike
}
let id = 0
const ThemeMap = new WeakMap()
export function createTheme>(
theme: A,
prefixName: string = '',
renameKeys = true,
): CompiledTheme {
if (ThemeMap.has(theme)) {
return theme
}
const name = `${prefixName ? prefixName + '-' : ''}${theme.name || ''}` || `theme-${id++}`
const res = Object.keys(theme).reduce((acc, key) => {
let val = theme[key]
const cssVariableName = `${key}`
if (key === 'coats' && val) {
const coats = val
val = {}
for (const coatKey in coats) {
const coat = coats[coatKey]
val[coatKey] =
typeof coat === 'function'
? coat
: createTheme({ ...coat, name: coatKey, coats: undefined }, `${name}-coat`)
}
} else if (val && typeof val.setCSSVariable === 'function') {
if (renameKeys) {
const res = val.setCSSVariable(cssVariableName)
if (res) {
val = res
}
}
} else {
if (key !== 'parent' && key !== 'name' && key[0] !== '_') {
// recurse into sub-themes
if (isPlainObj(val)) {
val = createTheme(val, `${name}-sub-${key}`)
} else if (!(val instanceof ThemeValue)) {
val = new ThemeValue(cssVariableName, val)
}
}
}
acc[key] = val
return acc
}, {}) as any
res.name = name
ThemeMap.set(res, true)
return res
}
const ThemesMap = new WeakMap()
export function createThemes>(themes: {
[key: string]: A
}): { [key: string]: CompiledTheme } {
if (ThemesMap.has(themes)) {
return ThemesMap.get(themes)
}
// ensure we don't have name collisions
const existing = new Set()
const duplicates = new Set>()
const next = Object.freeze(
Object.keys(themes).reduce((acc, key) => {
let theme = themes[key]
if (typeof theme !== 'function') {
if (duplicates.has(theme)) {
acc[key] = theme!
return acc
} else {
theme.name = key
}
}
const name = theme.name!
if (process.env.NODE_ENV === 'development') {
if (existing.has(name)) {
console.warn(
'Themes error on themes:',
themes,
Object.keys(themes).map(k => `key: ${k}, name: ${themes[k].name}`),
)
throw new Error(`Name collision on theme: ${name}`)
}
existing.add(name)
}
duplicates.add(theme)
if (name) {
acc[key] = createTheme(theme)
} else {
throw new Error(`Unreachable`)
}
return acc
}, {}),
)
ThemesMap.set(themes, next)
return next
}