Skip to content

Commit 8930816

Browse files
authored
feat(preset-wind4): improve css variable usage in bracket syntax (#5174)
1 parent e5f8946 commit 8930816

File tree

6 files changed

+80
-68
lines changed

6 files changed

+80
-68
lines changed

packages-integrations/language-server/package.json

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,36 +52,36 @@
5252
"tsdown": "catalog:build"
5353
},
5454
"inlinedDependencies": {
55+
"@antfu/install-pkg": "1.1.0",
56+
"@iconify/utils": "3.1.0",
5557
"@jridgewell/sourcemap-codec": "1.5.5",
58+
"@quansync/fs": "1.0.0",
59+
"acorn": "8.16.0",
60+
"colorette": "2.0.20",
61+
"consola": "3.4.2",
62+
"defu": "6.1.4",
63+
"destr": "2.0.5",
64+
"fzf": "0.5.2",
65+
"jiti": "2.6.1",
66+
"lru-cache": "11.2.6",
5667
"magic-string": "0.30.21",
57-
"picomatch": "4.0.3",
58-
"unplugin-utils": "0.3.1",
59-
"@iconify/utils": "3.1.0",
60-
"ufo": "1.6.2",
61-
"pathe": "2.0.3",
68+
"mlly": "1.8.0",
6269
"node-fetch-native": "1.6.7",
63-
"destr": "2.0.5",
6470
"ofetch": "1.5.1",
6571
"package-manager-detector": "1.6.0",
66-
"tinyexec": "1.0.2",
67-
"@antfu/install-pkg": "1.1.0",
68-
"acorn": "8.15.0",
69-
"mlly": "1.8.0",
70-
"vscode-languageserver-textdocument": "1.0.12",
71-
"vscode-languageserver": "9.0.1",
72-
"vscode-jsonrpc": "8.2.0",
73-
"vscode-languageserver-types": "3.17.5",
74-
"vscode-languageserver-protocol": "3.17.5",
72+
"pathe": "2.0.3",
73+
"picomatch": "4.0.3",
7574
"prettier": "2.8.8",
76-
"fzf": "0.5.2",
77-
"lru-cache": "11.2.6",
78-
"colorette": "2.0.20",
79-
"consola": "3.4.2",
80-
"unconfig": "7.5.0",
8175
"quansync": "1.0.0",
82-
"@quansync/fs": "1.0.0",
83-
"defu": "6.1.4",
76+
"tinyexec": "1.0.2",
77+
"ufo": "1.6.3",
78+
"unconfig": "7.5.0",
8479
"unconfig-core": "7.5.0",
85-
"jiti": "2.6.1"
80+
"unplugin-utils": "0.3.1",
81+
"vscode-jsonrpc": "8.2.0",
82+
"vscode-languageserver": "9.0.1",
83+
"vscode-languageserver-protocol": "3.17.5",
84+
"vscode-languageserver-textdocument": "1.0.12",
85+
"vscode-languageserver-types": "3.17.5"
8686
}
8787
}

packages-presets/preset-wind4/src/rules/size.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function getSizeValue(theme: Theme, hw: string, prop: string) {
4646
v = `calc(var(--spacing) * ${h.number(prop)})`
4747
}
4848

49-
return v ?? h.bracket.cssvar.global.auto.none.fraction.rem(prop)
49+
return v ?? h.bracket.cssvar.global.auto.none.fraction.rem(prop, theme)
5050
}
5151

5252
export const sizes: Rule<Theme>[] = [

packages-presets/preset-wind4/src/utils/handlers/handlers.ts

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { Theme } from '../../theme'
22
import { escapeSelector } from '@unocss/core'
3+
import { transformThemeFn } from '@unocss/rule-utils'
34
import { globalKeywords } from '../mappings'
45
import { themeTracking } from '../track'
56
import { getThemeByKey } from '../utilities'
6-
import { bracketTypeRe, numberRE, numberWithUnitRE, unitOnlyMap, unitOnlyRE } from './regex'
7+
import { bracketTypeRe, cssVarsRE, numberRE, numberWithUnitRE, unitOnlyMap, unitOnlyRE } from './regex'
78

89
// Not all, but covers most high frequency attributes
910
const cssProps = [
@@ -153,18 +154,28 @@ export function fraction(str: string) {
153154
}
154155
}
155156

156-
function processThemeVariable(name: string, key: keyof Theme, paths: string[], theme: Theme) {
157-
const valOrObj = getThemeByKey(theme, key, paths)
157+
/**
158+
* Process a theme variable reference and retrieve its value and corresponding CSS variable key.
159+
*
160+
* @example theme => Theme object
161+
* @example themeKey => 'colors
162+
* @example themeKeyPaths => ['blue', '500']
163+
* @example varPaths => 'colors.blue.500'
164+
*
165+
* @returns An object containing the resolved value from the theme and the corresponding CSS variable key.
166+
*/
167+
function processThemeVariable(theme: Theme, themeKey: string, themeKeyPaths: string[], varPaths: string) {
168+
const valOrObj = getThemeByKey(theme, themeKey as keyof Theme, themeKeyPaths)
158169
const hasDefault = typeof valOrObj === 'object' && 'DEFAULT' in valOrObj
159170

160171
if (hasDefault)
161-
paths.push('DEFAULT')
172+
themeKeyPaths.push('DEFAULT')
162173

163174
const val = hasDefault ? valOrObj.DEFAULT : valOrObj
164-
const varKey = hasDefault && key !== 'spacing' ? `${name}.DEFAULT` : name
175+
const varKey = hasDefault && themeKey !== 'spacing' ? `${varPaths}.DEFAULT` : varPaths
165176

166177
if (val != null)
167-
themeTracking(key, paths.length ? paths : undefined)
178+
themeTracking(themeKey, themeKeyPaths.length ? themeKeyPaths : undefined)
168179

169180
return { val, varKey }
170181
}
@@ -196,34 +207,34 @@ function bracketWithType(str: string, requiredType?: string, theme?: Theme) {
196207
if (base === '=""')
197208
return
198209

199-
if (base.startsWith('--')) {
200-
const calcMatch = base.match(/^--([\w.-]+)\(([^)]+)\)$/)
201-
if (calcMatch != null && theme) {
202-
// Handle theme function with calculation: --theme.key(factor)
203-
const [, name, factor] = calcMatch
204-
const [key, ...paths] = name.split('.') as [keyof Theme, ...string[]]
205-
const { val, varKey } = processThemeVariable(name, key, paths, theme)
210+
if (theme) {
211+
base = transformThemeFn(base, theme)
212+
}
206213

207-
if (val != null)
208-
base = `calc(var(--${escapeSelector(varKey.replaceAll('.', '-'))}) * ${factor})`
209-
}
210-
else {
211-
// Handle regular CSS variable: --name or --theme.key with optional default
212-
const [name, defaultValue] = base.slice(2).split(',')
213-
const suffix = defaultValue ? `, ${defaultValue}` : ''
214-
const escapedName = escapeSelector(name)
215-
216-
if (theme) {
217-
const [key, ...paths] = name.split('.') as [keyof Theme, ...string[]]
218-
const { val, varKey } = processThemeVariable(name, key, paths, theme)
219-
base = val != null
220-
? `var(--${escapeSelector(varKey.replaceAll('.', '-'))}${suffix})`
221-
: `var(--${escapedName}${suffix})`
222-
}
223-
else {
224-
base = `var(--${escapedName}${suffix})`
214+
const matches = Array.from(base.matchAll(cssVarsRE))
215+
for (const match of matches) {
216+
const [full, varPaths, _value] = match
217+
218+
if (theme) {
219+
const [key, ...paths] = varPaths.split('.')
220+
const { val, varKey } = processThemeVariable(theme, key, paths, varPaths)
221+
222+
if (val != null) {
223+
const cssVar = `--${varKey.replaceAll('.', '-')}`
224+
// use theme value with multiplier
225+
if (_value && !_value.startsWith(',')) {
226+
base = base.replace(full, `calc(var(${cssVar}) * ${_value.slice(1, -1)})`)
227+
}
228+
// default value
229+
else {
230+
const fallback = _value?.slice(1)
231+
base = base.replace(full, `var(${cssVar}${fallback ? `, ${fallback}` : ''})`)
232+
}
233+
continue
225234
}
226235
}
236+
237+
base = base.replace(full, `var(${full})`)
227238
}
228239

229240
let curly = 0

packages-presets/preset-wind4/src/utils/handlers/regex.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ export const unitOnlyMap: Record<string, number> = {
1515
export const bracketTypeRe = /^\[(color|image|length|size|position|quoted|string|number|family):/i
1616
export const splitComma = /,(?![^()]*\))/g
1717
export const remRE = /(-?[.\d]+)rem/g
18+
export const cssVarsRE = /(?<!var\()--([\w.-]+)(\([^)]+\)|,[#.\s\w]+)?/g
19+
// ^ There may not have been any other special cases matched; this needs further improvement.

test/assets/output/preset-wind4-targets.css

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
.text-size-\$variable{font-size:var(--variable);}
230230
.text-\[length\:var\(--size\)\]{font-size:var(--size);}
231231
.text-\[length\:var\(--size\)\]\:\$leading{font-size:var(--size);line-height:var(--leading);}
232+
.text-\[theme\(spacing\.sm\)\]{font-size:0.875rem;}
232233
.text-4xl,
233234
.group:has(:placeholder-shown) .group-has-placeholder-shown\:text-4xl,
234235
.group:where(:placeholder-shown) .group-where-placeholder-shown\:text-4xl{font-size:var(--text-4xl-fontSize);line-height:var(--un-leading, var(--text-4xl-lineHeight));}
@@ -253,7 +254,6 @@
253254
.text-\[calc\(1rem-1px\)\]{font-size:calc(1rem - 1px);}
254255
.text-\[red\]\:50\/display-p3{color:color-mix(in display-p3, red 50%, transparent) /* red */;}
255256
.text-\[red\]\/50{color:color-mix(in oklab, red 50%, transparent) /* red */;}
256-
.text-\[theme\(spacing\.sm\)\]{color:color-mix(in oklab, 0.875rem var(--un-text-opacity), transparent) /* 0.875rem */;}
257257
.text-\[var\(--color\)\]{color:color-mix(in oklab, var(--color) var(--un-text-opacity), transparent) /* var(--color) */;}
258258
.text-black\/10{color:color-mix(in srgb, var(--colors-black) 10%, transparent) /* #000 */;}
259259
.aria-busy\:aria-pressed\:text-green-600[aria-pressed="true"][aria-busy="true"],
@@ -691,7 +691,7 @@
691691
.border-block-style-double{--un-border-style:double;border-block-start-style:double;border-block-end-style:double;}
692692
.border-ie-unset{--un-border-style:unset;border-inline-end-style:unset;}
693693
.border-is-style-double{--un-border-style:double;border-inline-start-style:double;}
694-
.bg-\[--css-spacing\,theme\(spacing\.sm\)\]{background-color:color-mix(in oklab, var(--css-spacing, 0.875rem) var(--un-bg-opacity), transparent) /* var(--css-spacing, 0.875rem) */;}
694+
.bg-\[--css-spacing\,theme\(spacing\.sm\)\]{background-color:color-mix(in oklab, var(--css-spacing,0.875rem) var(--un-bg-opacity), transparent) /* var(--css-spacing,0.875rem) */;}
695695
.bg-\[--test-variable\],
696696
.bg-\$test-variable{background-color:color-mix(in oklab, var(--test-variable) var(--un-bg-opacity), transparent) /* var(--test-variable) */;}
697697
.bg-\[\#153\]\/10{background-color:color-mix(in oklab, #153 10%, transparent) /* #153 */;}
@@ -1864,9 +1864,6 @@ unocss .scope-\[unocss\]\:block{display:block;}
18641864
@layer utility{
18651865
.layer-\[utility\]\:block{display:block;}
18661866
}
1867-
@media (--cssvar){
1868-
.media-\[\(--cssvar\)\]\:block{display:block;}
1869-
}
18701867
@media (forced-colors: active){
18711868
.outline-hidden{outline:2px solid transparent;outline-offset:2px;}
18721869
.forced-colors\:block{display:block;}
@@ -1910,6 +1907,9 @@ unocss .scope-\[unocss\]\:block{display:block;}
19101907
.noscript\:text-red-500,
19111908
.scripting-none\:text-red-500{color:color-mix(in srgb, var(--colors-red-500) var(--un-text-opacity), transparent) /* oklch(63.7% 0.237 25.331) */;}
19121909
}
1910+
@media (var(--cssvar)){
1911+
.media-\[\(--cssvar\)\]\:block{display:block;}
1912+
}
19131913
@media not (prefers-color-scheme: dark){
19141914
.not-dark\:text-xl{font-size:var(--text-xl-fontSize);line-height:var(--un-leading, var(--text-xl-lineHeight));}
19151915
}

test/preset-wind4.test.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -724,22 +724,21 @@ describe('important', () => {
724724
'text-[--colors.blue,#000]',
725725
'text-[--colors.red.200,#fff]',
726726
'[--foo:--bar(8)]',
727+
`w-[calc(var(--sidebar-width-icon)+--spacing(8))]`,
728+
`w-[--sidebar-width-icon+--spacing(8)+2px)+var(--foo)+theme(spacing.sm)]`,
727729
]
728730

729-
const { css } = await uno.generate(cases)
731+
const { getLayer } = await uno.generate(cases)
730732

731-
expect(css).toMatchInlineSnapshot(`
732-
"/* layer: properties */
733-
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*, ::before, ::after, ::backdrop{--un-text-opacity:100%;}}
734-
@property --un-text-opacity{syntax:"<percentage>";inherits:false;initial-value:100%;}
735-
/* layer: theme */
736-
:root, :host { --spacing: 0.25rem; --spacing-sm: 0.875rem; --colors-blue-DEFAULT: oklch(70.7% 0.165 254.624); --colors-red-200: oklch(88.5% 0.062 18.334); }
737-
/* layer: default */
733+
expect(getLayer('default')).toMatchInlineSnapshot(`
734+
"/* layer: default */
738735
.text-\\[--colors\\.blue\\,\\#000\\]{color:color-mix(in oklab, var(--colors-blue-DEFAULT, #000) var(--un-text-opacity), transparent);}
739736
.text-\\[--colors\\.red\\.200\\,\\#fff\\]{color:color-mix(in oklab, var(--colors-red-200, #fff) var(--un-text-opacity), transparent);}
740737
.m-\\[--spacing\\(2\\)\\]{margin:calc(var(--spacing) * 2);}
741738
.m-\\[--spacing\\]{margin:var(--spacing);}
742739
.px-\\[--spacing\\.sm\\(2\\.5\\)\\]{padding-inline:calc(var(--spacing-sm) * 2.5);}
740+
.w-\\[--sidebar-width-icon\\+--spacing\\(8\\)\\+2px\\)\\+var\\(--foo\\)\\+theme\\(spacing\\.sm\\)\\]{width:var(--sidebar-width-icon)+calc(var(--spacing) * 8) + 2px) + var(--foo) + 0.875rem;}
741+
.w-\\[calc\\(var\\(--sidebar-width-icon\\)\\+--spacing\\(8\\)\\)\\]{width:calc(var(--sidebar-width-icon) + calc(var(--spacing) * 8));}
743742
.\\[--foo\\:--bar\\(8\\)\\]{--foo:calc(var(--bar) * 8);}"
744743
`)
745744
})

0 commit comments

Comments
 (0)