From 79b6eac029493b78d257031bcfb5153979d05f15 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 23 Jul 2015 11:23:27 -0400 Subject: [PATCH] [release] 0.12.8 --- component.json | 2 +- dist/vue.js | 7422 ++++++++++++++++++++++++----------------------- dist/vue.min.js | 10 +- package.json | 2 +- 4 files changed, 3813 insertions(+), 3623 deletions(-) diff --git a/component.json b/component.json index 4a7bc8369b3..457ec46036b 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "0.12.7", + "version": "0.12.8", "main": "src/vue.js", "author": "Evan You ", "description": "Simple, Fast & Composable MVVM for building interative interfaces", diff --git a/dist/vue.js b/dist/vue.js index 7fb3ae842d8..7ca453745f8 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -1,9 +1,8 @@ -/** - * Vue.js v0.12.7 +/*! + * Vue.js v0.12.8 * (c) 2015 Evan You * Released under the MIT License. */ - (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); @@ -98,9 +97,9 @@ return /******/ (function(modules) { // webpackBootstrap Vue.options = { replace: true, - directives: __webpack_require__(28), - elementDirectives: __webpack_require__(50), - filters: __webpack_require__(53), + directives: __webpack_require__(25), + elementDirectives: __webpack_require__(47), + filters: __webpack_require__(50), transitions: {}, components: {}, partials: {} @@ -132,9 +131,9 @@ return /******/ (function(modules) { // webpackBootstrap * Mixin internal instance methods */ - extend(p, __webpack_require__(55)) - extend(p, __webpack_require__(56)) - extend(p, __webpack_require__(57)) + extend(p, __webpack_require__(52)) + extend(p, __webpack_require__(53)) + extend(p, __webpack_require__(54)) extend(p, __webpack_require__(58)) extend(p, __webpack_require__(60)) @@ -155,12 +154,12 @@ return /******/ (function(modules) { // webpackBootstrap /* 1 */ /***/ function(module, exports, __webpack_require__) { - var lang = __webpack_require__(3) + var lang = __webpack_require__(4) var extend = lang.extend extend(exports, lang) - extend(exports, __webpack_require__(4)) extend(exports, __webpack_require__(5)) + extend(exports, __webpack_require__(6)) extend(exports, __webpack_require__(2)) extend(exports, __webpack_require__(7)) extend(exports, __webpack_require__(8)) @@ -171,6 +170,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) + var config = __webpack_require__(3) var extend = _.extend /** @@ -216,7 +216,7 @@ return /******/ (function(modules) { // webpackBootstrap return parentVal } if (typeof childVal !== 'function') { - _.warn( + ("development") !== 'production' && _.warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.' @@ -261,7 +261,7 @@ return /******/ (function(modules) { // webpackBootstrap strats.el = function (parentVal, childVal, vm) { if (!vm && childVal && typeof childVal !== 'function') { - _.warn( + ("development") !== 'production' && _.warn( 'The "el" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.' @@ -303,7 +303,7 @@ return /******/ (function(modules) { // webpackBootstrap strats.paramAttributes = function () { /* istanbul ignore next */ - _.warn( + ("development") !== 'production' && _.warn( '"paramAttributes" option has been deprecated in 0.12. ' + 'Use "props" instead.' ) @@ -317,18 +317,17 @@ return /******/ (function(modules) { // webpackBootstrap * options and parent options. */ - strats.directives = - strats.filters = - strats.transitions = - strats.components = - strats.partials = - strats.elementDirectives = function (parentVal, childVal) { + function mergeAssets (parentVal, childVal) { var res = Object.create(parentVal) return childVal - ? extend(res, childVal) + ? extend(res, guardArrayAssets(childVal)) : res } + config._assetTypes.forEach(function (type) { + strats[type + 's'] = mergeAssets + }) + /** * Events & Watchers. * @@ -382,23 +381,28 @@ return /******/ (function(modules) { // webpackBootstrap * Make sure component options get converted to actual * constructors. * - * @param {Object} components + * @param {Object} options */ - function guardComponents (components) { - if (components) { + function guardComponents (options) { + if (options.components) { + var components = options.components = + guardArrayAssets(options.components) var def - for (var key in components) { + var ids = Object.keys(components) + for (var i = 0, l = ids.length; i < l; i++) { + var key = ids[i] if (_.commonTagRE.test(key)) { - _.warn( + ("development") !== 'production' && _.warn( 'Do not use built-in HTML elements as component ' + - 'name: ' + key + 'id: ' + key ) + continue } def = components[key] if (_.isPlainObject(def)) { - def.name = key - components[key] = _.Vue.extend(def) + def.id = def.id || key + components[key] = def._Ctor || (def._Ctor = _.Vue.extend(def)) } } } @@ -431,6 +435,35 @@ return /******/ (function(modules) { // webpackBootstrap } } + /** + * Guard an Array-format assets option and converted it + * into the key-value Object format. + * + * @param {Object|Array} assets + * @return {Object} + */ + + function guardArrayAssets (assets) { + if (_.isArray(assets)) { + var res = {} + var i = assets.length + var asset + while (i--) { + asset = assets[i] + var id = asset.id || (asset.options && asset.options.id) + if (!id) { + ("development") !== 'production' && _.warn( + 'Array-syntax assets must provide an id field.' + ) + } else { + res[id] = asset + } + } + return res + } + return assets + } + /** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. @@ -442,7 +475,7 @@ return /******/ (function(modules) { // webpackBootstrap */ exports.mergeOptions = function merge (parent, child, vm) { - guardComponents(child.components) + guardComponents(child) guardProps(child) var options = {} var key @@ -479,7 +512,7 @@ return /******/ (function(modules) { // webpackBootstrap exports.resolveAsset = function resolve (options, type, id) { var asset = options[type][id] - while (!asset && options._parent) { + while (!config.strict && !asset && options._parent) { options = options._parent.$options asset = options[type][id] } @@ -489,6 +522,129 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, /* 3 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = { + + /** + * The prefix to look for when parsing directives. + * + * @type {String} + */ + + prefix: 'v-', + + /** + * Whether to print debug messages. + * Also enables stack trace for warnings. + * + * @type {Boolean} + */ + + debug: false, + + /** + * Whether to suppress warnings. + * + * @type {Boolean} + */ + + silent: false, + + /** + * Whether allow observer to alter data objects' + * __proto__. + * + * @type {Boolean} + */ + + proto: true, + + /** + * Whether to parse mustache tags in templates. + * + * @type {Boolean} + */ + + interpolate: true, + + /** + * Whether to use async rendering. + */ + + async: true, + + /** + * Whether to warn against errors caught when evaluating + * expressions. + */ + + warnExpressionErrors: true, + + /** + * Internal flag to indicate the delimiters have been + * changed. + * + * @type {Boolean} + */ + + _delimitersChanged: true, + + /** + * List of asset types that a component can own. + * + * @type {Array} + */ + + _assetTypes: [ + 'component', + 'directive', + 'elementDirective', + 'filter', + 'transition', + 'partial' + ], + + /** + * prop binding modes + */ + + _propBindingModes: { + ONE_WAY: 0, + TWO_WAY: 1, + ONE_TIME: 2 + }, + + /** + * Max circular updates allowed in a batcher flush cycle. + */ + + _maxUpdateCount: 100 + + } + + /** + * Interpolation delimiters. + * We need to mark the changed flag so that the text parser + * knows it needs to recompile the regex. + * + * @type {Array} + */ + + var delimiters = ['{{', '}}'] + Object.defineProperty(module.exports, 'delimiters', { + get: function () { + return delimiters + }, + set: function (val) { + delimiters = val + this._delimitersChanged = true + } + }) + + +/***/ }, +/* 4 */ /***/ function(module, exports, __webpack_require__) { /** @@ -781,7 +937,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 4 */ +/* 5 */ /***/ function(module, exports, __webpack_require__) { // can we use __proto__? @@ -872,11 +1028,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 5 */ +/* 6 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var config = __webpack_require__(6) + var config = __webpack_require__(3) /** * Query an element selector if it's not an element already. @@ -890,7 +1046,9 @@ return /******/ (function(modules) { // webpackBootstrap var selector = el el = document.querySelector(el) if (!el) { - _.warn('Cannot find element: ' + selector) + ("development") !== 'production' && _.warn( + 'Cannot find element: ' + selector + ) } } return el @@ -1138,193 +1296,75 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 6 */ +/* 7 */ /***/ function(module, exports, __webpack_require__) { - module.exports = { - - /** - * The prefix to look for when parsing directives. - * - * @type {String} - */ - - prefix: 'v-', - - /** - * Whether to print debug messages. - * Also enables stack trace for warnings. - * - * @type {Boolean} - */ - - debug: false, - - /** - * Whether to suppress warnings. - * - * @type {Boolean} - */ - - silent: false, - - /** - * Whether allow observer to alter data objects' - * __proto__. - * - * @type {Boolean} - */ - - proto: true, - - /** - * Whether to parse mustache tags in templates. - * - * @type {Boolean} - */ + var _ = __webpack_require__(1) - interpolate: true, + /** + * Check if an element is a component, if yes return its + * component id. + * + * @param {Element} el + * @param {Object} options + * @return {String|undefined} + */ - /** - * Whether to use async rendering. - */ + exports.commonTagRE = /^(div|p|span|img|a|br|ul|ol|li|h1|h2|h3|h4|h5|code|pre)$/ + exports.checkComponent = function (el, options) { + var tag = el.tagName.toLowerCase() + if (tag === 'component') { + // dynamic syntax + var exp = el.getAttribute('is') + el.removeAttribute('is') + return exp + } else if ( + !exports.commonTagRE.test(tag) && + _.resolveAsset(options, 'components', tag) + ) { + return tag + /* eslint-disable no-cond-assign */ + } else if (tag = _.attr(el, 'component')) { + /* eslint-enable no-cond-assign */ + return tag + } + } - async: true, + /** + * Set a prop's initial value on a vm and its data object. + * The vm may have inherit:true so we need to make sure + * we don't accidentally overwrite parent value. + * + * @param {Vue} vm + * @param {Object} prop + * @param {*} value + */ - /** - * Whether to warn against errors caught when evaluating - * expressions. - */ + exports.initProp = function (vm, prop, value) { + if (exports.assertProp(prop, value)) { + var key = prop.path + if (key in vm) { + _.define(vm, key, value, true) + } else { + vm[key] = value + } + vm._data[key] = value + } + } - warnExpressionErrors: true, - - /** - * Internal flag to indicate the delimiters have been - * changed. - * - * @type {Boolean} - */ - - _delimitersChanged: true, - - /** - * List of asset types that a component can own. - * - * @type {Array} - */ - - _assetTypes: [ - 'component', - 'directive', - 'elementDirective', - 'filter', - 'transition', - 'partial' - ], - - /** - * prop binding modes - */ - - _propBindingModes: { - ONE_WAY: 0, - TWO_WAY: 1, - ONE_TIME: 2 - }, - - /** - * Max circular updates allowed in a batcher flush cycle. - */ - - _maxUpdateCount: 100 - - } - - /** - * Interpolation delimiters. - * We need to mark the changed flag so that the text parser - * knows it needs to recompile the regex. - * - * @type {Array} - */ - - var delimiters = ['{{', '}}'] - Object.defineProperty(module.exports, 'delimiters', { - get: function () { - return delimiters - }, - set: function (val) { - delimiters = val - this._delimitersChanged = true - } - }) - - -/***/ }, -/* 7 */ -/***/ function(module, exports, __webpack_require__) { - - var _ = __webpack_require__(1) - - /** - * Check if an element is a component, if yes return its - * component id. - * - * @param {Element} el - * @param {Object} options - * @return {String|undefined} - */ - - exports.commonTagRE = /^(div|p|span|img|a|br|ul|ol|li|h1|h2|h3|h4|h5|code|pre)$/ - exports.checkComponent = function (el, options) { - var tag = el.tagName.toLowerCase() - if (tag === 'component') { - // dynamic syntax - var exp = el.getAttribute('is') - el.removeAttribute('is') - return exp - } else if ( - !exports.commonTagRE.test(tag) && - _.resolveAsset(options, 'components', tag) - ) { - return tag - /* eslint-disable no-cond-assign */ - } else if (tag = _.attr(el, 'component')) { - /* eslint-enable no-cond-assign */ - return tag - } - } - - /** - * Set a prop's initial value on a vm and its data object. - * The vm may have inherit:true so we need to make sure - * we don't accidentally overwrite parent value. - * - * @param {Vue} vm - * @param {Object} prop - * @param {*} value - */ - - exports.initProp = function (vm, prop, value) { - if (exports.assertProp(prop, value)) { - var key = prop.path - if (key in vm) { - _.define(vm, key, value, true) - } else { - vm[key] = value - } - vm._data[key] = value - } - } - - /** - * Assert whether a prop is valid. - * - * @param {Object} prop - * @param {*} value - */ + /** + * Assert whether a prop is valid. + * + * @param {Object} prop + * @param {*} value + */ exports.assertProp = function (prop, value) { + // if a prop is not provided and is not required, + // skip the check. + if (prop.raw === null && !prop.required) { + return true + } var options = prop.options var type = options.type var valid = true @@ -1353,7 +1393,7 @@ return /******/ (function(modules) { // webpackBootstrap } } if (!valid) { - _.warn( + ("development") !== 'production' && _.warn( 'Invalid prop: type check failed for ' + prop.path + '="' + prop.raw + '".' + ' Expected ' + formatType(expectedType) + @@ -1364,7 +1404,7 @@ return /******/ (function(modules) { // webpackBootstrap var validator = options.validator if (validator) { if (!validator.call(null, value)) { - _.warn( + ("development") !== 'production' && _.warn( 'Invalid prop: custom validator check failed for ' + prop.path + '="' + prop.raw + '"' ) @@ -1389,18 +1429,13 @@ return /******/ (function(modules) { // webpackBootstrap /* 8 */ /***/ function(module, exports, __webpack_require__) { - var config = __webpack_require__(6) - /** - * Enable debug utilities. The enableDebug() function and - * all _.log() & _.warn() calls will be dropped in the - * minified production build. + * Enable debug utilities. */ - enableDebug() - - function enableDebug () { + if (true) { + var config = __webpack_require__(3) var hasConsole = typeof console !== 'undefined' /** @@ -1465,23 +1500,23 @@ return /******/ (function(modules) { // webpackBootstrap /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var config = __webpack_require__(6) + var config = __webpack_require__(3) /** * Expose useful internals */ exports.util = _ + exports.config = config exports.nextTick = _.nextTick - exports.config = __webpack_require__(6) - exports.compiler = __webpack_require__(10) + exports.compiler = __webpack_require__(13) exports.parsers = { - path: __webpack_require__(23), - text: __webpack_require__(13), - template: __webpack_require__(25), - directive: __webpack_require__(15), - expression: __webpack_require__(22) + path: __webpack_require__(16), + text: __webpack_require__(10), + template: __webpack_require__(22), + directive: __webpack_require__(12), + expression: __webpack_require__(19) } /** @@ -1494,7 +1529,7 @@ return /******/ (function(modules) { // webpackBootstrap var cid = 1 /** - * Class inehritance + * Class inheritance * * @param {Object} extendOptions */ @@ -1589,2144 +1624,2069 @@ return /******/ (function(modules) { // webpackBootstrap /* 10 */ /***/ function(module, exports, __webpack_require__) { - var _ = __webpack_require__(1) - - _.extend(exports, __webpack_require__(11)) - _.extend(exports, __webpack_require__(27)) - - -/***/ }, -/* 11 */ -/***/ function(module, exports, __webpack_require__) { - - var _ = __webpack_require__(1) - var compileProps = __webpack_require__(12) - var config = __webpack_require__(6) - var textParser = __webpack_require__(13) - var dirParser = __webpack_require__(15) - var templateParser = __webpack_require__(25) - var resolveAsset = _.resolveAsset - var componentDef = __webpack_require__(26) - - // terminal directives - var terminalDirectives = [ - 'repeat', - 'if' - ] + var Cache = __webpack_require__(11) + var config = __webpack_require__(3) + var dirParser = __webpack_require__(12) + var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g + var cache, tagRE, htmlRE, firstChar, lastChar /** - * Compile a template and return a reusable composite link - * function, which recursively contains more link functions - * inside. This top level compile function would normally - * be called on instance root nodes, but can also be used - * for partial compilation if the partial argument is true. - * - * The returned composite link function, when called, will - * return an unlink function that tearsdown all directives - * created during the linking phase. + * Escape a string so it can be used in a RegExp + * constructor. * - * @param {Element|DocumentFragment} el - * @param {Object} options - * @param {Boolean} partial - * @param {Vue} [host] - host vm of transcluded content - * @return {Function} + * @param {String} str */ - exports.compile = function (el, options, partial, host) { - // link function for the node itself. - var nodeLinkFn = partial || !options._asComponent - ? compileNode(el, options) - : null - // link function for the childNodes - var childLinkFn = - !(nodeLinkFn && nodeLinkFn.terminal) && - el.tagName !== 'SCRIPT' && - el.hasChildNodes() - ? compileNodeList(el.childNodes, options) - : null - - /** - * A composite linker function to be called on a already - * compiled piece of DOM, which instantiates all directive - * instances. - * - * @param {Vue} vm - * @param {Element|DocumentFragment} el - * @return {Function|undefined} - */ - - return function compositeLinkFn (vm, el) { - // cache childNodes before linking parent, fix #657 - var childNodes = _.toArray(el.childNodes) - // link - var dirs = linkAndCapture(function () { - if (nodeLinkFn) nodeLinkFn(vm, el, host) - if (childLinkFn) childLinkFn(vm, childNodes, host) - }, vm) - return makeUnlinkFn(vm, dirs) - } + function escapeRegex (str) { + return str.replace(regexEscapeRE, '\\$&') } /** - * Apply a linker to a vm/element pair and capture the - * directives created during the process. + * Compile the interpolation tag regex. * - * @param {Function} linker - * @param {Vue} vm + * @return {RegExp} */ - function linkAndCapture (linker, vm) { - var originalDirCount = vm._directives.length - linker() - return vm._directives.slice(originalDirCount) + function compileRegex () { + config._delimitersChanged = false + var open = config.delimiters[0] + var close = config.delimiters[1] + firstChar = open.charAt(0) + lastChar = close.charAt(close.length - 1) + var firstCharRE = escapeRegex(firstChar) + var lastCharRE = escapeRegex(lastChar) + var openRE = escapeRegex(open) + var closeRE = escapeRegex(close) + tagRE = new RegExp( + firstCharRE + '?' + openRE + + '(.+?)' + + closeRE + lastCharRE + '?', + 'g' + ) + htmlRE = new RegExp( + '^' + firstCharRE + openRE + + '.*' + + closeRE + lastCharRE + '$' + ) + // reset cache + cache = new Cache(1000) } /** - * Linker functions return an unlink function that - * tearsdown all directives instances generated during - * the process. - * - * We create unlink functions with only the necessary - * information to avoid retaining additional closures. + * Parse a template text string into an array of tokens. * - * @param {Vue} vm - * @param {Array} dirs - * @param {Vue} [context] - * @param {Array} [contextDirs] - * @return {Function} + * @param {String} text + * @return {Array | null} + * - {String} type + * - {String} value + * - {Boolean} [html] + * - {Boolean} [oneTime] */ - function makeUnlinkFn (vm, dirs, context, contextDirs) { - return function unlink (destroying) { - teardownDirs(vm, dirs, destroying) - if (context && contextDirs) { - teardownDirs(context, contextDirs) + exports.parse = function (text) { + if (config._delimitersChanged) { + compileRegex() + } + var hit = cache.get(text) + if (hit) { + return hit + } + text = text.replace(/\n/g, '') + if (!tagRE.test(text)) { + return null + } + var tokens = [] + var lastIndex = tagRE.lastIndex = 0 + var match, index, value, first, oneTime, twoWay + /* eslint-disable no-cond-assign */ + while (match = tagRE.exec(text)) { + /* eslint-enable no-cond-assign */ + index = match.index + // push text token + if (index > lastIndex) { + tokens.push({ + value: text.slice(lastIndex, index) + }) } + // tag token + first = match[1].charCodeAt(0) + oneTime = first === 42 // * + twoWay = first === 64 // @ + value = oneTime || twoWay + ? match[1].slice(1) + : match[1] + tokens.push({ + tag: true, + value: value.trim(), + html: htmlRE.test(match[0]), + oneTime: oneTime, + twoWay: twoWay + }) + lastIndex = index + match[0].length } + if (lastIndex < text.length) { + tokens.push({ + value: text.slice(lastIndex) + }) + } + cache.put(text, tokens) + return tokens } /** - * Teardown partial linked directives. + * Format a list of tokens into an expression. + * e.g. tokens parsed from 'a {{b}} c' can be serialized + * into one single expression as '"a " + b + " c"'. * - * @param {Vue} vm - * @param {Array} dirs - * @param {Boolean} destroying + * @param {Array} tokens + * @param {Vue} [vm] + * @return {String} */ - function teardownDirs (vm, dirs, destroying) { - var i = dirs.length - while (i--) { - dirs[i]._teardown() - if (!destroying) { - vm._directives.$remove(dirs[i]) - } - } + exports.tokensToExp = function (tokens, vm) { + return tokens.length > 1 + ? tokens.map(function (token) { + return formatToken(token, vm) + }).join('+') + : formatToken(tokens[0], vm, true) } /** - * Compile link props on an instance. + * Format a single token. * - * @param {Vue} vm - * @param {Element} el - * @param {Object} options - * @return {Function} + * @param {Object} token + * @param {Vue} [vm] + * @param {Boolean} single + * @return {String} */ - exports.compileAndLinkProps = function (vm, el, props) { - var propsLinkFn = compileProps(el, props) - var propDirs = linkAndCapture(function () { - propsLinkFn(vm, null) - }, vm) - return makeUnlinkFn(vm, propDirs) + function formatToken (token, vm, single) { + return token.tag + ? vm && token.oneTime + ? '"' + vm.$eval(token.value) + '"' + : inlineFilters(token.value, single) + : '"' + token.value + '"' } /** - * Compile the root element of an instance. - * - * 1. attrs on context container (context scope) - * 2. attrs on the component template root node, if - * replace:true (child scope) - * - * If this is a block instance, we only need to compile 1. - * - * This function does compile and link at the same time, - * since root linkers can not be reused. It returns the - * unlink function for potential context directives on the - * container. + * For an attribute with multiple interpolation tags, + * e.g. attr="some-{{thing | filter}}", in order to combine + * the whole thing into a single watchable expression, we + * have to inline those filters. This function does exactly + * that. This is a bit hacky but it avoids heavy changes + * to directive parser and watcher mechanism. * - * @param {Vue} vm - * @param {Element} el - * @param {Object} options - * @return {Function} + * @param {String} exp + * @param {Boolean} single + * @return {String} */ - exports.compileAndLinkRoot = function (vm, el, options) { - var containerAttrs = options._containerAttrs - var replacerAttrs = options._replacerAttrs - var contextLinkFn, replacerLinkFn - - // only need to compile other attributes for - // non-block instances - if (el.nodeType !== 11) { - // for components, container and replacer need to be - // compiled separately and linked in different scopes. - if (options._asComponent) { - // 2. container attributes - if (containerAttrs) { - contextLinkFn = compileDirectives(containerAttrs, options) - } - if (replacerAttrs) { - // 3. replacer attributes - replacerLinkFn = compileDirectives(replacerAttrs, options) - } + var filterRE = /[^|]\|[^|]/ + function inlineFilters (exp, single) { + if (!filterRE.test(exp)) { + return single + ? exp + : '(' + exp + ')' + } else { + var dir = dirParser.parse(exp)[0] + if (!dir.filters) { + return '(' + exp + ')' } else { - // non-component, just compile as a normal element. - replacerLinkFn = compileDirectives(el, options) + return 'this._applyFilters(' + + dir.expression + // value + ',null,' + // oldValue (null for read) + JSON.stringify(dir.filters) + // filter descriptors + ',false)' // write? } } + } - // link context scope dirs - var context = vm._context - var contextDirs - if (context && contextLinkFn) { - contextDirs = linkAndCapture(function () { - contextLinkFn(context, el) - }, context) - } - // link self - var selfDirs = linkAndCapture(function () { - if (replacerLinkFn) replacerLinkFn(vm, el) - }, vm) +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { - // return the unlink function that tearsdown context - // container directives. - return makeUnlinkFn(vm, selfDirs, context, contextDirs) + /** + * A doubly linked list-based Least Recently Used (LRU) + * cache. Will keep most recently used items while + * discarding least recently used items when its limit is + * reached. This is a bare-bone version of + * Rasmus Andersson's js-lru: + * + * https://github.com/rsms/js-lru + * + * @param {Number} limit + * @constructor + */ + + function Cache (limit) { + this.size = 0 + this.limit = limit + this.head = this.tail = undefined + this._keymap = {} } + var p = Cache.prototype + /** - * Compile a node and return a nodeLinkFn based on the - * node type. + * Put into the cache associated with . + * Returns the entry which was removed to make room for + * the new entry. Otherwise undefined is returned. + * (i.e. if there was enough room already). * - * @param {Node} node - * @param {Object} options - * @return {Function|null} + * @param {String} key + * @param {*} value + * @return {Entry|undefined} */ - function compileNode (node, options) { - var type = node.nodeType - if (type === 1 && node.tagName !== 'SCRIPT') { - return compileElement(node, options) - } else if (type === 3 && config.interpolate && node.data.trim()) { - return compileTextNode(node, options) + p.put = function (key, value) { + var entry = { + key: key, + value: value + } + this._keymap[key] = entry + if (this.tail) { + this.tail.newer = entry + entry.older = this.tail } else { - return null + this.head = entry + } + this.tail = entry + if (this.size === this.limit) { + return this.shift() + } else { + this.size++ } } /** - * Compile an element and return a nodeLinkFn. - * - * @param {Element} el - * @param {Object} options - * @return {Function|null} + * Purge the least recently used (oldest) entry from the + * cache. Returns the removed entry or undefined if the + * cache was empty. */ - function compileElement (el, options) { - var linkFn - var hasAttrs = el.hasAttributes() - // check terminal directives (repeat & if) - if (hasAttrs) { - linkFn = checkTerminalDirectives(el, options) - } - // check element directives - if (!linkFn) { - linkFn = checkElementDirectives(el, options) + p.shift = function () { + var entry = this.head + if (entry) { + this.head = this.head.newer + this.head.older = undefined + entry.newer = entry.older = undefined + this._keymap[entry.key] = undefined } - // check component - if (!linkFn) { - linkFn = checkComponent(el, options) - } - // normal directives - if (!linkFn && hasAttrs) { - linkFn = compileDirectives(el, options) - } - // if the element is a textarea, we need to interpolate - // its content on initial render. - if (el.tagName === 'TEXTAREA') { - var realLinkFn = linkFn - linkFn = function (vm, el) { - el.value = vm.$interpolate(el.value) - if (realLinkFn) realLinkFn(vm, el) - } - linkFn.terminal = true - } - return linkFn + return entry } /** - * Compile a textNode and return a nodeLinkFn. + * Get and register recent use of . Returns the value + * associated with or undefined if not in cache. * - * @param {TextNode} node - * @param {Object} options - * @return {Function|null} textNodeLinkFn + * @param {String} key + * @param {Boolean} returnEntry + * @return {Entry|*} */ - function compileTextNode (node, options) { - var tokens = textParser.parse(node.data) - if (!tokens) { - return null + p.get = function (key, returnEntry) { + var entry = this._keymap[key] + if (entry === undefined) return + if (entry === this.tail) { + return returnEntry + ? entry + : entry.value } - var frag = document.createDocumentFragment() - var el, token - for (var i = 0, l = tokens.length; i < l; i++) { - token = tokens[i] - el = token.tag - ? processTextToken(token, options) - : document.createTextNode(token.value) - frag.appendChild(el) + // HEAD--------------TAIL + // <.older .newer> + // <--- add direction -- + // A B C E + if (entry.newer) { + if (entry === this.head) { + this.head = entry.newer + } + entry.newer.older = entry.older // C <-- E. } - return makeTextNodeLinkFn(tokens, frag, options) + if (entry.older) { + entry.older.newer = entry.newer // C. --> E + } + entry.newer = undefined // D --x + entry.older = this.tail // D. --> E + if (this.tail) { + this.tail.newer = entry // E. <-- D + } + this.tail = entry + return returnEntry + ? entry + : entry.value } + module.exports = Cache + + +/***/ }, +/* 12 */ +/***/ function(module, exports, __webpack_require__) { + + var _ = __webpack_require__(1) + var Cache = __webpack_require__(11) + var cache = new Cache(1000) + var argRE = /^[^\{\?]+$|^'[^']*'$|^"[^"]*"$/ + var filterTokenRE = /[^\s'"]+|'[^']+'|"[^"]+"/g + var reservedArgRE = /^in$|^-?\d+/ + /** - * Process a single text token. - * - * @param {Object} token - * @param {Object} options - * @return {Node} + * Parser state */ - function processTextToken (token, options) { - var el - if (token.oneTime) { - el = document.createTextNode(token.value) - } else { - if (token.html) { - el = document.createComment('v-html') - setTokenType('html') - } else { - // IE will clean up empty textNodes during - // frag.cloneNode(true), so we have to give it - // something here... - el = document.createTextNode(' ') - setTokenType('text') - } - } - function setTokenType (type) { - token.type = type - token.def = resolveAsset(options, 'directives', type) - token.descriptor = dirParser.parse(token.value)[0] - } - return el - } + var str + var c, i, l + var inSingle + var inDouble + var curly + var square + var paren + var begin + var argIndex + var dirs + var dir + var lastFilterIndex + var arg /** - * Build a function that processes a textNode. - * - * @param {Array} tokens - * @param {DocumentFragment} frag + * Push a directive object into the result Array */ - function makeTextNodeLinkFn (tokens, frag) { - return function textNodeLinkFn (vm, el) { - var fragClone = frag.cloneNode(true) - var childNodes = _.toArray(fragClone.childNodes) - var token, value, node - for (var i = 0, l = tokens.length; i < l; i++) { - token = tokens[i] - value = token.value - if (token.tag) { - node = childNodes[i] - if (token.oneTime) { - value = vm.$eval(value) - if (token.html) { - _.replace(node, templateParser.parse(value, true)) - } else { - node.data = value - } - } else { - vm._bindDir(token.type, node, - token.descriptor, token.def) - } - } - } - _.replace(el, fragClone) + function pushDir () { + dir.raw = str.slice(begin, i).trim() + if (dir.expression === undefined) { + dir.expression = str.slice(argIndex, i).trim() + } else if (lastFilterIndex !== begin) { + pushFilter() + } + if (i === 0 || dir.expression) { + dirs.push(dir) } } /** - * Compile a node list and return a childLinkFn. - * - * @param {NodeList} nodeList - * @param {Object} options - * @return {Function|undefined} + * Push a filter to the current directive object */ - function compileNodeList (nodeList, options) { - var linkFns = [] - var nodeLinkFn, childLinkFn, node - for (var i = 0, l = nodeList.length; i < l; i++) { - node = nodeList[i] - nodeLinkFn = compileNode(node, options) - childLinkFn = - !(nodeLinkFn && nodeLinkFn.terminal) && - node.tagName !== 'SCRIPT' && - node.hasChildNodes() - ? compileNodeList(node.childNodes, options) - : null - linkFns.push(nodeLinkFn, childLinkFn) + function pushFilter () { + var exp = str.slice(lastFilterIndex, i).trim() + var filter + if (exp) { + filter = {} + var tokens = exp.match(filterTokenRE) + filter.name = tokens[0] + if (tokens.length > 1) { + filter.args = tokens.slice(1).map(processFilterArg) + } } - return linkFns.length - ? makeChildLinkFn(linkFns) - : null + if (filter) { + (dir.filters = dir.filters || []).push(filter) + } + lastFilterIndex = i + 1 } /** - * Make a child link function for a node's childNodes. + * Check if an argument is dynamic and strip quotes. * - * @param {Array} linkFns - * @return {Function} childLinkFn + * @param {String} arg + * @return {Object} */ - function makeChildLinkFn (linkFns) { - return function childLinkFn (vm, nodes, host) { - var node, nodeLinkFn, childrenLinkFn - for (var i = 0, n = 0, l = linkFns.length; i < l; n++) { - node = nodes[n] - nodeLinkFn = linkFns[i++] - childrenLinkFn = linkFns[i++] - // cache childNodes before linking parent, fix #657 - var childNodes = _.toArray(node.childNodes) - if (nodeLinkFn) { - nodeLinkFn(vm, node, host) - } - if (childrenLinkFn) { - childrenLinkFn(vm, childNodes, host) - } - } + function processFilterArg (arg) { + var stripped = reservedArgRE.test(arg) + ? arg + : _.stripQuotes(arg) + return { + value: stripped || arg, + dynamic: !stripped } } /** - * Check for element directives (custom elements that should - * be resovled as terminal directives). + * Parse a directive string into an Array of AST-like + * objects representing directives. * - * @param {Element} el - * @param {Object} options + * Example: + * + * "click: a = a + 1 | uppercase" will yield: + * { + * arg: 'click', + * expression: 'a = a + 1', + * filters: [ + * { name: 'uppercase', args: null } + * ] + * } + * + * @param {String} str + * @return {Array} */ - function checkElementDirectives (el, options) { - var tag = el.tagName.toLowerCase() - if (_.commonTagRE.test(tag)) return - var def = resolveAsset(options, 'elementDirectives', tag) - if (def) { - return makeTerminalNodeLinkFn(el, tag, '', options, def) + exports.parse = function (s) { + + var hit = cache.get(s) + if (hit) { + return hit } - } - /** - * Check if an element is a component. If yes, return - * a component link function. - * - * @param {Element} el - * @param {Object} options - * @param {Boolean} hasAttrs - * @return {Function|undefined} - */ + // reset parser state + str = s + inSingle = inDouble = false + curly = square = paren = begin = argIndex = 0 + lastFilterIndex = 0 + dirs = [] + dir = {} + arg = null - function checkComponent (el, options, hasAttrs) { - var componentId = _.checkComponent(el, options, hasAttrs) - if (componentId) { - var componentLinkFn = function (vm, el, host) { - vm._bindDir('component', el, { - expression: componentId - }, componentDef, host) + for (i = 0, l = str.length; i < l; i++) { + c = str.charCodeAt(i) + if (inSingle) { + // check single quote + if (c === 0x27) inSingle = !inSingle + } else if (inDouble) { + // check double quote + if (c === 0x22) inDouble = !inDouble + } else if ( + c === 0x2C && // comma + !paren && !curly && !square + ) { + // reached the end of a directive + pushDir() + // reset & skip the comma + dir = {} + begin = argIndex = lastFilterIndex = i + 1 + } else if ( + c === 0x3A && // colon + !dir.expression && + !dir.arg + ) { + // argument + arg = str.slice(begin, i).trim() + // test for valid argument here + // since we may have caught stuff like first half of + // an object literal or a ternary expression. + if (argRE.test(arg)) { + argIndex = i + 1 + dir.arg = _.stripQuotes(arg) || arg + } + } else if ( + c === 0x7C && // pipe + str.charCodeAt(i + 1) !== 0x7C && + str.charCodeAt(i - 1) !== 0x7C + ) { + if (dir.expression === undefined) { + // first filter, end of expression + lastFilterIndex = i + 1 + dir.expression = str.slice(argIndex, i).trim() + } else { + // already has filter + pushFilter() + } + } else { + switch (c) { + case 0x22: inDouble = true; break // " + case 0x27: inSingle = true; break // ' + case 0x28: paren++; break // ( + case 0x29: paren--; break // ) + case 0x5B: square++; break // [ + case 0x5D: square--; break // ] + case 0x7B: curly++; break // { + case 0x7D: curly--; break // } + } } - componentLinkFn.terminal = true - return componentLinkFn } + + if (i === 0 || begin !== i) { + pushDir() + } + + cache.put(s, dirs) + return dirs } + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + var _ = __webpack_require__(1) + + _.extend(exports, __webpack_require__(14)) + _.extend(exports, __webpack_require__(24)) + + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + var _ = __webpack_require__(1) + var compileProps = __webpack_require__(15) + var config = __webpack_require__(3) + var textParser = __webpack_require__(10) + var dirParser = __webpack_require__(12) + var templateParser = __webpack_require__(22) + var resolveAsset = _.resolveAsset + var componentDef = __webpack_require__(23) + + // terminal directives + var terminalDirectives = [ + 'repeat', + 'if' + ] + /** - * Check an element for terminal directives in fixed order. - * If it finds one, return a terminal link function. + * Compile a template and return a reusable composite link + * function, which recursively contains more link functions + * inside. This top level compile function would normally + * be called on instance root nodes, but can also be used + * for partial compilation if the partial argument is true. * - * @param {Element} el + * The returned composite link function, when called, will + * return an unlink function that tearsdown all directives + * created during the linking phase. + * + * @param {Element|DocumentFragment} el * @param {Object} options - * @return {Function} terminalLinkFn + * @param {Boolean} partial + * @param {Vue} [host] - host vm of transcluded content + * @return {Function} */ - function checkTerminalDirectives (el, options) { - if (_.attr(el, 'pre') !== null) { - return skip - } - var value, dirName - for (var i = 0, l = terminalDirectives.length; i < l; i++) { - dirName = terminalDirectives[i] - if ((value = _.attr(el, dirName)) !== null) { - return makeTerminalNodeLinkFn(el, dirName, value, options) - } + exports.compile = function (el, options, partial, host) { + // link function for the node itself. + var nodeLinkFn = partial || !options._asComponent + ? compileNode(el, options) + : null + // link function for the childNodes + var childLinkFn = + !(nodeLinkFn && nodeLinkFn.terminal) && + el.tagName !== 'SCRIPT' && + el.hasChildNodes() + ? compileNodeList(el.childNodes, options) + : null + + /** + * A composite linker function to be called on a already + * compiled piece of DOM, which instantiates all directive + * instances. + * + * @param {Vue} vm + * @param {Element|DocumentFragment} el + * @return {Function|undefined} + */ + + return function compositeLinkFn (vm, el) { + // cache childNodes before linking parent, fix #657 + var childNodes = _.toArray(el.childNodes) + // link + var dirs = linkAndCapture(function () { + if (nodeLinkFn) nodeLinkFn(vm, el, host) + if (childLinkFn) childLinkFn(vm, childNodes, host) + }, vm) + return makeUnlinkFn(vm, dirs) } } - function skip () {} - skip.terminal = true - /** - * Build a node link function for a terminal directive. - * A terminal link function terminates the current - * compilation recursion and handles compilation of the - * subtree in the directive. + * Apply a linker to a vm/element pair and capture the + * directives created during the process. * - * @param {Element} el - * @param {String} dirName - * @param {String} value - * @param {Object} options - * @param {Object} [def] - * @return {Function} terminalLinkFn + * @param {Function} linker + * @param {Vue} vm */ - function makeTerminalNodeLinkFn (el, dirName, value, options, def) { - var descriptor = dirParser.parse(value)[0] - // no need to call resolveAsset since terminal directives - // are always internal - def = def || options.directives[dirName] - var fn = function terminalNodeLinkFn (vm, el, host) { - vm._bindDir(dirName, el, descriptor, def, host) - } - fn.terminal = true - return fn + function linkAndCapture (linker, vm) { + var originalDirCount = vm._directives.length + linker() + return vm._directives.slice(originalDirCount) } /** - * Compile the directives on an element and return a linker. + * Linker functions return an unlink function that + * tearsdown all directives instances generated during + * the process. * - * @param {Element|Object} elOrAttrs - * - could be an object of already-extracted - * container attributes. - * @param {Object} options + * We create unlink functions with only the necessary + * information to avoid retaining additional closures. + * + * @param {Vue} vm + * @param {Array} dirs + * @param {Vue} [context] + * @param {Array} [contextDirs] * @return {Function} */ - function compileDirectives (elOrAttrs, options) { - var attrs = _.isPlainObject(elOrAttrs) - ? mapToList(elOrAttrs) - : elOrAttrs.attributes - var i = attrs.length - var dirs = [] - var attr, name, value, dir, dirName, dirDef - while (i--) { - attr = attrs[i] - name = attr.name - value = attr.value - if (name.indexOf(config.prefix) === 0) { - dirName = name.slice(config.prefix.length) - dirDef = resolveAsset(options, 'directives', dirName) - _.assertAsset(dirDef, 'directive', dirName) - if (dirDef) { - dirs.push({ - name: dirName, - descriptors: dirParser.parse(value), - def: dirDef - }) - } - } else if (config.interpolate) { - dir = collectAttrDirective(name, value, options) - if (dir) { - dirs.push(dir) - } + function makeUnlinkFn (vm, dirs, context, contextDirs) { + return function unlink (destroying) { + teardownDirs(vm, dirs, destroying) + if (context && contextDirs) { + teardownDirs(context, contextDirs) } } - // sort by priority, LOW to HIGH - if (dirs.length) { - dirs.sort(directiveComparator) - return makeNodeLinkFn(dirs) - } } /** - * Convert a map (Object) of attributes to an Array. + * Teardown partial linked directives. * - * @param {Object} map - * @return {Array} - */ + * @param {Vue} vm + * @param {Array} dirs + * @param {Boolean} destroying + */ - function mapToList (map) { - var list = [] - for (var key in map) { - list.push({ - name: key, - value: map[key] - }) + function teardownDirs (vm, dirs, destroying) { + var i = dirs.length + while (i--) { + dirs[i]._teardown() + if (!destroying) { + vm._directives.$remove(dirs[i]) + } } - return list } /** - * Build a link function for all directives on a single node. + * Compile link props on an instance. * - * @param {Array} directives - * @return {Function} directivesLinkFn + * @param {Vue} vm + * @param {Element} el + * @param {Object} options + * @return {Function} */ - function makeNodeLinkFn (directives) { - return function nodeLinkFn (vm, el, host) { - // reverse apply because it's sorted low to high - var i = directives.length - var dir, j, k - while (i--) { - dir = directives[i] - if (dir._link) { - // custom link fn - dir._link(vm, el) - } else { - k = dir.descriptors.length - for (j = 0; j < k; j++) { - vm._bindDir(dir.name, el, - dir.descriptors[j], dir.def, host) - } - } - } - } + exports.compileAndLinkProps = function (vm, el, props) { + var propsLinkFn = compileProps(el, props) + var propDirs = linkAndCapture(function () { + propsLinkFn(vm, null) + }, vm) + return makeUnlinkFn(vm, propDirs) } /** - * Check an attribute for potential dynamic bindings, - * and return a directive object. + * Compile the root element of an instance. * - * @param {String} name - * @param {String} value + * 1. attrs on context container (context scope) + * 2. attrs on the component template root node, if + * replace:true (child scope) + * + * If this is a block instance, we only need to compile 1. + * + * This function does compile and link at the same time, + * since root linkers can not be reused. It returns the + * unlink function for potential context directives on the + * container. + * + * @param {Vue} vm + * @param {Element} el * @param {Object} options - * @return {Object} + * @return {Function} */ - function collectAttrDirective (name, value, options) { - var tokens = textParser.parse(value) - if (tokens) { - var def = options.directives.attr - var i = tokens.length - var allOneTime = true - while (i--) { - var token = tokens[i] - if (token.tag && !token.oneTime) { - allOneTime = false + exports.compileAndLinkRoot = function (vm, el, options) { + var containerAttrs = options._containerAttrs + var replacerAttrs = options._replacerAttrs + var contextLinkFn, replacerLinkFn + + // only need to compile other attributes for + // non-block instances + if (el.nodeType !== 11) { + // for components, container and replacer need to be + // compiled separately and linked in different scopes. + if (options._asComponent) { + // 2. container attributes + if (containerAttrs) { + contextLinkFn = compileDirectives(containerAttrs, options) } + if (replacerAttrs) { + // 3. replacer attributes + replacerLinkFn = compileDirectives(replacerAttrs, options) + } + } else { + // non-component, just compile as a normal element. + replacerLinkFn = compileDirectives(el, options) } - return { - def: def, - _link: allOneTime - ? function (vm, el) { - el.setAttribute(name, vm.$interpolate(value)) - } - : function (vm, el) { - var value = textParser.tokensToExp(tokens, vm) - var desc = dirParser.parse(name + ':' + value)[0] - vm._bindDir('attr', el, desc, def) - } - } } + + // link context scope dirs + var context = vm._context + var contextDirs + if (context && contextLinkFn) { + contextDirs = linkAndCapture(function () { + contextLinkFn(context, el) + }, context) + } + + // link self + var selfDirs = linkAndCapture(function () { + if (replacerLinkFn) replacerLinkFn(vm, el) + }, vm) + + // return the unlink function that tearsdown context + // container directives. + return makeUnlinkFn(vm, selfDirs, context, contextDirs) } /** - * Directive priority sort comparator + * Compile a node and return a nodeLinkFn based on the + * node type. * - * @param {Object} a - * @param {Object} b + * @param {Node} node + * @param {Object} options + * @return {Function|null} */ - function directiveComparator (a, b) { - a = a.def.priority || 0 - b = b.def.priority || 0 - return a > b ? 1 : -1 + function compileNode (node, options) { + var type = node.nodeType + if (type === 1 && node.tagName !== 'SCRIPT') { + return compileElement(node, options) + } else if (type === 3 && config.interpolate && node.data.trim()) { + return compileTextNode(node, options) + } else { + return null + } } - -/***/ }, -/* 12 */ -/***/ function(module, exports, __webpack_require__) { - - var _ = __webpack_require__(1) - var textParser = __webpack_require__(13) - var propDef = __webpack_require__(16) - var propBindingModes = __webpack_require__(6)._propBindingModes - - // regexes - var identRE = __webpack_require__(23).identRE - var dataAttrRE = /^data-/ - var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/ - var literalValueRE = /^(true|false)$|^\d.*/ - /** - * Compile param attributes on a root element and return - * a props link function. + * Compile an element and return a nodeLinkFn. * - * @param {Element|DocumentFragment} el - * @param {Array} propOptions - * @return {Function} propsLinkFn + * @param {Element} el + * @param {Object} options + * @return {Function|null} */ - module.exports = function compileProps (el, propOptions) { - var props = [] - var i = propOptions.length - var options, name, value, path, prop, literal, single - while (i--) { - options = propOptions[i] - name = options.name - // props could contain dashes, which will be - // interpreted as minus calculations by the parser - // so we need to camelize the path here - path = _.camelize(name.replace(dataAttrRE, '')) - if (!identRE.test(path)) { - _.warn( - 'Invalid prop key: "' + name + '". Prop keys ' + - 'must be valid identifiers.' - ) - } - value = el.getAttribute(_.hyphenate(name)) - // create a prop descriptor - prop = { - name: name, - raw: value, - path: path, - options: options, - mode: propBindingModes.ONE_WAY - } - if (value !== null) { - // important so that this doesn't get compiled - // again as a normal attribute binding - el.removeAttribute(name) - var tokens = textParser.parse(value) - if (tokens) { - if (el && el.nodeType === 1) { - el.removeAttribute(name) - } - prop.dynamic = true - prop.parentPath = textParser.tokensToExp(tokens) - // check prop binding type. - single = tokens.length === 1 - literal = literalValueRE.test(prop.parentPath) - // one time: {{* prop}} - if (literal || (single && tokens[0].oneTime)) { - prop.mode = propBindingModes.ONE_TIME - } else if ( - !literal && - (single && tokens[0].twoWay) - ) { - if (settablePathRE.test(prop.parentPath)) { - prop.mode = propBindingModes.TWO_WAY - } else { - _.warn( - 'Cannot bind two-way prop with non-settable ' + - 'parent path: ' + prop.parentPath - ) - } - } - } - } else if (options && options.required) { - _.warn('Missing required prop: ' + name) + function compileElement (el, options) { + var linkFn + var hasAttrs = el.hasAttributes() + // check terminal directives (repeat & if) + if (hasAttrs) { + linkFn = checkTerminalDirectives(el, options) + } + // check element directives + if (!linkFn) { + linkFn = checkElementDirectives(el, options) + } + // check component + if (!linkFn) { + linkFn = checkComponent(el, options) + } + // normal directives + if (!linkFn && hasAttrs) { + linkFn = compileDirectives(el, options) + } + // if the element is a textarea, we need to interpolate + // its content on initial render. + if (el.tagName === 'TEXTAREA') { + var realLinkFn = linkFn + linkFn = function (vm, el) { + el.value = vm.$interpolate(el.value) + if (realLinkFn) realLinkFn(vm, el) } - props.push(prop) + linkFn.terminal = true } - return makePropsLinkFn(props) + return linkFn } /** - * Build a function that applies props to a vm. + * Compile a textNode and return a nodeLinkFn. * - * @param {Array} props - * @return {Function} propsLinkFn + * @param {TextNode} node + * @param {Object} options + * @return {Function|null} textNodeLinkFn */ - function makePropsLinkFn (props) { - return function propsLinkFn (vm, el) { - // store resolved props info - vm._props = {} - var i = props.length - var prop, path, options, value - while (i--) { - prop = props[i] - path = prop.path - vm._props[path] = prop - options = prop.options - if (prop.raw === null) { - // initialize absent prop - vm._data[path] = options.type === Boolean - ? false - : options.hasOwnProperty('default') - ? options.default - : undefined - } else if (prop.dynamic) { - // dynamic prop - if (vm._context) { - if (prop.mode === propBindingModes.ONE_TIME) { - // one time binding - value = vm._context.$get(prop.parentPath) - _.initProp(vm, prop, value) + function compileTextNode (node, options) { + var tokens = textParser.parse(node.data) + if (!tokens) { + return null + } + var frag = document.createDocumentFragment() + var el, token + for (var i = 0, l = tokens.length; i < l; i++) { + token = tokens[i] + el = token.tag + ? processTextToken(token, options) + : document.createTextNode(token.value) + frag.appendChild(el) + } + return makeTextNodeLinkFn(tokens, frag, options) + } + + /** + * Process a single text token. + * + * @param {Object} token + * @param {Object} options + * @return {Node} + */ + + function processTextToken (token, options) { + var el + if (token.oneTime) { + el = document.createTextNode(token.value) + } else { + if (token.html) { + el = document.createComment('v-html') + setTokenType('html') + } else { + // IE will clean up empty textNodes during + // frag.cloneNode(true), so we have to give it + // something here... + el = document.createTextNode(' ') + setTokenType('text') + } + } + function setTokenType (type) { + token.type = type + token.def = resolveAsset(options, 'directives', type) + token.descriptor = dirParser.parse(token.value)[0] + } + return el + } + + /** + * Build a function that processes a textNode. + * + * @param {Array} tokens + * @param {DocumentFragment} frag + */ + + function makeTextNodeLinkFn (tokens, frag) { + return function textNodeLinkFn (vm, el) { + var fragClone = frag.cloneNode(true) + var childNodes = _.toArray(fragClone.childNodes) + var token, value, node + for (var i = 0, l = tokens.length; i < l; i++) { + token = tokens[i] + value = token.value + if (token.tag) { + node = childNodes[i] + if (token.oneTime) { + value = vm.$eval(value) + if (token.html) { + _.replace(node, templateParser.parse(value, true)) } else { - // dynamic binding - vm._bindDir('prop', el, prop, propDef) + node.data = value } } else { - _.warn( - 'Cannot bind dynamic prop on a root instance' + - ' with no parent: ' + prop.name + '="' + - prop.raw + '"' - ) + vm._bindDir(token.type, node, + token.descriptor, token.def) } - } else { - // literal, cast it and just set once - value = options.type === Boolean && prop.raw === '' - ? true - : _.toBoolean(_.toNumber(prop.raw)) - _.initProp(vm, prop, value) } } + _.replace(el, fragClone) } } + /** + * Compile a node list and return a childLinkFn. + * + * @param {NodeList} nodeList + * @param {Object} options + * @return {Function|undefined} + */ -/***/ }, -/* 13 */ -/***/ function(module, exports, __webpack_require__) { - - var Cache = __webpack_require__(14) - var config = __webpack_require__(6) - var dirParser = __webpack_require__(15) - var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g - var cache, tagRE, htmlRE, firstChar, lastChar + function compileNodeList (nodeList, options) { + var linkFns = [] + var nodeLinkFn, childLinkFn, node + for (var i = 0, l = nodeList.length; i < l; i++) { + node = nodeList[i] + nodeLinkFn = compileNode(node, options) + childLinkFn = + !(nodeLinkFn && nodeLinkFn.terminal) && + node.tagName !== 'SCRIPT' && + node.hasChildNodes() + ? compileNodeList(node.childNodes, options) + : null + linkFns.push(nodeLinkFn, childLinkFn) + } + return linkFns.length + ? makeChildLinkFn(linkFns) + : null + } /** - * Escape a string so it can be used in a RegExp - * constructor. + * Make a child link function for a node's childNodes. * - * @param {String} str + * @param {Array} linkFns + * @return {Function} childLinkFn */ - function escapeRegex (str) { - return str.replace(regexEscapeRE, '\\$&') + function makeChildLinkFn (linkFns) { + return function childLinkFn (vm, nodes, host) { + var node, nodeLinkFn, childrenLinkFn + for (var i = 0, n = 0, l = linkFns.length; i < l; n++) { + node = nodes[n] + nodeLinkFn = linkFns[i++] + childrenLinkFn = linkFns[i++] + // cache childNodes before linking parent, fix #657 + var childNodes = _.toArray(node.childNodes) + if (nodeLinkFn) { + nodeLinkFn(vm, node, host) + } + if (childrenLinkFn) { + childrenLinkFn(vm, childNodes, host) + } + } + } } /** - * Compile the interpolation tag regex. + * Check for element directives (custom elements that should + * be resovled as terminal directives). * - * @return {RegExp} + * @param {Element} el + * @param {Object} options */ - function compileRegex () { - config._delimitersChanged = false - var open = config.delimiters[0] - var close = config.delimiters[1] - firstChar = open.charAt(0) - lastChar = close.charAt(close.length - 1) - var firstCharRE = escapeRegex(firstChar) - var lastCharRE = escapeRegex(lastChar) - var openRE = escapeRegex(open) - var closeRE = escapeRegex(close) - tagRE = new RegExp( - firstCharRE + '?' + openRE + - '(.+?)' + - closeRE + lastCharRE + '?', - 'g' - ) - htmlRE = new RegExp( - '^' + firstCharRE + openRE + - '.*' + - closeRE + lastCharRE + '$' - ) - // reset cache - cache = new Cache(1000) + function checkElementDirectives (el, options) { + var tag = el.tagName.toLowerCase() + if (_.commonTagRE.test(tag)) return + var def = resolveAsset(options, 'elementDirectives', tag) + if (def) { + return makeTerminalNodeLinkFn(el, tag, '', options, def) + } } /** - * Parse a template text string into an array of tokens. + * Check if an element is a component. If yes, return + * a component link function. * - * @param {String} text - * @return {Array | null} - * - {String} type - * - {String} value - * - {Boolean} [html] - * - {Boolean} [oneTime] + * @param {Element} el + * @param {Object} options + * @param {Boolean} hasAttrs + * @return {Function|undefined} */ - exports.parse = function (text) { - if (config._delimitersChanged) { - compileRegex() + function checkComponent (el, options, hasAttrs) { + var componentId = _.checkComponent(el, options, hasAttrs) + if (componentId) { + var componentLinkFn = function (vm, el, host) { + vm._bindDir('component', el, { + expression: componentId + }, componentDef, host) + } + componentLinkFn.terminal = true + return componentLinkFn } - var hit = cache.get(text) - if (hit) { - return hit + } + + /** + * Check an element for terminal directives in fixed order. + * If it finds one, return a terminal link function. + * + * @param {Element} el + * @param {Object} options + * @return {Function} terminalLinkFn + */ + + function checkTerminalDirectives (el, options) { + if (_.attr(el, 'pre') !== null) { + return skip } - text = text.replace(/\n/g, '') - if (!tagRE.test(text)) { - return null + var value, dirName + for (var i = 0, l = terminalDirectives.length; i < l; i++) { + dirName = terminalDirectives[i] + if ((value = _.attr(el, dirName)) !== null) { + return makeTerminalNodeLinkFn(el, dirName, value, options) + } } - var tokens = [] - var lastIndex = tagRE.lastIndex = 0 - var match, index, value, first, oneTime, twoWay - /* eslint-disable no-cond-assign */ - while (match = tagRE.exec(text)) { - /* eslint-enable no-cond-assign */ - index = match.index - // push text token - if (index > lastIndex) { - tokens.push({ - value: text.slice(lastIndex, index) - }) - } - // tag token - first = match[1].charCodeAt(0) - oneTime = first === 42 // * - twoWay = first === 64 // @ - value = oneTime || twoWay - ? match[1].slice(1) - : match[1] - tokens.push({ - tag: true, - value: value.trim(), - html: htmlRE.test(match[0]), - oneTime: oneTime, - twoWay: twoWay - }) - lastIndex = index + match[0].length - } - if (lastIndex < text.length) { - tokens.push({ - value: text.slice(lastIndex) - }) - } - cache.put(text, tokens) - return tokens } + function skip () {} + skip.terminal = true + /** - * Format a list of tokens into an expression. - * e.g. tokens parsed from 'a {{b}} c' can be serialized - * into one single expression as '"a " + b + " c"'. + * Build a node link function for a terminal directive. + * A terminal link function terminates the current + * compilation recursion and handles compilation of the + * subtree in the directive. * - * @param {Array} tokens - * @param {Vue} [vm] - * @return {String} + * @param {Element} el + * @param {String} dirName + * @param {String} value + * @param {Object} options + * @param {Object} [def] + * @return {Function} terminalLinkFn */ - exports.tokensToExp = function (tokens, vm) { - return tokens.length > 1 - ? tokens.map(function (token) { - return formatToken(token, vm) - }).join('+') - : formatToken(tokens[0], vm, true) + function makeTerminalNodeLinkFn (el, dirName, value, options, def) { + var descriptor = dirParser.parse(value)[0] + // no need to call resolveAsset since terminal directives + // are always internal + def = def || options.directives[dirName] + var fn = function terminalNodeLinkFn (vm, el, host) { + vm._bindDir(dirName, el, descriptor, def, host) + } + fn.terminal = true + return fn } /** - * Format a single token. + * Compile the directives on an element and return a linker. * - * @param {Object} token - * @param {Vue} [vm] - * @param {Boolean} single - * @return {String} + * @param {Element|Object} elOrAttrs + * - could be an object of already-extracted + * container attributes. + * @param {Object} options + * @return {Function} */ - function formatToken (token, vm, single) { - return token.tag - ? vm && token.oneTime - ? '"' + vm.$eval(token.value) + '"' - : inlineFilters(token.value, single) - : '"' + token.value + '"' + function compileDirectives (elOrAttrs, options) { + var attrs = _.isPlainObject(elOrAttrs) + ? mapToList(elOrAttrs) + : elOrAttrs.attributes + var i = attrs.length + var dirs = [] + var attr, name, value, dir, dirName, dirDef + while (i--) { + attr = attrs[i] + name = attr.name + value = attr.value + if (name.indexOf(config.prefix) === 0) { + dirName = name.slice(config.prefix.length) + dirDef = resolveAsset(options, 'directives', dirName) + if (true) { + _.assertAsset(dirDef, 'directive', dirName) + } + if (dirDef) { + dirs.push({ + name: dirName, + descriptors: dirParser.parse(value), + def: dirDef + }) + } + } else if (config.interpolate) { + dir = collectAttrDirective(name, value, options) + if (dir) { + dirs.push(dir) + } + } + } + // sort by priority, LOW to HIGH + if (dirs.length) { + dirs.sort(directiveComparator) + return makeNodeLinkFn(dirs) + } } /** - * For an attribute with multiple interpolation tags, - * e.g. attr="some-{{thing | filter}}", in order to combine - * the whole thing into a single watchable expression, we - * have to inline those filters. This function does exactly - * that. This is a bit hacky but it avoids heavy changes - * to directive parser and watcher mechanism. + * Convert a map (Object) of attributes to an Array. * - * @param {String} exp - * @param {Boolean} single - * @return {String} + * @param {Object} map + * @return {Array} */ - var filterRE = /[^|]\|[^|]/ - function inlineFilters (exp, single) { - if (!filterRE.test(exp)) { - return single - ? exp - : '(' + exp + ')' - } else { - var dir = dirParser.parse(exp)[0] - if (!dir.filters) { - return '(' + exp + ')' - } else { - return 'this._applyFilters(' + - dir.expression + // value - ',null,' + // oldValue (null for read) - JSON.stringify(dir.filters) + // filter descriptors - ',false)' // write? - } + function mapToList (map) { + var list = [] + for (var key in map) { + list.push({ + name: key, + value: map[key] + }) } + return list } - -/***/ }, -/* 14 */ -/***/ function(module, exports, __webpack_require__) { - /** - * A doubly linked list-based Least Recently Used (LRU) - * cache. Will keep most recently used items while - * discarding least recently used items when its limit is - * reached. This is a bare-bone version of - * Rasmus Andersson's js-lru: - * - * https://github.com/rsms/js-lru + * Build a link function for all directives on a single node. * - * @param {Number} limit - * @constructor + * @param {Array} directives + * @return {Function} directivesLinkFn */ - function Cache (limit) { - this.size = 0 - this.limit = limit - this.head = this.tail = undefined - this._keymap = {} + function makeNodeLinkFn (directives) { + return function nodeLinkFn (vm, el, host) { + // reverse apply because it's sorted low to high + var i = directives.length + var dir, j, k + while (i--) { + dir = directives[i] + if (dir._link) { + // custom link fn + dir._link(vm, el) + } else { + k = dir.descriptors.length + for (j = 0; j < k; j++) { + vm._bindDir(dir.name, el, + dir.descriptors[j], dir.def, host) + } + } + } + } } - var p = Cache.prototype - /** - * Put into the cache associated with . - * Returns the entry which was removed to make room for - * the new entry. Otherwise undefined is returned. - * (i.e. if there was enough room already). + * Check an attribute for potential dynamic bindings, + * and return a directive object. * - * @param {String} key - * @param {*} value - * @return {Entry|undefined} + * Special case: class interpolations are translated into + * v-class instead v-attr, so that it can work with user + * provided v-class bindings. + * + * @param {String} name + * @param {String} value + * @param {Object} options + * @return {Object} */ - p.put = function (key, value) { - var entry = { - key: key, - value: value - } - this._keymap[key] = entry - if (this.tail) { - this.tail.newer = entry - entry.older = this.tail - } else { - this.head = entry - } - this.tail = entry - if (this.size === this.limit) { - return this.shift() - } else { - this.size++ + function collectAttrDirective (name, value, options) { + var tokens = textParser.parse(value) + var isClass = name === 'class' + if (tokens) { + var dirName = isClass ? 'class' : 'attr' + var def = options.directives[dirName] + var i = tokens.length + var allOneTime = true + while (i--) { + var token = tokens[i] + if (token.tag && !token.oneTime) { + allOneTime = false + } + } + return { + def: def, + _link: allOneTime + ? function (vm, el) { + el.setAttribute(name, vm.$interpolate(value)) + } + : function (vm, el) { + var exp = textParser.tokensToExp(tokens, vm) + var desc = isClass + ? dirParser.parse(exp)[0] + : dirParser.parse(name + ':' + exp)[0] + if (isClass) { + desc._rawClass = value + } + vm._bindDir(dirName, el, desc, def) + } + } } } /** - * Purge the least recently used (oldest) entry from the - * cache. Returns the removed entry or undefined if the - * cache was empty. - */ - - p.shift = function () { - var entry = this.head - if (entry) { - this.head = this.head.newer - this.head.older = undefined - entry.newer = entry.older = undefined - this._keymap[entry.key] = undefined - } - return entry - } - - /** - * Get and register recent use of . Returns the value - * associated with or undefined if not in cache. + * Directive priority sort comparator * - * @param {String} key - * @param {Boolean} returnEntry - * @return {Entry|*} + * @param {Object} a + * @param {Object} b */ - p.get = function (key, returnEntry) { - var entry = this._keymap[key] - if (entry === undefined) return - if (entry === this.tail) { - return returnEntry - ? entry - : entry.value - } - // HEAD--------------TAIL - // <.older .newer> - // <--- add direction -- - // A B C E - if (entry.newer) { - if (entry === this.head) { - this.head = entry.newer - } - entry.newer.older = entry.older // C <-- E. - } - if (entry.older) { - entry.older.newer = entry.newer // C. --> E - } - entry.newer = undefined // D --x - entry.older = this.tail // D. --> E - if (this.tail) { - this.tail.newer = entry // E. <-- D - } - this.tail = entry - return returnEntry - ? entry - : entry.value + function directiveComparator (a, b) { + a = a.def.priority || 0 + b = b.def.priority || 0 + return a > b ? 1 : -1 } - module.exports = Cache - /***/ }, /* 15 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var Cache = __webpack_require__(14) - var cache = new Cache(1000) - var argRE = /^[^\{\?]+$|^'[^']*'$|^"[^"]*"$/ - var filterTokenRE = /[^\s'"]+|'[^']+'|"[^"]+"/g - var reservedArgRE = /^in$|^-?\d+/ - - /** - * Parser state - */ - - var str - var c, i, l - var inSingle - var inDouble - var curly - var square - var paren - var begin - var argIndex - var dirs - var dir - var lastFilterIndex - var arg - - /** - * Push a directive object into the result Array - */ - - function pushDir () { - dir.raw = str.slice(begin, i).trim() - if (dir.expression === undefined) { - dir.expression = str.slice(argIndex, i).trim() - } else if (lastFilterIndex !== begin) { - pushFilter() - } - if (i === 0 || dir.expression) { - dirs.push(dir) - } - } + var textParser = __webpack_require__(10) + var propDef = __webpack_require__(17) + var propBindingModes = __webpack_require__(3)._propBindingModes - /** - * Push a filter to the current directive object - */ - - function pushFilter () { - var exp = str.slice(lastFilterIndex, i).trim() - var filter - if (exp) { - filter = {} - var tokens = exp.match(filterTokenRE) - filter.name = tokens[0] - if (tokens.length > 1) { - filter.args = tokens.slice(1).map(processFilterArg) - } - } - if (filter) { - (dir.filters = dir.filters || []).push(filter) - } - lastFilterIndex = i + 1 - } - - /** - * Check if an argument is dynamic and strip quotes. - * - * @param {String} arg - * @return {Object} - */ - - function processFilterArg (arg) { - var stripped = reservedArgRE.test(arg) - ? arg - : _.stripQuotes(arg) - return { - value: stripped || arg, - dynamic: !stripped - } - } + // regexes + var identRE = __webpack_require__(16).identRE + var dataAttrRE = /^data-/ + var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/ + var literalValueRE = /^(true|false)$|^\d.*/ /** - * Parse a directive string into an Array of AST-like - * objects representing directives. - * - * Example: - * - * "click: a = a + 1 | uppercase" will yield: - * { - * arg: 'click', - * expression: 'a = a + 1', - * filters: [ - * { name: 'uppercase', args: null } - * ] - * } + * Compile param attributes on a root element and return + * a props link function. * - * @param {String} str - * @return {Array} + * @param {Element|DocumentFragment} el + * @param {Array} propOptions + * @return {Function} propsLinkFn */ - exports.parse = function (s) { - - var hit = cache.get(s) - if (hit) { - return hit - } - - // reset parser state - str = s - inSingle = inDouble = false - curly = square = paren = begin = argIndex = 0 - lastFilterIndex = 0 - dirs = [] - dir = {} - arg = null - - for (i = 0, l = str.length; i < l; i++) { - c = str.charCodeAt(i) - if (inSingle) { - // check single quote - if (c === 0x27) inSingle = !inSingle - } else if (inDouble) { - // check double quote - if (c === 0x22) inDouble = !inDouble - } else if ( - c === 0x2C && // comma - !paren && !curly && !square - ) { - // reached the end of a directive - pushDir() - // reset & skip the comma - dir = {} - begin = argIndex = lastFilterIndex = i + 1 - } else if ( - c === 0x3A && // colon - !dir.expression && - !dir.arg - ) { - // argument - arg = str.slice(begin, i).trim() - // test for valid argument here - // since we may have caught stuff like first half of - // an object literal or a ternary expression. - if (argRE.test(arg)) { - argIndex = i + 1 - dir.arg = _.stripQuotes(arg) || arg - } - } else if ( - c === 0x7C && // pipe - str.charCodeAt(i + 1) !== 0x7C && - str.charCodeAt(i - 1) !== 0x7C - ) { - if (dir.expression === undefined) { - // first filter, end of expression - lastFilterIndex = i + 1 - dir.expression = str.slice(argIndex, i).trim() - } else { - // already has filter - pushFilter() - } - } else { - switch (c) { - case 0x22: inDouble = true; break // " - case 0x27: inSingle = true; break // ' - case 0x28: paren++; break // ( - case 0x29: paren--; break // ) - case 0x5B: square++; break // [ - case 0x5D: square--; break // ] - case 0x7B: curly++; break // { - case 0x7D: curly--; break // } - } - } - } - - if (i === 0 || begin !== i) { - pushDir() - } - - cache.put(s, dirs) - return dirs - } - - -/***/ }, -/* 16 */ -/***/ function(module, exports, __webpack_require__) { - - // NOTE: the prop internal directive is compiled and linked - // during _initScope(), before the created hook is called. - // The purpose is to make the initial prop values available - // inside `created` hooks and `data` functions. - - var _ = __webpack_require__(1) - var Watcher = __webpack_require__(17) - var bindingModes = __webpack_require__(6)._propBindingModes - - module.exports = { - - bind: function () { - - var child = this.vm - var parent = child._context - // passed in from compiler directly - var prop = this._descriptor - var childKey = prop.path - var parentKey = prop.parentPath - - // simple lock to avoid circular updates. - // without this it would stabilize too, but this makes - // sure it doesn't cause other watchers to re-evaluate. - var locked = false - function withLock (fn) { - return function (val) { - if (!locked) { - locked = true - fn(val) - _.nextTick(function () { - locked = false - }) - } - } - } - - this.parentWatcher = new Watcher( - parent, - parentKey, - withLock(function (val) { - if (_.assertProp(prop, val)) { - child[childKey] = val - } - }) - ) - - // set the child initial value. - // !!! We need to set it also on raw data here, because - // props are initialized before data is fully observed - var value = this.parentWatcher.value - if (childKey === '$data') { - child._data = value - } else { - _.initProp(child, prop, value) + module.exports = function compileProps (el, propOptions) { + var props = [] + var i = propOptions.length + var options, name, attr, value, path, prop, literal, single + while (i--) { + options = propOptions[i] + name = options.name + // props could contain dashes, which will be + // interpreted as minus calculations by the parser + // so we need to camelize the path here + path = _.camelize(name.replace(dataAttrRE, '')) + if (!identRE.test(path)) { + ("development") !== 'production' && _.warn( + 'Invalid prop key: "' + name + '". Prop keys ' + + 'must be valid identifiers.' + ) + continue } - - // only setup two-way binding if this is not a one-way - // binding. - if (prop.mode === bindingModes.TWO_WAY) { - // important: defer the child watcher creation until - // the created hook (after data observation) - var self = this - child.$once('hook:created', function () { - self.childWatcher = new Watcher( - child, - childKey, - withLock(function (val) { - parent.$set(parentKey, val) - }) - ) - }) + attr = _.hyphenate(name) + value = el.getAttribute(attr) + if (value === null) { + attr = 'data-' + attr + value = el.getAttribute(attr) } - }, - - unbind: function () { - if (this.parentWatcher) { - this.parentWatcher.teardown() + // create a prop descriptor + prop = { + name: name, + raw: value, + path: path, + options: options, + mode: propBindingModes.ONE_WAY } - if (this.childWatcher) { - this.childWatcher.teardown() + if (value !== null) { + // important so that this doesn't get compiled + // again as a normal attribute binding + el.removeAttribute(attr) + var tokens = textParser.parse(value) + if (tokens) { + prop.dynamic = true + prop.parentPath = textParser.tokensToExp(tokens) + // check prop binding type. + single = tokens.length === 1 + literal = literalValueRE.test(prop.parentPath) + // one time: {{* prop}} + if (literal || (single && tokens[0].oneTime)) { + prop.mode = propBindingModes.ONE_TIME + } else if ( + !literal && + (single && tokens[0].twoWay) + ) { + if (settablePathRE.test(prop.parentPath)) { + prop.mode = propBindingModes.TWO_WAY + } else { + ("development") !== 'production' && _.warn( + 'Cannot bind two-way prop with non-settable ' + + 'parent path: ' + prop.parentPath + ) + } + } + if ( + ("development") !== 'production' && + options.twoWay && + prop.mode !== propBindingModes.TWO_WAY + ) { + _.warn( + 'Prop "' + name + '" expects a two-way binding type.' + ) + } + } + } else if (options && options.required) { + ("development") !== 'production' && _.warn( + 'Missing required prop: ' + name + ) } + props.push(prop) } + return makePropsLinkFn(props) } - -/***/ }, -/* 17 */ -/***/ function(module, exports, __webpack_require__) { - - var _ = __webpack_require__(1) - var config = __webpack_require__(6) - var Observer = __webpack_require__(18) - var expParser = __webpack_require__(22) - var batcher = __webpack_require__(24) - var uid = 0 - - /** - * A watcher parses an expression, collects dependencies, - * and fires callback when the expression value changes. - * This is used for both the $watch() api and directives. - * - * @param {Vue} vm - * @param {String} expression - * @param {Function} cb - * @param {Object} options - * - {Array} filters - * - {Boolean} twoWay - * - {Boolean} deep - * - {Boolean} user - * - {Function} [preProcess] - * @constructor - */ - - function Watcher (vm, expOrFn, cb, options) { - var isFn = typeof expOrFn === 'function' - this.vm = vm - vm._watchers.push(this) - this.expression = isFn ? '' : expOrFn - this.cb = cb - this.id = ++uid // uid for batching - this.active = true - options = options || {} - this.deep = !!options.deep - this.user = !!options.user - this.twoWay = !!options.twoWay - this.filters = options.filters - this.preProcess = options.preProcess - this.deps = [] - this.newDeps = [] - // parse expression for getter/setter - if (isFn) { - this.getter = expOrFn - this.setter = undefined - } else { - var res = expParser.parse(expOrFn, options.twoWay) - this.getter = res.get - this.setter = res.set - } - this.value = this.get() - // state for avoiding false triggers for deep and Array - // watchers during vm._digest() - this.queued = this.shallow = false - } - - var p = Watcher.prototype - /** - * Add a dependency to this directive. + * Build a function that applies props to a vm. * - * @param {Dep} dep - */ - - p.addDep = function (dep) { - var newDeps = this.newDeps - var old = this.deps - if (_.indexOf(newDeps, dep) < 0) { - newDeps.push(dep) - var i = _.indexOf(old, dep) - if (i < 0) { - dep.addSub(this) - } else { - old[i] = null - } - } - } - - /** - * Evaluate the getter, and re-collect dependencies. + * @param {Array} props + * @return {Function} propsLinkFn */ - p.get = function () { - this.beforeGet() - var vm = this.vm - var value - try { - value = this.getter.call(vm, vm) - } catch (e) { - if (config.warnExpressionErrors) { - _.warn( - 'Error when evaluating expression "' + - this.expression + '". ' + - (config.debug - ? '' : - 'Turn on debug mode to see stack trace.' - ), e - ) + function makePropsLinkFn (props) { + return function propsLinkFn (vm, el) { + // store resolved props info + vm._props = {} + var i = props.length + var prop, path, options, value + while (i--) { + prop = props[i] + path = prop.path + vm._props[path] = prop + options = prop.options + if (prop.raw === null) { + // initialize absent prop + _.initProp(vm, prop, getDefault(options)) + } else if (prop.dynamic) { + // dynamic prop + if (vm._context) { + if (prop.mode === propBindingModes.ONE_TIME) { + // one time binding + value = vm._context.$get(prop.parentPath) + _.initProp(vm, prop, value) + } else { + // dynamic binding + vm._bindDir('prop', el, prop, propDef) + } + } else { + ("development") !== 'production' && _.warn( + 'Cannot bind dynamic prop on a root instance' + + ' with no parent: ' + prop.name + '="' + + prop.raw + '"' + ) + } + } else { + // literal, cast it and just set once + value = options.type === Boolean && prop.raw === '' + ? true + : _.toBoolean(_.toNumber(prop.raw)) + _.initProp(vm, prop, value) + } } } - // "touch" every property so they are all tracked as - // dependencies for deep watching - if (this.deep) { - traverse(value) - } - if (this.preProcess) { - value = this.preProcess(value) - } - if (this.filters) { - value = vm._applyFilters(value, null, this.filters, false) - } - this.afterGet() - return value } /** - * Set the corresponding value with the setter. + * Get the default value of a prop. * - * @param {*} value + * @param {Object} options + * @return {*} */ - p.set = function (value) { - var vm = this.vm - if (this.filters) { - value = vm._applyFilters( - value, this.value, this.filters, true) + function getDefault (options) { + // absent boolean value + if (options.type === Boolean) { + return false } - try { - this.setter.call(vm, vm, value) - } catch (e) { - if (config.warnExpressionErrors) { - _.warn( - 'Error when evaluating setter "' + - this.expression + '"', e - ) - } + // no default, return undefined + if (!options.hasOwnProperty('default')) { + return + } + var def = options.default + // warn against non-factory defaults for Object & Array + if (_.isObject(def)) { + ("development") !== 'production' && _.warn( + 'Object/Array as default prop values will be shared ' + + 'across multiple instances. Use a factory function ' + + 'to return the default value instead.' + ) } + // call factory function for non-Function types + return typeof def === 'function' && options.type !== Function + ? def() + : def } - /** - * Prepare for dependency collection. - */ - p.beforeGet = function () { - Observer.setTarget(this) +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + var _ = __webpack_require__(1) + var Cache = __webpack_require__(11) + var pathCache = new Cache(1000) + var identRE = exports.identRE = /^[$_a-zA-Z]+[\w$]*$/ + + // actions + var APPEND = 0 + var PUSH = 1 + + // states + var BEFORE_PATH = 0 + var IN_PATH = 1 + var BEFORE_IDENT = 2 + var IN_IDENT = 3 + var BEFORE_ELEMENT = 4 + var AFTER_ZERO = 5 + var IN_INDEX = 6 + var IN_SINGLE_QUOTE = 7 + var IN_DOUBLE_QUOTE = 8 + var IN_SUB_PATH = 9 + var AFTER_ELEMENT = 10 + var AFTER_PATH = 11 + var ERROR = 12 + + var pathStateMachine = [] + + pathStateMachine[BEFORE_PATH] = { + 'ws': [BEFORE_PATH], + 'ident': [IN_IDENT, APPEND], + '[': [BEFORE_ELEMENT], + 'eof': [AFTER_PATH] } - /** - * Clean up for dependency collection. - */ + pathStateMachine[IN_PATH] = { + 'ws': [IN_PATH], + '.': [BEFORE_IDENT], + '[': [BEFORE_ELEMENT], + 'eof': [AFTER_PATH] + } - p.afterGet = function () { - Observer.setTarget(null) - var i = this.deps.length - while (i--) { - var dep = this.deps[i] - if (dep) { - dep.removeSub(this) - } - } - this.deps = this.newDeps - this.newDeps = [] + pathStateMachine[BEFORE_IDENT] = { + 'ws': [BEFORE_IDENT], + 'ident': [IN_IDENT, APPEND] } - /** - * Subscriber interface. - * Will be called when a dependency changes. - * - * @param {Boolean} shallow - */ + pathStateMachine[IN_IDENT] = { + 'ident': [IN_IDENT, APPEND], + '0': [IN_IDENT, APPEND], + 'number': [IN_IDENT, APPEND], + 'ws': [IN_PATH, PUSH], + '.': [BEFORE_IDENT, PUSH], + '[': [BEFORE_ELEMENT, PUSH], + 'eof': [AFTER_PATH, PUSH] + } - p.update = function (shallow) { - if (!config.async) { - this.run() - } else { - // if queued, only overwrite shallow with non-shallow, - // but not the other way around. - this.shallow = this.queued - ? shallow - ? this.shallow - : false - : !!shallow - this.queued = true - batcher.push(this) - } + pathStateMachine[BEFORE_ELEMENT] = { + 'ws': [BEFORE_ELEMENT], + '0': [AFTER_ZERO, APPEND], + 'number': [IN_INDEX, APPEND], + "'": [IN_SINGLE_QUOTE, APPEND, ''], + '"': [IN_DOUBLE_QUOTE, APPEND, ''], + 'ident': [IN_SUB_PATH, APPEND, '*'] } - /** - * Batcher job interface. - * Will be called by the batcher. - */ + pathStateMachine[AFTER_ZERO] = { + 'ws': [AFTER_ELEMENT, PUSH], + ']': [IN_PATH, PUSH] + } - p.run = function () { - if (this.active) { - var value = this.get() - if ( - value !== this.value || - // Deep watchers and Array watchers should fire even - // when the value is the same, because the value may - // have mutated; but only do so if this is a - // non-shallow update (caused by a vm digest). - ((_.isArray(value) || this.deep) && !this.shallow) - ) { - var oldValue = this.value - this.value = value - this.cb(value, oldValue) - } - this.queued = this.shallow = false - } + pathStateMachine[IN_INDEX] = { + '0': [IN_INDEX, APPEND], + 'number': [IN_INDEX, APPEND], + 'ws': [AFTER_ELEMENT], + ']': [IN_PATH, PUSH] } - /** - * Remove self from all dependencies' subcriber list. - */ + pathStateMachine[IN_SINGLE_QUOTE] = { + "'": [AFTER_ELEMENT], + 'eof': ERROR, + 'else': [IN_SINGLE_QUOTE, APPEND] + } - p.teardown = function () { - if (this.active) { - // remove self from vm's watcher list - // we can skip this if the vm if being destroyed - // which can improve teardown performance. - if (!this.vm._isBeingDestroyed) { - this.vm._watchers.$remove(this) - } - var i = this.deps.length - while (i--) { - this.deps[i].removeSub(this) - } - this.active = false - this.vm = this.cb = this.value = null - } + pathStateMachine[IN_DOUBLE_QUOTE] = { + '"': [AFTER_ELEMENT], + 'eof': ERROR, + 'else': [IN_DOUBLE_QUOTE, APPEND] + } + + pathStateMachine[IN_SUB_PATH] = { + 'ident': [IN_SUB_PATH, APPEND], + '0': [IN_SUB_PATH, APPEND], + 'number': [IN_SUB_PATH, APPEND], + 'ws': [AFTER_ELEMENT], + ']': [IN_PATH, PUSH] + } + + pathStateMachine[AFTER_ELEMENT] = { + 'ws': [AFTER_ELEMENT], + ']': [IN_PATH, PUSH] } /** - * Recrusively traverse an object to evoke all converted - * getters, so that every nested property inside the object - * is collected as a "deep" dependency. + * Determine the type of a character in a keypath. * - * @param {Object} obj + * @param {Char} ch + * @return {String} type */ - function traverse (obj) { - var key, val, i - for (key in obj) { - val = obj[key] - if (_.isArray(val)) { - i = val.length - while (i--) traverse(val[i]) - } else if (_.isObject(val)) { - traverse(val) - } + function getPathCharType (ch) { + if (ch === undefined) { + return 'eof' } - } - module.exports = Watcher + var code = ch.charCodeAt(0) + switch (code) { + case 0x5B: // [ + case 0x5D: // ] + case 0x2E: // . + case 0x22: // " + case 0x27: // ' + case 0x30: // 0 + return ch -/***/ }, -/* 18 */ -/***/ function(module, exports, __webpack_require__) { + case 0x5F: // _ + case 0x24: // $ + return 'ident' - var _ = __webpack_require__(1) - var config = __webpack_require__(6) - var Dep = __webpack_require__(19) - var arrayMethods = __webpack_require__(20) - var arrayKeys = Object.getOwnPropertyNames(arrayMethods) - __webpack_require__(21) + case 0x20: // Space + case 0x09: // Tab + case 0x0A: // Newline + case 0x0D: // Return + case 0xA0: // No-break space + case 0xFEFF: // Byte Order Mark + case 0x2028: // Line Separator + case 0x2029: // Paragraph Separator + return 'ws' + } - /** - * Observer class that are attached to each observed - * object. Once attached, the observer converts target - * object's property keys into getter/setters that - * collect dependencies and dispatches updates. - * - * @param {Array|Object} value - * @constructor - */ + // a-z, A-Z + if ( + (code >= 0x61 && code <= 0x7A) || + (code >= 0x41 && code <= 0x5A) + ) { + return 'ident' + } - function Observer (value) { - this.value = value - this.active = true - this.deps = [] - _.define(value, '__ob__', this) - if (_.isArray(value)) { - var augment = config.proto && _.hasProto - ? protoAugment - : copyAugment - augment(value, arrayMethods, arrayKeys) - this.observeArray(value) - } else { - this.walk(value) + // 1-9 + if (code >= 0x31 && code <= 0x39) { + return 'number' } - } - // Static methods + return 'else' + } /** - * Attempt to create an observer instance for a value, - * returns the new observer if successfully observed, - * or the existing observer if the value already has one. + * Parse a string path into an array of segments + * Todo implement cache * - * @param {*} value - * @param {Vue} [vm] - * @return {Observer|undefined} - * @static + * @param {String} path + * @return {Array|undefined} */ - Observer.create = function (value, vm) { - var ob - if ( - value && - value.hasOwnProperty('__ob__') && - value.__ob__ instanceof Observer - ) { - ob = value.__ob__ - } else if ( - _.isObject(value) && - !Object.isFrozen(value) && - !value._isVue - ) { - ob = new Observer(value) + function parsePath (path) { + var keys = [] + var index = -1 + var mode = BEFORE_PATH + var c, newChar, key, type, transition, action, typeMap + + var actions = [] + actions[PUSH] = function () { + if (key === undefined) { + return + } + keys.push(key) + key = undefined } - if (ob && vm) { - ob.addVm(vm) + actions[APPEND] = function () { + if (key === undefined) { + key = newChar + } else { + key += newChar + } + } + + function maybeUnescapeQuote () { + var nextChar = path[index + 1] + if ((mode === IN_SINGLE_QUOTE && nextChar === "'") || + (mode === IN_DOUBLE_QUOTE && nextChar === '"')) { + index++ + newChar = nextChar + actions[APPEND]() + return true + } + } + + while (mode != null) { + index++ + c = path[index] + + if (c === '\\' && maybeUnescapeQuote()) { + continue + } + + type = getPathCharType(c) + typeMap = pathStateMachine[mode] + transition = typeMap[type] || typeMap['else'] || ERROR + + if (transition === ERROR) { + return // parse error + } + + mode = transition[0] + action = actions[transition[1]] + if (action) { + newChar = transition[2] + newChar = newChar === undefined + ? c + : newChar === '*' + ? newChar + c + : newChar + action() + } + + if (mode === AFTER_PATH) { + keys.raw = path + return keys + } } - return ob } /** - * Set the target watcher that is currently being evaluated. + * Format a accessor segment based on its type. * - * @param {Watcher} watcher + * @param {String} key + * @return {Boolean} */ - Observer.setTarget = function (watcher) { - Dep.target = watcher + function formatAccessor (key) { + if (identRE.test(key)) { // identifier + return '.' + key + } else if (+key === key >>> 0) { // bracket index + return '[' + key + ']' + } else if (key.charAt(0) === '*') { + return '[o' + formatAccessor(key.slice(1)) + ']' + } else { // bracket string + return '["' + key.replace(/"/g, '\\"') + '"]' + } } - // Instance methods - - var p = Observer.prototype - /** - * Walk through each property and convert them into - * getter/setters. This method should only be called when - * value type is Object. Properties prefixed with `$` or `_` - * and accessor properties are ignored. + * Compiles a getter function with a fixed path. + * The fixed path getter supresses errors. * - * @param {Object} obj + * @param {Array} path + * @return {Function} */ - p.walk = function (obj) { - var keys = Object.keys(obj) - var i = keys.length - var key, prefix - while (i--) { - key = keys[i] - prefix = key.charCodeAt(0) - if (prefix !== 0x24 && prefix !== 0x5F) { // skip $ or _ - this.convert(key, obj[key]) - } - } + exports.compileGetter = function (path) { + var body = 'return o' + path.map(formatAccessor).join('') + return new Function('o', body) } /** - * Try to carete an observer for a child value, - * and if value is array, link dep to the array. + * External parse that check for a cache hit first * - * @param {*} val - * @return {Dep|undefined} + * @param {String} path + * @return {Array|undefined} */ - p.observe = function (val) { - return Observer.create(val) + exports.parse = function (path) { + var hit = pathCache.get(path) + if (!hit) { + hit = parsePath(path) + if (hit) { + hit.get = exports.compileGetter(hit) + pathCache.put(path, hit) + } + } + return hit } /** - * Observe a list of Array items. + * Get from an object from a path string * - * @param {Array} items + * @param {Object} obj + * @param {String} path */ - p.observeArray = function (items) { - var i = items.length - while (i--) { - this.observe(items[i]) + exports.get = function (obj, path) { + path = exports.parse(path) + if (path) { + return path.get(obj) } } /** - * Convert a property into getter/setter so we can emit - * the events when the property is accessed/changed. + * Set on an object from a path * - * @param {String} key + * @param {Object} obj + * @param {String | Array} path * @param {*} val */ - p.convert = function (key, val) { - var ob = this - var childOb = ob.observe(val) - var dep = new Dep() - if (childOb) { - childOb.deps.push(dep) + exports.set = function (obj, path, val) { + var original = obj + if (typeof path === 'string') { + path = exports.parse(path) } - Object.defineProperty(ob.value, key, { - enumerable: true, - configurable: true, - get: function () { - if (ob.active) { - dep.depend() - } - return val - }, - set: function (newVal) { - if (newVal === val) return - // remove dep from old value - var oldChildOb = val && val.__ob__ - if (oldChildOb) { - oldChildOb.deps.$remove(dep) + if (!path || !_.isObject(obj)) { + return false + } + var last, key + for (var i = 0, l = path.length; i < l; i++) { + last = obj + key = path[i] + if (key.charAt(0) === '*') { + key = original[key.slice(1)] + } + if (i < l - 1) { + obj = obj[key] + if (!_.isObject(obj)) { + warnNonExistent(path) + obj = {} + last.$add(key, obj) } - val = newVal - // add dep to new value - var newChildOb = ob.observe(newVal) - if (newChildOb) { - newChildOb.deps.push(dep) + } else { + if (_.isArray(obj)) { + obj.$set(key, val) + } else if (key in obj) { + obj[key] = val + } else { + warnNonExistent(path) + obj.$add(key, val) } - dep.notify() } - }) + } + return true } - /** - * Notify change on all self deps on an observer. - * This is called when a mutable value mutates. e.g. - * when an Array's mutating methods are called, or an - * Object's $add/$delete are called. - */ - - p.notify = function () { - var deps = this.deps - for (var i = 0, l = deps.length; i < l; i++) { - deps[i].notify() - } + function warnNonExistent (path) { + ("development") !== 'production' && _.warn( + 'You are setting a non-existent path "' + path.raw + '" ' + + 'on a vm instance. Consider pre-initializing the property ' + + 'with the "data" option for more reliable reactivity ' + + 'and better performance.' + ) } - /** - * Add an owner vm, so that when $add/$delete mutations - * happen we can notify owner vms to proxy the keys and - * digest the watchers. This is only called when the object - * is observed as an instance's root $data. - * - * @param {Vue} vm - */ - p.addVm = function (vm) { - (this.vms || (this.vms = [])).push(vm) - } +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { - /** - * Remove an owner vm. This is called when the object is - * swapped out as an instance's $data object. - * - * @param {Vue} vm - */ + // NOTE: the prop internal directive is compiled and linked + // during _initScope(), before the created hook is called. + // The purpose is to make the initial prop values available + // inside `created` hooks and `data` functions. - p.removeVm = function (vm) { - this.vms.$remove(vm) - } + var _ = __webpack_require__(1) + var Watcher = __webpack_require__(18) + var bindingModes = __webpack_require__(3)._propBindingModes - // helpers + module.exports = { - /** - * Augment an target Object or Array by intercepting - * the prototype chain using __proto__ - * - * @param {Object|Array} target - * @param {Object} proto - */ + bind: function () { - function protoAugment (target, src) { - target.__proto__ = src - } + var child = this.vm + var parent = child._context + // passed in from compiler directly + var prop = this._descriptor + var childKey = prop.path + var parentKey = prop.parentPath - /** - * Augment an target Object or Array by defining - * hidden properties. - * - * @param {Object|Array} target - * @param {Object} proto - */ + this.parentWatcher = new Watcher( + parent, + parentKey, + function (val) { + if (_.assertProp(prop, val)) { + child[childKey] = val + } + } + ) - function copyAugment (target, src, keys) { - var i = keys.length - var key - while (i--) { - key = keys[i] - _.define(target, key, src[key]) + // set the child initial value. + var value = this.parentWatcher.value + if (childKey === '$data') { + child._data = value + } else { + _.initProp(child, prop, value) + } + + // setup two-way binding + if (prop.mode === bindingModes.TWO_WAY) { + // important: defer the child watcher creation until + // the created hook (after data observation) + var self = this + child.$once('hook:created', function () { + self.childWatcher = new Watcher( + child, + childKey, + function (val) { + parent.$set(parentKey, val) + } + ) + }) + } + }, + + unbind: function () { + this.parentWatcher.teardown() + if (this.childWatcher) { + this.childWatcher.teardown() + } } } - module.exports = Observer - /***/ }, -/* 19 */ +/* 18 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) + var config = __webpack_require__(3) + var Dep = __webpack_require__(20) + var expParser = __webpack_require__(19) + var batcher = __webpack_require__(21) + var uid = 0 /** - * A dep is an observable that can have multiple - * directives subscribing to it. + * A watcher parses an expression, collects dependencies, + * and fires callback when the expression value changes. + * This is used for both the $watch() api and directives. * + * @param {Vue} vm + * @param {String} expression + * @param {Function} cb + * @param {Object} options + * - {Array} filters + * - {Boolean} twoWay + * - {Boolean} deep + * - {Boolean} user + * - {Boolean} lazy + * - {Function} [preProcess] * @constructor */ - function Dep () { - this.subs = [] + function Watcher (vm, expOrFn, cb, options) { + var isFn = typeof expOrFn === 'function' + this.vm = vm + vm._watchers.push(this) + this.expression = isFn ? '' : expOrFn + this.cb = cb + this.id = ++uid // uid for batching + this.active = true + options = options || {} + this.deep = !!options.deep + this.user = !!options.user + this.twoWay = !!options.twoWay + this.lazy = !!options.lazy + this.dirty = this.lazy + this.filters = options.filters + this.preProcess = options.preProcess + this.deps = [] + this.newDeps = null + // parse expression for getter/setter + if (isFn) { + this.getter = expOrFn + this.setter = undefined + } else { + var res = expParser.parse(expOrFn, options.twoWay) + this.getter = res.get + this.setter = res.set + } + this.value = this.lazy + ? undefined + : this.get() + // state for avoiding false triggers for deep and Array + // watchers during vm._digest() + this.queued = this.shallow = false } - // the current target watcher being evaluated. - // this is globally unique because there could be only one - // watcher being evaluated at any time. - Dep.target = null - - var p = Dep.prototype + var p = Watcher.prototype /** - * Add a directive subscriber. + * Add a dependency to this directive. * - * @param {Directive} sub + * @param {Dep} dep */ - p.addSub = function (sub) { - this.subs.push(sub) + p.addDep = function (dep) { + var newDeps = this.newDeps + var old = this.deps + if (_.indexOf(newDeps, dep) < 0) { + newDeps.push(dep) + var i = _.indexOf(old, dep) + if (i < 0) { + dep.addSub(this) + } else { + old[i] = null + } + } } /** - * Remove a directive subscriber. - * - * @param {Directive} sub + * Evaluate the getter, and re-collect dependencies. */ - p.removeSub = function (sub) { - this.subs.$remove(sub) + p.get = function () { + this.beforeGet() + var vm = this.vm + var value + try { + value = this.getter.call(vm, vm) + } catch (e) { + if ( + ("development") !== 'production' && + config.warnExpressionErrors + ) { + _.warn( + 'Error when evaluating expression "' + + this.expression + '". ' + + (config.debug + ? '' : + 'Turn on debug mode to see stack trace.' + ), e + ) + } + } + // "touch" every property so they are all tracked as + // dependencies for deep watching + if (this.deep) { + traverse(value) + } + if (this.preProcess) { + value = this.preProcess(value) + } + if (this.filters) { + value = vm._applyFilters(value, null, this.filters, false) + } + this.afterGet() + return value } /** - * Add self as a dependency to the target watcher. + * Set the corresponding value with the setter. + * + * @param {*} value */ - p.depend = function () { - if (Dep.target) { - Dep.target.addDep(this) + p.set = function (value) { + var vm = this.vm + if (this.filters) { + value = vm._applyFilters( + value, this.value, this.filters, true) + } + try { + this.setter.call(vm, vm, value) + } catch (e) { + if ( + ("development") !== 'production' && + config.warnExpressionErrors + ) { + _.warn( + 'Error when evaluating setter "' + + this.expression + '"', e + ) + } } } /** - * Notify all subscribers of a new value. + * Prepare for dependency collection. */ - p.notify = function () { - // stablize the subscriber list first - var subs = _.toArray(this.subs) - for (var i = 0, l = subs.length; i < l; i++) { - subs[i].update() - } + p.beforeGet = function () { + Dep.target = this + this.newDeps = [] } - module.exports = Dep - - -/***/ }, -/* 20 */ -/***/ function(module, exports, __webpack_require__) { - - var _ = __webpack_require__(1) - var arrayProto = Array.prototype - var arrayMethods = Object.create(arrayProto) - /** - * Intercept mutating methods and emit events + * Clean up for dependency collection. */ - ;[ - 'push', - 'pop', - 'shift', - 'unshift', - 'splice', - 'sort', - 'reverse' - ] - .forEach(function (method) { - // cache original method - var original = arrayProto[method] - _.define(arrayMethods, method, function mutator () { - // avoid leaking arguments: - // http://jsperf.com/closure-with-arguments - var i = arguments.length - var args = new Array(i) - while (i--) { - args[i] = arguments[i] - } - var result = original.apply(this, args) - var ob = this.__ob__ - var inserted - switch (method) { - case 'push': - inserted = args - break - case 'unshift': - inserted = args - break - case 'splice': - inserted = args.slice(2) - break + p.afterGet = function () { + Dep.target = null + var i = this.deps.length + while (i--) { + var dep = this.deps[i] + if (dep) { + dep.removeSub(this) } - if (inserted) ob.observeArray(inserted) - // notify change - ob.notify() - return result - }) - }) + } + this.deps = this.newDeps + this.newDeps = null + } /** - * Swap the element at the given index with a new value - * and emits corresponding event. + * Subscriber interface. + * Will be called when a dependency changes. * - * @param {Number} index - * @param {*} val - * @return {*} - replaced element + * @param {Boolean} shallow */ - _.define( - arrayProto, - '$set', - function $set (index, val) { - if (index >= this.length) { - this.length = index + 1 - } - return this.splice(index, 1, val)[0] + p.update = function (shallow) { + if (this.lazy) { + this.dirty = true + } else if (!config.async) { + this.run() + } else { + // if queued, only overwrite shallow with non-shallow, + // but not the other way around. + this.shallow = this.queued + ? shallow + ? this.shallow + : false + : !!shallow + this.queued = true + batcher.push(this) } - ) + } /** - * Convenience method to remove the element at given index. - * - * @param {Number} index - * @param {*} val + * Batcher job interface. + * Will be called by the batcher. */ - _.define( - arrayProto, - '$remove', - function $remove (index) { - /* istanbul ignore if */ - if (!this.length) return - if (typeof index !== 'number') { - index = _.indexOf(this, index) - } - if (index > -1) { - return this.splice(index, 1) + p.run = function () { + if (this.active) { + var value = this.get() + if ( + value !== this.value || + // Deep watchers and Array watchers should fire even + // when the value is the same, because the value may + // have mutated; but only do so if this is a + // non-shallow update (caused by a vm digest). + ((_.isArray(value) || this.deep) && !this.shallow) + ) { + var oldValue = this.value + this.value = value + this.cb(value, oldValue) } + this.queued = this.shallow = false } - ) - - module.exports = arrayMethods - - -/***/ }, -/* 21 */ -/***/ function(module, exports, __webpack_require__) { + } - var _ = __webpack_require__(1) - var objProto = Object.prototype + /** + * Evaluate the value of the watcher. + * This only gets called for lazy watchers. + */ + + p.evaluate = function () { + // avoid overwriting another watcher that is being + // collected. + var current = Dep.target + this.value = this.get() + this.dirty = false + Dep.target = current + } /** - * Add a new property to an observed object - * and emits corresponding event - * - * @param {String} key - * @param {*} val - * @public + * Depend on all deps collected by this watcher. */ - _.define( - objProto, - '$add', - function $add (key, val) { - if (this.hasOwnProperty(key)) return - var ob = this.__ob__ - if (!ob || _.isReserved(key)) { - this[key] = val - return - } - ob.convert(key, val) - ob.notify() - if (ob.vms) { - var i = ob.vms.length - while (i--) { - var vm = ob.vms[i] - vm._proxy(key) - vm._digest() - } - } + p.depend = function () { + var i = this.deps.length + while (i--) { + this.deps[i].depend() } - ) + } /** - * Set a property on an observed object, calling add to - * ensure the property is observed. - * - * @param {String} key - * @param {*} val - * @public + * Remove self from all dependencies' subcriber list. */ - _.define( - objProto, - '$set', - function $set (key, val) { - this.$add(key, val) - this[key] = val + p.teardown = function () { + if (this.active) { + // remove self from vm's watcher list + // we can skip this if the vm if being destroyed + // which can improve teardown performance. + if (!this.vm._isBeingDestroyed) { + this.vm._watchers.$remove(this) + } + var i = this.deps.length + while (i--) { + this.deps[i].removeSub(this) + } + this.active = false + this.vm = this.cb = this.value = null } - ) + } /** - * Deletes a property from an observed object - * and emits corresponding event + * Recrusively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. * - * @param {String} key - * @public + * @param {Object} obj */ - _.define( - objProto, - '$delete', - function $delete (key) { - if (!this.hasOwnProperty(key)) return - delete this[key] - var ob = this.__ob__ - if (!ob || _.isReserved(key)) { - return - } - ob.notify() - if (ob.vms) { - var i = ob.vms.length - while (i--) { - var vm = ob.vms[i] - vm._unproxy(key) - vm._digest() - } + function traverse (obj) { + var key, val, i + for (key in obj) { + val = obj[key] + if (_.isArray(val)) { + i = val.length + while (i--) traverse(val[i]) + } else if (_.isObject(val)) { + traverse(val) } } - ) + } + + module.exports = Watcher /***/ }, -/* 22 */ +/* 19 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var Path = __webpack_require__(23) - var Cache = __webpack_require__(14) + var Path = __webpack_require__(16) + var Cache = __webpack_require__(11) var expressionCache = new Cache(1000) var allowedKeywords = @@ -3832,7 +3792,7 @@ return /******/ (function(modules) { // webpackBootstrap function compileExpFns (exp, needSet) { if (improperKeywordsRE.test(exp)) { - _.warn( + ("development") !== 'production' && _.warn( 'Avoid using reserved keywords in expression: ' + exp ) } @@ -3901,7 +3861,7 @@ return /******/ (function(modules) { // webpackBootstrap try { return new Function('scope', 'return ' + body + ';') } catch (e) { - _.warn( + ("development") !== 'production' && _.warn( 'Invalid expression. ' + 'Generated function body: ' + body ) @@ -3916,417 +3876,153 @@ return /******/ (function(modules) { // webpackBootstrap * * This setter function may throw error when called if the * expression body is not a valid left-hand expression in - * assignment. - * - * @param {String} body - * @return {Function|undefined} - */ - - function makeSetter (body) { - try { - return new Function('scope', 'value', body + '=value;') - } catch (e) { - _.warn('Invalid setter function body: ' + body) - } - } - - /** - * Check for setter existence on a cache hit. - * - * @param {Function} hit - */ - - function checkSetter (hit) { - if (!hit.set) { - hit.set = makeSetter(hit.body) - } - } - - /** - * Parse an expression into re-written getter/setters. - * - * @param {String} exp - * @param {Boolean} needSet - * @return {Function} - */ - - exports.parse = function (exp, needSet) { - exp = exp.trim() - // try cache - var hit = expressionCache.get(exp) - if (hit) { - if (needSet) { - checkSetter(hit) - } - return hit - } - // we do a simple path check to optimize for them. - // the check fails valid paths with unusal whitespaces, - // but that's too rare and we don't care. - // also skip boolean literals and paths that start with - // global "Math" - var res = exports.isSimplePath(exp) - ? compilePathFns(exp) - : compileExpFns(exp, needSet) - expressionCache.put(exp, res) - return res - } - - /** - * Check if an expression is a simple path. - * - * @param {String} exp - * @return {Boolean} - */ - - exports.isSimplePath = function (exp) { - return pathTestRE.test(exp) && - // don't treat true/false as paths - !booleanLiteralRE.test(exp) && - // Math constants e.g. Math.PI, Math.E etc. - exp.slice(0, 5) !== 'Math.' - } - - -/***/ }, -/* 23 */ -/***/ function(module, exports, __webpack_require__) { - - var _ = __webpack_require__(1) - var Cache = __webpack_require__(14) - var pathCache = new Cache(1000) - var identRE = exports.identRE = /^[$_a-zA-Z]+[\w$]*$/ - - /** - * Path-parsing algorithm scooped from Polymer/observe-js - */ - - var pathStateMachine = { - 'beforePath': { - 'ws': ['beforePath'], - 'ident': ['inIdent', 'append'], - '[': ['beforeElement'], - 'eof': ['afterPath'] - }, - - 'inPath': { - 'ws': ['inPath'], - '.': ['beforeIdent'], - '[': ['beforeElement'], - 'eof': ['afterPath'] - }, - - 'beforeIdent': { - 'ws': ['beforeIdent'], - 'ident': ['inIdent', 'append'] - }, - - 'inIdent': { - 'ident': ['inIdent', 'append'], - '0': ['inIdent', 'append'], - 'number': ['inIdent', 'append'], - 'ws': ['inPath', 'push'], - '.': ['beforeIdent', 'push'], - '[': ['beforeElement', 'push'], - 'eof': ['afterPath', 'push'], - ']': ['inPath', 'push'] - }, - - 'beforeElement': { - 'ws': ['beforeElement'], - '0': ['afterZero', 'append'], - 'number': ['inIndex', 'append'], - "'": ['inSingleQuote', 'append', ''], - '"': ['inDoubleQuote', 'append', ''], - 'ident': ['inIdent', 'append', '*'] - }, - - 'afterZero': { - 'ws': ['afterElement', 'push'], - ']': ['inPath', 'push'] - }, - - 'inIndex': { - '0': ['inIndex', 'append'], - 'number': ['inIndex', 'append'], - 'ws': ['afterElement'], - ']': ['inPath', 'push'] - }, - - 'inSingleQuote': { - "'": ['afterElement'], - 'eof': 'error', - 'else': ['inSingleQuote', 'append'] - }, - - 'inDoubleQuote': { - '"': ['afterElement'], - 'eof': 'error', - 'else': ['inDoubleQuote', 'append'] - }, - - 'afterElement': { - 'ws': ['afterElement'], - ']': ['inPath', 'push'] - } - } - - function noop () {} - - /** - * Determine the type of a character in a keypath. - * - * @param {Char} ch - * @return {String} type - */ - - function getPathCharType (ch) { - if (ch === undefined) { - return 'eof' - } - - var code = ch.charCodeAt(0) - - switch (code) { - case 0x5B: // [ - case 0x5D: // ] - case 0x2E: // . - case 0x22: // " - case 0x27: // ' - case 0x30: // 0 - return ch - - case 0x5F: // _ - case 0x24: // $ - return 'ident' - - case 0x20: // Space - case 0x09: // Tab - case 0x0A: // Newline - case 0x0D: // Return - case 0xA0: // No-break space - case 0xFEFF: // Byte Order Mark - case 0x2028: // Line Separator - case 0x2029: // Paragraph Separator - return 'ws' - } - - // a-z, A-Z - if ( - (code >= 0x61 && code <= 0x7A) || - (code >= 0x41 && code <= 0x5A) - ) { - return 'ident' - } + * assignment. + * + * @param {String} body + * @return {Function|undefined} + */ - // 1-9 - if (code >= 0x31 && code <= 0x39) { - return 'number' + function makeSetter (body) { + try { + return new Function('scope', 'value', body + '=value;') + } catch (e) { + ("development") !== 'production' && _.warn( + 'Invalid setter function body: ' + body + ) } - - return 'else' } /** - * Parse a string path into an array of segments - * Todo implement cache + * Check for setter existence on a cache hit. * - * @param {String} path - * @return {Array|undefined} + * @param {Function} hit */ - function parsePath (path) { - var keys = [] - var index = -1 - var mode = 'beforePath' - var c, newChar, key, type, transition, action, typeMap - - var actions = { - push: function () { - if (key === undefined) { - return - } - keys.push(key) - key = undefined - }, - append: function () { - if (key === undefined) { - key = newChar - } else { - key += newChar - } - } + function checkSetter (hit) { + if (!hit.set) { + hit.set = makeSetter(hit.body) } + } - function maybeUnescapeQuote () { - var nextChar = path[index + 1] - if ((mode === 'inSingleQuote' && nextChar === "'") || - (mode === 'inDoubleQuote' && nextChar === '"')) { - index++ - newChar = nextChar - actions.append() - return true + /** + * Parse an expression into re-written getter/setters. + * + * @param {String} exp + * @param {Boolean} needSet + * @return {Function} + */ + + exports.parse = function (exp, needSet) { + exp = exp.trim() + // try cache + var hit = expressionCache.get(exp) + if (hit) { + if (needSet) { + checkSetter(hit) } + return hit } + // we do a simple path check to optimize for them. + // the check fails valid paths with unusal whitespaces, + // but that's too rare and we don't care. + // also skip boolean literals and paths that start with + // global "Math" + var res = exports.isSimplePath(exp) + ? compilePathFns(exp) + : compileExpFns(exp, needSet) + expressionCache.put(exp, res) + return res + } - while (mode) { - index++ - c = path[index] + /** + * Check if an expression is a simple path. + * + * @param {String} exp + * @return {Boolean} + */ - if (c === '\\' && maybeUnescapeQuote()) { - continue - } + exports.isSimplePath = function (exp) { + return pathTestRE.test(exp) && + // don't treat true/false as paths + !booleanLiteralRE.test(exp) && + // Math constants e.g. Math.PI, Math.E etc. + exp.slice(0, 5) !== 'Math.' + } - type = getPathCharType(c) - typeMap = pathStateMachine[mode] - transition = typeMap[type] || typeMap['else'] || 'error' - if (transition === 'error') { - return // parse error - } +/***/ }, +/* 20 */ +/***/ function(module, exports, __webpack_require__) { - mode = transition[0] - action = actions[transition[1]] || noop - newChar = transition[2] - newChar = newChar === undefined - ? c - : newChar === '*' - ? newChar + c - : newChar - action() - - if (mode === 'afterPath') { - keys.raw = path - return keys - } - } - } + var _ = __webpack_require__(1) /** - * Format a accessor segment based on its type. + * A dep is an observable that can have multiple + * directives subscribing to it. * - * @param {String} key - * @return {Boolean} + * @constructor */ - function formatAccessor (key) { - if (identRE.test(key)) { // identifier - return '.' + key - } else if (+key === key >>> 0) { // bracket index - return '[' + key + ']' - } else if (key.charAt(0) === '*') { - return '[o' + formatAccessor(key.slice(1)) + ']' - } else { // bracket string - return '["' + key.replace(/"/g, '\\"') + '"]' - } + function Dep () { + this.subs = [] } + // the current target watcher being evaluated. + // this is globally unique because there could be only one + // watcher being evaluated at any time. + Dep.target = null + + var p = Dep.prototype + /** - * Compiles a getter function with a fixed path. - * The fixed path getter supresses errors. + * Add a directive subscriber. * - * @param {Array} path - * @return {Function} + * @param {Directive} sub */ - exports.compileGetter = function (path) { - var body = 'return o' + path.map(formatAccessor).join('') - return new Function('o', body) + p.addSub = function (sub) { + this.subs.push(sub) } /** - * External parse that check for a cache hit first + * Remove a directive subscriber. * - * @param {String} path - * @return {Array|undefined} + * @param {Directive} sub */ - exports.parse = function (path) { - var hit = pathCache.get(path) - if (!hit) { - hit = parsePath(path) - if (hit) { - hit.get = exports.compileGetter(hit) - pathCache.put(path, hit) - } - } - return hit + p.removeSub = function (sub) { + this.subs.$remove(sub) } /** - * Get from an object from a path string - * - * @param {Object} obj - * @param {String} path + * Add self as a dependency to the target watcher. */ - exports.get = function (obj, path) { - path = exports.parse(path) - if (path) { - return path.get(obj) - } + p.depend = function () { + Dep.target.addDep(this) } /** - * Set on an object from a path - * - * @param {Object} obj - * @param {String | Array} path - * @param {*} val + * Notify all subscribers of a new value. */ - exports.set = function (obj, path, val) { - var original = obj - if (typeof path === 'string') { - path = exports.parse(path) - } - if (!path || !_.isObject(obj)) { - return false - } - var last, key - for (var i = 0, l = path.length; i < l; i++) { - last = obj - key = path[i] - if (key.charAt(0) === '*') { - key = original[key.slice(1)] - } - if (i < l - 1) { - obj = obj[key] - if (!_.isObject(obj)) { - obj = {} - last.$add(key, obj) - warnNonExistent(path) - } - } else { - if (_.isArray(obj)) { - obj.$set(key, val) - } else if (key in obj) { - obj[key] = val - } else { - obj.$add(key, val) - warnNonExistent(path) - } - } + p.notify = function () { + // stablize the subscriber list first + var subs = _.toArray(this.subs) + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update() } - return true } - function warnNonExistent (path) { - _.warn( - 'You are setting a non-existent path "' + path.raw + '" ' + - 'on a vm instance. Consider pre-initializing the property ' + - 'with the "data" option for more reliable reactivity ' + - 'and better performance.' - ) - } + module.exports = Dep /***/ }, -/* 24 */ +/* 21 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var config = __webpack_require__(6) + var config = __webpack_require__(3) // we have two separate queues: one for directive updates // and one for user watcher registered via $watch(). @@ -4398,7 +4094,7 @@ return /******/ (function(modules) { // webpackBootstrap has[id]++ // detect possible infinite update loops if (has[id] > config._maxUpdateCount) { - _.warn( + ("development") !== 'production' && _.warn( 'You may have an infinite update loop for the ' + 'watcher with expression: "' + job.expression + '".' ) @@ -4423,11 +4119,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 25 */ +/* 22 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var Cache = __webpack_require__(14) + var Cache = __webpack_require__(11) var templateCache = new Cache(1000) var idSelectorCache = new Cache(1000) @@ -4690,11 +4386,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 26 */ +/* 23 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var templateParser = __webpack_require__(25) + var templateParser = __webpack_require__(22) module.exports = { @@ -4745,7 +4441,7 @@ return /******/ (function(modules) { // webpackBootstrap this.transMode = this._checkParam('transition-mode') } } else { - _.warn( + ("development") !== 'production' && _.warn( 'Do not create a component that only contains ' + 'a single other component - they will be mounted to ' + 'the same element and cause conflict. Wrap it with ' + @@ -4997,12 +4693,12 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 27 */ +/* 24 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var config = __webpack_require__(6) - var templateParser = __webpack_require__(25) + var config = __webpack_require__(3) + var templateParser = __webpack_require__(22) /** * Process an element or a DocumentFragment based on a @@ -5061,15 +4757,13 @@ return /******/ (function(modules) { // webpackBootstrap function transcludeTemplate (el, options) { var template = options.template var frag = templateParser.parse(template, true) - if (!frag) { - _.warn('Invalid template option: ' + template) - } else { + if (frag) { var replacer = frag.firstChild var tag = replacer.tagName && replacer.tagName.toLowerCase() if (options.replace) { /* istanbul ignore if */ if (el === document.body) { - _.warn( + ("development") !== 'production' && _.warn( 'You are mounting an instance with a template to ' + '. This will replace entirely. You ' + 'should probably use `replace: false` here.' @@ -5098,6 +4792,10 @@ return /******/ (function(modules) { // webpackBootstrap el.appendChild(frag) return el } + } else { + ("development") !== 'production' && _.warn( + 'Invalid template option: ' + template + ) } } @@ -5146,37 +4844,37 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 28 */ +/* 25 */ /***/ function(module, exports, __webpack_require__) { // manipulation directives - exports.text = __webpack_require__(30) - exports.html = __webpack_require__(31) - exports.attr = __webpack_require__(32) - exports.show = __webpack_require__(33) - exports['class'] = __webpack_require__(35) - exports.el = __webpack_require__(36) - exports.ref = __webpack_require__(37) - exports.cloak = __webpack_require__(38) - exports.style = __webpack_require__(29) - exports.transition = __webpack_require__(39) + exports.text = __webpack_require__(27) + exports.html = __webpack_require__(28) + exports.attr = __webpack_require__(29) + exports.show = __webpack_require__(30) + exports['class'] = __webpack_require__(32) + exports.el = __webpack_require__(33) + exports.ref = __webpack_require__(34) + exports.cloak = __webpack_require__(35) + exports.style = __webpack_require__(26) + exports.transition = __webpack_require__(36) // event listener directives - exports.on = __webpack_require__(42) - exports.model = __webpack_require__(43) + exports.on = __webpack_require__(39) + exports.model = __webpack_require__(40) // logic control directives - exports.repeat = __webpack_require__(48) - exports['if'] = __webpack_require__(49) + exports.repeat = __webpack_require__(45) + exports['if'] = __webpack_require__(46) // internal directives that should not be used directly // but we still want to expose them for advanced usage. - exports._component = __webpack_require__(26) - exports._prop = __webpack_require__(16) + exports._component = __webpack_require__(23) + exports._prop = __webpack_require__(17) /***/ }, -/* 29 */ +/* 26 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -5292,7 +4990,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 30 */ +/* 27 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -5312,11 +5010,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 31 */ +/* 28 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var templateParser = __webpack_require__(25) + var templateParser = __webpack_require__(22) module.exports = { @@ -5358,7 +5056,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 32 */ +/* 29 */ /***/ function(module, exports, __webpack_require__) { // xlink @@ -5407,16 +5105,19 @@ return /******/ (function(modules) { // webpackBootstrap } else { this.el.removeAttribute(attr) } + if (attr in this.el) { + this.el[attr] = value + } } } /***/ }, -/* 33 */ +/* 30 */ /***/ function(module, exports, __webpack_require__) { - var transition = __webpack_require__(34) + var transition = __webpack_require__(31) module.exports = function (value) { var el = this.el @@ -5427,7 +5128,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 34 */ +/* 31 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -5561,7 +5262,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 35 */ +/* 32 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -5570,49 +5271,74 @@ return /******/ (function(modules) { // webpackBootstrap module.exports = { + bind: function () { + // interpolations like class="{{abc}}" are converted + // to v-class, and we need to remove the raw, + // uninterpolated className at binding time. + var raw = this._descriptor._rawClass + if (raw) { + this.prevKeys = raw.trim().split(/\s+/) + } + }, + update: function (value) { if (this.arg) { // single toggle - var method = value ? addClass : removeClass - method(this.el, this.arg) + if (value) { + addClass(this.el, this.arg) + } else { + removeClass(this.el, this.arg) + } } else { - this.cleanup() if (value && typeof value === 'string') { - // raw class text - addClass(this.el, value) - this.lastVal = value + this.handleObject(stringToObject(value)) } else if (_.isPlainObject(value)) { - // object toggle - for (var key in value) { - if (value[key]) { - addClass(this.el, key) - } else { - removeClass(this.el, key) - } - } - this.prevKeys = Object.keys(value) + this.handleObject(value) + } else { + this.cleanup() + } + } + }, + + handleObject: function (value) { + this.cleanup(value) + var keys = this.prevKeys = Object.keys(value) + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i] + if (value[key]) { + addClass(this.el, key) + } else { + removeClass(this.el, key) } } }, cleanup: function (value) { - if (this.lastVal) { - removeClass(this.el, this.lastVal) - } if (this.prevKeys) { var i = this.prevKeys.length while (i--) { - if (!value || !value[this.prevKeys[i]]) { - removeClass(this.el, this.prevKeys[i]) + var key = this.prevKeys[i] + if (!value || !value.hasOwnProperty(key)) { + removeClass(this.el, key) } } } } } + function stringToObject (value) { + var res = {} + var keys = value.trim().split(/\s+/) + var i = keys.length + while (i--) { + res[keys[i]] = true + } + return res + } + /***/ }, -/* 36 */ +/* 33 */ /***/ function(module, exports, __webpack_require__) { module.exports = { @@ -5630,7 +5356,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 37 */ +/* 34 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -5642,7 +5368,7 @@ return /******/ (function(modules) { // webpackBootstrap bind: function () { var vm = this.el.__vue__ if (!vm) { - _.warn( + ("development") !== 'production' && _.warn( 'v-ref should only be used on a component root element.' ) return @@ -5658,10 +5384,10 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 38 */ +/* 35 */ /***/ function(module, exports, __webpack_require__) { - var config = __webpack_require__(6) + var config = __webpack_require__(3) module.exports = { bind: function () { @@ -5674,11 +5400,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 39 */ +/* 36 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var Transition = __webpack_require__(40) + var Transition = __webpack_require__(37) module.exports = { @@ -5706,11 +5432,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 40 */ +/* 37 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) - var queue = __webpack_require__(41) + var queue = __webpack_require__(38) var addClass = _.addClass var removeClass = _.removeClass var transitionEndEvent = _.transitionEndEvent @@ -5850,7 +5576,7 @@ return /******/ (function(modules) { // webpackBootstrap this.cb = cb addClass(this.el, this.leaveClass) this.callHookWithCb('leave') - this.cancel = this.hooks && this.hooks.enterCancelled + this.cancel = this.hooks && this.hooks.leaveCancelled // only need to do leaveNextTick if there's no explicit // js callback if (!this.pendingJsCb) { @@ -6020,7 +5746,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 41 */ +/* 38 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -6061,7 +5787,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 42 */ +/* 39 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) @@ -6087,9 +5813,10 @@ return /******/ (function(modules) { // webpackBootstrap update: function (handler) { if (typeof handler !== 'function') { - _.warn( - 'Directive "v-on:' + this.expression + '" ' + - 'expects a function value.' + ("development") !== 'production' && _.warn( + 'Directive v-on="' + this.arg + ': ' + + this.expression + '" expects a function value, ' + + 'got ' + handler ) return } @@ -6126,87 +5853,294 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 43 */ +/* 40 */ /***/ function(module, exports, __webpack_require__) { var _ = __webpack_require__(1) var handlers = { - text: __webpack_require__(44), - radio: __webpack_require__(45), - select: __webpack_require__(46), - checkbox: __webpack_require__(47) + text: __webpack_require__(42), + radio: __webpack_require__(43), + select: __webpack_require__(41), + checkbox: __webpack_require__(44) + } + + module.exports = { + + priority: 800, + twoWay: true, + handlers: handlers, + + /** + * Possible elements: + *