diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 6226ec60..47735f04 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -92,8 +92,8 @@ function genData (el) { data += `style:${el.styleBinding},` } // transition - if (el.transition) { - data += `transition:${el.transition},` + if (el.transition != null) { + data += `transition:__resolveTransition__(${el.transition}),` } // props if (el.props) { diff --git a/src/compiler/helpers.js b/src/compiler/helpers.js index 2c1fa14b..ed34fc20 100644 --- a/src/compiler/helpers.js +++ b/src/compiler/helpers.js @@ -49,7 +49,7 @@ export function addHandler (el, name, value, modifiers) { export function getBindingAttr (el, name, getStatic) { const staticValue = getStatic !== false && getAndRemoveAttr(el, name) - return staticValue + return staticValue || staticValue === '' ? JSON.stringify(staticValue) : (getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind:' + name)) } diff --git a/src/runtime/dom-backend/class-util.js b/src/runtime/dom-backend/class-util.js new file mode 100644 index 00000000..5b462ade --- /dev/null +++ b/src/runtime/dom-backend/class-util.js @@ -0,0 +1,79 @@ +import { isIE9 } from '../util/index' +import { svgNS } from './node-ops' + +/** + * In IE9, setAttribute('class') will result in empty class + * if the element also has the :class attribute; However in + * PhantomJS, setting `className` does not work on SVG elements... + * So we have to do a conditional check here. + * + * @param {Element} el + * @param {String} cls + */ + +export function setClass (el, cls) { + /* istanbul ignore else */ + if (!isIE9 || el.namespaceURI === svgNS) { + el.setAttribute('class', cls) + } else { + el.className = cls + } +} + +/** + * Add class with compatibility for IE & SVG + * + * @param {Element} el + * @param {String} cls + */ + +export function addClass (el, cls) { + if (el.classList) { + el.classList.add(cls) + } else { + let cur = ' ' + getClass(el) + ' ' + if (cur.indexOf(' ' + cls + ' ') < 0) { + setClass(el, (cur + cls).trim()) + } + } +} + +/** + * Remove class with compatibility for IE & SVG + * + * @param {Element} el + * @param {String} cls + */ + +export function removeClass (el, cls) { + if (el.classList) { + el.classList.remove(cls) + } else { + let cur = ' ' + getClass(el) + ' ' + let tar = ' ' + cls + ' ' + while (cur.indexOf(tar) >= 0) { + cur = cur.replace(tar, ' ') + } + setClass(el, cur.trim()) + } + if (!el.className) { + el.removeAttribute('class') + } +} + +/** + * For IE9 compat: when both class and :class are present + * getAttribute('class') returns wrong value... but className + * on SVG elements returns an object. + * + * @param {Element} el + * @return {String} + */ + +function getClass (el) { + var classname = el.className + if (typeof classname === 'object') { + classname = classname.baseVal || '' + } + return classname +} diff --git a/src/runtime/dom-backend/index.js b/src/runtime/dom-backend/index.js index cca78e97..9c94f727 100644 --- a/src/runtime/dom-backend/index.js +++ b/src/runtime/dom-backend/index.js @@ -6,6 +6,7 @@ import props from './modules/props' import attrs from './modules/attrs' import events from './modules/events' import directives from './modules/directives' +import transition from './modules/transition' export const patch = createPatchFunction({ nodeOps, @@ -15,6 +16,7 @@ export const patch = createPatchFunction({ attrs, style, events, - directives + directives, + transition ] }) diff --git a/src/runtime/dom-backend/modules/class.js b/src/runtime/dom-backend/modules/class.js index 4fc38ff6..337b993d 100644 --- a/src/runtime/dom-backend/modules/class.js +++ b/src/runtime/dom-backend/modules/class.js @@ -1,20 +1,25 @@ -import { isIE9, isArray, isObject } from '../../util/index' +import { isArray, isObject } from '../../util/index' +import { setClass } from '../class-util' function updateClass (oldVnode, vnode) { let dynamicClass = vnode.data.class let staticClass = vnode.data.staticClass - if (staticClass || dynamicClass) { + const el = vnode.elm + const activeClass = el._activeClass + if (staticClass || dynamicClass || activeClass) { dynamicClass = genClass(dynamicClass) - let cls = staticClass - ? staticClass + (dynamicClass ? ' ' + dynamicClass : '') - : dynamicClass + const cls = concatClass(concatClass(staticClass, dynamicClass), activeClass) if (cls !== oldVnode.class) { - setClass(vnode.elm, cls) + setClass(el, cls) } vnode.class = cls } } +function concatClass (a, b) { + return a ? b ? (a + ' ' + b) : a : (b || '') +} + function genClass (data) { if (!data) { return '' @@ -38,25 +43,6 @@ function genClass (data) { } } -/** - * In IE9, setAttribute('class') will result in empty class - * if the element also has the :class attribute; However in - * PhantomJS, setting `className` does not work on SVG elements... - * So we have to do a conditional check here. - * - * @param {Element} el - * @param {String} cls - */ - -export function setClass (el, cls) { - /* istanbul ignore if */ - if (isIE9 && !/svg$/.test(el.namespaceURI)) { - el.className = cls - } else { - el.setAttribute('class', cls) - } -} - export default { create: updateClass, update: updateClass diff --git a/src/runtime/dom-backend/modules/directives.js b/src/runtime/dom-backend/modules/directives.js index 242ba98c..e44fdbd2 100644 --- a/src/runtime/dom-backend/modules/directives.js +++ b/src/runtime/dom-backend/modules/directives.js @@ -1,11 +1,11 @@ export default { - create: function (oldVnode, vnode) { + create: function bindDirectives (oldVnode, vnode) { applyDirectives(oldVnode, vnode, 'bind') }, - update: function (oldVnode, vnode) { + update: function updateDirectives (oldVnode, vnode) { applyDirectives(oldVnode, vnode, 'update', true) }, - destroy: function (vnode) { + destroy: function unbindDirectives (vnode) { applyDirectives(null, vnode, 'unbind') } } diff --git a/src/runtime/dom-backend/modules/transition.js b/src/runtime/dom-backend/modules/transition.js new file mode 100644 index 00000000..ed9506e3 --- /dev/null +++ b/src/runtime/dom-backend/modules/transition.js @@ -0,0 +1,60 @@ +import { addClass, removeClass } from '../class-util' +import { + isIE9, + inBrowser, + transitionProp, + transitionEndEvent, + animationProp, + animationEndEvent +} from '../../util/index' + +export default isIE9 ? {} : { + create: function applyEnterTransition (_, vnode) { + let data = vnode.data.transition + const el = vnode.elm + if (data != null) { + if (typeof data === 'string') { + // pure CSS + data = cssTransition(data) + } + // apply enter class + const enterClass = data.enterClass + if (enterClass) { + addClass(el, enterClass) + nextFrame(() => { + removeClass(el, enterClass) + }) + } + const enterActiveClass = data.enterActiveClass + if (enterActiveClass) { + el._activeClass = enterActiveClass + addClass(el, enterActiveClass) + el.addEventListener(transitionEndEvent, () => { + el._activeClass = null + removeClass(el, enterActiveClass) + }) + } + } + }, + + remove: function applyLeaveTransition (vnode, rm) { + + } +} + +const raf = (inBrowser && window.requestAnimationFrame) || setTimeout +function nextFrame (fn) { + raf(() => { + raf(fn) + }) +} + +function cssTransition (name) { + name = name || 'v' + return { + enterClass: `${name}-enter`, + leaveClass: `${name}-leave`, + enterActiveClass: `${name}-enter-active`, + leaveActiveClass: `${name}-leave-active` + } +} diff --git a/src/runtime/dom-backend/node-ops.js b/src/runtime/dom-backend/node-ops.js index 8ea3aba5..4fd6fbfa 100644 --- a/src/runtime/dom-backend/node-ops.js +++ b/src/runtime/dom-backend/node-ops.js @@ -1,4 +1,4 @@ -const svgNS = 'http://www.w3.org/2000/svg' +export const svgNS = 'http://www.w3.org/2000/svg' export function createElement (tagName) { return document.createElement(tagName) diff --git a/src/runtime/instance/render.js b/src/runtime/instance/render.js index 4a0ee9c0..425be389 100644 --- a/src/runtime/instance/render.js +++ b/src/runtime/instance/render.js @@ -34,6 +34,13 @@ export function renderMixin (Vue) { return resolveAsset(this.$options, 'directives', id, true) } + // resolve transition + Vue.prototype.__resolveTransition__ = function (id) { + return id && typeof id === 'string' + ? resolveAsset(this.$options, 'transitions', id) || id + : id + } + // toString for mustaches Vue.prototype.__toString__ = function (val) { return val == null