init claude-code
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { Text, useTheme } from '../../ink.js';
|
||||
import { getTheme, type Theme } from '../../utils/theme.js';
|
||||
import { interpolateColor, parseRGB, toRGBColor } from './utils.js';
|
||||
type Props = {
|
||||
char: string;
|
||||
flashOpacity: number;
|
||||
messageColor: keyof Theme;
|
||||
shimmerColor: keyof Theme;
|
||||
};
|
||||
export function FlashingChar(t0) {
|
||||
const $ = _c(9);
|
||||
const {
|
||||
char,
|
||||
flashOpacity,
|
||||
messageColor,
|
||||
shimmerColor
|
||||
} = t0;
|
||||
const [themeName] = useTheme();
|
||||
let t1;
|
||||
if ($[0] !== char || $[1] !== flashOpacity || $[2] !== messageColor || $[3] !== shimmerColor || $[4] !== themeName) {
|
||||
t1 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const theme = getTheme(themeName);
|
||||
const baseColorStr = theme[messageColor];
|
||||
const shimmerColorStr = theme[shimmerColor];
|
||||
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
|
||||
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null;
|
||||
if (baseRGB && shimmerRGB) {
|
||||
const interpolated = interpolateColor(baseRGB, shimmerRGB, flashOpacity);
|
||||
t1 = <Text color={toRGBColor(interpolated)}>{char}</Text>;
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = char;
|
||||
$[1] = flashOpacity;
|
||||
$[2] = messageColor;
|
||||
$[3] = shimmerColor;
|
||||
$[4] = themeName;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
if (t1 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t1;
|
||||
}
|
||||
const shouldUseShimmer = flashOpacity > 0.5;
|
||||
const t2 = shouldUseShimmer ? shimmerColor : messageColor;
|
||||
let t3;
|
||||
if ($[6] !== char || $[7] !== t2) {
|
||||
t3 = <Text color={t2}>{char}</Text>;
|
||||
$[6] = char;
|
||||
$[7] = t2;
|
||||
$[8] = t3;
|
||||
} else {
|
||||
t3 = $[8];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlRleHQiLCJ1c2VUaGVtZSIsImdldFRoZW1lIiwiVGhlbWUiLCJpbnRlcnBvbGF0ZUNvbG9yIiwicGFyc2VSR0IiLCJ0b1JHQkNvbG9yIiwiUHJvcHMiLCJjaGFyIiwiZmxhc2hPcGFjaXR5IiwibWVzc2FnZUNvbG9yIiwic2hpbW1lckNvbG9yIiwiRmxhc2hpbmdDaGFyIiwidDAiLCIkIiwiX2MiLCJ0aGVtZU5hbWUiLCJ0MSIsIlN5bWJvbCIsImZvciIsImJiMCIsInRoZW1lIiwiYmFzZUNvbG9yU3RyIiwic2hpbW1lckNvbG9yU3RyIiwiYmFzZVJHQiIsInNoaW1tZXJSR0IiLCJpbnRlcnBvbGF0ZWQiLCJzaG91bGRVc2VTaGltbWVyIiwidDIiLCJ0MyJdLCJzb3VyY2VzIjpbIkZsYXNoaW5nQ2hhci50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBUZXh0LCB1c2VUaGVtZSB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7IGdldFRoZW1lLCB0eXBlIFRoZW1lIH0gZnJvbSAnLi4vLi4vdXRpbHMvdGhlbWUuanMnXG5pbXBvcnQgeyBpbnRlcnBvbGF0ZUNvbG9yLCBwYXJzZVJHQiwgdG9SR0JDb2xvciB9IGZyb20gJy4vdXRpbHMuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIGNoYXI6IHN0cmluZ1xuICBmbGFzaE9wYWNpdHk6IG51bWJlclxuICBtZXNzYWdlQ29sb3I6IGtleW9mIFRoZW1lXG4gIHNoaW1tZXJDb2xvcjoga2V5b2YgVGhlbWVcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIEZsYXNoaW5nQ2hhcih7XG4gIGNoYXIsXG4gIGZsYXNoT3BhY2l0eSxcbiAgbWVzc2FnZUNvbG9yLFxuICBzaGltbWVyQ29sb3IsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFt0aGVtZU5hbWVdID0gdXNlVGhlbWUoKVxuICBjb25zdCB0aGVtZSA9IGdldFRoZW1lKHRoZW1lTmFtZSlcblxuICBjb25zdCBiYXNlQ29sb3JTdHIgPSB0aGVtZVttZXNzYWdlQ29sb3JdXG4gIGNvbnN0IHNoaW1tZXJDb2xvclN0ciA9IHRoZW1lW3NoaW1tZXJDb2xvcl1cblxuICBjb25zdCBiYXNlUkdCID0gYmFzZUNvbG9yU3RyID8gcGFyc2VSR0IoYmFzZUNvbG9yU3RyKSA6IG51bGxcbiAgY29uc3Qgc2hpbW1lclJHQiA9IHNoaW1tZXJDb2xvclN0ciA/IHBhcnNlUkdCKHNoaW1tZXJDb2xvclN0cikgOiBudWxsXG5cbiAgaWYgKGJhc2VSR0IgJiYgc2hpbW1lclJHQikge1xuICAgIC8vIFNtb290aCBpbnRlcnBvbGF0aW9uIGJldHdlZW4gY29sb3JzXG4gICAgY29uc3QgaW50ZXJwb2xhdGVkID0gaW50ZXJwb2xhdGVDb2xvcihiYXNlUkdCLCBzaGltbWVyUkdCLCBmbGFzaE9wYWNpdHkpXG4gICAgcmV0dXJuIDxUZXh0IGNvbG9yPXt0b1JHQkNvbG9yKGludGVycG9sYXRlZCl9PntjaGFyfTwvVGV4dD5cbiAgfVxuXG4gIC8vIEZhbGxiYWNrIGZvciBBTlNJIHRoZW1lczogYmluYXJ5IHN3aXRjaFxuICBjb25zdCBzaG91bGRVc2VTaGltbWVyID0gZmxhc2hPcGFjaXR5ID4gMC41XG4gIHJldHVybiAoXG4gICAgPFRleHQgY29sb3I9e3Nob3VsZFVzZVNoaW1tZXIgPyBzaGltbWVyQ29sb3IgOiBtZXNzYWdlQ29sb3J9PntjaGFyfTwvVGV4dD5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxJQUFJLEVBQUVDLFFBQVEsUUFBUSxjQUFjO0FBQzdDLFNBQVNDLFFBQVEsRUFBRSxLQUFLQyxLQUFLLFFBQVEsc0JBQXNCO0FBQzNELFNBQVNDLGdCQUFnQixFQUFFQyxRQUFRLEVBQUVDLFVBQVUsUUFBUSxZQUFZO0FBRW5FLEtBQUtDLEtBQUssR0FBRztFQUNYQyxJQUFJLEVBQUUsTUFBTTtFQUNa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
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { Text } from '../../ink.js';
|
||||
import type { Theme } from '../../utils/theme.js';
|
||||
type Props = {
|
||||
char: string;
|
||||
index: number;
|
||||
glimmerIndex: number;
|
||||
messageColor: keyof Theme;
|
||||
shimmerColor: keyof Theme;
|
||||
};
|
||||
export function ShimmerChar(t0) {
|
||||
const $ = _c(3);
|
||||
const {
|
||||
char,
|
||||
index,
|
||||
glimmerIndex,
|
||||
messageColor,
|
||||
shimmerColor
|
||||
} = t0;
|
||||
const isHighlighted = index === glimmerIndex;
|
||||
const isNearHighlight = Math.abs(index - glimmerIndex) === 1;
|
||||
const shouldUseShimmer = isHighlighted || isNearHighlight;
|
||||
const t1 = shouldUseShimmer ? shimmerColor : messageColor;
|
||||
let t2;
|
||||
if ($[0] !== char || $[1] !== t1) {
|
||||
t2 = <Text color={t1}>{char}</Text>;
|
||||
$[0] = char;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlRleHQiLCJUaGVtZSIsIlByb3BzIiwiY2hhciIsImluZGV4IiwiZ2xpbW1lckluZGV4IiwibWVzc2FnZUNvbG9yIiwic2hpbW1lckNvbG9yIiwiU2hpbW1lckNoYXIiLCJ0MCIsIiQiLCJfYyIsImlzSGlnaGxpZ2h0ZWQiLCJpc05lYXJIaWdobGlnaHQiLCJNYXRoIiwiYWJzIiwic2hvdWxkVXNlU2hpbW1lciIsInQxIiwidDIiXSwic291cmNlcyI6WyJTaGltbWVyQ2hhci50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHR5cGUgeyBUaGVtZSB9IGZyb20gJy4uLy4uL3V0aWxzL3RoZW1lLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBjaGFyOiBzdHJpbmdcbiAgaW5kZXg6IG51bWJlclxuICBnbGltbWVySW5kZXg6IG51bWJlclxuICBtZXNzYWdlQ29sb3I6IGtleW9mIFRoZW1lXG4gIHNoaW1tZXJDb2xvcjoga2V5b2YgVGhlbWVcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFNoaW1tZXJDaGFyKHtcbiAgY2hhcixcbiAgaW5kZXgsXG4gIGdsaW1tZXJJbmRleCxcbiAgbWVzc2FnZUNvbG9yLFxuICBzaGltbWVyQ29sb3IsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGlzSGlnaGxpZ2h0ZWQgPSBpbmRleCA9PT0gZ2xpbW1lckluZGV4XG4gIGNvbnN0IGlzTmVhckhpZ2hsaWdodCA9IE1hdGguYWJzKGluZGV4IC0gZ2xpbW1lckluZGV4KSA9PT0gMVxuICBjb25zdCBzaG91bGRVc2VTaGltbWVyID0gaXNIaWdobGlnaHRlZCB8fCBpc05lYXJIaWdobGlnaHRcblxuICByZXR1cm4gKFxuICAgIDxUZXh0IGNvbG9yPXtzaG91bGRVc2VTaGltbWVyID8gc2hpbW1lckNvbG9yIDogbWVzc2FnZUNvbG9yfT57Y2hhcn08L1RleHQ+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsSUFBSSxRQUFRLGNBQWM7QUFDbkMsY0FBY0MsS0FBSyxRQUFRLHNCQUFzQjtBQUVqRCxLQUFLQyxLQUFLLEdBQUc7RUFDWEMsSUFBSSxFQUFFLE1BQU07RUFDWkMsS0FBSyxFQUFFLE1BQU07RUFDYkMsWUFBWSxFQUFFLE1BQU07RUFDcEJDLFlBQVksRUFBRSxNQUFNTCxLQUFLO0VBQ3pCTSxZQUFZLEVBQUUsTUFBTU4sS0FBSztBQUMzQixDQUFDO0FBRUQsT0FBTyxTQUFBTyxZQUFBQyxFQUFBO0VBQUEsTUFBQUMsQ0FBQSxHQUFBQyxFQUFBO0VBQXFCO0lBQUFSLElBQUE7SUFBQUMsS0FBQTtJQUFBQyxZQUFBO0lBQUFDLFlBQUE7SUFBQUM7RUFBQSxJQUFBRSxFQU1wQjtFQUNOLE1BQUFHLGFBQUEsR0FBc0JSLEtBQUssS0FBS0MsWUFBWTtFQUM1QyxNQUFBUSxlQUFBLEdBQXdCQyxJQUFJLENBQUFDLEdBQUksQ0FBQ1gsS0FBSyxHQUFHQyxZQUFZLENBQUMsS0FBSyxDQUFDO0VBQzVELE1BQUFXLGdCQUFBLEdBQXlCSixhQUFnQyxJQUFoQ0MsZUFBZ0M7RUFHMUMsTUFBQUksRUFBQSxHQUFBRCxnQkFBZ0IsR0FBaEJULFlBQThDLEdBQTlDRCxZQUE4QztFQUFBLElBQUFZLEVBQUE7RUFBQSxJQUFBUixDQUFBLFFBQUFQLElBQUEsSUFBQU8sQ0FBQSxRQUFBTyxFQUFBO0lBQTNEQyxFQUFBLElBQUMsSUFBSSxDQUFRLEtBQThDLENBQTlDLENBQUFELEVBQTZDLENBQUMsQ0FBR2QsS0FBRyxDQUFFLEVBQWxFLElBQUksQ0FBcUU7SUFBQU8sQ0FBQSxNQUFBUCxJQUFBO0lBQUFPLENBQUEsTUFBQU8sRUFBQTtJQUFBUCxDQUFBLE1BQUFRLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFSLENBQUE7RUFBQTtFQUFBLE9BQTFFUSxFQUEwRTtBQUFBIiwiaWdub3JlTGlzdCI6W119
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
||||
export { FlashingChar } from './FlashingChar.js'
|
||||
export { GlimmerMessage } from './GlimmerMessage.js'
|
||||
export { ShimmerChar } from './ShimmerChar.js'
|
||||
export { SpinnerGlyph } from './SpinnerGlyph.js'
|
||||
export type { SpinnerMode } from './types.js'
|
||||
export { useShimmerAnimation } from './useShimmerAnimation.js'
|
||||
export { useStalledAnimation } from './useStalledAnimation.js'
|
||||
export { getDefaultCharacters, interpolateColor } from './utils.js'
|
||||
// Teammate components are NOT exported here - use dynamic require() to enable dead code elimination
|
||||
// See REPL.tsx and Spinner.tsx for the correct import pattern
|
||||
@@ -0,0 +1 @@
|
||||
export const TEAMMATE_SELECT_HINT = 'shift + ↑/↓ to select'
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react'
|
||||
import { stringWidth } from '../../ink/stringWidth.js'
|
||||
import { type DOMElement, useAnimationFrame } from '../../ink.js'
|
||||
import type { SpinnerMode } from './types.js'
|
||||
|
||||
export function useShimmerAnimation(
|
||||
mode: SpinnerMode,
|
||||
message: string,
|
||||
isStalled: boolean,
|
||||
): [ref: (element: DOMElement | null) => void, glimmerIndex: number] {
|
||||
const glimmerSpeed = mode === 'requesting' ? 50 : 200
|
||||
// Pass null when stalled to unsubscribe from the clock — otherwise the
|
||||
// setInterval keeps firing at 20fps even when the shimmer isn't visible.
|
||||
// Notably, if the caller never attaches `ref` (e.g. conditional JSX),
|
||||
// useTerminalViewport stays at its initial isVisible:true and the
|
||||
// viewport-pause never kicks in, so this is the only stop mechanism.
|
||||
const [ref, time] = useAnimationFrame(isStalled ? null : glimmerSpeed)
|
||||
const messageWidth = useMemo(() => stringWidth(message), [message])
|
||||
|
||||
if (isStalled) {
|
||||
return [ref, -100]
|
||||
}
|
||||
|
||||
const cyclePosition = Math.floor(time / glimmerSpeed)
|
||||
const cycleLength = messageWidth + 20
|
||||
|
||||
if (mode === 'requesting') {
|
||||
return [ref, (cyclePosition % cycleLength) - 10]
|
||||
}
|
||||
return [ref, messageWidth + 10 - (cyclePosition % cycleLength)]
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useRef } from 'react'
|
||||
|
||||
// Hook to handle the transition to red when tokens stop flowing.
|
||||
// Driven by the parent's animation clock time instead of independent intervals,
|
||||
// so it slows down when the terminal is blurred.
|
||||
export function useStalledAnimation(
|
||||
time: number,
|
||||
currentResponseLength: number,
|
||||
hasActiveTools = false,
|
||||
reducedMotion = false,
|
||||
): {
|
||||
isStalled: boolean
|
||||
stalledIntensity: number
|
||||
} {
|
||||
const lastTokenTime = useRef(time)
|
||||
const lastResponseLength = useRef(currentResponseLength)
|
||||
const mountTime = useRef(time)
|
||||
const stalledIntensityRef = useRef(0)
|
||||
const lastSmoothTime = useRef(time)
|
||||
|
||||
// Reset timer when new tokens arrive (check actual length change)
|
||||
if (currentResponseLength > lastResponseLength.current) {
|
||||
lastTokenTime.current = time
|
||||
lastResponseLength.current = currentResponseLength
|
||||
stalledIntensityRef.current = 0
|
||||
lastSmoothTime.current = time
|
||||
}
|
||||
|
||||
// Derive time since last token from animation clock
|
||||
let timeSinceLastToken: number
|
||||
if (hasActiveTools) {
|
||||
timeSinceLastToken = 0
|
||||
lastTokenTime.current = time
|
||||
} else if (currentResponseLength > 0) {
|
||||
timeSinceLastToken = time - lastTokenTime.current
|
||||
} else {
|
||||
timeSinceLastToken = time - mountTime.current
|
||||
}
|
||||
|
||||
// Calculate stalled intensity based on time since last token
|
||||
// Start showing red after 3 seconds of no new tokens (only when no tools are active)
|
||||
const isStalled = timeSinceLastToken > 3000 && !hasActiveTools
|
||||
const intensity = isStalled
|
||||
? Math.min((timeSinceLastToken - 3000) / 2000, 1) // Fade over 2 seconds
|
||||
: 0
|
||||
|
||||
// Smooth intensity transition driven by animation frame ticks
|
||||
if (!reducedMotion && (intensity > 0 || stalledIntensityRef.current > 0)) {
|
||||
const dt = time - lastSmoothTime.current
|
||||
if (dt >= 50) {
|
||||
const steps = Math.floor(dt / 50)
|
||||
let current = stalledIntensityRef.current
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const diff = intensity - current
|
||||
if (Math.abs(diff) < 0.01) {
|
||||
current = intensity
|
||||
break
|
||||
}
|
||||
current += diff * 0.1
|
||||
}
|
||||
stalledIntensityRef.current = current
|
||||
lastSmoothTime.current = time
|
||||
}
|
||||
} else {
|
||||
stalledIntensityRef.current = intensity
|
||||
lastSmoothTime.current = time
|
||||
}
|
||||
|
||||
// When reducedMotion is enabled, use instant intensity change
|
||||
const effectiveIntensity = reducedMotion
|
||||
? intensity
|
||||
: stalledIntensityRef.current
|
||||
|
||||
return { isStalled, stalledIntensity: effectiveIntensity }
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { RGBColor as RGBColorString } from '../../ink/styles.js'
|
||||
import type { RGBColor as RGBColorType } from './types.js'
|
||||
|
||||
export function getDefaultCharacters(): string[] {
|
||||
if (process.env.TERM === 'xterm-ghostty') {
|
||||
return ['·', '✢', '✳', '✶', '✻', '*'] // Use * instead of ✽ for Ghostty because the latter renders in a way that's slightly offset
|
||||
}
|
||||
return process.platform === 'darwin'
|
||||
? ['·', '✢', '✳', '✶', '✻', '✽']
|
||||
: ['·', '✢', '*', '✶', '✻', '✽']
|
||||
}
|
||||
|
||||
// Interpolate between two RGB colors
|
||||
export function interpolateColor(
|
||||
color1: RGBColorType,
|
||||
color2: RGBColorType,
|
||||
t: number, // 0 to 1
|
||||
): RGBColorType {
|
||||
return {
|
||||
r: Math.round(color1.r + (color2.r - color1.r) * t),
|
||||
g: Math.round(color1.g + (color2.g - color1.g) * t),
|
||||
b: Math.round(color1.b + (color2.b - color1.b) * t),
|
||||
}
|
||||
}
|
||||
|
||||
// Convert RGB object to rgb() color string for Text component
|
||||
export function toRGBColor(color: RGBColorType): RGBColorString {
|
||||
return `rgb(${color.r},${color.g},${color.b})`
|
||||
}
|
||||
|
||||
// HSL hue (0-360) to RGB, using voice-mode waveform parameters (s=0.7, l=0.6).
|
||||
export function hueToRgb(hue: number): RGBColorType {
|
||||
const h = ((hue % 360) + 360) % 360
|
||||
const s = 0.7
|
||||
const l = 0.6
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s
|
||||
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
|
||||
const m = l - c / 2
|
||||
let r = 0
|
||||
let g = 0
|
||||
let b = 0
|
||||
if (h < 60) {
|
||||
r = c
|
||||
g = x
|
||||
} else if (h < 120) {
|
||||
r = x
|
||||
g = c
|
||||
} else if (h < 180) {
|
||||
g = c
|
||||
b = x
|
||||
} else if (h < 240) {
|
||||
g = x
|
||||
b = c
|
||||
} else if (h < 300) {
|
||||
r = x
|
||||
b = c
|
||||
} else {
|
||||
r = c
|
||||
b = x
|
||||
}
|
||||
return {
|
||||
r: Math.round((r + m) * 255),
|
||||
g: Math.round((g + m) * 255),
|
||||
b: Math.round((b + m) * 255),
|
||||
}
|
||||
}
|
||||
|
||||
const RGB_CACHE = new Map<string, RGBColorType | null>()
|
||||
|
||||
export function parseRGB(colorStr: string): RGBColorType | null {
|
||||
const cached = RGB_CACHE.get(colorStr)
|
||||
if (cached !== undefined) return cached
|
||||
|
||||
const match = colorStr.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/)
|
||||
const result = match
|
||||
? {
|
||||
r: parseInt(match[1]!, 10),
|
||||
g: parseInt(match[2]!, 10),
|
||||
b: parseInt(match[3]!, 10),
|
||||
}
|
||||
: null
|
||||
RGB_CACHE.set(colorStr, result)
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user