Skip to content

Commit 8e7db6e

Browse files
authored
fix(preset-mini): resolve nested theme colors with dashed keys (#5021)
1 parent f0cdaa6 commit 8e7db6e

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

packages-presets/preset-mini/src/_utils/utilities.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,31 @@ export function directionSize(propertyPrefix: string): DynamicMatcher {
3434
type ThemeColorKeys = 'colors' | 'borderColor' | 'backgroundColor' | 'textColor' | 'shadowColor' | 'accentColor'
3535

3636
function getThemeColorForKey(theme: Theme, colors: string[], key: ThemeColorKeys = 'colors') {
37-
let obj = theme[key] as Theme['colors'] | string
38-
let index = -1
39-
40-
for (const c of colors) {
41-
index += 1
42-
if (obj && typeof obj !== 'string') {
43-
const camel = colors.slice(index).join('-').replace(/(-[a-z])/g, n => n.slice(1).toUpperCase())
44-
if (obj[camel])
45-
return obj[camel]
46-
47-
if (obj[c]) {
48-
obj = obj[c]
49-
continue
37+
const obj = theme[key] as Theme['colors'] | string
38+
39+
function deepGet(current: any, path: string[]): any {
40+
if (path.length === 0)
41+
return current
42+
if (!current || typeof current !== 'object')
43+
return undefined
44+
45+
// Try progressively shorter flat keys (e.g., 'dark-blue-foo', 'dark-blue', 'dark')
46+
for (let i = path.length; i > 0; i--) {
47+
const flatKey = path.slice(0, i).join('-')
48+
const camelKey = flatKey.replace(/(-[a-z])/g, n => n.slice(1).toUpperCase())
49+
50+
// Try camelCase first, then dashed
51+
const value = current[camelKey] ?? current[flatKey]
52+
if (value != null) {
53+
if (i === path.length)
54+
return value
55+
return deepGet(value, path.slice(i))
5056
}
5157
}
5258
return undefined
5359
}
5460

55-
return obj
61+
return deepGet(obj, colors)
5662
}
5763

5864
/**

packages-presets/preset-mini/test/color.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ describe('preset-mini color utils', () => {
4949
colorMix: 'color-mix(in hsl, oklch(95% 0.10 var(--hue)), oklch(100% 0 360))',
5050
colorMixUnoAlpha: 'color-mix(in hsl, oklch(95% 0.10 var(--hue) / %alpha), oklch(100% 0 360))',
5151
colorMixDosAlpha: 'color-mix(in srgb, oklch(95% 0.10 var(--hue) / %alpha) 30%, oklch(100% 0 360 / %alpha))',
52+
brand: {
53+
'dark-blue': {
54+
'DEFAULT': '#123456',
55+
'foo-bar': {
56+
baz: '#fedcba',
57+
},
58+
},
59+
},
5260
},
5361
},
5462
symbols,
@@ -133,6 +141,26 @@ describe('preset-mini color utils', () => {
133141
prop: 'color-mix(in srgb, oklch(95% 0.10 var(--hue) / 0.2) 30%, oklch(100% 0 360 / 0.2))',
134142
})
135143

144+
// nested color with dash in key name (with DEFAULT)
145+
expect(fn('brand-dark-blue')).eql({
146+
'--un-v-opacity': 1,
147+
'prop': 'rgb(18 52 86 / var(--un-v-opacity))',
148+
})
149+
150+
expect(fn('brand-dark-blue/50')).eql({
151+
prop: 'rgb(18 52 86 / 0.5)',
152+
})
153+
154+
// deeply nested color with multiple dashed keys
155+
expect(fn('brand-dark-blue-foo-bar-baz')).eql({
156+
'--un-v-opacity': 1,
157+
'prop': 'rgb(254 220 186 / var(--un-v-opacity))',
158+
})
159+
160+
expect(fn('brand-dark-blue-foo-bar-baz/25')).eql({
161+
prop: 'rgb(254 220 186 / 0.25)',
162+
})
163+
136164
// invalid
137165
expect(fn('hex-invalid')).eql({})
138166
expect(fn('5px')).eql(undefined)

0 commit comments

Comments
 (0)