Skip to content

Commit 8567a73

Browse files
committed
fix(css-vars): handle empty string and other data types
1 parent 4b8e266 commit 8567a73

File tree

8 files changed

+73
-11
lines changed

8 files changed

+73
-11
lines changed

packages/runtime-core/src/hydration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
isReservedProp,
2929
isString,
3030
normalizeClass,
31+
normalizeCssVarValue,
3132
normalizeStyle,
3233
stringifyStyle,
3334
} from '@vue/shared'
@@ -938,7 +939,7 @@ function resolveCssVars(
938939
) {
939940
const cssVars = instance.getCssVars()
940941
for (const key in cssVars) {
941-
const value = String(cssVars[key] ?? 'initial')
942+
const value = normalizeCssVarValue(cssVars[key])
942943
expectedMap.set(`--${getEscapedCssVarName(key, false)}`, value)
943944
}
944945
}

packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,13 @@ describe('useCssVars', () => {
467467
})
468468

469469
test('should set vars as `initial` for nullish values', async () => {
470+
// `getPropertyValue` cannot reflect the real value for white spaces and JSDOM also
471+
// doesn't 100% reflect the real behavior of browsers, so we only keep the test for
472+
// `initial` value here.
473+
// The value normalization is tested in packages/shared/__tests__/cssVars.spec.ts.
470474
const state = reactive<Record<string, unknown>>({
471-
color: undefined,
472-
size: null,
475+
foo: undefined,
476+
bar: null,
473477
})
474478
const root = document.createElement('div')
475479
const App = {
@@ -481,7 +485,7 @@ describe('useCssVars', () => {
481485
render(h(App), root)
482486
await nextTick()
483487
const style = (root.children[0] as HTMLElement).style
484-
expect(style.getPropertyValue(`--color`)).toBe('initial')
485-
expect(style.getPropertyValue(`--size`)).toBe('initial')
488+
expect(style.getPropertyValue('--foo')).toBe('initial')
489+
expect(style.getPropertyValue('--bar')).toBe('initial')
486490
})
487491
})

packages/runtime-dom/src/helpers/useCssVars.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
warn,
1111
watch,
1212
} from '@vue/runtime-core'
13-
import { NOOP, ShapeFlags } from '@vue/shared'
13+
import { NOOP, ShapeFlags, normalizeCssVarValue } from '@vue/shared'
1414

1515
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
1616
/**
@@ -101,7 +101,7 @@ function setVarsOnNode(el: Node, vars: Record<string, unknown>) {
101101
const style = (el as HTMLElement).style
102102
let cssText = ''
103103
for (const key in vars) {
104-
const value = String(vars[key] ?? 'initial')
104+
const value = normalizeCssVarValue(vars[key])
105105
style.setProperty(`--${key}`, value)
106106
cssText += `--${key}: ${value};`
107107
}

packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,14 @@ describe('ssr: renderStyle', () => {
208208
expect(
209209
ssrRenderStyle({
210210
fontSize: null,
211-
':--color': undefined,
212-
':--size': null,
211+
':--v1': undefined,
212+
':--v2': null,
213+
':--v3': '',
214+
':--v4': ' ',
215+
':--v5': 'foo',
216+
':--v6': 0,
213217
'--foo': 1,
214218
}),
215-
).toBe(`--color:initial;--size:initial;--foo:1;`)
219+
).toBe(`--v1:initial;--v2:initial;--v3: ;--v4: ;--v5:foo;--v6:0;--foo:1;`)
216220
})
217221
})

packages/server-renderer/src/helpers/ssrRenderAttrs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
isString,
1515
makeMap,
1616
normalizeClass,
17+
normalizeCssVarValue,
1718
normalizeStyle,
1819
propsToAttrMap,
1920
} from '@vue/shared'
@@ -105,7 +106,7 @@ function ssrResetCssVars(raw: unknown) {
105106
for (const key in raw) {
106107
// `:` prefixed keys are coming from `ssrCssVars`
107108
if (key.startsWith(':--')) {
108-
res[key.slice(1)] = raw[key] ?? 'initial'
109+
res[key.slice(1)] = normalizeCssVarValue(raw[key])
109110
} else {
110111
res[key] = raw[key]
111112
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { normalizeCssVarValue } from '../src'
2+
3+
describe('utils/cssVars', () => {
4+
test('should normalize css binding values correctly', () => {
5+
expect(normalizeCssVarValue(null)).toBe('initial')
6+
expect(normalizeCssVarValue(undefined)).toBe('initial')
7+
expect(normalizeCssVarValue('')).toBe(' ')
8+
expect(normalizeCssVarValue(' ')).toBe(' ')
9+
expect(normalizeCssVarValue('foo')).toBe('foo')
10+
expect(normalizeCssVarValue(0)).toBe('0')
11+
})
12+
13+
test('should warn on invalid css binding values', () => {
14+
const warning =
15+
'[Vue warn] Invalid value used for CSS binding. Expected a string or a finite number but received:'
16+
expect(normalizeCssVarValue(NaN)).toBe('NaN')
17+
expect(warning).toHaveBeenWarnedTimes(1)
18+
expect(normalizeCssVarValue(Infinity)).toBe('Infinity')
19+
expect(warning).toHaveBeenWarnedTimes(2)
20+
expect(normalizeCssVarValue(-Infinity)).toBe('-Infinity')
21+
expect(warning).toHaveBeenWarnedTimes(3)
22+
expect(normalizeCssVarValue({})).toBe('[object Object]')
23+
expect(warning).toHaveBeenWarnedTimes(4)
24+
expect(normalizeCssVarValue([])).toBe('')
25+
expect(warning).toHaveBeenWarnedTimes(5)
26+
})
27+
})

packages/shared/src/cssVars.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Normalize CSS var value created by `v-bind` in `<style>` block
3+
* See https://github.com/vuejs/core/pull/12461#issuecomment-2495804664
4+
*/
5+
export function normalizeCssVarValue(value: unknown): string {
6+
if (value == null) {
7+
return 'initial'
8+
}
9+
10+
if (typeof value === 'string') {
11+
return value === '' ? ' ' : value
12+
}
13+
14+
if (typeof value !== 'number' || !Number.isFinite(value)) {
15+
if (__DEV__) {
16+
console.warn(
17+
'[Vue warn] Invalid value used for CSS binding. Expected a string or a finite number but received:',
18+
value,
19+
)
20+
}
21+
}
22+
23+
return String(value)
24+
}

packages/shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './escapeHtml'
1212
export * from './looseEqual'
1313
export * from './toDisplayString'
1414
export * from './typeUtils'
15+
export * from './cssVars'

0 commit comments

Comments
 (0)