mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): support patch style (#126)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
b5e12eaca7
commit
3d10925c53
|
@ -90,13 +90,133 @@ describe('patchProp', () => {
|
|||
setStyle(el, 'color: red')
|
||||
expect(el.style.cssText).toBe('color: red;')
|
||||
})
|
||||
test.fails('shoud set style with object and array property', () => {
|
||||
|
||||
test('should work with camelCase', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { fontSize: '12px' })
|
||||
expect(el.style.cssText).toBe('font-size: 12px;')
|
||||
})
|
||||
|
||||
test('shoud set style with object and array property', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { color: 'red' })
|
||||
expect(el.style.cssText).toBe('color: red;')
|
||||
setStyle(el, [{ color: 'blue' }, { fontSize: '12px' }])
|
||||
expect(el.style.cssText).toBe('color: blue; font-size: 12px;')
|
||||
})
|
||||
|
||||
test('should remove if falsy value', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { color: undefined, borderRadius: null })
|
||||
expect(el.style.cssText).toBe('')
|
||||
setStyle(el, { color: 'red' })
|
||||
expect(el.style.cssText).toBe('color: red;')
|
||||
setStyle(el, { color: undefined, borderRadius: null })
|
||||
expect(el.style.cssText).toBe('')
|
||||
})
|
||||
|
||||
test('should work with !important', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { color: 'red !important' })
|
||||
expect(el.style.cssText).toBe('color: red !important;')
|
||||
})
|
||||
|
||||
test('should work with camelCase and !important', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { fontSize: '12px !important' })
|
||||
expect(el.style.cssText).toBe('font-size: 12px !important;')
|
||||
})
|
||||
|
||||
test('should work with multiple entries', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { color: 'red', marginRight: '10px' })
|
||||
expect(el.style.getPropertyValue('color')).toBe('red')
|
||||
expect(el.style.getPropertyValue('margin-right')).toBe('10px')
|
||||
})
|
||||
|
||||
test('should patch with falsy style value', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { width: '100px' })
|
||||
expect(el.style.cssText).toBe('width: 100px;')
|
||||
setStyle(el, { width: 0 })
|
||||
expect(el.style.cssText).toBe('width: 0px;')
|
||||
})
|
||||
|
||||
test('should remove style attribute on falsy value', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { width: '100px' })
|
||||
expect(el.style.cssText).toBe('width: 100px;')
|
||||
setStyle(el, { width: undefined })
|
||||
expect(el.style.cssText).toBe('')
|
||||
|
||||
setStyle(el, { width: '100px' })
|
||||
expect(el.style.cssText).toBe('width: 100px;')
|
||||
setStyle(el, null)
|
||||
expect(el.hasAttribute('style')).toBe(false)
|
||||
expect(el.style.cssText).toBe('')
|
||||
})
|
||||
|
||||
test('should warn for trailing semicolons', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { color: 'red;' })
|
||||
expect(
|
||||
`Unexpected semicolon at the end of 'color' style value: 'red;'`,
|
||||
).toHaveBeenWarned()
|
||||
|
||||
setStyle(el, { '--custom': '100; ' })
|
||||
expect(
|
||||
`Unexpected semicolon at the end of '--custom' style value: '100; '`,
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should not warn for trailing semicolons', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { '--custom': '100\\;' })
|
||||
expect(el.style.getPropertyValue('--custom')).toBe('100\\;')
|
||||
})
|
||||
|
||||
test('should work with shorthand properties', () => {
|
||||
const el = document.createElement('div')
|
||||
setStyle(el, { borderBottom: '1px solid red', border: '1px solid green' })
|
||||
expect(el.style.border).toBe('1px solid green')
|
||||
expect(el.style.borderBottom).toBe('1px solid green')
|
||||
})
|
||||
|
||||
// JSDOM doesn't support custom properties on style object so we have to
|
||||
// mock it here.
|
||||
function mockElementWithStyle() {
|
||||
const store: any = {}
|
||||
return {
|
||||
style: {
|
||||
display: '',
|
||||
WebkitTransition: '',
|
||||
setProperty(key: string, val: string) {
|
||||
store[key] = val
|
||||
},
|
||||
getPropertyValue(key: string) {
|
||||
return store[key]
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('should work with css custom properties', () => {
|
||||
const el = mockElementWithStyle()
|
||||
setStyle(el as any, { '--theme': 'red' })
|
||||
expect(el.style.getPropertyValue('--theme')).toBe('red')
|
||||
})
|
||||
|
||||
test('should auto vendor prefixing', () => {
|
||||
const el = mockElementWithStyle()
|
||||
setStyle(el as any, { transition: 'all 1s' })
|
||||
expect(el.style.WebkitTransition).toBe('all 1s')
|
||||
})
|
||||
|
||||
test('should work with multiple values', () => {
|
||||
const el = mockElementWithStyle()
|
||||
setStyle(el as any, { display: ['-webkit-box', '-ms-flexbox', 'flex'] })
|
||||
expect(el.style.display).toBe('flex')
|
||||
})
|
||||
})
|
||||
|
||||
describe('setAttr', () => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { isArray, toDisplayString } from '@vue/shared'
|
||||
import type { Block, ParentBlock } from './render'
|
||||
|
||||
export * from './dom/style'
|
||||
export * from './dom/prop'
|
||||
export * from './dom/event'
|
||||
export * from './dom/templateRef'
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from '@vue/shared'
|
||||
import { currentInstance } from '../component'
|
||||
import { warn } from '../warning'
|
||||
import { setStyle } from './style'
|
||||
|
||||
export function recordPropMetadata(el: Node, key: string, value: any): any {
|
||||
if (!currentInstance) {
|
||||
|
@ -34,17 +35,6 @@ export function setClass(el: Element, value: any) {
|
|||
}
|
||||
}
|
||||
|
||||
export function setStyle(el: HTMLElement, value: any) {
|
||||
const prev = recordPropMetadata(el, 'style', (value = normalizeStyle(value)))
|
||||
if (value !== prev && (value || prev)) {
|
||||
if (typeof value === 'string') {
|
||||
el.style.cssText = value
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setAttr(el: Element, key: string, value: any) {
|
||||
const oldVal = recordPropMetadata(el, key, value)
|
||||
if (value !== oldVal) {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import {
|
||||
camelize,
|
||||
capitalize,
|
||||
hyphenate,
|
||||
isArray,
|
||||
isString,
|
||||
normalizeStyle,
|
||||
} from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { recordPropMetadata } from './prop'
|
||||
|
||||
export function setStyle(el: HTMLElement, value: any) {
|
||||
const prev = recordPropMetadata(el, 'style', (value = normalizeStyle(value)))
|
||||
patchStyle(el, prev, value)
|
||||
}
|
||||
|
||||
// TODO copied from packages/runtime-dom/src/modules/style.ts
|
||||
|
||||
type Style = string | Record<string, string | string[]> | null
|
||||
|
||||
function patchStyle(el: Element, prev: Style, next: Style) {
|
||||
const style = (el as HTMLElement).style
|
||||
const isCssString = isString(next)
|
||||
if (next && !isCssString) {
|
||||
if (prev && !isString(prev)) {
|
||||
for (const key in prev) {
|
||||
if (next[key] == null) {
|
||||
setStyleValue(style, key, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in next) {
|
||||
setStyleValue(style, key, next[key])
|
||||
}
|
||||
} else {
|
||||
if (isCssString) {
|
||||
// TODO: combine with v-show
|
||||
if (prev !== next) {
|
||||
style.cssText = next
|
||||
}
|
||||
} else if (prev) {
|
||||
el.removeAttribute('style')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const semicolonRE = /[^\\];\s*$/
|
||||
const importantRE = /\s*!important$/
|
||||
|
||||
function setStyleValue(
|
||||
style: CSSStyleDeclaration,
|
||||
name: string,
|
||||
val: string | string[],
|
||||
) {
|
||||
if (isArray(val)) {
|
||||
val.forEach(v => setStyleValue(style, name, v))
|
||||
} else {
|
||||
if (val == null) val = ''
|
||||
if (__DEV__) {
|
||||
if (semicolonRE.test(val)) {
|
||||
warn(
|
||||
`Unexpected semicolon at the end of '${name}' style value: '${val}'`,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (name.startsWith('--')) {
|
||||
// custom property definition
|
||||
style.setProperty(name, val)
|
||||
} else {
|
||||
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<string, string> = {}
|
||||
|
||||
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
|
||||
}
|
Loading…
Reference in New Issue