import negateValue from './negateValue'
import corePluginList from '../corePluginList'
import configurePlugins from './configurePlugins'
import defaultConfig from '../../stubs/defaultConfig.stub'
import colors from '../public/colors'
import { defaults } from './defaults'
import { toPath } from './toPath'
import { normalizeConfig } from './normalizeConfig'
import isPlainObject from './isPlainObject'
import { cloneDeep } from './cloneDeep'
function isFunction(input) {
return typeof input === 'function'
}
function isObject(input) {
return typeof input === 'object' && input !== null
}
function mergeWith(target, ...sources) {
let customizer = sources.pop()
for (let source of sources) {
for (let k in source) {
let merged = customizer(target[k], source[k])
if (merged === undefined) {
if (isObject(target[k]) && isObject(source[k])) {
target[k] = mergeWith(target[k], source[k], customizer)
} else {
target[k] = source[k]
}
} else {
target[k] = merged
}
}
}
return target
}
const configUtils = {
colors,
negative(scale) {
return Object.keys(scale)
.filter((key) => scale[key] !== '0')
.reduce((negativeScale, key) => {
let negativeValue = negateValue(scale[key])
if (negativeValue !== undefined) {
negativeScale[`-${key}`] = negativeValue
}
return negativeScale
}, {})
},
breakpoints(screens) {
return Object.keys(screens)
.filter((key) => typeof screens[key] === 'string')
.reduce(
(breakpoints, key) => ({
...breakpoints,
[`screen-${key}`]: screens[key],
}),
{}
)
},
rgb(property) {
if (!property.startsWith('--')) {
throw new Error(
'The rgb() helper requires a custom property name to be passed as the first argument.'
)
}
return ({ opacityValue }) => {
if (opacityValue === undefined || opacityValue === 1) {
return `rgb(var(${property}) / 1.0)`
}
return `rgb(var(${property}) / ${opacityValue})`
}
},
hsl(property) {
if (!property.startsWith('--')) {
throw new Error(
'The hsl() helper requires a custom property name to be passed as the first argument.'
)
}
return ({ opacityValue }) => {
if (opacityValue === undefined || opacityValue === 1) {
return `hsl(var(${property}) / 1)`
}
return `hsl(var(${property}) / ${opacityValue})`
}
},
}
function value(valueToResolve, ...args) {
return isFunction(valueToResolve) ? valueToResolve(...args) : valueToResolve
}
function collectExtends(items) {
return items.reduce((merged, { extend }) => {
return mergeWith(merged, extend, (mergedValue, extendValue) => {
if (mergedValue === undefined) {
return [extendValue]
}
if (Array.isArray(mergedValue)) {
return [extendValue, ...mergedValue]
}
return [extendValue, mergedValue]
})
}, {})
}
function mergeThemes(themes) {
return {
...themes.reduce((merged, theme) => defaults(merged, theme), {}),
extend: collectExtends(themes),
}
}
function mergeExtensionCustomizer(merged, value) {
if (Array.isArray(merged) && isObject(merged[0])) {
return merged.concat(value)
}
if (Array.isArray(value) && isObject(value[0]) && isObject(merged)) {
return [merged, ...value]
}
if (Array.isArray(value)) {
return value
}
return undefined
}
function mergeExtensions({ extend, ...theme }) {
return mergeWith(theme, extend, (themeValue, extensions) => {
if (!isFunction(themeValue) && !extensions.some(isFunction)) {
return mergeWith({}, themeValue, ...extensions, mergeExtensionCustomizer)
}
return (resolveThemePath, utils) =>
mergeWith(
{},
...[themeValue, ...extensions].map((e) => value(e, resolveThemePath, utils)),
mergeExtensionCustomizer
)
})
}
function resolveFunctionKeys(object) {
const resolvePath = (key, defaultValue) => {
const path = toPath(key)
let index = 0
let val = object
while (val !== undefined && val !== null && index < path.length) {
val = val[path[index++]]
val = isFunction(val) ? val(resolvePath, configUtils) : val
}
if (val === undefined) {
return defaultValue
}
if (isPlainObject(val)) {
return cloneDeep(val)
}
return val
}
resolvePath.theme = resolvePath
for (let key in configUtils) {
resolvePath[key] = configUtils[key]
}
return Object.keys(object).reduce((resolved, key) => {
return {
...resolved,
[key]: isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key],
}
}, {})
}
function extractPluginConfigs(configs) {
let allConfigs = []
configs.forEach((config) => {
allConfigs = [...allConfigs, config]
const plugins = config?.plugins ?? []
if (plugins.length === 0) {
return
}
plugins.forEach((plugin) => {
if (plugin.__isOptionsFunction) {
plugin = plugin()
}
allConfigs = [...allConfigs, ...extractPluginConfigs([plugin?.config ?? {}])]
})
})
return allConfigs
}
function resolveCorePlugins(corePluginConfigs) {
const result = [...corePluginConfigs].reduceRight((resolved, corePluginConfig) => {
if (isFunction(corePluginConfig)) {
return corePluginConfig({ corePlugins: resolved })
}
return configurePlugins(corePluginConfig, resolved)
}, corePluginList)
return result
}
function resolvePluginLists(pluginLists) {
const result = [...pluginLists].reduceRight((resolved, pluginList) => {
return [...resolved, ...pluginList]
}, [])
return result
}
export default function resolveConfig(configs) {
let allConfigs = [
...extractPluginConfigs(configs),
{
prefix: '',
important: false,
separator: ':',
variantOrder: defaultConfig.variantOrder,
},
]
return normalizeConfig(
defaults(
{
theme: resolveFunctionKeys(
mergeExtensions(mergeThemes(allConfigs.map((t) => t?.theme ?? {})))
),
corePlugins: resolveCorePlugins(allConfigs.map((c) => c.corePlugins)),
plugins: resolvePluginLists(configs.map((c) => c?.plugins ?? [])),
},
...allConfigs
)
)
}