Skip to content

enable style merge behavior between parent-child components (fix #3997) #4138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions flow/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ declare type ASTElement = {

staticClass?: string;
classBinding?: string;
staticStyle?: string;
styleBinding?: string;
events?: ASTElementHandlers;
nativeEvents?: ASTElementHandlers;
Expand Down
1 change: 1 addition & 0 deletions flow/vnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ declare interface VNodeData {
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: string;
style?: Array<Object> | Object;
props?: { [key: string]: any };
attrs?: { [key: string]: string };
Expand Down
36 changes: 31 additions & 5 deletions src/platforms/web/compiler/modules/style.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
/* @flow */

import { parseText } from 'compiler/parser/text-parser'
import {
getBindingAttr
getAndRemoveAttr,
getBindingAttr,
baseWarn
} from 'compiler/helpers'

function transformNode (el: ASTElement) {
function transformNode (el: ASTElement, options: CompilerOptions) {
const warn = options.warn || baseWarn
const staticStyle = getAndRemoveAttr(el, 'style')
if (staticStyle) {
if (process.env.NODE_ENV !== 'production') {
const expression = parseText(staticStyle, options.delimiters)
if (expression) {
warn(
`style="${staticStyle}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div style="{{ val }}">, use <div :style="val">.'
)
}
}
el.staticStyle = JSON.stringify(staticStyle)
}

const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
if (styleBinding) {
el.styleBinding = styleBinding
}
}

function genData (el: ASTElement): string {
return el.styleBinding
? `style:(${el.styleBinding}),`
: ''
let data = ''
if (el.staticStyle) {
data += `staticStyle:${el.staticStyle},`
}
if (el.styleBinding) {
data += `style:(${el.styleBinding}),`
}
return data
}

export default {
staticKeys: ['staticStyle'],
transformNode,
genData
}
39 changes: 17 additions & 22 deletions src/platforms/web/runtime/modules/style.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* @flow */

import { cached, extend, camelize, toObject } from 'shared/util'
import { cached, camelize, extend, looseEqual } from 'shared/util'
import { normalizeBindingStyle, getStyle } from 'web/util/style'

const cssVarRE = /^--/
const setProp = (el, name, val) => {
Expand Down Expand Up @@ -31,45 +32,39 @@ const normalize = cached(function (prop) {
})

function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if ((!oldVnode.data || !oldVnode.data.style) && !vnode.data.style) {
const data = vnode.data
const oldData = oldVnode.data

if (!data.staticStyle && !data.style &&
!oldData.staticStyle && !oldData.style) {
return
}

let cur, name
const el: any = vnode.elm
const oldStyle: any = oldVnode.data.style || {}
let style: any = vnode.data.style || {}

// handle string
if (typeof style === 'string') {
el.style.cssText = style
return
}

const needClone = style.__ob__
const style: Object = normalizeBindingStyle(vnode.data.style || {})
vnode.data.style = extend({}, style)

// handle array syntax
if (Array.isArray(style)) {
style = vnode.data.style = toObject(style)
}
const newStyle: Object = getStyle(vnode, true)

// clone the style for future updates,
// in case the user mutates the style object in-place.
if (needClone) {
style = vnode.data.style = extend({}, style)
if (looseEqual(el._prevStyle, newStyle)) {
return
}

for (name in oldStyle) {
if (style[name] == null) {
if (newStyle[name] == null) {
setProp(el, name, '')
}
}
for (name in style) {
cur = style[name]
for (name in newStyle) {
cur = newStyle[name]
if (cur !== oldStyle[name]) {
// ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur)
}
}
el._prevStyle = newStyle
}

export default {
Expand Down
49 changes: 12 additions & 37 deletions src/platforms/web/server/modules/style.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
/* @flow */
import { hyphenate } from 'shared/util'
import { getStyle } from 'web/util/style'

import { hyphenate, toObject } from 'shared/util'

function concatStyleString (former: string, latter: string) {
if (former === '' || latter === '' || former.charAt(former.length - 1) === ';') {
return former + latter
}
return former + ';' + latter
}

function generateStyleText (node) {
const staticStyle = node.data.attrs && node.data.attrs.style
let styles = node.data.style
const parentStyle = node.parent ? generateStyleText(node.parent) : ''

if (!styles && !staticStyle) {
return parentStyle
}

let dynamicStyle = ''
if (styles) {
if (typeof styles === 'string') {
dynamicStyle += styles
} else {
if (Array.isArray(styles)) {
styles = toObject(styles)
}
for (const key in styles) {
dynamicStyle += `${hyphenate(key)}:${styles[key]};`
}
}
function genStyleText (vnode: VNode): string {
let styleText = ''
const style = getStyle(vnode, false)
for (const key in style) {
styleText += `${hyphenate(key)}:${style[key]};`
}

dynamicStyle = concatStyleString(parentStyle, dynamicStyle)
return concatStyleString(dynamicStyle, staticStyle || '')
return styleText.slice(0, -1)
}

export default function renderStyle (node: VNodeWithData): ?string {
const res = generateStyleText(node)
if (res) {
return ` style=${JSON.stringify(res)}`
export default function renderStyle (vnode: VNodeWithData): ?string {
const styleText = genStyleText(vnode)
if (styleText) {
return ` style=${JSON.stringify(styleText)}`
}
}
66 changes: 66 additions & 0 deletions src/platforms/web/util/style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* @flow */

import { cached, extend, toObject } from 'shared/util'

const parseStyleText = cached(function (cssText) {
const rs = {}
if (!cssText) {
return rs
}
const hasBackground = cssText.indexOf('background') >= 0
// maybe with background-image: url(http://xxx) or base64 img
const listDelimiter = hasBackground ? /;(?![^(]*\))/g : ';'
const propertyDelimiter = hasBackground ? /:(.+)/ : ':'
cssText.split(listDelimiter).forEach(function (item) {
if (item) {
var tmp = item.split(propertyDelimiter)
tmp.length > 1 && (rs[tmp[0].trim()] = tmp[1].trim())
}
})
return rs
})

function normalizeStyleData (styleData: Object): Object {
const style = normalizeBindingStyle(styleData.style)
const staticStyle = parseStyleText(styleData.staticStyle)
return extend(extend({}, staticStyle), style)
}

export function normalizeBindingStyle (bindingStyle: any): Object {
if (Array.isArray(bindingStyle)) {
return toObject(bindingStyle)
}

if (typeof bindingStyle === 'string') {
return parseStyleText(bindingStyle)
}
return bindingStyle
}

/**
* parent component style should be after child's
* so that parent component's style could override it
*/
export function getStyle (vnode: VNode, checkChild: boolean): Object {
let data = vnode.data
let parentNode = vnode
let childNode = vnode

data = normalizeStyleData(data)

if (checkChild) {
while (childNode.child) {
childNode = childNode.child._vnode
if (childNode.data) {
data = extend(normalizeStyleData(childNode.data), data)
}
}
}
while ((parentNode = parentNode.parent)) {
if (parentNode.data) {
data = extend(data, normalizeStyleData(parentNode.data))
}
}
return data
}

8 changes: 4 additions & 4 deletions test/ssr/ssr-string.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('SSR: renderToString', () => {
}
}, result => {
expect(result).toContain(
'<div server-rendered="true" style="font-size:14px;color:red;background-color:black"></div>'
'<div server-rendered="true" style="background-color:black;font-size:14px;color:red"></div>'
)
done()
})
Expand Down Expand Up @@ -107,13 +107,13 @@ describe('SSR: renderToString', () => {

it('nested custom component style', done => {
renderVmWithOptions({
template: '<comp :style="style"></comp>',
template: '<comp style="color: blue" :style="style"></comp>',
data: {
style: 'color:red'
},
components: {
comp: {
template: '<nested style="font-size:520rem"></nested>',
template: '<nested style="text-align: left;" :style="{fontSize:\'520rem\'}"></nested>',
components: {
nested: {
template: '<div></div>'
Expand All @@ -123,7 +123,7 @@ describe('SSR: renderToString', () => {
}
}, result => {
expect(result).toContain(
'<div server-rendered="true" style="color:red;font-size:520rem"></div>'
'<div server-rendered="true" style="text-align:left;font-size:520rem;color:red"></div>'
)
done()
})
Expand Down
Loading