import tinycolor, { ColorFormats } from 'tinycolor2'; import { RGBA, HSV, IPalettes, CSS_DEFAULT_PALETTES } from '@vev/utils'; export function rgbToHex(color: RGBA | string | number[]): string { let hex: string; if (typeof color === 'string') { hex = color; } else if (Array.isArray(color)) { hex = getHexValue(color[0]) + getHexValue(color[1]) + getHexValue(color[2]); } else { hex = getHexValue(color.r) + getHexValue(color.g) + getHexValue(color.b); } if (hex[0] === hex[1] && hex[2] === hex[3] && hex[4] === hex[5]) { return `${hex[0]}${hex[2]}${hex[4]}`; } return hex; } export function getColorVariableValue( color: string, variables: { [varName: string]: string }, ): string { const varName = color.trim().replace(/^var\((.*)\)$/, '$1'); return variables[varName]; } export function getVariableValueV2( color: string, variables: { key: string; value?: string }[], ): string | undefined { // Strip var(--vev-...) wrapper, ignoring optional CSS fallback after comma const match = color.trim().match(/^var\(--vev-([^,)]+)/); if (!match) return undefined; return variables.find((v) => v.key === match[1])?.value; } export function isColorVariable(color: string): boolean { return !!color && /^var\(/.test(color.trim()); } export function isVariableV2(color: string | undefined): boolean { return !!color && color.startsWith('var(--vev-'); } export function rgbaToString(rgba: RGBA | number[] | string): string { if (!rgba) { return ''; } if (typeof rgba === 'string') { return rgba; } let r: number, g: number, b: number, a: number | undefined; if (Array.isArray(rgba)) { [r, g, b, a] = rgba; if (typeof r === 'string') { return r; } } else { ({ r, g, b, a } = rgba); } if (a === 1) { return '#' + rgbToHex(rgba); } return `rgba(${r},${g},${b},${typeof a === 'undefined' ? 1 : a})`; } type Palettes = { key: 'primary' | 'accent' | 'bg' | 'text' | 'project'; label: string; colors: string[]; }[]; type PaletteColorKey = keyof Omit; export function buildPalettes(variables: { [variableName: string]: string }): Palettes { const keys = ['primary', 'accent', 'bg', 'text']; return keys.map((key) => { const colors: string[] = []; for (let i = 0; i < 7; i++) { const value = variables[`--${key}${i}`]; if (value) colors.push(value); } return { key, label: key, colors, }; }) as Palettes; } export function getPaletteFromVariables(variables: { [variableName: string]: string }) { const res: { [variableName: string]: string } = {}; for (const key of ['primary', 'accent', 'bg', 'text'] as (keyof IPalettes)[]) { res[key] = variables[key] || (CSS_DEFAULT_PALETTES[key] as string); } return res as IPalettes; } export function getSwatchesFromVariables(variables: { [variableName: string]: string }) { const res: string[] = []; Object.keys(variables).map((v) => { if (/^--swatch/g.test(v)) res.push(variables[v] as string); }); return res; } export function getPalette(color?: string, baseDark?: string): string[] { if (!color) throw new Error('Missing color'); const baseLight = tinycolor('#ffffff'); const dark = !baseDark ? multiplyColor(tinycolor(color).toRgb(), tinycolor(color).toRgb()) : tinycolor(baseDark).toRgb(); return [ tinycolor.mix(baseLight, color, 12).toRgbString(), tinycolor.mix(baseLight, color, 50).toRgbString(), tinycolor.mix(baseLight, color, 75).toRgbString(), tinycolor(color).toRgbString(), tinycolor.mix(dark, color, 66).toRgbString(), tinycolor.mix(dark, color, 33).toRgbString(), tinycolor.mix(dark, color, 0).toRgbString(), ]; } export function getTextPalette(primaryLightText: boolean): string[] { const dark = [0.87, 0.54, 0.38].map((a) => `rgba(0, 0, 0, ${a})`); const white = [0.5, 0.7, 1].map((a) => `rgba(255, 255, 255, ${a})`); return primaryLightText ? white.concat(dark) : dark.concat(white); } function multiplyColor(rgb1: RGBA, rgb2: RGBA): RGBA { const factor = 0.7; return { ...rgb1, b: Math.floor((rgb1.b * rgb2.b * factor) / 255), g: Math.floor((rgb1.g * rgb2.g * factor) / 255), r: Math.floor((rgb1.r * rgb2.r * factor) / 255), }; } function getHexValue(value = 0): string { return ('0' + value.toString(16)).slice(-2); } export function colorFromArray(array: number[], startIndex = 0, endIndex = array.length): string { if (array[endIndex - 1] === 1) { return rgbToHex([array[startIndex], array[startIndex + 1], array[startIndex + 2]]); } let res = 'rgba('; for (let i = startIndex; i < endIndex; i++) { res += array[i] + ','; } return res.substr(0, res.length - 1) + ')'; } export function hexToRgb(hex: string): RGBA { const colors = ['r', 'g', 'b'] as (keyof RGBA)[]; const color: RGBA = { r: 0, g: 0, b: 0 }; const step = Math.ceil(hex.length / 3); for (let i = 0; i < colors.length; i++) { let colorValue = hex.substr(i * step, step) || '00'; if (colorValue.length === 1) { colorValue += colorValue; } color[colors[i]] = Math.max(0, Math.min(255, parseInt(colorValue, 16) || 0)); } return color; } export function hsvToHslString(hsv: HSV): string { return 'hsl(' + Math.round((hsv.h || 0) * 360) + ',100%, 50%)'; } export function getHue({ r, g, b }: RGBA): number { const max = Math.max(r, g, b); const min = Math.min(r, g, b); const delta = max - min; let hue: number; if (max === r) { hue = (g - b) / delta; } else if (max === g) { hue = 2 + (b - r) / delta; } else { hue = 4 + (r - g) / delta; } hue = hue * 60; if (hue < 0) { hue = hue + 360; } return Math.round(hue); } export function rgbToHsv( hsv: Partial, { r, g, b, a }: ColorFormats.RGBA, ): ColorFormats.HSVA { const result = { ...hsv }; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const d = max - min; const s = max === 0 ? 0 : d / max; const v = max / 255; let h: number; switch (max) { case min: h = 0; break; case r: h = g - b + d * (g < b ? 6 : 0); h /= 6 * d; break; case g: h = b - r + d * 2; h /= 6 * d; break; default: h = r - g + d * 4; h /= 6 * d; } result.v = v; if (v) { result.s = s; } // Don't update hue if s or v is 0 because it will screw up the hsl color in the color picker if (s && v) { result.h = h; } if (a) result.a = a; return result as ColorFormats.HSVA; } export function hsvToRgb(color: ColorFormats.HSVA): RGBA { const { h = 0, s = 0, v = 0, a = 0 } = color; const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); const mod = i % 6; const r = [v, q, p, p, t, v][mod]; const g = [t, v, v, q, p, p][mod]; const b = [p, p, t, v, v, q][mod]; return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a }; }