1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
const dlv = require('dlv')
import { State } from './state'
import removeMeta from './removeMeta'
import { TinyColor } from '@ctrl/tinycolor'
import { ensureArray, dedupe, flatten } from './array'
const COLOR_PROPS = [
'caret-color',
'color',
'column-rule-color',
'background-color',
'border-color',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
'fill',
'outline-color',
'stop-color',
'stroke',
'text-decoration-color',
]
function isKeyword(value: string): boolean {
return ['transparent', 'currentcolor'].includes(value.toLowerCase())
}
export function getColor(
state: State,
keys: string[]
): TinyColor | string | null {
const item = dlv(state.classNames.classNames, [...keys, '__info'])
if (!item.__rule) return null
const props = Object.keys(removeMeta(item))
if (props.length === 0) return null
const nonCustomProps = props.filter((prop) => !prop.startsWith('--'))
const areAllCustom = nonCustomProps.length === 0
if (
!areAllCustom &&
nonCustomProps.some((prop) => !COLOR_PROPS.includes(prop))
) {
// they should all be color-based props
return null
}
const propsToCheck = areAllCustom ? props : nonCustomProps
const colors = flatten(
propsToCheck.map((prop) => ensureArray(item[prop]).map(createColor))
)
// check that all of the values are valid colors
if (colors.some((color) => typeof color !== 'string' && !color.isValid)) {
return null
}
// check that all of the values are the same color, ignoring alpha
const colorStrings = dedupe(
colors.map((color) =>
typeof color === 'string' ? color : `${color.r}-${color.g}-${color.b}`
)
)
if (colorStrings.length !== 1) {
return null
}
if (isKeyword(colorStrings[0])) {
return colorStrings[0]
}
const nonKeywordColors = colors.filter(
(color): color is TinyColor => typeof color !== 'string'
)
const alphas = dedupe(nonKeywordColors.map((color) => color.a))
if (alphas.length === 1) {
return nonKeywordColors[0]
}
if (alphas.length === 2 && alphas.includes(0)) {
return nonKeywordColors.find((color) => color.a !== 0)
}
return null
}
export function getColorFromValue(value: unknown): string {
if (typeof value !== 'string') return null
if (value === 'transparent') {
return 'rgba(0, 0, 0, 0.01)'
}
const color = new TinyColor(value)
if (color.isValid) {
return color.toRgbString()
}
return null
}
function createColor(str: string): TinyColor | string {
if (isKeyword(str)) {
return str
}
// matches: rgba(<r>, <g>, <b>, var(--bg-opacity))
// TODO: support other formats? e.g. hsla, css level 4
const match = str.match(
/^\s*rgba\(\s*(?<r>[0-9]{1,3})\s*,\s*(?<g>[0-9]{1,3})\s*,\s*(?<b>[0-9]{1,3})\s*,\s*var/
)
if (match) {
return new TinyColor({
r: match.groups.r,
g: match.groups.g,
b: match.groups.b,
})
}
return new TinyColor(str)
}
|