Skip to content

Commit 7139a35

Browse files
authored
refactor: migrate to colorjs.io (#5463)
colord library we used does not support new color spaces like oklch. colorjs.io is mature library and handles this stuff just well.
1 parent 725d459 commit 7139a35

File tree

14 files changed

+192
-144
lines changed

14 files changed

+192
-144
lines changed

apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { colord, extend, type RgbaColor } from "colord";
2-
import namesPlugin from "colord/plugins/names";
31
import {
42
toValue,
53
type CssProperty,
@@ -16,9 +14,6 @@ import {
1614
} from "../../shared/repeated-style";
1715
import { parseCssFragment } from "../../shared/css-fragment";
1816

19-
// To support color names
20-
extend([namesPlugin]);
21-
2217
export const properties = ["box-shadow"] satisfies [
2318
CssProperty,
2419
...CssProperty[],
@@ -46,11 +41,7 @@ const getItemProps = (layer: StyleValue, computedLayer?: StyleValue) => {
4641
} else {
4742
labels.push(toValue(shadowValue));
4843
}
49-
let color: undefined | RgbaColor;
50-
const colordValue = colord(toValue(shadowValue?.color));
51-
if (colordValue.isValid()) {
52-
color = colordValue.toRgb();
53-
}
44+
const color = shadowValue?.color ? toValue(shadowValue.color) : undefined;
5445
return { label: labels.join(" "), color };
5546
};
5647

apps/builder/app/builder/features/style-panel/sections/text-shadows/text-shadows.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { colord, extend, type RgbaColor } from "colord";
2-
import namesPlugin from "colord/plugins/names";
31
import {
42
toValue,
53
type CssProperty,
@@ -16,9 +14,6 @@ import {
1614
import { parseCssFragment } from "../../shared/css-fragment";
1715
import { useComputedStyleDecl } from "../../shared/model";
1816

19-
// To support color names
20-
extend([namesPlugin]);
21-
2217
export const properties = ["text-shadow"] satisfies [
2318
CssProperty,
2419
...CssProperty[],
@@ -40,11 +35,7 @@ const getItemProps = (layer: StyleValue, computedLayer?: StyleValue) => {
4035
} else {
4136
labels.push(toValue(shadowValue));
4237
}
43-
let color: undefined | RgbaColor;
44-
const colordValue = colord(toValue(shadowValue?.color));
45-
if (colordValue.isValid()) {
46-
color = colordValue.toRgb();
47-
}
38+
const color = shadowValue?.color ? toValue(shadowValue.color) : undefined;
4839
return { label: labels.join(" "), color };
4940
};
5041

apps/builder/app/builder/features/style-panel/shared/css-value-input/css-value-input.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -933,14 +933,7 @@ export const CssValueInput = ({
933933
</Text>
934934
)}
935935
{item.fallback?.type === "rgb" && (
936-
<ColorThumb
937-
color={{
938-
r: item.fallback.r,
939-
g: item.fallback.g,
940-
b: item.fallback.b,
941-
a: item.fallback.alpha,
942-
}}
943-
/>
936+
<ColorThumb color={toValue(item.fallback)} />
944937
)}
945938
</Flex>
946939
) : (

apps/builder/app/builder/features/style-panel/shared/repeated-style.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useMemo, type ComponentProps, type JSX, type ReactNode } from "react";
2-
import type { RgbaColor } from "colord";
32
import {
43
toValue,
54
type CssProperty,
@@ -328,7 +327,7 @@ export const RepeatedStyle = (props: {
328327
getItemProps: (
329328
index: number,
330329
primaryValue: StyleValue
331-
) => { label: string; color?: RgbaColor };
330+
) => { label: string; color?: string };
332331
floatingPanelOffset?: ComponentProps<typeof FloatingPanel>["offset"];
333332
renderThumbnail?: (index: number, primaryItem: StyleValue) => JSX.Element;
334333
renderItemContent: (index: number, primaryItem: StyleValue) => JSX.Element;
@@ -426,7 +425,7 @@ export const RepeatedStyle = (props: {
426425
hidden={isHidden}
427426
thumbnail={
428427
renderThumbnail?.(index, primaryItem) ??
429-
(itemColor && <ColorThumb color={itemColor} />)
428+
(itemColor ? <ColorThumb color={itemColor} /> : undefined)
430429
}
431430
buttons={
432431
<>

apps/builder/app/builder/shared/css-editor/css-editor.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { mergeRefs } from "@react-aria/utils";
2-
import { colord } from "colord";
2+
import Color from "colorjs.io";
33
import {
44
memo,
55
useEffect,
@@ -132,7 +132,13 @@ const AdvancedPropertyValue = ({
132132
inputRef?: RefObject<HTMLInputElement>;
133133
}) => {
134134
const inputRef = useRef<HTMLInputElement>(null);
135-
const isColor = colord(toValue(styleDecl.usedValue)).isValid();
135+
let isColor = false;
136+
try {
137+
new Color(toValue(styleDecl.usedValue));
138+
isColor = true;
139+
} catch {
140+
isColor = false;
141+
}
136142

137143
return (
138144
<CssValueInputContainer

apps/builder/app/canvas/features/text-editor/text-editor.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import {
6060
import { isDescendantOrSelf, type InstanceSelector } from "~/shared/tree-utils";
6161
import { ToolbarConnectorPlugin } from "./toolbar-connector";
6262
import { type Refs, $convertToLexical, $convertToUpdates } from "./interop";
63-
import { colord } from "colord";
63+
import Color from "colorjs.io";
6464
import { useEffectEvent } from "~/shared/hook-utils/effect-event";
6565
import {
6666
deleteInstanceMutable,
@@ -144,8 +144,16 @@ const CaretColorPlugin = () => {
144144

145145
const elementColor = window.getComputedStyle(rootElement).color;
146146

147-
const color = colord(elementColor).toRgb();
148-
if (color.a < 0.1) {
147+
let isLightBackground = false;
148+
try {
149+
const color = new Color(elementColor);
150+
const alpha = color.alpha ?? 1;
151+
isLightBackground = alpha < 0.1;
152+
} catch {
153+
// If we can't parse the color, assume it's not light
154+
}
155+
156+
if (isLightBackground) {
149157
// Apply caret color with animated color
150158
const sheet = createRegularStyleSheet({ name: "text-editor-caret" });
151159

apps/builder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"args-tokenizer": "^0.3.0",
8787
"bcp-47": "^2.1.0",
8888
"change-case": "^5.4.4",
89-
"colord": "^2.9.3",
89+
"colorjs.io": "^0.5.0",
9090
"cookie": "^1.0.1",
9191
"css-tree": "^3.1.0",
9292
"debug": "^4.3.7",

packages/css-data/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"dependencies": {
3535
"@webstudio-is/css-engine": "workspace:*",
3636
"change-case": "^5.4.4",
37-
"colord": "^2.9.3",
37+
"colorjs.io": "^0.5.0",
3838
"css-tree": "^3.1.0",
3939
"openai": "^3.2.1",
4040
"p-retry": "^6.2.1",

packages/css-data/src/parse-css-value.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { colord, extend } from "colord";
2-
import namesPlugin from "colord/plugins/names";
1+
import Color from "colorjs.io";
32
import {
43
type CssNode,
54
type FunctionNode,
@@ -30,9 +29,6 @@ import {
3029
import { keywordValues } from "./__generated__/keyword-values";
3130
import { units } from "./__generated__/units";
3231

33-
// To support color names
34-
extend([namesPlugin]);
35-
3632
export const cssTryParseValue = (input: string): undefined | CssNode => {
3733
try {
3834
const ast = parse(input, { context: "value" });
@@ -159,16 +155,20 @@ const tupleProps = new Set<CssProperty>([
159155
const availableUnits = new Set<string>(Object.values(units).flat());
160156

161157
const parseColor = (colorString: string): undefined | RgbValue => {
162-
const color = colord(colorString);
163-
if (color.isValid()) {
164-
const rgb = color.toRgb();
158+
try {
159+
const color = new Color(colorString).to("srgb");
160+
// colorjs.io represents RGB values as 0-1 range
161+
const [r, g, b] = color.coords.map((coord) => coord.valueOf());
162+
const alpha = Number(color.alpha.valueOf().toFixed(2));
165163
return {
166164
type: "rgb",
167-
alpha: rgb.a,
168-
r: rgb.r,
169-
g: rgb.g,
170-
b: rgb.b,
165+
alpha: alpha ?? 1,
166+
r: Math.round(r * 255),
167+
g: Math.round(g * 255),
168+
b: Math.round(b * 255),
171169
};
170+
} catch {
171+
return;
172172
}
173173
};
174174

packages/css-data/src/property-parsers/gradient-utils.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ import {
66
type VarValue,
77
toValue,
88
} from "@webstudio-is/css-engine";
9-
import { colord, extend } from "colord";
10-
import namesPlugin from "colord/plugins/names";
9+
import Color from "colorjs.io";
1110
import type { GradientColorValue, GradientStop } from "./types";
1211

13-
extend([namesPlugin]);
14-
1512
export const angleUnitIdentifiers = ["deg", "grad", "rad", "turn"] as const;
1613
const angleUnitSet = new Set<string>(angleUnitIdentifiers);
1714

@@ -119,16 +116,19 @@ export const getColor = (
119116
return parsed;
120117
}
121118
if (parsed.type === "keyword") {
122-
const color = colord(parsed.value);
123-
if (color.isValid()) {
124-
const { r, g, b, a } = color.toRgb();
119+
try {
120+
const color = new Color(parsed.value);
121+
const [r, g, b] = color.coords;
122+
const alpha = color.alpha;
125123
return {
126124
type: "rgb",
127-
r,
128-
g,
129-
b,
130-
alpha: a,
125+
r: r * 255,
126+
g: g * 255,
127+
b: b * 255,
128+
alpha: alpha ?? 1,
131129
} satisfies GradientColorValue;
130+
} catch {
131+
// Invalid color
132132
}
133133
}
134134
};

0 commit comments

Comments
 (0)