(input: string | undefined | null)
| 89 | |
| 90 | /** Parse `#rgb`, `#rrggbb`, `rgb(r,g,b)`, or `r g b` / `r, g, b`. */ |
| 91 | export function parseColor(input: string | undefined | null): Rgb | null { |
| 92 | if (!input) return null |
| 93 | const s = String(input).trim() |
| 94 | let m = /^#?([0-9a-f]{3})$/i.exec(s) |
| 95 | if (m) { |
| 96 | const h = m[1] |
| 97 | return [ |
| 98 | parseInt(h[0] + h[0], 16), |
| 99 | parseInt(h[1] + h[1], 16), |
| 100 | parseInt(h[2] + h[2], 16) |
| 101 | ] |
| 102 | } |
| 103 | m = /^#?([0-9a-f]{6})$/i.exec(s) |
| 104 | if (m) { |
| 105 | const h = m[1] |
| 106 | return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)] |
| 107 | } |
| 108 | const body = /^rgba?\(([^)]+)\)$/i.exec(s)?.[1] ?? s |
| 109 | const parts = body |
| 110 | .split(/[\s,]+/) |
| 111 | .filter(Boolean) |
| 112 | .map(Number) |
| 113 | if (parts.length >= 3 && parts.slice(0, 3).every((n) => Number.isFinite(n) && n >= 0 && n <= 255)) { |
| 114 | return [parts[0], parts[1], parts[2]] |
| 115 | } |
| 116 | return null |
| 117 | } |
| 118 | |
| 119 | /** Linear blend from `a` to `b`; `t=0` → a, `t=1` → b. */ |
| 120 | const mix = (a: Rgb, b: Rgb, t: number): Rgb => [ |
no outgoing calls
no test coverage detected