diff --git a/packages/runtime-dom/__tests__/modules/style.spec.ts b/packages/runtime-dom/__tests__/modules/style.spec.ts index 1e3770ce2..28eac3b42 100644 --- a/packages/runtime-dom/__tests__/modules/style.spec.ts +++ b/packages/runtime-dom/__tests__/modules/style.spec.ts @@ -45,4 +45,33 @@ describe(`module style`, () => { expect(el.style.getPropertyValue('color')).toBe('red') expect(el.style.getPropertyValue('margin-right')).toBe('10px') }) + + // JSDOM doesn't support custom properties on style object so we have to + // mock it here. + function mockElementWithStyle() { + const store: any = {} + return { + style: { + WebkitTransition: '', + setProperty(key: string, val: string) { + store[key] = val + }, + getPropertyValue(key: string) { + return store[key] + } + } + } + } + + it('CSS custom properties', () => { + const el = mockElementWithStyle() + patchStyle(el as any, {}, { '--theme': 'red' } as any) + expect(el.style.getPropertyValue('--theme')).toBe('red') + }) + + it('auto vendor prefixing', () => { + const el = mockElementWithStyle() + patchStyle(el as any, {}, { transition: 'all 1s' }) + expect(el.style.WebkitTransition).toBe('all 1s') + }) }) diff --git a/packages/runtime-dom/src/modules/style.ts b/packages/runtime-dom/src/modules/style.ts index 55ef38709..67c5593fe 100644 --- a/packages/runtime-dom/src/modules/style.ts +++ b/packages/runtime-dom/src/modules/style.ts @@ -1,4 +1,5 @@ -import { isString, hyphenate } from '@vue/shared' +import { isString, hyphenate, capitalize } from '@vue/shared' +import { camelize } from '@vue/runtime-core' type Style = string | Partial | null @@ -25,16 +26,42 @@ export function patchStyle(el: Element, prev: Style, next: Style) { const importantRE = /\s*!important$/ function setStyle(style: CSSStyleDeclaration, name: string, val: string) { - let rawName = hyphenate(name) - if (importantRE.test(val)) { - style.setProperty(rawName, val.replace(importantRE, ''), 'important') + if (name.startsWith('--')) { + // custom property definition + style.setProperty(name, val) } else { - /** - * TODO: - * 1. support values array created by autoprefixer. - * 2. support css variable, maybe should import 'csstype'. - * similar to https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L1450 - */ - style.setProperty(rawName, val) + const prefixed = autoPrefix(style, name) + if (importantRE.test(val)) { + // !important + style.setProperty( + hyphenate(prefixed), + val.replace(importantRE, ''), + 'important' + ) + } else { + style[prefixed as any] = val + } } } + +const prefixes = ['Webkit', 'Moz', 'ms'] +const prefixCache: Record = {} + +function autoPrefix(style: CSSStyleDeclaration, rawName: string): string { + const cached = prefixCache[rawName] + if (cached) { + return cached + } + let name = camelize(rawName) + if (name !== 'filter' && name in style) { + return (prefixCache[rawName] = name) + } + name = capitalize(name) + for (let i = 0; i < prefixes.length; i++) { + const prefixed = prefixes[i] + name + if (prefixed in style) { + return (prefixCache[rawName] = prefixed) + } + } + return rawName +}