代码大概6600-8600行
var validDivisionCharRE = /[\w).+\-_$\]]/;
function parseFilters (exp) {
var inSingle = false;
var inDouble = false;
var inTemplateString = false;
var inRegex = false;
var curly = 0;
var square = 0;
var paren = 0;
var lastFilterIndex = 0;
var c, prev, i, expression, filters;
for (i = 0; i < exp.length; i++) {
prev = c;
c = exp.charCodeAt(i);
if (inSingle) {
if (c === 0x27 && prev !== 0x5C) { inSingle = false; }
} else if (inDouble) {
if (c === 0x22 && prev !== 0x5C) { inDouble = false; }
} else if (inTemplateString) {
if (c === 0x60 && prev !== 0x5C) { inTemplateString = false; }
} else if (inRegex) {
if (c === 0x2f && prev !== 0x5C) { inRegex = false; }
} else if (
c === 0x7C && // pipe
exp.charCodeAt(i + 1) !== 0x7C &&
exp.charCodeAt(i - 1) !== 0x7C &&
!curly && !square && !paren
) {
if (expression === undefined) {
// first filter, end of expression
lastFilterIndex = i + 1;
expression = exp.slice(0, i).trim();
} else {
pushFilter();
}
} else {
switch (c) {
case 0x22: inDouble = true; break // "
case 0x27: inSingle = true; break // '
case 0x60: inTemplateString = 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 (c === 0x2f) { // /
var j = i - 1;
var p = (void 0);
// find first non-whitespace prev char
for (; j >= 0; j--) {
p = exp.charAt(j);
if (p !== ' ') { break }
}
if (!p || !validDivisionCharRE.test(p)) {
inRegex = true;
}
}
}
}
if (expression === undefined) {
expression = exp.slice(0, i).trim();
} else if (lastFilterIndex !== 0) {
pushFilter();
}
function pushFilter () {
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
lastFilterIndex = i + 1;
}
if (filters) {
for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i]);
}
}
return expression
}
// filter区分名字和参数
function wrapFilter (exp, filter) {
var i = filter.indexOf('(');
if (i < 0) {
// _f: resolveFilter
return ("_f(\"" + filter + "\")(" + exp + ")")
} else {
var name = filter.slice(0, i);
var args = filter.slice(i + 1);
return ("_f(\"" + name + "\")(" + exp + (args !== ')' ? ',' + args : args))
}
}
/* */
// 解析错误信息
function baseWarn (msg) {
console.error(("[Vue compiler]: " + msg));
}
// 匹配modules[keys]的通过条件的新数组
function pluckModuleFunction (
modules,
key
) {
return modules
? modules.map(function (m) { return m[key]; }).filter(function (_) { return _; })
: []
}
// 添加prop
function addProp (el, name, value) {
(el.props || (el.props = [])).push({ name: name, value: value });
el.plain = false;
}
// 添加属性 attr
function addAttr (el, name, value) {
(el.attrs || (el.attrs = [])).push({ name: name, value: value });
el.plain = false;
}
// add a raw attr (use this in preTransforms)
// 添加attrList,attrMap
function addRawAttr (el, name, value) {
el.attrsMap[name] = value;
el.attrsList.push({ name: name, value: value });
}
// 为元素添加指令
function addDirective (
el,
name,
rawName,
value,
arg,
modifiers
) {
(el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers });
el.plain = false;
}
// 添加回调
function addHandler (
el,
name,
value,
modifiers,
important,
warn
) {
modifiers = modifiers || emptyObject;
// warn prevent and passive modifier
/* istanbul ignore if */
if (
"development" !== 'production' && warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.'
);
}
// check capture modifier
if (modifiers.capture) {
delete modifiers.capture;
name = '!' + name; // mark the event as captured
}
if (modifiers.once) {
delete modifiers.once;
name = '~' + name; // mark the event as once
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive;
name = '&' + name; // mark the event as passive
}
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific, but at least for now browsers are
// the only target envs that have right/middle clicks.
if (name === 'click') {
if (modifiers.right) {
name = 'contextmenu';
delete modifiers.right;
} else if (modifiers.middle) {
name = 'mouseup';
}
}
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
var newHandler = {
value: value.trim()
};
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers;
}
var handlers = events[name];
/* istanbul ignore if */
if (Array.isArray(handlers)) {
important ? handlers.unshift(newHandler) : handlers.push(newHandler);
} else if (handlers) {
events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
} else {
events[name] = newHandler;
}
el.plain = false;
}
function getBindingAttr (
el,
name,
getStatic
) {
var dynamicValue =
// 获取 v-bind绑定的值并删除
getAndRemoveAttr(el, ':' + name) ||
getAndRemoveAttr(el, 'v-bind:' + name);
if (dynamicValue != null) {
return parseFilters(dynamicValue)
} else if (getStatic !== false) {
var staticValue = getAndRemoveAttr(el, name);
if (staticValue != null) {
return JSON.stringify(staticValue)
}
}
}
// note: this only removes the attr from the Array (attrsList) so that it
// doesn't get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
// 将el.attrMap中el.attrsList中的属性删除
function getAndRemoveAttr (
el,
name,
removeFromMap
) {
var val;
if ((val = el.attrsMap[name]) != null) {
var list = el.attrsList;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {
list.splice(i, 1);
break
}
}
}
if (removeFromMap) {
delete el.attrsMap[name];
}
return val
}
/* */
/**
* Cross-platform code generation for component v-model
*/
// v-model解析
function genComponentModel (
el,
value,
modifiers
) {
var ref = modifiers || {};
var number = ref.number;
var trim = ref.trim;
var baseValueExpression = '$$v';
var valueExpression = baseValueExpression;
if (trim) {
valueExpression =
"(typeof " + baseValueExpression + " === 'string'" +
"? " + baseValueExpression + ".trim()" +
": " + baseValueExpression + ")";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
var assignment = genAssignmentCode(value, valueExpression);
el.model = {
value: ("(" + value + ")"),
expression: ("\"" + value + "\""),
callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
};
}
/**
* Cross-platform codegen helper for generating v-model value assignment code.
*/
function genAssignmentCode (
value,
assignment
) {
var res = parseModel(value);
if (res.key === null) {
return (value + "=" + assignment)
} else {
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
/**
* Parse a v-model expression into a base path and a final key segment.
* Handles both dot-path and possible square brackets.
*
* Possible cases:
*
* - test
* - test[key]
* - test[test1[key]]
* - test["a"][key]
* - xxx.test[a[a].test1[key]]
* - test.xxx.a["asa"][test1[key]]
*
*/
var len;
var str;
var chr;
var index$1;
var expressionPos;
var expressionEndPos;
//
function parseModel (val) {
// Fix https://github.com/vuejs/vue/pull/7730
// allow v-model="obj.val " (trailing whitespace)
val = val.trim();
len = val.length;
if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
index$1 = val.lastIndexOf('.');
if (index$1 > -1) {
return {
exp: val.slice(0, index$1),
key: '"' + val.slice(index$1 + 1) + '"'
}
} else {
return {
exp: val,
key: null
}
}
}
str = val;
index$1 = expressionPos = expressionEndPos = 0;
while (!eof()) {
chr = next();
/* istanbul ignore if */
if (isStringStart(chr)) {
parseString(chr);
} else if (chr === 0x5B) {
parseBracket(chr);
}
}
return {
exp: val.slice(0, expressionPos),
key: val.slice(expressionPos + 1, expressionEndPos)
}
}
// 下一个字符
function next () {
return str.charCodeAt(++index$1)
}
// 大于最后一个
function eof () {
return index$1 >= len
}
function isStringStart (chr) {
return chr === 0x22 || chr === 0x27
}
function parseBracket (chr) {
var inBracket = 1;
expressionPos = index$1;
while (!eof()) {
chr = next();
if (isStringStart(chr)) {
parseString(chr);
continue
}
if (chr === 0x5B) { inBracket++; }
if (chr === 0x5D) { inBracket--; }
if (inBracket === 0) {
expressionEndPos = index$1;
break
}
}
}
// 解析字符串
function parseString (chr) {
var stringQuote = chr;
while (!eof()) {
chr = next();
if (chr === stringQuote) {
break
}
}
}
/* */
var warn$1;
// in some cases, the event used has to be determined at runtime
// so we used some reserved tokens during compile.
var RANGE_TOKEN = '__r';
var CHECKBOX_RADIO_TOKEN = '__c';
function model (
el,
dir,
_warn
) {
warn$1 = _warn;
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
{
// inputs with type="file" are read only and setting the input's
// value will throw an error.
if (tag === 'input' && type === 'file') {
warn$1(
"<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +
"File inputs are read only. Use a v-on:change listener instead."
);
}
}
if (el.component) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
genSelect(el, value, modifiers);
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers);
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el, value, modifiers);
} else if (tag === 'input' || tag === 'textarea') {
genDefaultModel(el, value, modifiers);
} else if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else {
warn$1(
"<" + (el.tag) + " v-model=\"" + value + "\">: " +
"v-model is not supported on this element type. " +
'If you are working with contenteditable, it\'s recommended to ' +
'wrap a library dedicated for that purpose inside a custom component.'
);
}
// ensure runtime directive metadata
return true
}
// 解析checkbox的 v-model
function genCheckboxModel (
el,
value,
modifiers
) {
var number = modifiers && modifiers.number;
var valueBinding = getBindingAttr(el, 'value') || 'null';
var trueValueBinding = getBindingAttr(el, 'true-value') || 'true';
var falseValueBinding = getBindingAttr(el, 'false-value') || 'false';
addProp(el, 'checked',
"Array.isArray(" + value + ")" +
"?_i(" + value + "," + valueBinding + ")>-1" + (
trueValueBinding === 'true'
? (":(" + value + ")")
: (":_q(" + value + "," + trueValueBinding + ")")
)
);
addHandler(el, 'change',
"var $$a=" + value + "," +
'$$el=$event.target,' +
"$$c=$$el.checked?(" + trueValueBinding + "):(" + falseValueBinding + ");" +
'if(Array.isArray($$a)){' +
"var $$v=" + (number ? '_n(' + valueBinding + ')' : valueBinding) + "," +
'$$i=_i($$a,$$v);' +
"if($$el.checked){$$i<0&&(" + (genAssignmentCode(value, '$$a.concat([$$v])')) + ")}" +
"else{$$i>-1&&(" + (genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')) + ")}" +
"}else{" + (genAssignmentCode(value, '$$c')) + "}",
null, true
);
}
// 解析 raido的v-model
function genRadioModel (
el,
value,
modifiers
) {
var number = modifiers && modifiers.number;
var valueBinding = getBindingAttr(el, 'value') || 'null';
valueBinding = number ? ("_n(" + valueBinding + ")") : valueBinding;
addProp(el, 'checked', ("_q(" + value + "," + valueBinding + ")"));
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true);
}
function genSelect (
el,
value,
modifiers
) {
var number = modifiers && modifiers.number;
var selectedVal = "Array.prototype.filter" +
".call($event.target.options,function(o){return o.selected})" +
".map(function(o){var val = \"_value\" in o ? o._value : o.value;" +
"return " + (number ? '_n(val)' : 'val') + "})";
var assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]';
var code = "var $$selectedVal = " + selectedVal + ";";
code = code + " " + (genAssignmentCode(value, assignment));
addHandler(el, 'change', code, null, true);
}
// v-model 默认值
function genDefaultModel (
el,
value,
modifiers
) {
var type = el.attrsMap.type;
// warn if v-bind:value conflicts with v-model
// except for inputs with v-bind:type
{
var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value'];
var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];
if (value$1 && !typeBinding) {
var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value';
warn$1(
binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +
'because the latter already expands to a value binding internally'
);
}
}
var ref = modifiers || {};
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
var event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input';
var valueExpression = '$event.target.value';
if (trim) {
valueExpression = "$event.target.value.trim()";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
code = "if($event.target.composing)return;" + code;
}
addProp(el, 'value', ("(" + value + ")"));
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}
/* */
// normalize v-model event tokens that can only be determined at runtime.
// it's important to place the event as the first in the array because
// the whole point is ensuring the v-model callback gets called before
// user-attached handlers.
// v-model改变的事件
function normalizeEvents (on) {
/* istanbul ignore if */
if (isDef(on[RANGE_TOKEN])) {
// IE input[type=range] only supports `change` event
var event = isIE ? 'change' : 'input';
on[event] = [].concat(on[RANGE_TOKEN], on[event] || []);
delete on[RANGE_TOKEN];
}
// This was originally intended to fix #4521 but no longer necessary
// after 2.5. Keeping it for backwards compat with generated code from < 2.4
/* istanbul ignore if */
if (isDef(on[CHECKBOX_RADIO_TOKEN])) {
on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || []);
delete on[CHECKBOX_RADIO_TOKEN];
}
}
var target$1;
function createOnceHandler (handler, event, capture) {
var _target = target$1; // save current target element in closure
return function onceHandler () {
var res = handler.apply(null, arguments);
if (res !== null) {
remove$2(event, onceHandler, capture, _target);
}
}
}
// 将函数和事件名进行绑定
function add$1 (
event,
handler,
once$$1,
capture,
passive
) {
handler = withMacroTask(handler);
if (once$$1) { handler = createOnceHandler(handler, event, capture); }
target$1.addEventListener(
event,
handler,
supportsPassive
? { capture: capture, passive: passive }
: capture
);
}
// 解绑事件
function remove$2 (
event,
handler,
capture,
_target
) {
(_target || target$1).removeEventListener(
event,
handler._withTask || handler,
capture
);
}
// 跟新监听事件
function updateDOMListeners (oldVnode, vnode) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, vnode.context);
target$1 = undefined;
}
var events = {
create: updateDOMListeners,
update: updateDOMListeners
}
/* */
// 跟新props
function updateDOMProps (oldVnode, vnode) {
if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {
return
}
var key, cur;
var elm = vnode.elm;
var oldProps = oldVnode.data.domProps || {};
var props = vnode.data.domProps || {};
// clone observed objects, as the user probably wants to mutate it
if (isDef(props.__ob__)) {
props = vnode.data.domProps = extend({}, props);
}
for (key in oldProps) {
if (isUndef(props[key])) {
elm[key] = '';
}
}
for (key in props) {
cur = props[key];
// ignore children if the node has textContent or innerHTML,
// as these will throw away existing DOM nodes and cause removal errors
// on subsequent patches (#3360)
if (key === 'textContent' || key === 'innerHTML') {
if (vnode.children) { vnode.children.length = 0; }
if (cur === oldProps[key]) { continue }
// #6601 work around Chrome version <= 55 bug where single textNode
// replaced by innerHTML/textContent retains its parentNode property
if (elm.childNodes.length === 1) {
elm.removeChild(elm.childNodes[0]);
}
}
if (key === 'value') {
// store value as _value as well since
// non-string values will be stringified
elm._value = cur;
// avoid resetting cursor position when value is the same
var strCur = isUndef(cur) ? '' : String(cur);
if (shouldUpdateValue(elm, strCur)) {
elm.value = strCur;
}
} else {
elm[key] = cur;
}
}
}
// check platforms/web/util/attrs.js acceptValue
// 判断你是否跟新value
function shouldUpdateValue (elm, checkVal) {
return (!elm.composing && (
elm.tagName === 'OPTION' ||
isNotInFocusAndDirty(elm, checkVal) ||
isDirtyWithModifiers(elm, checkVal)
))
}
function isNotInFocusAndDirty (elm, checkVal) {
// return true when textbox (.number and .trim) loses focus and its value is
// not equal to the updated value
var notInFocus = true;
// #6157
// work around IE bug when accessing document.activeElement in an iframe
try { notInFocus = document.activeElement !== elm; } catch (e) {}
return notInFocus && elm.value !== checkVal
}
function isDirtyWithModifiers (elm, newVal) {
var value = elm.value;
var modifiers = elm._vModifiers; // injected by v-model runtime
if (isDef(modifiers)) {
if (modifiers.lazy) {
// inputs with lazy should only be updated when not in focus
return false
}
if (modifiers.number) {
return toNumber(value) !== toNumber(newVal)
}
if (modifiers.trim) {
return value.trim() !== newVal.trim()
}
}
return value !== newVal
}
var domProps = {
create: updateDOMProps,
update: updateDOMProps
}
/* */
var parseStyleText = cached(function (cssText) {
var res = {};
var listDelimiter = /;(?![^(]*\))/g;
var propertyDelimiter = /:(.+)/;
cssText.split(listDelimiter).forEach(function (item) {
if (item) {
var tmp = item.split(propertyDelimiter);
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim());
}
});
return res
});
// merge static and dynamic style data on the same vnode
function normalizeStyleData (data) {
var style = normalizeStyleBinding(data.style);
// static style is pre-processed into an object during compilation
// and is always a fresh object, so it's safe to merge into it
return data.staticStyle
? extend(data.staticStyle, style)
: style
}
// normalize possible array / string values into Object
function normalizeStyleBinding (bindingStyle) {
if (Array.isArray(bindingStyle)) {
return toObject(bindingStyle)
}
if (typeof bindingStyle === 'string') {
return parseStyleText(bindingStyle)
}
return bindingStyle
}
/**
* parent component style should be after child's
* so that parent component's style could override it
*/
function getStyle (vnode, checkChild) {
var res = {};
var styleData;
if (checkChild) {
var childNode = vnode;
while (childNode.componentInstance) {
childNode = childNode.componentInstance._vnode;
if (
childNode && childNode.data &&
(styleData = normalizeStyleData(childNode.data))
) {
extend(res, styleData);
}
}
}
if ((styleData = normalizeStyleData(vnode.data))) {
extend(res, styleData);
}
var parentNode = vnode;
while ((parentNode = parentNode.parent)) {
if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {
extend(res, styleData);
}
}
return res
}
/* */
var cssVarRE = /^--/;
var importantRE = /\s*!important$/;
var setProp = function (el, name, val) {
/* istanbul ignore if */
if (cssVarRE.test(name)) {
el.style.setProperty(name, val);
} else if (importantRE.test(val)) {
el.style.setProperty(name, val.replace(importantRE, ''), 'important');
} else {
var normalizedName = normalize(name);
if (Array.isArray(val)) {
// Support values array created by autoprefixer, e.g.
// {display: ["-webkit-box", "-ms-flexbox", "flex"]}
// Set them one by one, and the browser will only set those it can recognize
for (var i = 0, len = val.length; i < len; i++) {
el.style[normalizedName] = val[i];
}
} else {
el.style[normalizedName] = val;
}
}
};
var vendorNames = ['Webkit', 'Moz', 'ms'];
var emptyStyle;
var normalize = cached(function (prop) {
emptyStyle = emptyStyle || document.createElement('div').style;
prop = camelize(prop);
if (prop !== 'filter' && (prop in emptyStyle)) {
return prop
}
var capName = prop.charAt(0).toUpperCase() + prop.slice(1);
for (var i = 0; i < vendorNames.length; i++) {
var name = vendorNames[i] + capName;
if (name in emptyStyle) {
return name
}
}
});
function updateStyle (oldVnode, vnode) {
var data = vnode.data;
var oldData = oldVnode.data;
if (isUndef(data.staticStyle) && isUndef(data.style) &&
isUndef(oldData.staticStyle) && isUndef(oldData.style)
) {
return
}
var cur, name;
var el = vnode.elm;
var oldStaticStyle = oldData.staticStyle;
var oldStyleBinding = oldData.normalizedStyle || oldData.style || {};
// if static style exists, stylebinding already merged into it when doing normalizeStyleData
var oldStyle = oldStaticStyle || oldStyleBinding;
var style = normalizeStyleBinding(vnode.data.style) || {};
// store normalized style under a different key for next diff
// make sure to clone it if it's reactive, since the user likely wants
// to mutate it.
vnode.data.normalizedStyle = isDef(style.__ob__)
? extend({}, style)
: style;
var newStyle = getStyle(vnode, true);
for (name in oldStyle) {
if (isUndef(newStyle[name])) {
setProp(el, name, '');
}
}
for (name in newStyle) {
cur = newStyle[name];
if (cur !== oldStyle[name]) {
// ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur);
}
}
}
var style = {
create: updateStyle,
update: updateStyle
}
/* */
/**
* Add class with compatibility for SVG since classList is not supported on
* SVG elements in IE
*/
//
function addClass (el, cls) {
/* istanbul ignore if */
if (!cls || !(cls = cls.trim())) {
return
}
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(/\s+/).forEach(function (c) { return el.classList.add(c); });
} else {
el.classList.add(cls);
}
} else {
var cur = " " + (el.getAttribute('class') || '') + " ";
if (cur.indexOf(' ' + cls + ' ') < 0) {
el.setAttribute('class', (cur + cls).trim());
}
}
}
/**
* Remove class with compatibility for SVG since classList is not supported on
* SVG elements in IE
*/
function removeClass (el, cls) {
/* istanbul ignore if */
if (!cls || !(cls = cls.trim())) {
return
}
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(/\s+/).forEach(function (c) { return el.classList.remove(c); });
} else {
el.classList.remove(cls);
}
if (!el.classList.length) {
el.removeAttribute('class');
}
} else {
var cur = " " + (el.getAttribute('class') || '') + " ";
var tar = ' ' + cls + ' ';
while (cur.indexOf(tar) >= 0) {
cur = cur.replace(tar, ' ');
}
cur = cur.trim();
if (cur) {
el.setAttribute('class', cur);
} else {
el.removeAttribute('class');
}
}
}
/* */
function resolveTransition (def) {
if (!def) {
return
}
/* istanbul ignore else */
if (typeof def === 'object') {
var res = {};
if (def.css !== false) {
extend(res, autoCssTransition(def.name || 'v'));
}
extend(res, def);
return res
} else if (typeof def === 'string') {
return autoCssTransition(def)
}
}
var autoCssTransition = cached(function (name) {
return {
enterClass: (name + "-enter"),
enterToClass: (name + "-enter-to"),
enterActiveClass: (name + "-enter-active"),
leaveClass: (name + "-leave"),
leaveToClass: (name + "-leave-to"),
leaveActiveClass: (name + "-leave-active")
}
});
var hasTransition = inBrowser && !isIE9;
var TRANSITION = 'transition';
var ANIMATION = 'animation';
// Transition property/event sniffing
var transitionProp = 'transition';
var transitionEndEvent = 'transitionend';
var animationProp = 'animation';
var animationEndEvent = 'animationend';
if (hasTransition) {
/* istanbul ignore if */
if (window.ontransitionend === undefined &&
window.onwebkittransitionend !== undefined
) {
transitionProp = 'WebkitTransition';
transitionEndEvent = 'webkitTransitionEnd';
}
if (window.onanimationend === undefined &&
window.onwebkitanimationend !== undefined
) {
animationProp = 'WebkitAnimation';
animationEndEvent = 'webkitAnimationEnd';
}
}
// binding to window is necessary to make hot reload work in IE in strict mode
var raf = inBrowser
? window.requestAnimationFrame
? window.requestAnimationFrame.bind(window)
: setTimeout
: /* istanbul ignore next */ function (fn) { return fn(); };
function nextFrame (fn) {
raf(function () {
raf(fn);
});
}
function addTransitionClass (el, cls) {
var transitionClasses = el._transitionClasses || (el._transitionClasses = []);
if (transitionClasses.indexOf(cls) < 0) {
transitionClasses.push(cls);
addClass(el, cls);
}
}
function removeTransitionClass (el, cls) {
if (el._transitionClasses) {
remove(el._transitionClasses, cls);
}
removeClass(el, cls);
}
// 动画结束
function whenTransitionEnds (
el,
expectedType,
cb
) {
var ref = getTransitionInfo(el, expectedType);
var type = ref.type;
var timeout = ref.timeout;
var propCount = ref.propCount;
if (!type) { return cb() }
var event = type === TRANSITION ? transitionEndEvent : animationEndEvent;
var ended = 0;
var end = function () {
el.removeEventListener(event, onEnd);
cb();
};
var onEnd = function (e) {
if (e.target === el) {
if (++ended >= propCount) {
end();
}
}
};
setTimeout(function () {
if (ended < propCount) {
end();
}
}, timeout + 1);
el.addEventListener(event, onEnd);
}
var transformRE = /\b(transform|all)(,|$)/;
// 获取dom属性上自带的动画
function getTransitionInfo (el, expectedType) {
var styles = window.getComputedStyle(el);
var transitionDelays = styles[transitionProp + 'Delay'].split(', ');
var transitionDurations = styles[transitionProp + 'Duration'].split(', ');
var transitionTimeout = getTimeout(transitionDelays, transitionDurations);
var animationDelays = styles[animationProp + 'Delay'].split(', ');
var animationDurations = styles[animationProp + 'Duration'].split(', ');
var animationTimeout = getTimeout(animationDelays, animationDurations);
var type;
var timeout = 0;
var propCount = 0;
/* istanbul ignore if */
if (expectedType === TRANSITION) {
if (transitionTimeout > 0) {
type = TRANSITION;
timeout = transitionTimeout;
propCount = transitionDurations.length;
}
} else if (expectedType === ANIMATION) {
if (animationTimeout > 0) {
type = ANIMATION;
timeout = animationTimeout;
propCount = animationDurations.length;
}
} else {
timeout = Math.max(transitionTimeout, animationTimeout);
type = timeout > 0
? transitionTimeout > animationTimeout
? TRANSITION
: ANIMATION
: null;
propCount = type
? type === TRANSITION
? transitionDurations.length
: animationDurations.length
: 0;
}
var hasTransform =
type === TRANSITION &&
transformRE.test(styles[transitionProp + 'Property']);
return {
type: type,
timeout: timeout,
propCount: propCount,
hasTransform: hasTransform
}
}
// 获取延迟时间
function getTimeout (delays, durations) {
/* istanbul ignore next */
while (delays.length < durations.length) {
delays = delays.concat(delays);
}
return Math.max.apply(null, durations.map(function (d, i) {
return toMs(d) + toMs(delays[i])
}))
}
function toMs (s) {
return Number(s.slice(0, -1)) * 1000
}
/* */
// 动画开始一系列操作,不想看了
function enter (vnode, toggleDisplay) {
var el = vnode.elm;
// call leave callback now
if (isDef(el._leaveCb)) {
el._leaveCb.cancelled = true;
el._leaveCb();
}
var data = resolveTransition(vnode.data.transition);
if (isUndef(data)) {
return
}
/* istanbul ignore if */
if (isDef(el._enterCb) || el.nodeType !== 1) {
return
}
var css = data.css;
var type = data.type;
var enterClass = data.enterClass;
var enterToClass = data.enterToClass;
var enterActiveClass = data.enterActiveClass;
var appearClass = data.appearClass;
var appearToClass = data.appearToClass;
var appearActiveClass = data.appearActiveClass;
var beforeEnter = data.beforeEnter;
var enter = data.enter;
var afterEnter = data.afterEnter;
var enterCancelled = data.enterCancelled;
var beforeAppear = data.beforeAppear;
var appear = data.appear;
var afterAppear = data.afterAppear;
var appearCancelled = data.appearCancelled;
var duration = data.duration;
// activeInstance will always be the <transition> component managing this
// transition. One edge case to check is when the <transition> is placed
// as the root node of a child component. In that case we need to check
// <transition>'s parent for appear check.
var context = activeInstance;
var transitionNode = activeInstance.$vnode;
while (transitionNode && transitionNode.parent) {
transitionNode = transitionNode.parent;
context = transitionNode.context;
}
var isAppear = !context._isMounted || !vnode.isRootInsert;
if (isAppear && !appear && appear !== '') {
return
}
var startClass = isAppear && appearClass
? appearClass
: enterClass;
var activeClass = isAppear && appearActiveClass
? appearActiveClass
: enterActiveClass;
var toClass = isAppear && appearToClass
? appearToClass
: enterToClass;
var beforeEnterHook = isAppear
? (beforeAppear || beforeEnter)
: beforeEnter;
var enterHook = isAppear
? (typeof appear === 'function' ? appear : enter)
: enter;
var afterEnterHook = isAppear
? (afterAppear || afterEnter)
: afterEnter;
var enterCancelledHook = isAppear
? (appearCancelled || enterCancelled)
: enterCancelled;
var explicitEnterDuration = toNumber(
isObject(duration)
? duration.enter
: duration
);
if ("development" !== 'production' && explicitEnterDuration != null) {
checkDuration(explicitEnterDuration, 'enter', vnode);
}
var expectsCSS = css !== false && !isIE9;
var userWantsControl = getHookArgumentsLength(enterHook);
var cb = el._enterCb = once(function () {
if (expectsCSS) {
removeTransitionClass(el, toClass);
removeTransitionClass(el, activeClass);
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass);
}
enterCancelledHook && enterCancelledHook(el);
} else {
afterEnterHook && afterEnterHook(el);
}
el._enterCb = null;
});
if (!vnode.data.show) {
// remove pending leave element on enter by injecting an insert hook
mergeVNodeHook(vnode, 'insert', function () {
var parent = el.parentNode;
var pendingNode = parent && parent._pending && parent._pending[vnode.key];
if (pendingNode &&
pendingNode.tag === vnode.tag &&
pendingNode.elm._leaveCb
) {
pendingNode.elm._leaveCb();
}
enterHook && enterHook(el, cb);
});
}
// start enter transition
beforeEnterHook && beforeEnterHook(el);
if (expectsCSS) {
addTransitionClass(el, startClass);
addTransitionClass(el, activeClass);
nextFrame(function () {
removeTransitionClass(el, startClass);
if (!cb.cancelled) {
addTransitionClass(el, toClass);
if (!userWantsControl) {
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration);
} else {
whenTransitionEnds(el, type, cb);
}
}
}
});
}
if (vnode.data.show) {
toggleDisplay && toggleDisplay();
enterHook && enterHook(el, cb);
}
if (!expectsCSS && !userWantsControl) {
cb();
}
}
// leave动画函数
function leave (vnode, rm) {
var el = vnode.elm;
// call enter callback now
if (isDef(el._enterCb)) {
el._enterCb.cancelled = true;
el._enterCb();
}
var data = resolveTransition(vnode.data.transition);
if (isUndef(data) || el.nodeType !== 1) {
return rm()
}
/* istanbul ignore if */
if (isDef(el._leaveCb)) {
return
}
var css = data.css;
var type = data.type;
var leaveClass = data.leaveClass;
var leaveToClass = data.leaveToClass;
var leaveActiveClass = data.leaveActiveClass;
var beforeLeave = data.beforeLeave;
var leave = data.leave;
var afterLeave = data.afterLeave;
var leaveCancelled = data.leaveCancelled;
var delayLeave = data.delayLeave;
var duration = data.duration;
var expectsCSS = css !== false && !isIE9;
var userWantsControl = getHookArgumentsLength(leave);
var explicitLeaveDuration = toNumber(
isObject(duration)
? duration.leave
: duration
);
if ("development" !== 'production' && isDef(explicitLeaveDuration)) {
checkDuration(explicitLeaveDuration, 'leave', vnode);
}
var cb = el._leaveCb = once(function () {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null;
}
if (expectsCSS) {
removeTransitionClass(el, leaveToClass);
removeTransitionClass(el, leaveActiveClass);
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, leaveClass);
}
leaveCancelled && leaveCancelled(el);
} else {
rm();
afterLeave && afterLeave(el);
}
el._leaveCb = null;
});
if (delayLeave) {
delayLeave(performLeave);
} else {
performLeave();
}
// 离开动画
function performLeave () {
// the delayed leave may have already been cancelled
if (cb.cancelled) {
return
}
// record leaving element
if (!vnode.data.show) {
(el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key)] = vnode;
}
//离开之前
beforeLeave && beforeLeave(el);
if (expectsCSS) {
//离开动画
addTransitionClass(el, leaveClass);
// 离开激活动画
addTransitionClass(el, leaveActiveClass);
// 结束动画
nextFrame(function () {
//移除离开动画
removeTransitionClass(el, leaveClass);
if (!cb.cancelled) {
//添加离开过度结束的动画
addTransitionClass(el, leaveToClass);
if (!userWantsControl) {
// 延迟执行函数
if (isValidDuration(explicitLeaveDuration)) {
setTimeout(cb, explicitLeaveDuration);
} else {
whenTransitionEnds(el, type, cb);
}
}
}
});
}
// 离开回调函数
leave && leave(el, cb);
if (!expectsCSS && !userWantsControl) {
cb();
}
}
}
// only used in dev mode
function checkDuration (val, name, vnode) {
// 动画时间应该是数字类型
if (typeof val !== 'number') {
warn(
"<transition> explicit " + name + " duration is not a valid number - " +
"got " + (JSON.stringify(val)) + ".",
vnode.context
);
} else if (isNaN(val)) {
warn(
"<transition> explicit " + name + " duration is NaN - " +
'the duration expression might be incorrect.',
vnode.context
);
}
}
// 是否是合法的动画时间
function isValidDuration (val) {
return typeof val === 'number' && !isNaN(val)
}
/**
* Normalize a transition hook's argument length. The hook may be:
* - a merged hook (invoker) with the original in .fns
* - a wrapped component method (check ._length)
* - a plain function (.length)
*/
//获取生命周期中函数的传参长度
function getHookArgumentsLength (fn) {
if (isUndef(fn)) {
return false
}
var invokerFns = fn.fns;
if (isDef(invokerFns)) {
// invoker
return getHookArgumentsLength(
Array.isArray(invokerFns)
? invokerFns[0]
: invokerFns
)
} else {
return (fn._length || fn.length) > 1
}
}
// 判断是否影藏,不是进入enter方法
function _enter (_, vnode) {
if (vnode.data.show !== true) {
enter(vnode);
}
}
// 设置transition的几个方法
var transition = inBrowser ? {
create: _enter,
activate: _enter,
// 动画移除的时候就是执行回调函数
remove: function remove$$1 (vnode, rm) {
/* istanbul ignore else */
if (vnode.data.show !== true) {
leave(vnode, rm);
} else {
rm();
}
}
} : {}
var platformModules = [
attrs,
klass,
events,
domProps,
style,
transition
]
/* */
// the directive module should be applied last, after all
// built-in modules have been applied.
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
/**
* Not type checking this file because flow doesn't like attaching
* properties to Elements.
*/
/* istanbul ignore if */
if (isIE9) {
// http://www.matts411.com/post/internet-explorer-9-oninput/
document.addEventListener('selectionchange', function () {
var el = document.activeElement;
if (el && el.vmodel) {
trigger(el, 'input');
}
});
}
var directive = {
inserted: function inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', function () {
directive.componentUpdated(el, binding, vnode);
});
} else {
setSelected(el, binding, vnode.context);
}
el._vOptions = [].map.call(el.options, getValue);
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers;
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart);
el.addEventListener('compositionend', onCompositionEnd);
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd);
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true;
}
}
}
},
componentUpdated: function componentUpdated (el, binding, vnode) {
if (vnode.tag === 'select') {
setSelected(el, binding, vnode.context);
// in case the options rendered by v-for have changed,
// it's possible that the value is out-of-sync with the rendered options.
// detect such cases and filter out values that no longer has a matching
// option in the DOM.
var prevOptions = el._vOptions;
var curOptions = el._vOptions = [].map.call(el.options, getValue);
if (curOptions.some(function (o, i) { return !looseEqual(o, prevOptions[i]); })) {
// trigger change event if
// no matching option found for at least one value
var needReset = el.multiple
? binding.value.some(function (v) { return hasNoMatchingOption(v, curOptions); })
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions);
if (needReset) {
trigger(el, 'change');
}
}
}
}
};
function setSelected (el, binding, vm) {
actuallySetSelected(el, binding, vm);
/* istanbul ignore if */
if (isIE || isEdge) {
setTimeout(function () {
actuallySetSelected(el, binding, vm);
}, 0);
}
}
function actuallySetSelected (el, binding, vm) {
var value = binding.value;
var isMultiple = el.multiple;
if (isMultiple && !Array.isArray(value)) {
"development" !== 'production' && warn(
"<select multiple v-model=\"" + (binding.expression) + "\"> " +
"expects an Array value for its binding, but got " + (Object.prototype.toString.call(value).slice(8, -1)),
vm
);
return
}
var selected, option;
for (var i = 0, l = el.options.length; i < l; i++) {
option = el.options[i];
if (isMultiple) {
selected = looseIndexOf(value, getValue(option)) > -1;
if (option.selected !== selected) {
option.selected = selected;
}
} else {
if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) {
el.selectedIndex = i;
}
return
}
}
}
if (!isMultiple) {
el.selectedIndex = -1;
}
}
// select options移除相同的
function hasNoMatchingOption (value, options) {
return options.every(function (o) { return !looseEqual(o, value); })
}
// 获取value
function getValue (option) {
return '_value' in option
? option._value
: option.value
}
// input事件开始
function onCompositionStart (e) {
e.target.composing = true;
}
// input事件结束
function onCompositionEnd (e) {
// prevent triggering an input event for no reason
if (!e.target.composing) { return }
e.target.composing = false;
trigger(e.target, 'input');
}
// 模拟事件方法
function trigger (el, type) {
var e = document.createEvent('HTMLEvents');
e.initEvent(type, true, true);
el.dispatchEvent(e);
}
/* */
// recursively search for possible transition defined inside the component root
function locateNode (vnode) {
return vnode.componentInstance && (!vnode.data || !vnode.data.transition)
? locateNode(vnode.componentInstance._vnode)
: vnode
}
// 元素开始展示出来的方法
var show = {
bind: function bind (el, ref, vnode) {
var value = ref.value;
vnode = locateNode(vnode);
var transition$$1 = vnode.data && vnode.data.transition;
var originalDisplay = el.__vOriginalDisplay =
el.style.display === 'none' ? '' : el.style.display;
if (value && transition$$1) {
vnode.data.show = true;
enter(vnode, function () {
el.style.display = originalDisplay;
});
} else {
el.style.display = value ? originalDisplay : 'none';
}
},
update: function update (el, ref, vnode) {
var value = ref.value;
var oldValue = ref.oldValue;
/* istanbul ignore if */
if (!value === !oldValue) { return }
vnode = locateNode(vnode);
var transition$$1 = vnode.data && vnode.data.transition;
if (transition$$1) {
vnode.data.show = true;
if (value) {
enter(vnode, function () {
el.style.display = el.__vOriginalDisplay;
});
} else {
leave(vnode, function () {
el.style.display = 'none';
});
}
} else {
el.style.display = value ? el.__vOriginalDisplay : 'none';
}
},
unbind: function unbind (
el,
binding,
vnode,
oldVnode,
isDestroy
) {
if (!isDestroy) {
el.style.display = el.__vOriginalDisplay;
}
}
}
var platformDirectives = {
model: directive,
show: show
}
/* */
// Provides transition support for a single element/component.
// supports transition mode (out-in / in-out)
// 动画的props
var transitionProps = {
name: String,
appear: Boolean,
css: Boolean,
mode: String,
type: String,
enterClass: String,
leaveClass: String,
enterToClass: String,
leaveToClass: String,
enterActiveClass: String,
leaveActiveClass: String,
appearClass: String,
appearActiveClass: String,
appearToClass: String,
duration: [Number, String, Object]
};
// in case the child is also an abstract component, e.g. <keep-alive>
// we want to recursively retrieve the real component to be rendered
// 获取子组件
function getRealChild (vnode) {
var compOptions = vnode && vnode.componentOptions;
if (compOptions && compOptions.Ctor.options.abstract) {
return getRealChild(getFirstComponentChild(compOptions.children))
} else {
return vnode
}
}
function extractTransitionData (comp) {
var data = {};
var options = comp.$options;
// props
for (var key in options.propsData) {
data[key] = comp[key];
}
// events.
// extract listeners and pass them directly to the transition methods
var listeners = options._parentListeners;
for (var key$1 in listeners) {
data[camelize(key$1)] = listeners[key$1];
}
return data
}
// 是否是使用了缓存组件
function placeholder (h, rawChild) {
if (/\d-keep-alive$/.test(rawChild.tag)) {
return h('keep-alive', {
props: rawChild.componentOptions.propsData
})
}
}
// 父组将是否有动画
function hasParentTransition (vnode) {
while ((vnode = vnode.parent)) {
if (vnode.data.transition) {
return true
}
}
}
// child是否相同
function isSameChild (child, oldChild) {
return oldChild.key === child.key && oldChild.tag === child.tag
}
var Transition = {
name: 'transition',
props: transitionProps,
abstract: true,
render: function render (h) {
var this$1 = this;
var children = this.$slots.default;
if (!children) {
return
}
// filter out text nodes (possible whitespaces)
children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); });
/* istanbul ignore if */
if (!children.length) {
return
}
// warn multiple elements
if ("development" !== 'production' && children.length > 1) {
warn(
'<transition> can only be used on a single element. Use ' +
'<transition-group> for lists.',
this.$parent
);
}
var mode = this.mode;
// warn invalid mode
if ("development" !== 'production' &&
mode && mode !== 'in-out' && mode !== 'out-in'
) {
warn(
'invalid <transition> mode: ' + mode,
this.$parent
);
}
var rawChild = children[0];
// if this is a component root node and the component's
// parent container node also has transition, skip.
if (hasParentTransition(this.$vnode)) {
return rawChild
}
// apply transition data to child
// use getRealChild() to ignore abstract components e.g. keep-alive
var child = getRealChild(rawChild);
/* istanbul ignore if */
if (!child) {
return rawChild
}
if (this._leaving) {
return placeholder(h, rawChild)
}
// ensure a key that is unique to the vnode type and to this transition
// component instance. This key will be used to remove pending leaving nodes
// during entering.
var id = "__transition-" + (this._uid) + "-";
child.key = child.key == null
? child.isComment
? id + 'comment'
: id + child.tag
: isPrimitive(child.key)
? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
: child.key;
var data = (child.data || (child.data = {})).transition = extractTransitionData(this);
var oldRawChild = this._vnode;
var oldChild = getRealChild(oldRawChild);
// mark v-show
// so that the transition module can hand over the control to the directive
if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) {
child.data.show = true;
}
if (
oldChild &&
oldChild.data &&
!isSameChild(child, oldChild) &&
!isAsyncPlaceholder(oldChild) &&
// #6687 component root is a comment node
!(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
) {
// replace old child transition data with fresh one
// important for dynamic transitions!
var oldData = oldChild.data.transition = extend({}, data);
// handle transition mode
if (mode === 'out-in') {
// return placeholder node and queue update when leave finishes
this._leaving = true;
mergeVNodeHook(oldData, 'afterLeave', function () {
this$1._leaving = false;
this$1.$forceUpdate();
});
return placeholder(h, rawChild)
} else if (mode === 'in-out') {
if (isAsyncPlaceholder(child)) {
return oldRawChild
}
var delayedLeave;
var performLeave = function () { delayedLeave(); };
mergeVNodeHook(data, 'afterEnter', performLeave);
mergeVNodeHook(data, 'enterCancelled', performLeave);
mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });
}
}
return rawChild
}
}
/* */
// Provides transition support for list items.
// supports move transitions using the FLIP technique.
// Because the vdom's children update algorithm is "unstable" - i.e.
// it doesn't guarantee the relative positioning of removed elements,
// we force transition-group to update its children into two passes:
// in the first pass, we remove all nodes that need to be removed,
// triggering their leaving transition; in the second pass, we insert/move
// into the final desired state. This way in the second pass removed
// nodes will remain where they should be.
var props = extend({
tag: String,
moveClass: String
}, transitionProps);
delete props.mode;
// transition-group
var TransitionGroup = {
props: props,
render: function render (h) {
var tag = this.tag || this.$vnode.data.tag || 'span';
var map = Object.create(null);
var prevChildren = this.prevChildren = this.children;
var rawChildren = this.$slots.default || [];
var children = this.children = [];
var transitionData = extractTransitionData(this);
for (var i = 0; i < rawChildren.length; i++) {
var c = rawChildren[i];
if (c.tag) {
if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
children.push(c);
map[c.key] = c
;(c.data || (c.data = {})).transition = transitionData;
} else {
var opts = c.componentOptions;
var name = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag;
warn(("<transition-group> children must be keyed: <" + name + ">"));
}
}
}
if (prevChildren) {
var kept = [];
var removed = [];
for (var i$1 = 0; i$1 < prevChildren.length; i$1++) {
var c$1 = prevChildren[i$1];
c$1.data.transition = transitionData;
c$1.data.pos = c$1.elm.getBoundingClientRect();
if (map[c$1.key]) {
kept.push(c$1);
} else {
removed.push(c$1);
}
}
this.kept = h(tag, null, kept);
this.removed = removed;
}
return h(tag, null, children)
},
beforeUpdate: function beforeUpdate () {
// force removing pass
this.__patch__(
this._vnode,
this.kept,
false, // hydrating
true // removeOnly (!important, avoids unnecessary moves)
);
this._vnode = this.kept;
},
updated: function updated () {
var children = this.prevChildren;
var moveClass = this.moveClass || ((this.name || 'v') + '-move');
if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
return
}
// we divide the work into three loops to avoid mixing DOM reads and writes
// in each iteration - which helps prevent layout thrashing.
children.forEach(callPendingCbs);
children.forEach(recordPosition);
children.forEach(applyTranslation);
// force reflow to put everything in position
// assign to this to avoid being removed in tree-shaking
// $flow-disable-line
this._reflow = document.body.offsetHeight;
children.forEach(function (c) {
if (c.data.moved) {
var el = c.elm;
var s = el.style;
addTransitionClass(el, moveClass);
s.transform = s.WebkitTransform = s.transitionDuration = '';
el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
if (!e || /transform$/.test(e.propertyName)) {
el.removeEventListener(transitionEndEvent, cb);
el._moveCb = null;
removeTransitionClass(el, moveClass);
}
});
}
});
},
methods: {
hasMove: function hasMove (el, moveClass) {
/* istanbul ignore if */
if (!hasTransition) {
return false
}
/* istanbul ignore if */
if (this._hasMove) {
return this._hasMove
}
// Detect whether an element with the move class applied has
// CSS transitions. Since the element may be inside an entering
// transition at this very moment, we make a clone of it and remove
// all other transition classes applied to ensure only the move class
// is applied.
var clone = el.cloneNode();
if (el._transitionClasses) {
el._transitionClasses.forEach(function (cls) { removeClass(clone, cls); });
}
addClass(clone, moveClass);
clone.style.display = 'none';
this.$el.appendChild(clone);
var info = getTransitionInfo(clone);
this.$el.removeChild(clone);
return (this._hasMove = info.hasTransform)
}
}
}