/ components / Spinner / utils.ts
utils.ts
 1  import type { RGBColor as RGBColorString } from '../../ink/styles.js'
 2  import type { RGBColor as RGBColorType } from './types.js'
 3  
 4  export function getDefaultCharacters(): string[] {
 5    if (process.env.TERM === 'xterm-ghostty') {
 6      return ['·', '✢', '✳', '✶', '✻', '*'] // Use * instead of ✽ for Ghostty because the latter renders in a way that's slightly offset
 7    }
 8    return process.platform === 'darwin'
 9      ? ['·', '✢', '✳', '✶', '✻', '✽']
10      : ['·', '✢', '*', '✶', '✻', '✽']
11  }
12  
13  // Interpolate between two RGB colors
14  export function interpolateColor(
15    color1: RGBColorType,
16    color2: RGBColorType,
17    t: number, // 0 to 1
18  ): RGBColorType {
19    return {
20      r: Math.round(color1.r + (color2.r - color1.r) * t),
21      g: Math.round(color1.g + (color2.g - color1.g) * t),
22      b: Math.round(color1.b + (color2.b - color1.b) * t),
23    }
24  }
25  
26  // Convert RGB object to rgb() color string for Text component
27  export function toRGBColor(color: RGBColorType): RGBColorString {
28    return `rgb(${color.r},${color.g},${color.b})`
29  }
30  
31  // HSL hue (0-360) to RGB, using voice-mode waveform parameters (s=0.7, l=0.6).
32  export function hueToRgb(hue: number): RGBColorType {
33    const h = ((hue % 360) + 360) % 360
34    const s = 0.7
35    const l = 0.6
36    const c = (1 - Math.abs(2 * l - 1)) * s
37    const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
38    const m = l - c / 2
39    let r = 0
40    let g = 0
41    let b = 0
42    if (h < 60) {
43      r = c
44      g = x
45    } else if (h < 120) {
46      r = x
47      g = c
48    } else if (h < 180) {
49      g = c
50      b = x
51    } else if (h < 240) {
52      g = x
53      b = c
54    } else if (h < 300) {
55      r = x
56      b = c
57    } else {
58      r = c
59      b = x
60    }
61    return {
62      r: Math.round((r + m) * 255),
63      g: Math.round((g + m) * 255),
64      b: Math.round((b + m) * 255),
65    }
66  }
67  
68  const RGB_CACHE = new Map<string, RGBColorType | null>()
69  
70  export function parseRGB(colorStr: string): RGBColorType | null {
71    const cached = RGB_CACHE.get(colorStr)
72    if (cached !== undefined) return cached
73  
74    const match = colorStr.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/)
75    const result = match
76      ? {
77          r: parseInt(match[1]!, 10),
78          g: parseInt(match[2]!, 10),
79          b: parseInt(match[3]!, 10),
80        }
81      : null
82    RGB_CACHE.set(colorStr, result)
83    return result
84  }