import * as ValueParser from '../value-parser'
import { walk, WalkAction } from '../walk'
import { isNamedColor } from './is-color'
import { segment } from './segment'
const KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
const LENGTH_FUNCTIONS = new Set(['calc', 'clamp', 'max', 'min', '--spacing'])
const COLOR_FUNCTIONS = new Set([
'color',
'color-mix',
'contrast-color',
'device-cmyk',
'hsl',
'hsla',
'hwb',
'lab',
'lch',
'light-dark',
'oklab',
'oklch',
'rgb',
'rgba',
'--alpha',
])
const LENGTH = /^-?(\d+|\.\d+)(.*?)$/
export function replaceShadowColors(input: string, replacement: (color: string) => string) {
function replaceAst(node: ValueParser.ValueAstNode): ValueParser.ValueAstNode[] {
let color = ValueParser.toCss([node])
let updatedColor = replacement(color)
let ast = ValueParser.parse(updatedColor)
return ast
}
let shadows = segment(input, ',').map((shadow) => {
shadow = shadow.trim()
let ast = ValueParser.parse(shadow)
let unknown: ValueParser.ValueAstNode | null = null
let unknowns = 0
let lengths = 0
let replaced = false
walk(ast, (node) => {
switch (node.kind) {
case 'word': {
if (KEYWORDS.has(node.value.toLowerCase())) {
return WalkAction.Continue
}
if (LENGTH.test(node.value.toLowerCase())) {
lengths++
return WalkAction.Continue
}
if (node.value[0] === '#' || isNamedColor(node.value)) {
replaced = true
return WalkAction.ReplaceStop(replaceAst(node))
}
unknown = node
unknowns++
break
}
case 'function': {
if (COLOR_FUNCTIONS.has(node.value.toLowerCase())) {
replaced = true
return WalkAction.ReplaceStop(replaceAst(node))
}
if (LENGTH_FUNCTIONS.has(node.value.toLowerCase())) {
lengths++
return WalkAction.Skip
}
unknown = node
unknowns++
return WalkAction.Skip
}
case 'separator':
return WalkAction.Continue
default:
node satisfies never
}
})
if (replaced) {
return ValueParser.toCss(ast)
}
if (lengths < 2) {
return shadow
}
if (unknowns === 0) {
return `${shadow} ${replacement('currentcolor')}`
}
if (unknowns === 1) {
walk(ast, (node) => {
if (node === unknown) {
replaced = true
return WalkAction.ReplaceStop(replaceAst(node))
}
return WalkAction.Skip
})
}
return replaced ? ValueParser.toCss(ast) : shadow
})
return shadows.join(', ')
}