UNPKG

reka-ui

Version:

Vue port for Radix UI Primitives.

148 lines (126 loc) 3.55 kB
import type { Color, HSBColor, HSLColor, RGBColor } from './types' const HEX3_RE = /^[0-9A-F]{3}$/i const HEX6_RE = /^[0-9A-F]{6}$/i const HEX8_RE = /^[0-9A-F]{8}$/i const RGB_RE = /rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)/ const HSL_RE = /hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*(?:,\s*([\d.]+)\s*)?\)/ const HSB_RE = /hsb[av]?\(\s*([\d.]+)\s*,\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*(?:,\s*([\d.]+)\s*)?\)/ /** * Parses a color string into a Color object. * Supports hex (#rrggbb, #rgb), rgb(), hsl(), and hsb()/hsv() formats. */ export function parseColor(value: string): Color { const trimmed = value.trim().toLowerCase() // Hex format if (trimmed.startsWith('#')) { return parseHex(trimmed) } // rgb() format if (trimmed.startsWith('rgb')) { return parseRgb(trimmed) } // hsl() format if (trimmed.startsWith('hsl')) { return parseHsl(trimmed) } // hsb() or hsv() format if (trimmed.startsWith('hsb') || trimmed.startsWith('hsv')) { return parseHsb(trimmed) } throw new Error(`Unable to parse color: ${value}`) } function parseHex(hex: string): RGBColor { let normalized = hex.slice(1) // Validate hex format (3, 6, or 8 hex digits) if (!HEX3_RE.test(normalized) && !HEX6_RE.test(normalized) && !HEX8_RE.test(normalized)) { throw new Error(`Invalid hex color: ${hex}. Expected format: #RGB, #RRGGBB, or #RRGGBBAA`) } // Expand shorthand (e.g., #f00 -> #ff0000) if (normalized.length === 3) { normalized = normalized.split('').map(c => c + c).join('') } // Handle 6-digit hex if (normalized.length === 6) { const bigint = parseInt(normalized, 16) return { space: 'rgb', r: (bigint >> 16) & 255, g: (bigint >> 8) & 255, b: bigint & 255, alpha: 1, } } // Handle 8-digit hex (with alpha) if (normalized.length === 8) { const bigint = parseInt(normalized, 16) return { space: 'rgb', r: (bigint >> 24) & 255, g: (bigint >> 16) & 255, b: (bigint >> 8) & 255, alpha: (bigint & 255) / 255, } } throw new Error(`Invalid hex color: ${hex}`) } function parseRgb(rgb: string): RGBColor { const match = rgb.match(RGB_RE) if (!match) { throw new Error(`Invalid RGB color: ${rgb}`) } return { space: 'rgb', r: parseFloat(match[1]), g: parseFloat(match[2]), b: parseFloat(match[3]), alpha: match[4] ? parseFloat(match[4]) : 1, } } function parseHsl(hsl: string): HSLColor { const match = hsl.match(HSL_RE) if (!match) { throw new Error(`Invalid HSL color: ${hsl}`) } return { space: 'hsl', h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]), alpha: match[4] ? parseFloat(match[4]) : 1, } } function parseHsb(hsb: string): HSBColor { const match = hsb.match(HSB_RE) if (!match) { throw new Error(`Invalid HSB color: ${hsb}`) } return { space: 'hsb', h: parseFloat(match[1]), s: parseFloat(match[2]), b: parseFloat(match[3]), alpha: match[4] ? parseFloat(match[4]) : 1, } } /** * Normalizes a value to a Color object. * If already a Color, returns it. If a string, parses it. */ export function normalizeColor(value: string | Color): Color { if (typeof value === 'string') { return parseColor(value) } return value } /** * Checks if a string is a valid color. */ export function isValidColor(value: string): boolean { try { parseColor(value) return true } catch { return false } }