Skip to content

Commit 06cc6ff

Browse files
authored
fix: handle color cases with var() without crashing (#7)
* fix: handle color cases with var() without crashing * fix colors in shadows
1 parent 1e65537 commit 06cc6ff

File tree

6 files changed

+169
-70
lines changed

6 files changed

+169
-70
lines changed

src/colors.ts

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,55 @@
11
import { KeywordSet } from './keyword-set.js'
2-
import { EXTENSION_AUTHORED_AS, type ColorToken } from './types.js'
2+
import { EXTENSION_AUTHORED_AS, type ColorToken, type UnparsedToken } from './types.js'
33
import {
44
parse,
55
ColorSpace,
6-
sRGB,
7-
P3,
6+
XYZ_D65,
7+
XYZ_D50,
8+
XYZ_ABS_D65,
9+
Lab_D65,
10+
Lab,
811
LCH,
12+
sRGB_Linear,
13+
sRGB,
914
HSL,
15+
HWB,
16+
HSV,
17+
P3_Linear,
18+
P3,
19+
A98RGB_Linear,
20+
A98RGB,
21+
ProPhoto_Linear,
22+
ProPhoto,
23+
REC_2020_Linear,
24+
REC_2020,
25+
OKLab,
1026
OKLCH,
27+
OKLrab,
1128
} from "colorjs.io/fn"
1229

1330
// Register color spaces for parsing and converting
1431
ColorSpace.register(sRGB) // Parses keywords and hex colors
15-
ColorSpace.register(P3)
16-
ColorSpace.register(HSL)
32+
ColorSpace.register(XYZ_D65)
33+
ColorSpace.register(XYZ_D50)
34+
ColorSpace.register(XYZ_ABS_D65)
35+
ColorSpace.register(Lab_D65)
36+
ColorSpace.register(Lab)
1737
ColorSpace.register(LCH)
38+
ColorSpace.register(sRGB_Linear)
39+
ColorSpace.register(HSL)
40+
ColorSpace.register(HWB)
41+
ColorSpace.register(HSV)
42+
ColorSpace.register(P3_Linear)
43+
ColorSpace.register(P3)
44+
ColorSpace.register(A98RGB_Linear)
45+
ColorSpace.register(A98RGB)
46+
ColorSpace.register(ProPhoto_Linear)
47+
ColorSpace.register(ProPhoto)
48+
ColorSpace.register(REC_2020_Linear)
49+
ColorSpace.register(REC_2020)
50+
ColorSpace.register(OKLab)
1851
ColorSpace.register(OKLCH)
52+
ColorSpace.register(OKLrab)
1953

2054
export const named_colors = new KeywordSet([
2155
// CSS Named Colors
@@ -220,7 +254,7 @@ const color_keywords = new KeywordSet([
220254
'revert-layer',
221255
])
222256

223-
export function color_to_token(color: string): ColorToken {
257+
export function color_to_token(color: string): ColorToken | UnparsedToken {
224258
let lowercased = color.toLowerCase()
225259

226260
// The keyword "transparent" specifies a transparent black.
@@ -253,22 +287,41 @@ export function color_to_token(color: string): ColorToken {
253287
}
254288
}
255289

256-
let parsed_color = parse(color)
257-
let [component_a, component_b, component_c] = parsed_color.coords
290+
if (lowercased.includes('var(')) {
291+
return {
292+
$value: color,
293+
$extensions: {
294+
[EXTENSION_AUTHORED_AS]: color
295+
}
296+
}
297+
}
258298

259-
return {
260-
$type: 'color',
261-
$value: {
262-
colorSpace: parsed_color.spaceId,
263-
components: [
264-
component_a ?? 'none',
265-
component_b ?? 'none',
266-
component_c ?? 'none',
267-
],
268-
alpha: parsed_color.alpha ?? 0,
269-
},
270-
$extensions: {
271-
[EXTENSION_AUTHORED_AS]: color
299+
try {
300+
let parsed_color = parse(color)
301+
let [component_a, component_b, component_c] = parsed_color.coords
302+
303+
return {
304+
$type: 'color',
305+
$value: {
306+
colorSpace: parsed_color.spaceId,
307+
components: [
308+
component_a ?? 'none',
309+
component_b ?? 'none',
310+
component_c ?? 'none',
311+
],
312+
alpha: parsed_color.alpha ?? 0,
313+
},
314+
$extensions: {
315+
[EXTENSION_AUTHORED_AS]: color
316+
}
317+
}
318+
} catch (error) {
319+
// A catch for edge cases that we don't support yet.
320+
return {
321+
$value: color,
322+
$extensions: {
323+
[EXTENSION_AUTHORED_AS]: color
324+
}
272325
}
273326
}
274327
}

src/destructure-box-shadow.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { parse, type CssNode, type Value } from 'css-tree'
22
import { named_colors, system_colors, color_functions, color_to_token } from './colors.js'
3-
import type { ColorToken } from './types.js'
3+
import type { ColorToken, UnparsedToken } from './types.js'
44

55
type CssLength = {
66
value: number
77
unit: string
88
}
99

1010
export type DestructuredShadow = {
11-
color: ColorToken | undefined
11+
color: ColorToken | UnparsedToken | undefined
1212
offsetX: CssLength | undefined
1313
offsetY: CssLength | undefined
1414
blur: CssLength | undefined

src/group-colors.ts

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,58 +29,54 @@ export const color_dict = new Map<number, string>([
2929
[unknown, 'unknown']
3030
])
3131

32-
export function group_colors(colors: Record<string, unknown>) {
33-
let color_groups = new Map<number, NormalizedColorWithAuthored[]>()
32+
type AuthoredColor = string
3433

35-
for (let color in colors) {
36-
if (color.includes('var(') || color.includes('calc(')) {
37-
continue
38-
}
34+
export function group_colors(colors: Record<AuthoredColor, unknown>) {
35+
let color_groups = new Map<number, AuthoredColor[]>()
3936

40-
let converted = convert(color)
37+
for (let color in colors) {
4138
let group = unknown
42-
let { hue, saturation, lightness } = converted
4339

44-
if (saturation < 10 && lightness === 100) {
45-
group = white
46-
} else if (saturation < 10 && lightness === 0) {
47-
group = black
48-
} else if (saturation < 5) {
49-
group = grey
50-
} else {
51-
if (hue < 22) {
52-
group = red
53-
} else if (hue < 50) {
54-
group = orange
55-
} else if (hue < 72) {
56-
group = yellow
57-
} else if (hue < 144) {
58-
group = green
59-
} else if (hue < 180) {
60-
group = cyan
61-
} else if (hue < 250) {
62-
group = blue
63-
} else if (hue < 300) {
64-
group = magenta
65-
} else if (hue < 350) {
66-
group = pink
40+
if (!color.includes('var(') && !color.includes('calc(')) {
41+
let converted = convert(color)
42+
let { hue, saturation, lightness } = converted
43+
44+
if (saturation < 10 && lightness === 100) {
45+
group = white
46+
} else if (saturation < 10 && lightness === 0) {
47+
group = black
48+
} else if (saturation < 5) {
49+
group = grey
6750
} else {
68-
group = red
51+
if (hue < 22) {
52+
group = red
53+
} else if (hue < 50) {
54+
group = orange
55+
} else if (hue < 72) {
56+
group = yellow
57+
} else if (hue < 144) {
58+
group = green
59+
} else if (hue < 180) {
60+
group = cyan
61+
} else if (hue < 250) {
62+
group = blue
63+
} else if (hue < 300) {
64+
group = magenta
65+
} else if (hue < 350) {
66+
group = pink
67+
} else {
68+
group = red
69+
}
6970
}
7071
}
7172

7273
if (color_groups.has(group)) {
73-
color_groups.get(group)!.push(converted)
74+
color_groups.get(group)!.push(color)
7475
} else {
75-
color_groups.set(group, [converted])
76+
color_groups.set(group, [color])
7677
}
7778
}
7879

79-
// Sort the colors in each group by lightness
80-
for (let group of color_groups.values()) {
81-
group.sort((a, b) => a.lightness - b.lightness)
82-
}
83-
8480
return Array.from(color_groups).sort((a, b) =>
8581
a[0] === unknown || b[0] === unknown ? -1 : b[1].length - a[1].length
8682
)

src/index.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { test, expect, describe } from 'vitest'
22
import { analysis_to_tokens, css_to_tokens } from './index.js'
33
import { EXTENSION_AUTHORED_AS } from './types.js'
44
import { analyze } from '@projectwallace/css-analyzer'
5+
import { hash } from './hash.js'
56

67
describe('analysis_to_tokens', () => {
78
test('exports a function', () => {
@@ -147,6 +148,24 @@ describe('css_to_tokens', () => {
147148
},
148149
})
149150
})
151+
152+
test('handles colors with var()', () => {
153+
let actual = css_to_tokens(`
154+
.my-design-system {
155+
color: var(--my-color);
156+
color: oklch(var(--my-color) / .4);
157+
}
158+
`)
159+
expect(actual.color).toEqual({
160+
// Skip `color: var(--my-color)` entirely
161+
'unknown-7d338ae5': {
162+
$value: 'oklch(var(--my-color) / .4)',
163+
$extensions: {
164+
[EXTENSION_AUTHORED_AS]: 'oklch(var(--my-color) / .4)'
165+
}
166+
},
167+
})
168+
})
150169
})
151170

152171
describe('font sizes', () => {
@@ -185,6 +204,38 @@ describe('css_to_tokens', () => {
185204
},
186205
})
187206
})
207+
208+
test('handles values with var()', () => {
209+
let actual = css_to_tokens(`
210+
.my-design-system {
211+
font-size: var(--font-size);
212+
}
213+
`)
214+
expect(actual.font_size).toEqual({
215+
'fontSize-f25d5b4b': {
216+
$value: 'var(--font-size)',
217+
$extensions: {
218+
[EXTENSION_AUTHORED_AS]: 'var(--font-size)'
219+
}
220+
},
221+
})
222+
})
223+
224+
test('handles values with calc()', () => {
225+
let actual = css_to_tokens(`
226+
.my-design-system {
227+
font-size: calc(16px + 20%);
228+
}
229+
`)
230+
expect(actual.font_size).toEqual({
231+
'fontSize-804e5477': {
232+
$value: 'calc(16px + 20%)',
233+
$extensions: {
234+
[EXTENSION_AUTHORED_AS]: 'calc(16px + 20%)'
235+
}
236+
},
237+
})
238+
})
188239
})
189240

190241
describe('font families', () => {

src/index.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import {
2020
} from './types.js'
2121
import { color_to_token } from './colors.js'
2222

23-
const TYPE_CUBIC_BEZIER = 'cubicBezier' as const
24-
2523
export function css_to_tokens(css: string) {
2624
let analysis = analyze(css)
2725
return analysis_to_tokens(analysis)
@@ -55,14 +53,15 @@ function get_unique(collection: Collection) {
5553
export function analysis_to_tokens(analysis: CssAnalysis) {
5654
return {
5755
color: (() => {
58-
let colors = Object.create(null) as Record<string, ColorToken>
56+
let colors = Object.create(null) as Record<string, ColorToken | UnparsedToken>
5957
let unique = get_unique(analysis.values.colors)
60-
let grouped_colors = group_colors(unique)
58+
let color_groups = group_colors(unique)
6159

62-
for (let [group, group_colors] of grouped_colors) {
60+
for (let [group, group_colors] of color_groups) {
6361
for (let color of group_colors) {
64-
let color_token = color_to_token(color.authored)
65-
colors[`${color_dict.get(group)}-${hash(color.authored)}`] = color_token
62+
let color_token = color_to_token(color)
63+
let name = `${color_dict.get(group)}-${hash(color)}`
64+
colors[name] = color_token
6665
}
6766
}
6867
return colors
@@ -249,7 +248,7 @@ export function analysis_to_tokens(analysis: CssAnalysis) {
249248
if (value) {
250249
easings[name] = {
251250
$value: value,
252-
$type: TYPE_CUBIC_BEZIER,
251+
$type: 'cubicBezier',
253252
$extensions: {
254253
[EXTENSION_AUTHORED_AS]: easing
255254
}

src/module.colors.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)