(value: string)
| 1173 | } |
| 1174 | |
| 1175 | export function canonicalizeColor(value: string): string | null { |
| 1176 | if (value.startsWith('#')) { |
| 1177 | return compressHex(value) |
| 1178 | } |
| 1179 | const rgb = value.match(/^rgba?\(([^)]+)\)$/) |
| 1180 | if (rgb) { |
| 1181 | const parts = rgb[1].split(',').map((p) => p.trim()) |
| 1182 | const [r, g, b, a = '1'] = parts |
| 1183 | const toInt = (v: string) => { |
| 1184 | const n = Number(v) |
| 1185 | return Number.isFinite(n) ? Math.round(n) : null |
| 1186 | } |
| 1187 | const ri = toInt(r) |
| 1188 | const gi = toInt(g) |
| 1189 | const bi = toInt(b) |
| 1190 | const an = Number(a) |
| 1191 | if ( |
| 1192 | ri != null && |
| 1193 | gi != null && |
| 1194 | bi != null && |
| 1195 | ri >= 0 && |
| 1196 | ri <= 255 && |
| 1197 | gi >= 0 && |
| 1198 | gi <= 255 && |
| 1199 | bi >= 0 && |
| 1200 | bi <= 255 |
| 1201 | ) { |
| 1202 | const hex = compressHex(formatHex(ri, gi, bi)) |
| 1203 | if (an >= 1 || Number.isNaN(an)) return hex |
| 1204 | const opacity = Math.max(0, Math.min(100, Math.round(an * 100))) |
| 1205 | return `${hex}/${opacity}` |
| 1206 | } |
| 1207 | } |
| 1208 | return null |
| 1209 | } |
| 1210 | |
| 1211 | function compressHex(hex: string): string { |
| 1212 | if (hex.length === 7 && hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) { |
no test coverage detected