// Adapted from jalcoui (MIT) — github.com/jal-co/ui import type { ColorFormat } from './types'; /** * Parse a `#rrggbb` / `#rgb` hex string into `{ r, g, b }` (0–255). * Returns `null` for malformed input. */ function parseHex(hex: string): { r: number; g: number; b: number } | null { const cleaned = hex.trim().replace(/^#/, ''); if (cleaned.length === 3) { const r = parseInt(cleaned[0] + cleaned[0], 16); const g = parseInt(cleaned[1] + cleaned[1], 16); const b = parseInt(cleaned[2] + cleaned[2], 16); if ([r, g, b].some(Number.isNaN)) return null; return { r, g, b }; } if (cleaned.length === 6) { const r = parseInt(cleaned.slice(0, 2), 16); const g = parseInt(cleaned.slice(2, 4), 16); const b = parseInt(cleaned.slice(4, 6), 16); if ([r, g, b].some(Number.isNaN)) return null; return { r, g, b }; } return null; } function rgbToHsl( r: number, g: number, b: number, ): { h: number; s: number; l: number } { const rn = r / 255; const gn = g / 255; const bn = b / 255; const max = Math.max(rn, gn, bn); const min = Math.min(rn, gn, bn); const l = (max + min) / 2; let h = 0; let s = 0; if (max !== min) { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case rn: h = (gn - bn) / d + (gn < bn ? 6 : 0); break; case gn: h = (bn - rn) / d + 2; break; case bn: h = (rn - gn) / d + 4; break; } h /= 6; } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }; } /** * Format a hex color into the requested {@link ColorFormat}. Falls * back to the original string when the hex is unparseable. */ export function formatColor(hex: string, format: ColorFormat): string { if (format === 'hex') return hex.toLowerCase(); const rgb = parseHex(hex); if (!rgb) return hex; if (format === 'rgb') return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`; const { h, s, l } = rgbToHsl(rgb.r, rgb.g, rgb.b); return `hsl(${h}, ${s}%, ${l}%)`; } /** * Return `'light'` when the hex color is bright enough that black * text reads better against it, otherwise `'dark'`. Uses standard * Rec. 601 luminance (`0.299·R + 0.587·G + 0.114·B`). */ export function getReadableContrast(hex: string): 'light' | 'dark' { const rgb = parseHex(hex); if (!rgb) return 'dark'; const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255; return luminance > 0.6 ? 'light' : 'dark'; }