diff options
Diffstat (limited to 'node_modules/vue/src/platforms')
91 files changed, 5122 insertions, 0 deletions
diff --git a/node_modules/vue/src/platforms/web/compiler/directives/html.js b/node_modules/vue/src/platforms/web/compiler/directives/html.js new file mode 100644 index 00000000..58569d8e --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/html.js @@ -0,0 +1,9 @@ +/* @flow */ + +import { addProp } from 'compiler/helpers' + +export default function html (el: ASTElement, dir: ASTDirective) { + if (dir.value) { + addProp(el, 'innerHTML', `_s(${dir.value})`) + } +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/index.js b/node_modules/vue/src/platforms/web/compiler/directives/index.js new file mode 100644 index 00000000..0955b51e --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/index.js @@ -0,0 +1,9 @@ +import model from './model' +import text from './text' +import html from './html' + +export default { + model, + text, + html +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/model.js b/node_modules/vue/src/platforms/web/compiler/directives/model.js new file mode 100644 index 00000000..126f0c7d --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/model.js @@ -0,0 +1,172 @@ +/* @flow */ + +import config from 'core/config' +import { addHandler, addProp, getBindingAttr } from 'compiler/helpers' +import { genComponentModel, genAssignmentCode } from 'compiler/directives/model' + +let warn + +// in some cases, the event used has to be determined at runtime +// so we used some reserved tokens during compile. +export const RANGE_TOKEN = '__r' +export const CHECKBOX_RADIO_TOKEN = '__c' + +export default function model ( + el: ASTElement, + dir: ASTDirective, + _warn: Function +): ?boolean { + warn = _warn + const value = dir.value + const modifiers = dir.modifiers + const tag = el.tag + const type = el.attrsMap.type + + if (process.env.NODE_ENV !== 'production') { + // inputs with type="file" are read only and setting the input's + // value will throw an error. + if (tag === 'input' && type === 'file') { + warn( + `<${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 if (process.env.NODE_ENV !== 'production') { + warn( + `<${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 +} + +function genCheckboxModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + const number = modifiers && modifiers.number + const valueBinding = getBindingAttr(el, 'value') || 'null' + const trueValueBinding = getBindingAttr(el, 'true-value') || 'true' + const 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 + ) +} + +function genRadioModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + const number = modifiers && modifiers.number + let 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: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + const number = modifiers && modifiers.number + const 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'}})` + + const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]' + let code = `var $$selectedVal = ${selectedVal};` + code = `${code} ${genAssignmentCode(value, assignment)}` + addHandler(el, 'change', code, null, true) +} + +function genDefaultModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +): ?boolean { + const type = el.attrsMap.type + + // warn if v-bind:value conflicts with v-model + // except for inputs with v-bind:type + if (process.env.NODE_ENV !== 'production') { + const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value'] + const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'] + if (value && !typeBinding) { + const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value' + warn( + `${binding}="${value}" conflicts with v-model on the same element ` + + 'because the latter already expands to a value binding internally' + ) + } + } + + const { lazy, number, trim } = modifiers || {} + const needCompositionGuard = !lazy && type !== 'range' + const event = lazy + ? 'change' + : type === 'range' + ? RANGE_TOKEN + : 'input' + + let valueExpression = '$event.target.value' + if (trim) { + valueExpression = `$event.target.value.trim()` + } + if (number) { + valueExpression = `_n(${valueExpression})` + } + + let 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()') + } +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/text.js b/node_modules/vue/src/platforms/web/compiler/directives/text.js new file mode 100644 index 00000000..1af914f8 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/text.js @@ -0,0 +1,9 @@ +/* @flow */ + +import { addProp } from 'compiler/helpers' + +export default function text (el: ASTElement, dir: ASTDirective) { + if (dir.value) { + addProp(el, 'textContent', `_s(${dir.value})`) + } +} diff --git a/node_modules/vue/src/platforms/web/compiler/index.js b/node_modules/vue/src/platforms/web/compiler/index.js new file mode 100644 index 00000000..7ace0936 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/index.js @@ -0,0 +1,8 @@ +/* @flow */ + +import { baseOptions } from './options' +import { createCompiler } from 'compiler/index' + +const { compile, compileToFunctions } = createCompiler(baseOptions) + +export { compile, compileToFunctions } diff --git a/node_modules/vue/src/platforms/web/compiler/modules/class.js b/node_modules/vue/src/platforms/web/compiler/modules/class.js new file mode 100644 index 00000000..ffdca250 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/class.js @@ -0,0 +1,48 @@ +/* @flow */ + +import { parseText } from 'compiler/parser/text-parser' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticClass = getAndRemoveAttr(el, 'class') + if (process.env.NODE_ENV !== 'production' && staticClass) { + const res = parseText(staticClass, options.delimiters) + if (res) { + warn( + `class="${staticClass}": ` + + 'Interpolation inside attributes has been removed. ' + + 'Use v-bind or the colon shorthand instead. For example, ' + + 'instead of <div class="{{ val }}">, use <div :class="val">.' + ) + } + } + if (staticClass) { + el.staticClass = JSON.stringify(staticClass) + } + const classBinding = getBindingAttr(el, 'class', false /* getStatic */) + if (classBinding) { + el.classBinding = classBinding + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticClass) { + data += `staticClass:${el.staticClass},` + } + if (el.classBinding) { + data += `class:${el.classBinding},` + } + return data +} + +export default { + staticKeys: ['staticClass'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/web/compiler/modules/index.js b/node_modules/vue/src/platforms/web/compiler/modules/index.js new file mode 100644 index 00000000..29114a53 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/index.js @@ -0,0 +1,9 @@ +import klass from './class' +import style from './style' +import model from './model' + +export default [ + klass, + style, + model +] diff --git a/node_modules/vue/src/platforms/web/compiler/modules/model.js b/node_modules/vue/src/platforms/web/compiler/modules/model.js new file mode 100644 index 00000000..8d784fe4 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/model.js @@ -0,0 +1,94 @@ +/* @flow */ + +/** + * Expand input[v-model] with dyanmic type bindings into v-if-else chains + * Turn this: + * <input v-model="data[type]" :type="type"> + * into this: + * <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]"> + * <input v-else-if="type === 'radio'" type="radio" v-model="data[type]"> + * <input v-else :type="type" v-model="data[type]"> + */ + +import { + addRawAttr, + getBindingAttr, + getAndRemoveAttr +} from 'compiler/helpers' + +import { + processFor, + processElement, + addIfCondition, + createASTElement +} from 'compiler/parser/index' + +function preTransformNode (el: ASTElement, options: CompilerOptions) { + if (el.tag === 'input') { + const map = el.attrsMap + if (!map['v-model']) { + return + } + + let typeBinding + if (map[':type'] || map['v-bind:type']) { + typeBinding = getBindingAttr(el, 'type') + } + if (!map.type && !typeBinding && map['v-bind']) { + typeBinding = `(${map['v-bind']}).type` + } + + if (typeBinding) { + const ifCondition = getAndRemoveAttr(el, 'v-if', true) + const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : `` + const hasElse = getAndRemoveAttr(el, 'v-else', true) != null + const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true) + // 1. checkbox + const branch0 = cloneASTElement(el) + // process for on the main node + processFor(branch0) + addRawAttr(branch0, 'type', 'checkbox') + processElement(branch0, options) + branch0.processed = true // prevent it from double-processed + branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra + addIfCondition(branch0, { + exp: branch0.if, + block: branch0 + }) + // 2. add radio else-if condition + const branch1 = cloneASTElement(el) + getAndRemoveAttr(branch1, 'v-for', true) + addRawAttr(branch1, 'type', 'radio') + processElement(branch1, options) + addIfCondition(branch0, { + exp: `(${typeBinding})==='radio'` + ifConditionExtra, + block: branch1 + }) + // 3. other + const branch2 = cloneASTElement(el) + getAndRemoveAttr(branch2, 'v-for', true) + addRawAttr(branch2, ':type', typeBinding) + processElement(branch2, options) + addIfCondition(branch0, { + exp: ifCondition, + block: branch2 + }) + + if (hasElse) { + branch0.else = true + } else if (elseIfCondition) { + branch0.elseif = elseIfCondition + } + + return branch0 + } + } +} + +function cloneASTElement (el) { + return createASTElement(el.tag, el.attrsList.slice(), el.parent) +} + +export default { + preTransformNode +} diff --git a/node_modules/vue/src/platforms/web/compiler/modules/style.js b/node_modules/vue/src/platforms/web/compiler/modules/style.js new file mode 100644 index 00000000..2722b92b --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/style.js @@ -0,0 +1,51 @@ +/* @flow */ + +import { parseText } from 'compiler/parser/text-parser' +import { parseStyleText } from 'web/util/style' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticStyle = getAndRemoveAttr(el, 'style') + if (staticStyle) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production') { + const res = parseText(staticStyle, options.delimiters) + if (res) { + warn( + `style="${staticStyle}": ` + + 'Interpolation inside attributes has been removed. ' + + 'Use v-bind or the colon shorthand instead. For example, ' + + 'instead of <div style="{{ val }}">, use <div :style="val">.' + ) + } + } + el.staticStyle = JSON.stringify(parseStyleText(staticStyle)) + } + + const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) + if (styleBinding) { + el.styleBinding = styleBinding + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticStyle) { + data += `staticStyle:${el.staticStyle},` + } + if (el.styleBinding) { + data += `style:(${el.styleBinding}),` + } + return data +} + +export default { + staticKeys: ['staticStyle'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/web/compiler/options.js b/node_modules/vue/src/platforms/web/compiler/options.js new file mode 100644 index 00000000..70c0c488 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/options.js @@ -0,0 +1,26 @@ +/* @flow */ + +import { + isPreTag, + mustUseProp, + isReservedTag, + getTagNamespace +} from '../util/index' + +import modules from './modules/index' +import directives from './directives/index' +import { genStaticKeys } from 'shared/util' +import { isUnaryTag, canBeLeftOpenTag } from './util' + +export const baseOptions: CompilerOptions = { + expectHTML: true, + modules, + directives, + isPreTag, + isUnaryTag, + mustUseProp, + canBeLeftOpenTag, + isReservedTag, + getTagNamespace, + staticKeys: genStaticKeys(modules) +} diff --git a/node_modules/vue/src/platforms/web/compiler/util.js b/node_modules/vue/src/platforms/web/compiler/util.js new file mode 100644 index 00000000..7d9394d3 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/util.js @@ -0,0 +1,24 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +export const isUnaryTag = makeMap( + 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' + + 'link,meta,param,source,track,wbr' +) + +// Elements that you can, intentionally, leave open +// (and which close themselves) +export const canBeLeftOpenTag = makeMap( + 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' +) + +// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3 +// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content +export const isNonPhrasingTag = makeMap( + 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + + 'title,tr,track' +) diff --git a/node_modules/vue/src/platforms/web/entry-compiler.js b/node_modules/vue/src/platforms/web/entry-compiler.js new file mode 100644 index 00000000..ee63476e --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-compiler.js @@ -0,0 +1,5 @@ +/* @flow */ + +export { parseComponent } from 'sfc/parser' +export { compile, compileToFunctions } from './compiler/index' +export { ssrCompile, ssrCompileToFunctions } from './server/compiler' diff --git a/node_modules/vue/src/platforms/web/entry-runtime-with-compiler.js b/node_modules/vue/src/platforms/web/entry-runtime-with-compiler.js new file mode 100644 index 00000000..1ebc102d --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-runtime-with-compiler.js @@ -0,0 +1,100 @@ +/* @flow */ + +import config from 'core/config' +import { warn, cached } from 'core/util/index' +import { mark, measure } from 'core/util/perf' + +import Vue from './runtime/index' +import { query } from './util/index' +import { compileToFunctions } from './compiler/index' +import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' + +const idToTemplate = cached(id => { + const el = query(id) + return el && el.innerHTML +}) + +const mount = Vue.prototype.$mount +Vue.prototype.$mount = function ( + el?: string | Element, + hydrating?: boolean +): Component { + el = el && query(el) + + /* istanbul ignore if */ + if (el === document.body || el === document.documentElement) { + process.env.NODE_ENV !== 'production' && warn( + `Do not mount Vue to <html> or <body> - mount to normal elements instead.` + ) + return this + } + + const options = this.$options + // resolve template/el and convert to render function + if (!options.render) { + let template = options.template + if (template) { + if (typeof template === 'string') { + if (template.charAt(0) === '#') { + template = idToTemplate(template) + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && !template) { + warn( + `Template element not found or is empty: ${options.template}`, + this + ) + } + } + } else if (template.nodeType) { + template = template.innerHTML + } else { + if (process.env.NODE_ENV !== 'production') { + warn('invalid template option:' + template, this) + } + return this + } + } else if (el) { + template = getOuterHTML(el) + } + if (template) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + mark('compile') + } + + const { render, staticRenderFns } = compileToFunctions(template, { + shouldDecodeNewlines, + shouldDecodeNewlinesForHref, + delimiters: options.delimiters, + comments: options.comments + }, this) + options.render = render + options.staticRenderFns = staticRenderFns + + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + mark('compile end') + measure(`vue ${this._name} compile`, 'compile', 'compile end') + } + } + } + return mount.call(this, el, hydrating) +} + +/** + * Get outerHTML of elements, taking care + * of SVG elements in IE as well. + */ +function getOuterHTML (el: Element): string { + if (el.outerHTML) { + return el.outerHTML + } else { + const container = document.createElement('div') + container.appendChild(el.cloneNode(true)) + return container.innerHTML + } +} + +Vue.compile = compileToFunctions + +export default Vue diff --git a/node_modules/vue/src/platforms/web/entry-runtime.js b/node_modules/vue/src/platforms/web/entry-runtime.js new file mode 100644 index 00000000..27c97ff0 --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-runtime.js @@ -0,0 +1,5 @@ +/* @flow */ + +import Vue from './runtime/index' + +export default Vue diff --git a/node_modules/vue/src/platforms/web/entry-server-basic-renderer.js b/node_modules/vue/src/platforms/web/entry-server-basic-renderer.js new file mode 100644 index 00000000..c4b973bd --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-server-basic-renderer.js @@ -0,0 +1,13 @@ +/* @flow */ + +import modules from './server/modules/index' +import directives from './server/directives/index' +import { isUnaryTag, canBeLeftOpenTag } from './compiler/util' +import { createBasicRenderer } from 'server/create-basic-renderer' + +export default createBasicRenderer({ + modules, + directives, + isUnaryTag, + canBeLeftOpenTag +}) diff --git a/node_modules/vue/src/platforms/web/entry-server-renderer.js b/node_modules/vue/src/platforms/web/entry-server-renderer.js new file mode 100644 index 00000000..f61f601b --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-server-renderer.js @@ -0,0 +1,27 @@ +/* @flow */ + +process.env.VUE_ENV = 'server' + +import { extend } from 'shared/util' +import modules from './server/modules/index' +import baseDirectives from './server/directives/index' +import { isUnaryTag, canBeLeftOpenTag } from './compiler/util' + +import { createRenderer as _createRenderer } from 'server/create-renderer' +import { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer' + +export function createRenderer (options?: Object = {}): { + renderToString: Function, + renderToStream: Function +} { + return _createRenderer(extend(extend({}, options), { + isUnaryTag, + canBeLeftOpenTag, + modules, + // user can provide server-side implementations for custom directives + // when creating the renderer. + directives: extend(baseDirectives, options.directives) + })) +} + +export const createBundleRenderer = createBundleRendererCreator(createRenderer) diff --git a/node_modules/vue/src/platforms/web/runtime/class-util.js b/node_modules/vue/src/platforms/web/runtime/class-util.js new file mode 100644 index 00000000..709526c2 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/class-util.js @@ -0,0 +1,61 @@ +/* @flow */ + +/** + * Add class with compatibility for SVG since classList is not supported on + * SVG elements in IE + */ +export function addClass (el: HTMLElement, cls: ?string) { + /* istanbul ignore if */ + if (!cls || !(cls = cls.trim())) { + return + } + + /* istanbul ignore else */ + if (el.classList) { + if (cls.indexOf(' ') > -1) { + cls.split(/\s+/).forEach(c => el.classList.add(c)) + } else { + el.classList.add(cls) + } + } else { + const 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 + */ +export function removeClass (el: HTMLElement, cls: ?string) { + /* istanbul ignore if */ + if (!cls || !(cls = cls.trim())) { + return + } + + /* istanbul ignore else */ + if (el.classList) { + if (cls.indexOf(' ') > -1) { + cls.split(/\s+/).forEach(c => el.classList.remove(c)) + } else { + el.classList.remove(cls) + } + if (!el.classList.length) { + el.removeAttribute('class') + } + } else { + let cur = ` ${el.getAttribute('class') || ''} ` + const tar = ' ' + cls + ' ' + while (cur.indexOf(tar) >= 0) { + cur = cur.replace(tar, ' ') + } + cur = cur.trim() + if (cur) { + el.setAttribute('class', cur) + } else { + el.removeAttribute('class') + } + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/components/index.js b/node_modules/vue/src/platforms/web/runtime/components/index.js new file mode 100644 index 00000000..6bfe5780 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/components/index.js @@ -0,0 +1,7 @@ +import Transition from './transition' +import TransitionGroup from './transition-group' + +export default { + Transition, + TransitionGroup +} diff --git a/node_modules/vue/src/platforms/web/runtime/components/transition-group.js b/node_modules/vue/src/platforms/web/runtime/components/transition-group.js new file mode 100644 index 00000000..219ff01c --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/components/transition-group.js @@ -0,0 +1,180 @@ +/* @flow */ + +// 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. + +import { warn, extend } from 'core/util/index' +import { addClass, removeClass } from '../class-util' +import { transitionProps, extractTransitionData } from './transition' + +import { + hasTransition, + getTransitionInfo, + transitionEndEvent, + addTransitionClass, + removeTransitionClass +} from '../transition-util' + +const props = extend({ + tag: String, + moveClass: String +}, transitionProps) + +delete props.mode + +export default { + props, + + render (h: Function) { + const tag: string = this.tag || this.$vnode.data.tag || 'span' + const map: Object = Object.create(null) + const prevChildren: Array<VNode> = this.prevChildren = this.children + const rawChildren: Array<VNode> = this.$slots.default || [] + const children: Array<VNode> = this.children = [] + const transitionData: Object = extractTransitionData(this) + + for (let i = 0; i < rawChildren.length; i++) { + const c: VNode = 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 if (process.env.NODE_ENV !== 'production') { + const opts: ?VNodeComponentOptions = c.componentOptions + const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag + warn(`<transition-group> children must be keyed: <${name}>`) + } + } + } + + if (prevChildren) { + const kept: Array<VNode> = [] + const removed: Array<VNode> = [] + for (let i = 0; i < prevChildren.length; i++) { + const c: VNode = prevChildren[i] + c.data.transition = transitionData + c.data.pos = c.elm.getBoundingClientRect() + if (map[c.key]) { + kept.push(c) + } else { + removed.push(c) + } + } + this.kept = h(tag, null, kept) + this.removed = removed + } + + return h(tag, null, children) + }, + + beforeUpdate () { + // force removing pass + this.__patch__( + this._vnode, + this.kept, + false, // hydrating + true // removeOnly (!important, avoids unnecessary moves) + ) + this._vnode = this.kept + }, + + updated () { + const children: Array<VNode> = this.prevChildren + const moveClass: string = 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((c: VNode) => { + if (c.data.moved) { + var el: any = c.elm + var s: any = 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 (el: any, moveClass: string): boolean { + /* 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. + const clone: HTMLElement = el.cloneNode() + if (el._transitionClasses) { + el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) }) + } + addClass(clone, moveClass) + clone.style.display = 'none' + this.$el.appendChild(clone) + const info: Object = getTransitionInfo(clone) + this.$el.removeChild(clone) + return (this._hasMove = info.hasTransform) + } + } +} + +function callPendingCbs (c: VNode) { + /* istanbul ignore if */ + if (c.elm._moveCb) { + c.elm._moveCb() + } + /* istanbul ignore if */ + if (c.elm._enterCb) { + c.elm._enterCb() + } +} + +function recordPosition (c: VNode) { + c.data.newPos = c.elm.getBoundingClientRect() +} + +function applyTranslation (c: VNode) { + const oldPos = c.data.pos + const newPos = c.data.newPos + const dx = oldPos.left - newPos.left + const dy = oldPos.top - newPos.top + if (dx || dy) { + c.data.moved = true + const s = c.elm.style + s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)` + s.transitionDuration = '0s' + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/components/transition.js b/node_modules/vue/src/platforms/web/runtime/components/transition.js new file mode 100644 index 00000000..6111d8cb --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/components/transition.js @@ -0,0 +1,194 @@ +/* @flow */ + +// Provides transition support for a single element/component. +// supports transition mode (out-in / in-out) + +import { warn } from 'core/util/index' +import { camelize, extend, isPrimitive } from 'shared/util' +import { + mergeVNodeHook, + isAsyncPlaceholder, + getFirstComponentChild +} from 'core/vdom/helpers/index' + +export const 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: ?VNode): ?VNode { + const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +export function extractTransitionData (comp: Component): Object { + const data = {} + const options: ComponentOptions = comp.$options + // props + for (const key in options.propsData) { + data[key] = comp[key] + } + // events. + // extract listeners and pass them directly to the transition methods + const listeners: ?Object = options._parentListeners + for (const key in listeners) { + data[camelize(key)] = listeners[key] + } + return data +} + +function placeholder (h: Function, rawChild: VNode): ?VNode { + if (/\d-keep-alive$/.test(rawChild.tag)) { + return h('keep-alive', { + props: rawChild.componentOptions.propsData + }) + } +} + +function hasParentTransition (vnode: VNode): ?boolean { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +function isSameChild (child: VNode, oldChild: VNode): boolean { + return oldChild.key === child.key && oldChild.tag === child.tag +} + +export default { + name: 'transition', + props: transitionProps, + abstract: true, + + render (h: Function) { + let children: any = this.$slots.default + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c)) + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (process.env.NODE_ENV !== 'production' && children.length > 1) { + warn( + '<transition> can only be used on a single element. Use ' + + '<transition-group> for lists.', + this.$parent + ) + } + + const mode: string = this.mode + + // warn invalid mode + if (process.env.NODE_ENV !== 'production' && + mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid <transition> mode: ' + mode, + this.$parent + ) + } + + const rawChild: VNode = 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 + const child: ?VNode = 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. + const id: string = `__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 + + const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this) + const oldRawChild: VNode = this._vnode + const oldChild: VNode = 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(d => 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! + const oldData: Object = 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', () => { + this._leaving = false + this.$forceUpdate() + }) + return placeholder(h, rawChild) + } else if (mode === 'in-out') { + if (isAsyncPlaceholder(child)) { + return oldRawChild + } + let delayedLeave + const performLeave = () => { delayedLeave() } + mergeVNodeHook(data, 'afterEnter', performLeave) + mergeVNodeHook(data, 'enterCancelled', performLeave) + mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave }) + } + } + + return rawChild + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/directives/index.js b/node_modules/vue/src/platforms/web/runtime/directives/index.js new file mode 100644 index 00000000..b673f11a --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/directives/index.js @@ -0,0 +1,7 @@ +import model from './model' +import show from './show' + +export default { + model, + show +} diff --git a/node_modules/vue/src/platforms/web/runtime/directives/model.js b/node_modules/vue/src/platforms/web/runtime/directives/model.js new file mode 100644 index 00000000..cba8ded0 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/directives/model.js @@ -0,0 +1,147 @@ +/** + * Not type checking this file because flow doesn't like attaching + * properties to Elements. + */ + +import { isTextInputType } from 'web/util/element' +import { looseEqual, looseIndexOf } from 'shared/util' +import { mergeVNodeHook } from 'core/vdom/helpers/index' +import { warn, isIE9, isIE, isEdge } from 'core/util/index' + +/* istanbul ignore if */ +if (isIE9) { + // http://www.matts411.com/post/internet-explorer-9-oninput/ + document.addEventListener('selectionchange', () => { + const el = document.activeElement + if (el && el.vmodel) { + trigger(el, 'input') + } + }) +} + +const directive = { + inserted (el, binding, vnode, oldVnode) { + if (vnode.tag === 'select') { + // #6903 + if (oldVnode.elm && !oldVnode.elm._vOptions) { + mergeVNodeHook(vnode, 'postpatch', () => { + 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 (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. + const prevOptions = el._vOptions + const curOptions = el._vOptions = [].map.call(el.options, getValue) + if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) { + // trigger change event if + // no matching option found for at least one value + const needReset = el.multiple + ? binding.value.some(v => 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(() => { + actuallySetSelected(el, binding, vm) + }, 0) + } +} + +function actuallySetSelected (el, binding, vm) { + const value = binding.value + const isMultiple = el.multiple + if (isMultiple && !Array.isArray(value)) { + process.env.NODE_ENV !== '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 + } + let selected, option + for (let 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 + } +} + +function hasNoMatchingOption (value, options) { + return options.every(o => !looseEqual(o, value)) +} + +function getValue (option) { + return '_value' in option + ? option._value + : option.value +} + +function onCompositionStart (e) { + e.target.composing = true +} + +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) { + const e = document.createEvent('HTMLEvents') + e.initEvent(type, true, true) + el.dispatchEvent(e) +} + +export default directive diff --git a/node_modules/vue/src/platforms/web/runtime/directives/show.js b/node_modules/vue/src/platforms/web/runtime/directives/show.js new file mode 100644 index 00000000..17fb5dec --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/directives/show.js @@ -0,0 +1,60 @@ +/* @flow */ + +import { enter, leave } from '../modules/transition' + +// recursively search for possible transition defined inside the component root +function locateNode (vnode: VNode): VNodeWithData { + return vnode.componentInstance && (!vnode.data || !vnode.data.transition) + ? locateNode(vnode.componentInstance._vnode) + : vnode +} + +export default { + bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) { + vnode = locateNode(vnode) + const transition = vnode.data && vnode.data.transition + const originalDisplay = el.__vOriginalDisplay = + el.style.display === 'none' ? '' : el.style.display + if (value && transition) { + vnode.data.show = true + enter(vnode, () => { + el.style.display = originalDisplay + }) + } else { + el.style.display = value ? originalDisplay : 'none' + } + }, + + update (el: any, { value, oldValue }: VNodeDirective, vnode: VNodeWithData) { + /* istanbul ignore if */ + if (!value === !oldValue) return + vnode = locateNode(vnode) + const transition = vnode.data && vnode.data.transition + if (transition) { + vnode.data.show = true + if (value) { + enter(vnode, () => { + el.style.display = el.__vOriginalDisplay + }) + } else { + leave(vnode, () => { + el.style.display = 'none' + }) + } + } else { + el.style.display = value ? el.__vOriginalDisplay : 'none' + } + }, + + unbind ( + el: any, + binding: VNodeDirective, + vnode: VNodeWithData, + oldVnode: VNodeWithData, + isDestroy: boolean + ) { + if (!isDestroy) { + el.style.display = el.__vOriginalDisplay + } + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/index.js b/node_modules/vue/src/platforms/web/runtime/index.js new file mode 100644 index 00000000..1283f521 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/index.js @@ -0,0 +1,77 @@ +/* @flow */ + +import Vue from 'core/index' +import config from 'core/config' +import { extend, noop } from 'shared/util' +import { mountComponent } from 'core/instance/lifecycle' +import { devtools, inBrowser, isChrome } from 'core/util/index' + +import { + query, + mustUseProp, + isReservedTag, + isReservedAttr, + getTagNamespace, + isUnknownElement +} from 'web/util/index' + +import { patch } from './patch' +import platformDirectives from './directives/index' +import platformComponents from './components/index' + +// install platform specific utils +Vue.config.mustUseProp = mustUseProp +Vue.config.isReservedTag = isReservedTag +Vue.config.isReservedAttr = isReservedAttr +Vue.config.getTagNamespace = getTagNamespace +Vue.config.isUnknownElement = isUnknownElement + +// install platform runtime directives & components +extend(Vue.options.directives, platformDirectives) +extend(Vue.options.components, platformComponents) + +// install platform patch function +Vue.prototype.__patch__ = inBrowser ? patch : noop + +// public mount method +Vue.prototype.$mount = function ( + el?: string | Element, + hydrating?: boolean +): Component { + el = el && inBrowser ? query(el) : undefined + return mountComponent(this, el, hydrating) +} + +// devtools global hook +/* istanbul ignore next */ +if (inBrowser) { + setTimeout(() => { + if (config.devtools) { + if (devtools) { + devtools.emit('init', Vue) + } else if ( + process.env.NODE_ENV !== 'production' && + process.env.NODE_ENV !== 'test' && + isChrome + ) { + console[console.info ? 'info' : 'log']( + 'Download the Vue Devtools extension for a better development experience:\n' + + 'https://github.com/vuejs/vue-devtools' + ) + } + } + if (process.env.NODE_ENV !== 'production' && + process.env.NODE_ENV !== 'test' && + config.productionTip !== false && + typeof console !== 'undefined' + ) { + console[console.info ? 'info' : 'log']( + `You are running Vue in development mode.\n` + + `Make sure to turn on production mode when deploying for production.\n` + + `See more tips at https://vuejs.org/guide/deployment.html` + ) + } + }, 0) +} + +export default Vue diff --git a/node_modules/vue/src/platforms/web/runtime/modules/attrs.js b/node_modules/vue/src/platforms/web/runtime/modules/attrs.js new file mode 100644 index 00000000..78ef28a1 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/attrs.js @@ -0,0 +1,118 @@ +/* @flow */ + +import { isIE, isIE9, isEdge } from 'core/util/env' + +import { + extend, + isDef, + isUndef +} from 'shared/util' + +import { + isXlink, + xlinkNS, + getXlinkProp, + isBooleanAttr, + isEnumeratedAttr, + isFalsyAttrValue +} from 'web/util/index' + +function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) { + const opts = vnode.componentOptions + if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) { + return + } + if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) { + return + } + let key, cur, old + const elm = vnode.elm + const oldAttrs = oldVnode.data.attrs || {} + let attrs: any = vnode.data.attrs || {} + // clone observed objects, as the user probably wants to mutate it + if (isDef(attrs.__ob__)) { + attrs = vnode.data.attrs = extend({}, attrs) + } + + for (key in attrs) { + cur = attrs[key] + old = oldAttrs[key] + if (old !== cur) { + setAttr(elm, key, cur) + } + } + // #4391: in IE9, setting type can reset value for input[type=radio] + // #6666: IE/Edge forces progress value down to 1 before setting a max + /* istanbul ignore if */ + if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { + setAttr(elm, 'value', attrs.value) + } + for (key in oldAttrs) { + if (isUndef(attrs[key])) { + if (isXlink(key)) { + elm.removeAttributeNS(xlinkNS, getXlinkProp(key)) + } else if (!isEnumeratedAttr(key)) { + elm.removeAttribute(key) + } + } + } +} + +function setAttr (el: Element, key: string, value: any) { + if (el.tagName.indexOf('-') > -1) { + baseSetAttr(el, key, value) + } else if (isBooleanAttr(key)) { + // set attribute for blank value + // e.g. <option disabled>Select one</option> + if (isFalsyAttrValue(value)) { + el.removeAttribute(key) + } else { + // technically allowfullscreen is a boolean attribute for <iframe>, + // but Flash expects a value of "true" when used on <embed> tag + value = key === 'allowfullscreen' && el.tagName === 'EMBED' + ? 'true' + : key + el.setAttribute(key, value) + } + } else if (isEnumeratedAttr(key)) { + el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true') + } else if (isXlink(key)) { + if (isFalsyAttrValue(value)) { + el.removeAttributeNS(xlinkNS, getXlinkProp(key)) + } else { + el.setAttributeNS(xlinkNS, key, value) + } + } else { + baseSetAttr(el, key, value) + } +} + +function baseSetAttr (el, key, value) { + if (isFalsyAttrValue(value)) { + el.removeAttribute(key) + } else { + // #7138: IE10 & 11 fires input event when setting placeholder on + // <textarea>... block the first input event and remove the blocker + // immediately. + /* istanbul ignore if */ + if ( + isIE && !isIE9 && + el.tagName === 'TEXTAREA' && + key === 'placeholder' && !el.__ieph + ) { + const blocker = e => { + e.stopImmediatePropagation() + el.removeEventListener('input', blocker) + } + el.addEventListener('input', blocker) + // $flow-disable-line + el.__ieph = true /* IE placeholder patched */ + } + el.setAttribute(key, value) + } +} + +export default { + create: updateAttrs, + update: updateAttrs +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/class.js b/node_modules/vue/src/platforms/web/runtime/modules/class.js new file mode 100644 index 00000000..29fd2c11 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/class.js @@ -0,0 +1,48 @@ +/* @flow */ + +import { + isDef, + isUndef +} from 'shared/util' + +import { + concat, + stringifyClass, + genClassForVnode +} from 'web/util/index' + +function updateClass (oldVnode: any, vnode: any) { + const el = vnode.elm + const data: VNodeData = vnode.data + const oldData: VNodeData = oldVnode.data + if ( + isUndef(data.staticClass) && + isUndef(data.class) && ( + isUndef(oldData) || ( + isUndef(oldData.staticClass) && + isUndef(oldData.class) + ) + ) + ) { + return + } + + let cls = genClassForVnode(vnode) + + // handle transition classes + const transitionClass = el._transitionClasses + if (isDef(transitionClass)) { + cls = concat(cls, stringifyClass(transitionClass)) + } + + // set the class + if (cls !== el._prevClass) { + el.setAttribute('class', cls) + el._prevClass = cls + } +} + +export default { + create: updateClass, + update: updateClass +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/dom-props.js b/node_modules/vue/src/platforms/web/runtime/modules/dom-props.js new file mode 100644 index 00000000..f3888aed --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/dom-props.js @@ -0,0 +1,95 @@ +/* @flow */ + +import { isDef, isUndef, extend, toNumber } from 'shared/util' + +function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) { + return + } + let key, cur + const elm: any = vnode.elm + const oldProps = oldVnode.data.domProps || {} + let 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 + const strCur = isUndef(cur) ? '' : String(cur) + if (shouldUpdateValue(elm, strCur)) { + elm.value = strCur + } + } else { + elm[key] = cur + } + } +} + +// check platforms/web/util/attrs.js acceptValue +type acceptValueElm = HTMLInputElement | HTMLSelectElement | HTMLOptionElement; + +function shouldUpdateValue (elm: acceptValueElm, checkVal: string): boolean { + return (!elm.composing && ( + elm.tagName === 'OPTION' || + isNotInFocusAndDirty(elm, checkVal) || + isDirtyWithModifiers(elm, checkVal) + )) +} + +function isNotInFocusAndDirty (elm: acceptValueElm, checkVal: string): boolean { + // return true when textbox (.number and .trim) loses focus and its value is + // not equal to the updated value + let 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: any, newVal: string): boolean { + const value = elm.value + const 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 +} + +export default { + create: updateDOMProps, + update: updateDOMProps +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/events.js b/node_modules/vue/src/platforms/web/runtime/modules/events.js new file mode 100644 index 00000000..1e71b667 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/events.js @@ -0,0 +1,87 @@ +/* @flow */ + +import { isDef, isUndef } from 'shared/util' +import { updateListeners } from 'core/vdom/helpers/index' +import { withMacroTask, isIE, supportsPassive } from 'core/util/index' +import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model' + +// 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. +function normalizeEvents (on) { + /* istanbul ignore if */ + if (isDef(on[RANGE_TOKEN])) { + // IE input[type=range] only supports `change` event + const 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] + } +} + +let target: any + +function createOnceHandler (handler, event, capture) { + const _target = target // save current target element in closure + return function onceHandler () { + const res = handler.apply(null, arguments) + if (res !== null) { + remove(event, onceHandler, capture, _target) + } + } +} + +function add ( + event: string, + handler: Function, + once: boolean, + capture: boolean, + passive: boolean +) { + handler = withMacroTask(handler) + if (once) handler = createOnceHandler(handler, event, capture) + target.addEventListener( + event, + handler, + supportsPassive + ? { capture, passive } + : capture + ) +} + +function remove ( + event: string, + handler: Function, + capture: boolean, + _target?: HTMLElement +) { + (_target || target).removeEventListener( + event, + handler._withTask || handler, + capture + ) +} + +function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) { + return + } + const on = vnode.data.on || {} + const oldOn = oldVnode.data.on || {} + target = vnode.elm + normalizeEvents(on) + updateListeners(on, oldOn, add, remove, vnode.context) + target = undefined +} + +export default { + create: updateDOMListeners, + update: updateDOMListeners +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/index.js b/node_modules/vue/src/platforms/web/runtime/modules/index.js new file mode 100644 index 00000000..9f6ad8f9 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/index.js @@ -0,0 +1,15 @@ +import attrs from './attrs' +import klass from './class' +import events from './events' +import domProps from './dom-props' +import style from './style' +import transition from './transition' + +export default [ + attrs, + klass, + events, + domProps, + style, + transition +] diff --git a/node_modules/vue/src/platforms/web/runtime/modules/style.js b/node_modules/vue/src/platforms/web/runtime/modules/style.js new file mode 100644 index 00000000..29e1c3c1 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/style.js @@ -0,0 +1,93 @@ +/* @flow */ + +import { getStyle, normalizeStyleBinding } from 'web/util/style' +import { cached, camelize, extend, isDef, isUndef } from 'shared/util' + +const cssVarRE = /^--/ +const importantRE = /\s*!important$/ +const setProp = (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 { + const 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 (let i = 0, len = val.length; i < len; i++) { + el.style[normalizedName] = val[i] + } + } else { + el.style[normalizedName] = val + } + } +} + +const vendorNames = ['Webkit', 'Moz', 'ms'] + +let emptyStyle +const normalize = cached(function (prop) { + emptyStyle = emptyStyle || document.createElement('div').style + prop = camelize(prop) + if (prop !== 'filter' && (prop in emptyStyle)) { + return prop + } + const capName = prop.charAt(0).toUpperCase() + prop.slice(1) + for (let i = 0; i < vendorNames.length; i++) { + const name = vendorNames[i] + capName + if (name in emptyStyle) { + return name + } + } +}) + +function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { + const data = vnode.data + const oldData = oldVnode.data + + if (isUndef(data.staticStyle) && isUndef(data.style) && + isUndef(oldData.staticStyle) && isUndef(oldData.style) + ) { + return + } + + let cur, name + const el: any = vnode.elm + const oldStaticStyle: any = oldData.staticStyle + const oldStyleBinding: any = oldData.normalizedStyle || oldData.style || {} + + // if static style exists, stylebinding already merged into it when doing normalizeStyleData + const oldStyle = oldStaticStyle || oldStyleBinding + + const 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 + + const 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) + } + } +} + +export default { + create: updateStyle, + update: updateStyle +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/transition.js b/node_modules/vue/src/platforms/web/runtime/modules/transition.js new file mode 100644 index 00000000..0796b5da --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/transition.js @@ -0,0 +1,343 @@ +/* @flow */ + +import { inBrowser, isIE9, warn } from 'core/util/index' +import { mergeVNodeHook } from 'core/vdom/helpers/index' +import { activeInstance } from 'core/instance/lifecycle' + +import { + once, + isDef, + isUndef, + isObject, + toNumber +} from 'shared/util' + +import { + nextFrame, + resolveTransition, + whenTransitionEnds, + addTransitionClass, + removeTransitionClass +} from '../transition-util' + +export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) { + const el: any = vnode.elm + + // call leave callback now + if (isDef(el._leaveCb)) { + el._leaveCb.cancelled = true + el._leaveCb() + } + + const data = resolveTransition(vnode.data.transition) + if (isUndef(data)) { + return + } + + /* istanbul ignore if */ + if (isDef(el._enterCb) || el.nodeType !== 1) { + return + } + + const { + css, + type, + enterClass, + enterToClass, + enterActiveClass, + appearClass, + appearToClass, + appearActiveClass, + beforeEnter, + enter, + afterEnter, + enterCancelled, + beforeAppear, + appear, + afterAppear, + appearCancelled, + duration + } = data + + // 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. + let context = activeInstance + let transitionNode = activeInstance.$vnode + while (transitionNode && transitionNode.parent) { + transitionNode = transitionNode.parent + context = transitionNode.context + } + + const isAppear = !context._isMounted || !vnode.isRootInsert + + if (isAppear && !appear && appear !== '') { + return + } + + const startClass = isAppear && appearClass + ? appearClass + : enterClass + const activeClass = isAppear && appearActiveClass + ? appearActiveClass + : enterActiveClass + const toClass = isAppear && appearToClass + ? appearToClass + : enterToClass + + const beforeEnterHook = isAppear + ? (beforeAppear || beforeEnter) + : beforeEnter + const enterHook = isAppear + ? (typeof appear === 'function' ? appear : enter) + : enter + const afterEnterHook = isAppear + ? (afterAppear || afterEnter) + : afterEnter + const enterCancelledHook = isAppear + ? (appearCancelled || enterCancelled) + : enterCancelled + + const explicitEnterDuration: any = toNumber( + isObject(duration) + ? duration.enter + : duration + ) + + if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) { + checkDuration(explicitEnterDuration, 'enter', vnode) + } + + const expectsCSS = css !== false && !isIE9 + const userWantsControl = getHookArgumentsLength(enterHook) + + const cb = el._enterCb = once(() => { + 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', () => { + const parent = el.parentNode + const 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(() => { + 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() + } +} + +export function leave (vnode: VNodeWithData, rm: Function) { + const el: any = vnode.elm + + // call enter callback now + if (isDef(el._enterCb)) { + el._enterCb.cancelled = true + el._enterCb() + } + + const data = resolveTransition(vnode.data.transition) + if (isUndef(data) || el.nodeType !== 1) { + return rm() + } + + /* istanbul ignore if */ + if (isDef(el._leaveCb)) { + return + } + + const { + css, + type, + leaveClass, + leaveToClass, + leaveActiveClass, + beforeLeave, + leave, + afterLeave, + leaveCancelled, + delayLeave, + duration + } = data + + const expectsCSS = css !== false && !isIE9 + const userWantsControl = getHookArgumentsLength(leave) + + const explicitLeaveDuration: any = toNumber( + isObject(duration) + ? duration.leave + : duration + ) + + if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) { + checkDuration(explicitLeaveDuration, 'leave', vnode) + } + + const cb = el._leaveCb = once(() => { + 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: any)] = vnode + } + beforeLeave && beforeLeave(el) + if (expectsCSS) { + addTransitionClass(el, leaveClass) + addTransitionClass(el, leaveActiveClass) + nextFrame(() => { + 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: Function): boolean { + if (isUndef(fn)) { + return false + } + const invokerFns = fn.fns + if (isDef(invokerFns)) { + // invoker + return getHookArgumentsLength( + Array.isArray(invokerFns) + ? invokerFns[0] + : invokerFns + ) + } else { + return (fn._length || fn.length) > 1 + } +} + +function _enter (_: any, vnode: VNodeWithData) { + if (vnode.data.show !== true) { + enter(vnode) + } +} + +export default inBrowser ? { + create: _enter, + activate: _enter, + remove (vnode: VNode, rm: Function) { + /* istanbul ignore else */ + if (vnode.data.show !== true) { + leave(vnode, rm) + } else { + rm() + } + } +} : {} diff --git a/node_modules/vue/src/platforms/web/runtime/node-ops.js b/node_modules/vue/src/platforms/web/runtime/node-ops.js new file mode 100644 index 00000000..3a2978a7 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/node-ops.js @@ -0,0 +1,59 @@ +/* @flow */ + +import { namespaceMap } from 'web/util/index' + +export function createElement (tagName: string, vnode: VNode): Element { + const elm = document.createElement(tagName) + if (tagName !== 'select') { + return elm + } + // false or null will remove the attribute but undefined will not + if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) { + elm.setAttribute('multiple', 'multiple') + } + return elm +} + +export function createElementNS (namespace: string, tagName: string): Element { + return document.createElementNS(namespaceMap[namespace], tagName) +} + +export function createTextNode (text: string): Text { + return document.createTextNode(text) +} + +export function createComment (text: string): Comment { + return document.createComment(text) +} + +export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) { + parentNode.insertBefore(newNode, referenceNode) +} + +export function removeChild (node: Node, child: Node) { + node.removeChild(child) +} + +export function appendChild (node: Node, child: Node) { + node.appendChild(child) +} + +export function parentNode (node: Node): ?Node { + return node.parentNode +} + +export function nextSibling (node: Node): ?Node { + return node.nextSibling +} + +export function tagName (node: Element): string { + return node.tagName +} + +export function setTextContent (node: Node, text: string) { + node.textContent = text +} + +export function setStyleScope (node: Element, scopeId: string) { + node.setAttribute(scopeId, '') +} diff --git a/node_modules/vue/src/platforms/web/runtime/patch.js b/node_modules/vue/src/platforms/web/runtime/patch.js new file mode 100644 index 00000000..d982afe9 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/patch.js @@ -0,0 +1,12 @@ +/* @flow */ + +import * as nodeOps from 'web/runtime/node-ops' +import { createPatchFunction } from 'core/vdom/patch' +import baseModules from 'core/vdom/modules/index' +import platformModules from 'web/runtime/modules/index' + +// the directive module should be applied last, after all +// built-in modules have been applied. +const modules = platformModules.concat(baseModules) + +export const patch: Function = createPatchFunction({ nodeOps, modules }) diff --git a/node_modules/vue/src/platforms/web/runtime/transition-util.js b/node_modules/vue/src/platforms/web/runtime/transition-util.js new file mode 100644 index 00000000..47668f0c --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/transition-util.js @@ -0,0 +1,185 @@ +/* @flow */ + +import { inBrowser, isIE9 } from 'core/util/index' +import { addClass, removeClass } from './class-util' +import { remove, extend, cached } from 'shared/util' + +export function resolveTransition (def?: string | Object): ?Object { + if (!def) { + return + } + /* istanbul ignore else */ + if (typeof def === 'object') { + const res = {} + if (def.css !== false) { + extend(res, autoCssTransition(def.name || 'v')) + } + extend(res, def) + return res + } else if (typeof def === 'string') { + return autoCssTransition(def) + } +} + +const autoCssTransition: (name: string) => Object = cached(name => { + return { + enterClass: `${name}-enter`, + enterToClass: `${name}-enter-to`, + enterActiveClass: `${name}-enter-active`, + leaveClass: `${name}-leave`, + leaveToClass: `${name}-leave-to`, + leaveActiveClass: `${name}-leave-active` + } +}) + +export const hasTransition = inBrowser && !isIE9 +const TRANSITION = 'transition' +const ANIMATION = 'animation' + +// Transition property/event sniffing +export let transitionProp = 'transition' +export let transitionEndEvent = 'transitionend' +export let animationProp = 'animation' +export let 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 +const raf = inBrowser + ? window.requestAnimationFrame + ? window.requestAnimationFrame.bind(window) + : setTimeout + : /* istanbul ignore next */ fn => fn() + +export function nextFrame (fn: Function) { + raf(() => { + raf(fn) + }) +} + +export function addTransitionClass (el: any, cls: string) { + const transitionClasses = el._transitionClasses || (el._transitionClasses = []) + if (transitionClasses.indexOf(cls) < 0) { + transitionClasses.push(cls) + addClass(el, cls) + } +} + +export function removeTransitionClass (el: any, cls: string) { + if (el._transitionClasses) { + remove(el._transitionClasses, cls) + } + removeClass(el, cls) +} + +export function whenTransitionEnds ( + el: Element, + expectedType: ?string, + cb: Function +) { + const { type, timeout, propCount } = getTransitionInfo(el, expectedType) + if (!type) return cb() + const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent + let ended = 0 + const end = () => { + el.removeEventListener(event, onEnd) + cb() + } + const onEnd = e => { + if (e.target === el) { + if (++ended >= propCount) { + end() + } + } + } + setTimeout(() => { + if (ended < propCount) { + end() + } + }, timeout + 1) + el.addEventListener(event, onEnd) +} + +const transformRE = /\b(transform|all)(,|$)/ + +export function getTransitionInfo (el: Element, expectedType?: ?string): { + type: ?string; + propCount: number; + timeout: number; + hasTransform: boolean; +} { + const styles: any = window.getComputedStyle(el) + const transitionDelays: Array<string> = styles[transitionProp + 'Delay'].split(', ') + const transitionDurations: Array<string> = styles[transitionProp + 'Duration'].split(', ') + const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations) + const animationDelays: Array<string> = styles[animationProp + 'Delay'].split(', ') + const animationDurations: Array<string> = styles[animationProp + 'Duration'].split(', ') + const animationTimeout: number = getTimeout(animationDelays, animationDurations) + + let type: ?string + let timeout = 0 + let 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 + } + const hasTransform: boolean = + type === TRANSITION && + transformRE.test(styles[transitionProp + 'Property']) + return { + type, + timeout, + propCount, + hasTransform + } +} + +function getTimeout (delays: Array<string>, durations: Array<string>): number { + /* istanbul ignore next */ + while (delays.length < durations.length) { + delays = delays.concat(delays) + } + + return Math.max.apply(null, durations.map((d, i) => { + return toMs(d) + toMs(delays[i]) + })) +} + +function toMs (s: string): number { + return Number(s.slice(0, -1)) * 1000 +} diff --git a/node_modules/vue/src/platforms/web/server/compiler.js b/node_modules/vue/src/platforms/web/server/compiler.js new file mode 100644 index 00000000..20e57367 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/compiler.js @@ -0,0 +1,11 @@ +/* @flow */ + +import { baseOptions } from '../compiler/options' +import { createCompiler } from 'server/optimizing-compiler/index' + +const { compile, compileToFunctions } = createCompiler(baseOptions) + +export { + compile as ssrCompile, + compileToFunctions as ssrCompileToFunctions +} diff --git a/node_modules/vue/src/platforms/web/server/directives/index.js b/node_modules/vue/src/platforms/web/server/directives/index.js new file mode 100644 index 00000000..fd7fce0a --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/directives/index.js @@ -0,0 +1,7 @@ +import show from './show' +import model from './model' + +export default { + show, + model +} diff --git a/node_modules/vue/src/platforms/web/server/directives/model.js b/node_modules/vue/src/platforms/web/server/directives/model.js new file mode 100644 index 00000000..7962d6c8 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/directives/model.js @@ -0,0 +1,44 @@ +/* @flow */ + +import { looseEqual, looseIndexOf } from 'shared/util' + +// this is only applied for <select v-model> because it is the only edge case +// that must be done at runtime instead of compile time. +export default function model (node: VNodeWithData, dir: VNodeDirective) { + if (!node.children) return + const value = dir.value + const isMultiple = node.data.attrs && node.data.attrs.multiple + for (let i = 0, l = node.children.length; i < l; i++) { + const option = node.children[i] + if (option.tag === 'option') { + if (isMultiple) { + const selected = + Array.isArray(value) && + (looseIndexOf(value, getValue(option)) > -1) + if (selected) { + setSelected(option) + } + } else { + if (looseEqual(value, getValue(option))) { + setSelected(option) + return + } + } + } + } +} + +function getValue (option) { + const data = option.data || {} + return ( + (data.attrs && data.attrs.value) || + (data.domProps && data.domProps.value) || + (option.children && option.children[0] && option.children[0].text) + ) +} + +function setSelected (option) { + const data = option.data || (option.data = {}) + const attrs = data.attrs || (data.attrs = {}) + attrs.selected = '' +} diff --git a/node_modules/vue/src/platforms/web/server/directives/show.js b/node_modules/vue/src/platforms/web/server/directives/show.js new file mode 100644 index 00000000..7493b27e --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/directives/show.js @@ -0,0 +1,12 @@ +/* @flow */ + +export default function show (node: VNodeWithData, dir: VNodeDirective) { + if (!dir.value) { + const style: any = node.data.style || (node.data.style = {}) + if (Array.isArray(style)) { + style.push({ display: 'none' }) + } else { + style.display = 'none' + } + } +} diff --git a/node_modules/vue/src/platforms/web/server/modules/attrs.js b/node_modules/vue/src/platforms/web/server/modules/attrs.js new file mode 100644 index 00000000..c3f73013 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/attrs.js @@ -0,0 +1,62 @@ +/* @flow */ + +import { escape } from '../util' + +import { + isDef, + isUndef, + extend +} from 'shared/util' + +import { + isBooleanAttr, + isEnumeratedAttr, + isFalsyAttrValue +} from 'web/util/attrs' + +import { isSSRUnsafeAttr } from 'web/server/util' + +export default function renderAttrs (node: VNodeWithData): string { + let attrs = node.data.attrs + let res = '' + + const opts = node.parent && node.parent.componentOptions + if (isUndef(opts) || opts.Ctor.options.inheritAttrs !== false) { + let parent = node.parent + while (isDef(parent)) { + if (isDef(parent.data) && isDef(parent.data.attrs)) { + attrs = extend(extend({}, attrs), parent.data.attrs) + } + parent = parent.parent + } + } + + if (isUndef(attrs)) { + return res + } + + for (const key in attrs) { + if (isSSRUnsafeAttr(key)) { + continue + } + if (key === 'style') { + // leave it to the style module + continue + } + res += renderAttr(key, attrs[key]) + } + return res +} + +export function renderAttr (key: string, value: string): string { + if (isBooleanAttr(key)) { + if (!isFalsyAttrValue(value)) { + return ` ${key}="${key}"` + } + } else if (isEnumeratedAttr(key)) { + return ` ${key}="${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}"` + } else if (!isFalsyAttrValue(value)) { + return ` ${key}="${escape(String(value))}"` + } + return '' +} diff --git a/node_modules/vue/src/platforms/web/server/modules/class.js b/node_modules/vue/src/platforms/web/server/modules/class.js new file mode 100644 index 00000000..c19040ed --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/class.js @@ -0,0 +1,11 @@ +/* @flow */ + +import { escape } from '../util' +import { genClassForVnode } from 'web/util/index' + +export default function renderClass (node: VNodeWithData): ?string { + const classList = genClassForVnode(node) + if (classList !== '') { + return ` class="${escape(classList)}"` + } +} diff --git a/node_modules/vue/src/platforms/web/server/modules/dom-props.js b/node_modules/vue/src/platforms/web/server/modules/dom-props.js new file mode 100644 index 00000000..57c18eef --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/dom-props.js @@ -0,0 +1,50 @@ +/* @flow */ + +import VNode from 'core/vdom/vnode' +import { renderAttr } from './attrs' +import { isDef, isUndef, extend } from 'shared/util' +import { propsToAttrMap, isRenderableAttr } from '../util' + +export default function renderDOMProps (node: VNodeWithData): string { + let props = node.data.domProps + let res = '' + + let parent = node.parent + while (isDef(parent)) { + if (parent.data && parent.data.domProps) { + props = extend(extend({}, props), parent.data.domProps) + } + parent = parent.parent + } + + if (isUndef(props)) { + return res + } + + const attrs = node.data.attrs + for (const key in props) { + if (key === 'innerHTML') { + setText(node, props[key], true) + } else if (key === 'textContent') { + setText(node, props[key], false) + } else if (key === 'value' && node.tag === 'textarea') { + setText(node, props[key], false) + } else { + // $flow-disable-line (WTF?) + const attr = propsToAttrMap[key] || key.toLowerCase() + if (isRenderableAttr(attr) && + // avoid rendering double-bound props/attrs twice + !(isDef(attrs) && isDef(attrs[attr])) + ) { + res += renderAttr(attr, props[key]) + } + } + } + return res +} + +function setText (node, text, raw) { + const child = new VNode(undefined, undefined, undefined, text) + child.raw = raw + node.children = [child] +} diff --git a/node_modules/vue/src/platforms/web/server/modules/index.js b/node_modules/vue/src/platforms/web/server/modules/index.js new file mode 100644 index 00000000..3d15bc50 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/index.js @@ -0,0 +1,11 @@ +import attrs from './attrs' +import domProps from './dom-props' +import klass from './class' +import style from './style' + +export default [ + attrs, + domProps, + klass, + style +] diff --git a/node_modules/vue/src/platforms/web/server/modules/style.js b/node_modules/vue/src/platforms/web/server/modules/style.js new file mode 100644 index 00000000..fc043ef3 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/style.js @@ -0,0 +1,28 @@ +/* @flow */ + +import { escape } from '../util' +import { hyphenate } from 'shared/util' +import { getStyle } from 'web/util/style' + +export function genStyle (style: Object): string { + let styleText = '' + for (const key in style) { + const value = style[key] + const hyphenatedKey = hyphenate(key) + if (Array.isArray(value)) { + for (let i = 0, len = value.length; i < len; i++) { + styleText += `${hyphenatedKey}:${value[i]};` + } + } else { + styleText += `${hyphenatedKey}:${value};` + } + } + return styleText +} + +export default function renderStyle (vnode: VNodeWithData): ?string { + const styleText = genStyle(getStyle(vnode, false)) + if (styleText !== '') { + return ` style=${JSON.stringify(escape(styleText))}` + } +} diff --git a/node_modules/vue/src/platforms/web/server/util.js b/node_modules/vue/src/platforms/web/server/util.js new file mode 100644 index 00000000..27489705 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/util.js @@ -0,0 +1,56 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +const isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' +) + +const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ +export const isSSRUnsafeAttr = (name: string): boolean => { + return unsafeAttrCharRE.test(name) +} + +/* istanbul ignore next */ +const isRenderableAttr = (name: string): boolean => { + return ( + isAttr(name) || + name.indexOf('data-') === 0 || + name.indexOf('aria-') === 0 + ) +} +export { isRenderableAttr } + +export const propsToAttrMap = { + acceptCharset: 'accept-charset', + className: 'class', + htmlFor: 'for', + httpEquiv: 'http-equiv' +} + +const ESC = { + '<': '<', + '>': '>', + '"': '"', + '&': '&' +} + +export function escape (s: string) { + return s.replace(/[<>"&]/g, escapeChar) +} + +function escapeChar (a) { + return ESC[a] || a +} diff --git a/node_modules/vue/src/platforms/web/util/attrs.js b/node_modules/vue/src/platforms/web/util/attrs.js new file mode 100644 index 00000000..59517da9 --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/attrs.js @@ -0,0 +1,43 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +// these are reserved for web because they are directly compiled away +// during template compilation +export const isReservedAttr = makeMap('style,class') + +// attributes that should be using props for binding +const acceptValue = makeMap('input,textarea,option,select,progress') +export const mustUseProp = (tag: string, type: ?string, attr: string): boolean => { + return ( + (attr === 'value' && acceptValue(tag)) && type !== 'button' || + (attr === 'selected' && tag === 'option') || + (attr === 'checked' && tag === 'input') || + (attr === 'muted' && tag === 'video') + ) +} + +export const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck') + +export const isBooleanAttr = makeMap( + 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + + 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + + 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + + 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + + 'required,reversed,scoped,seamless,selected,sortable,translate,' + + 'truespeed,typemustmatch,visible' +) + +export const xlinkNS = 'http://www.w3.org/1999/xlink' + +export const isXlink = (name: string): boolean => { + return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink' +} + +export const getXlinkProp = (name: string): string => { + return isXlink(name) ? name.slice(6, name.length) : '' +} + +export const isFalsyAttrValue = (val: any): boolean => { + return val == null || val === false +} diff --git a/node_modules/vue/src/platforms/web/util/class.js b/node_modules/vue/src/platforms/web/util/class.js new file mode 100644 index 00000000..542dcbc4 --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/class.js @@ -0,0 +1,85 @@ +/* @flow */ + +import { isDef, isObject } from 'shared/util' + +export function genClassForVnode (vnode: VNodeWithData): string { + let data = vnode.data + let parentNode = vnode + let childNode = vnode + while (isDef(childNode.componentInstance)) { + childNode = childNode.componentInstance._vnode + if (childNode && childNode.data) { + data = mergeClassData(childNode.data, data) + } + } + while (isDef(parentNode = parentNode.parent)) { + if (parentNode && parentNode.data) { + data = mergeClassData(data, parentNode.data) + } + } + return renderClass(data.staticClass, data.class) +} + +function mergeClassData (child: VNodeData, parent: VNodeData): { + staticClass: string, + class: any +} { + return { + staticClass: concat(child.staticClass, parent.staticClass), + class: isDef(child.class) + ? [child.class, parent.class] + : parent.class + } +} + +export function renderClass ( + staticClass: ?string, + dynamicClass: any +): string { + if (isDef(staticClass) || isDef(dynamicClass)) { + return concat(staticClass, stringifyClass(dynamicClass)) + } + /* istanbul ignore next */ + return '' +} + +export function concat (a: ?string, b: ?string): string { + return a ? b ? (a + ' ' + b) : a : (b || '') +} + +export function stringifyClass (value: any): string { + if (Array.isArray(value)) { + return stringifyArray(value) + } + if (isObject(value)) { + return stringifyObject(value) + } + if (typeof value === 'string') { + return value + } + /* istanbul ignore next */ + return '' +} + +function stringifyArray (value: Array<any>): string { + let res = '' + let stringified + for (let i = 0, l = value.length; i < l; i++) { + if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') { + if (res) res += ' ' + res += stringified + } + } + return res +} + +function stringifyObject (value: Object): string { + let res = '' + for (const key in value) { + if (value[key]) { + if (res) res += ' ' + res += key + } + } + return res +} diff --git a/node_modules/vue/src/platforms/web/util/compat.js b/node_modules/vue/src/platforms/web/util/compat.js new file mode 100644 index 00000000..d95759cc --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/compat.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { inBrowser } from 'core/util/index' + +// check whether current browser encodes a char inside attribute values +let div +function getShouldDecode (href: boolean): boolean { + div = div || document.createElement('div') + div.innerHTML = href ? `<a href="\n"/>` : `<div a="\n"/>` + return div.innerHTML.indexOf(' ') > 0 +} + +// #3663: IE encodes newlines inside attribute values while other browsers don't +export const shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false +// #6828: chrome encodes content in a[href] +export const shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false diff --git a/node_modules/vue/src/platforms/web/util/element.js b/node_modules/vue/src/platforms/web/util/element.js new file mode 100644 index 00000000..65f1aafb --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/element.js @@ -0,0 +1,77 @@ +/* @flow */ + +import { inBrowser } from 'core/util/env' +import { makeMap } from 'shared/util' + +export const namespaceMap = { + svg: 'http://www.w3.org/2000/svg', + math: 'http://www.w3.org/1998/Math/MathML' +} + +export const isHTMLTag = makeMap( + 'html,body,base,head,link,meta,style,title,' + + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + + 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' + + 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + + 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + + 'embed,object,param,source,canvas,script,noscript,del,ins,' + + 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + + 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + + 'output,progress,select,textarea,' + + 'details,dialog,menu,menuitem,summary,' + + 'content,element,shadow,template,blockquote,iframe,tfoot' +) + +// this map is intentionally selective, only covering SVG elements that may +// contain child elements. +export const isSVG = makeMap( + 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + + 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + + 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', + true +) + +export const isPreTag = (tag: ?string): boolean => tag === 'pre' + +export const isReservedTag = (tag: string): ?boolean => { + return isHTMLTag(tag) || isSVG(tag) +} + +export function getTagNamespace (tag: string): ?string { + if (isSVG(tag)) { + return 'svg' + } + // basic support for MathML + // note it doesn't support other MathML elements being component roots + if (tag === 'math') { + return 'math' + } +} + +const unknownElementCache = Object.create(null) +export function isUnknownElement (tag: string): boolean { + /* istanbul ignore if */ + if (!inBrowser) { + return true + } + if (isReservedTag(tag)) { + return false + } + tag = tag.toLowerCase() + /* istanbul ignore if */ + if (unknownElementCache[tag] != null) { + return unknownElementCache[tag] + } + const el = document.createElement(tag) + if (tag.indexOf('-') > -1) { + // http://stackoverflow.com/a/28210364/1070244 + return (unknownElementCache[tag] = ( + el.constructor === window.HTMLUnknownElement || + el.constructor === window.HTMLElement + )) + } else { + return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString())) + } +} + +export const isTextInputType = makeMap('text,number,password,search,email,tel,url') diff --git a/node_modules/vue/src/platforms/web/util/index.js b/node_modules/vue/src/platforms/web/util/index.js new file mode 100644 index 00000000..da5e46ed --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/index.js @@ -0,0 +1,25 @@ +/* @flow */ + +import { warn } from 'core/util/index' + +export * from './attrs' +export * from './class' +export * from './element' + +/** + * Query an element selector if it's not an element already. + */ +export function query (el: string | Element): Element { + if (typeof el === 'string') { + const selected = document.querySelector(el) + if (!selected) { + process.env.NODE_ENV !== 'production' && warn( + 'Cannot find element: ' + el + ) + return document.createElement('div') + } + return selected + } else { + return el + } +} diff --git a/node_modules/vue/src/platforms/web/util/style.js b/node_modules/vue/src/platforms/web/util/style.js new file mode 100644 index 00000000..fc119043 --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/style.js @@ -0,0 +1,72 @@ +/* @flow */ + +import { cached, extend, toObject } from 'shared/util' + +export const parseStyleText = cached(function (cssText) { + const res = {} + const listDelimiter = /;(?![^(]*\))/g + const 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: VNodeData): ?Object { + const 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 +export function normalizeStyleBinding (bindingStyle: any): ?Object { + if (Array.isArray(bindingStyle)) { + return toObject(bindingStyle) + } + if (typeof bindingStyle === 'string') { + return parseStyleText(bindingStyle) + } + return bindingStyle +} + +/** + * parent component style should be after child's + * so that parent component's style could override it + */ +export function getStyle (vnode: VNodeWithData, checkChild: boolean): Object { + const res = {} + let styleData + + if (checkChild) { + let 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) + } + + let parentNode = vnode + while ((parentNode = parentNode.parent)) { + if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) { + extend(res, styleData) + } + } + return res +} + diff --git a/node_modules/vue/src/platforms/weex/compiler/directives/index.js b/node_modules/vue/src/platforms/weex/compiler/directives/index.js new file mode 100755 index 00000000..12ced224 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/directives/index.js @@ -0,0 +1,5 @@ +import model from './model' + +export default { + model +} diff --git a/node_modules/vue/src/platforms/weex/compiler/directives/model.js b/node_modules/vue/src/platforms/weex/compiler/directives/model.js new file mode 100644 index 00000000..6c9b7685 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/directives/model.js @@ -0,0 +1,34 @@ +/* @flow */ + +import { addHandler, addAttr } from 'compiler/helpers' +import { genComponentModel, genAssignmentCode } from 'compiler/directives/model' + +export default function model ( + el: ASTElement, + dir: ASTDirective, + _warn: Function +): ?boolean { + if (el.tag === 'input' || el.tag === 'textarea') { + genDefaultModel(el, dir.value, dir.modifiers) + } else { + genComponentModel(el, dir.value, dir.modifiers) + } +} + +function genDefaultModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +): ?boolean { + const { lazy, trim, number } = modifiers || {} + const event = lazy ? 'change' : 'input' + + let valueExpression = `$event.target.attr.value${trim ? '.trim()' : ''}` + if (number) { + valueExpression = `_n(${valueExpression})` + } + + const code = genAssignmentCode(value, valueExpression) + addAttr(el, 'value', `(${value})`) + addHandler(el, event, code, null, true) +} diff --git a/node_modules/vue/src/platforms/weex/compiler/index.js b/node_modules/vue/src/platforms/weex/compiler/index.js new file mode 100644 index 00000000..94f350b8 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/index.js @@ -0,0 +1,52 @@ +/* @flow */ + +import { genStaticKeys } from 'shared/util' +import { createCompiler } from 'compiler/index' + +import modules from './modules/index' +import directives from './directives/index' + +import { + isUnaryTag, + mustUseProp, + isReservedTag, + canBeLeftOpenTag, + getTagNamespace +} from '../util/element' + +export const baseOptions: WeexCompilerOptions = { + modules, + directives, + isUnaryTag, + mustUseProp, + canBeLeftOpenTag, + isReservedTag, + getTagNamespace, + preserveWhitespace: false, + recyclable: false, + staticKeys: genStaticKeys(modules) +} + +const compiler = createCompiler(baseOptions) + +export function compile ( + template: string, + options?: WeexCompilerOptions +): WeexCompiledResult { + let generateAltRender = false + if (options && options.recyclable === true) { + generateAltRender = true + options.recyclable = false + } + const result = compiler.compile(template, options) + + // generate @render function for <recycle-list> + if (options && generateAltRender) { + options.recyclable = true + // disable static optimizations + options.optimize = false + const { render } = compiler.compile(template, options) + result['@render'] = render + } + return result +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/append.js b/node_modules/vue/src/platforms/weex/compiler/modules/append.js new file mode 100644 index 00000000..6e7be267 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/append.js @@ -0,0 +1,27 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +// The "unitary tag" means that the tag node and its children +// must be sent to the native together. +const isUnitaryTag = makeMap('cell,header,cell-slot,recycle-list', true) + +function preTransformNode (el: ASTElement, options: CompilerOptions) { + if (isUnitaryTag(el.tag) && !el.attrsList.some(item => item.name === 'append')) { + el.attrsMap.append = 'tree' + el.attrsList.push({ name: 'append', value: 'tree' }) + } + if (el.attrsMap.append === 'tree') { + el.appendAsTree = true + } +} + +function genData (el: ASTElement): string { + return el.appendAsTree ? `appendAsTree:true,` : '' +} + +export default { + staticKeys: ['appendAsTree'], + preTransformNode, + genData +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/class.js b/node_modules/vue/src/platforms/weex/compiler/modules/class.js new file mode 100644 index 00000000..371a08ea --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/class.js @@ -0,0 +1,73 @@ +/* @flow */ + +import { parseText } from 'compiler/parser/text-parser' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +type StaticClassResult = { + dynamic: boolean, + classResult: string +}; + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticClass = getAndRemoveAttr(el, 'class') + const { dynamic, classResult } = parseStaticClass(staticClass, options) + if (process.env.NODE_ENV !== 'production' && dynamic && staticClass) { + warn( + `class="${staticClass}": ` + + 'Interpolation inside attributes has been deprecated. ' + + 'Use v-bind or the colon shorthand instead.' + ) + } + if (!dynamic && classResult) { + el.staticClass = classResult + } + const classBinding = getBindingAttr(el, 'class', false /* getStatic */) + if (classBinding) { + el.classBinding = classBinding + } else if (dynamic) { + el.classBinding = classResult + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticClass) { + data += `staticClass:${el.staticClass},` + } + if (el.classBinding) { + data += `class:${el.classBinding},` + } + return data +} + +function parseStaticClass (staticClass: ?string, options: CompilerOptions): StaticClassResult { + // "a b c" -> ["a", "b", "c"] => staticClass: ["a", "b", "c"] + // "a {{x}} c" -> ["a", x, "c"] => classBinding: '["a", x, "c"]' + let dynamic = false + let classResult = '' + if (staticClass) { + const classList = staticClass.trim().split(' ').map(name => { + const result = parseText(name, options.delimiters) + if (result) { + dynamic = true + return result.expression + } + return JSON.stringify(name) + }) + if (classList.length) { + classResult = '[' + classList.join(',') + ']' + } + } + return { dynamic, classResult } +} + +export default { + staticKeys: ['staticClass'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/index.js b/node_modules/vue/src/platforms/weex/compiler/modules/index.js new file mode 100644 index 00000000..ac17809e --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/index.js @@ -0,0 +1,13 @@ +import klass from './class' +import style from './style' +import props from './props' +import append from './append' +import recycleList from './recycle-list/index' + +export default [ + recycleList, + klass, + style, + props, + append +] diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/props.js b/node_modules/vue/src/platforms/weex/compiler/modules/props.js new file mode 100644 index 00000000..8b0bb5f2 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/props.js @@ -0,0 +1,32 @@ +/* @flow */ + +import { cached, camelize } from 'shared/util' + +const normalize = cached(camelize) + +function normalizeKeyName (str: string): string { + if (str.match(/^v\-/)) { + return str.replace(/(v-[a-z\-]+\:)([a-z\-]+)$/i, ($, directive, prop) => { + return directive + normalize(prop) + }) + } + return normalize(str) +} + +function transformNode (el: ASTElement, options: CompilerOptions) { + if (Array.isArray(el.attrsList)) { + el.attrsList.forEach(attr => { + if (attr.name && attr.name.match(/\-/)) { + const realName = normalizeKeyName(attr.name) + if (el.attrsMap) { + el.attrsMap[realName] = el.attrsMap[attr.name] + delete el.attrsMap[attr.name] + } + attr.name = realName + } + }) + } +} +export default { + transformNode +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js new file mode 100644 index 00000000..7d9fedea --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { addAttr } from 'compiler/helpers' + +// mark component root nodes as +export function postTransformComponentRoot ( + el: ASTElement, + options: WeexCompilerOptions +) { + if (!el.parent) { + // component root + addAttr(el, '@isComponentRoot', 'true') + addAttr(el, '@templateId', '_uid') + addAttr(el, '@componentProps', '$props || {}') + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component.js new file mode 100644 index 00000000..36d7dfd5 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { addAttr } from 'compiler/helpers' +import { RECYCLE_LIST_MARKER } from 'weex/util/index' + +// mark components as inside recycle-list so that we know we need to invoke +// their special @render function instead of render in create-component.js +export function postTransformComponent ( + el: ASTElement, + options: WeexCompilerOptions +) { + // $flow-disable-line (we know isReservedTag is there) + if (!options.isReservedTag(el.tag) && el.tag !== 'cell-slot') { + addAttr(el, RECYCLE_LIST_MARKER, 'true') + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/index.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/index.js new file mode 100644 index 00000000..502cc782 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/index.js @@ -0,0 +1,60 @@ +/* @flow */ + +import { preTransformRecycleList } from './recycle-list' +import { postTransformComponent } from './component' +import { postTransformComponentRoot } from './component-root' +import { postTransformText } from './text' +import { preTransformVBind } from './v-bind' +import { preTransformVIf } from './v-if' +import { preTransformVFor } from './v-for' +import { postTransformVOn } from './v-on' +import { preTransformVOnce } from './v-once' + +let currentRecycleList = null + +function shouldCompile (el: ASTElement, options: WeexCompilerOptions) { + return options.recyclable || + (currentRecycleList && el !== currentRecycleList) +} + +function preTransformNode (el: ASTElement, options: WeexCompilerOptions) { + if (el.tag === 'recycle-list') { + preTransformRecycleList(el, options) + currentRecycleList = el + } + if (shouldCompile(el, options)) { + preTransformVBind(el, options) + preTransformVIf(el, options) // also v-else-if and v-else + preTransformVFor(el, options) + preTransformVOnce(el, options) + } +} + +function transformNode (el: ASTElement, options: WeexCompilerOptions) { + if (shouldCompile(el, options)) { + // do nothing yet + } +} + +function postTransformNode (el: ASTElement, options: WeexCompilerOptions) { + if (shouldCompile(el, options)) { + // mark child component in parent template + postTransformComponent(el, options) + // mark root in child component template + postTransformComponentRoot(el, options) + // <text>: transform children text into value attr + if (el.tag === 'text') { + postTransformText(el, options) + } + postTransformVOn(el, options) + } + if (el === currentRecycleList) { + currentRecycleList = null + } +} + +export default { + preTransformNode, + transformNode, + postTransformNode +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js new file mode 100644 index 00000000..7fb65167 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js @@ -0,0 +1,50 @@ +/* @flow */ + +import { parseFor } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +/** + * Map the following syntax to corresponding attrs: + * + * <recycle-list for="(item, i) in longList" switch="cellType"> + * <cell-slot case="A"> ... </cell-slot> + * <cell-slot case="B"> ... </cell-slot> + * </recycle-list> + */ + +export function preTransformRecycleList ( + el: ASTElement, + options: WeexCompilerOptions +) { + const exp = getAndRemoveAttr(el, 'for') + if (!exp) { + if (options.warn) { + options.warn(`Invalid <recycle-list> syntax: missing "for" expression.`) + } + return + } + + const res = parseFor(exp) + if (!res) { + if (options.warn) { + options.warn(`Invalid <recycle-list> syntax: ${exp}.`) + } + return + } + + addRawAttr(el, ':list-data', res.for) + addRawAttr(el, 'binding-expression', res.for) + addRawAttr(el, 'alias', res.alias) + if (res.iterator2) { + // (item, key, index) for object iteration + // is this even supported? + addRawAttr(el, 'index', res.iterator2) + } else if (res.iterator1) { + addRawAttr(el, 'index', res.iterator1) + } + + const switchKey = getAndRemoveAttr(el, 'switch') + if (switchKey) { + addRawAttr(el, 'switch', switchKey) + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/text.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/text.js new file mode 100644 index 00000000..575ec7f6 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/text.js @@ -0,0 +1,23 @@ +/* @flow */ + +import { addAttr } from 'compiler/helpers' + +function genText (node: ASTNode) { + const value = node.type === 3 + ? node.text + : node.type === 2 + ? node.tokens.length === 1 + ? node.tokens[0] + : node.tokens + : '' + return JSON.stringify(value) +} + +export function postTransformText (el: ASTElement, options: WeexCompilerOptions) { + // weex <text> can only contain text, so the parser + // always generates a single child. + if (el.children.length) { + addAttr(el, 'value', genText(el.children[0])) + el.children = [] + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js new file mode 100644 index 00000000..effdf351 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js @@ -0,0 +1,21 @@ +/* @flow */ + +import { camelize } from 'shared/util' +import { generateBinding } from 'weex/util/parser' +import { bindRE } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +function parseAttrName (name: string): string { + return camelize(name.replace(bindRE, '')) +} + +export function preTransformVBind (el: ASTElement, options: WeexCompilerOptions) { + for (const attr in el.attrsMap) { + if (bindRE.test(attr)) { + const name: string = parseAttrName(attr) + const value = generateBinding(getAndRemoveAttr(el, attr)) + delete el.attrsMap[attr] + addRawAttr(el, name, value) + } + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js new file mode 100644 index 00000000..9f09e4b4 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js @@ -0,0 +1,33 @@ +/* @flow */ + +import { parseFor } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +export function preTransformVFor (el: ASTElement, options: WeexCompilerOptions) { + const exp = getAndRemoveAttr(el, 'v-for') + if (!exp) { + return + } + + const res = parseFor(exp) + if (!res) { + if (process.env.NODE_ENV !== 'production' && options.warn) { + options.warn(`Invalid v-for expression: ${exp}`) + } + return + } + + const desc: Object = { + '@expression': res.for, + '@alias': res.alias + } + if (res.iterator2) { + desc['@key'] = res.iterator1 + desc['@index'] = res.iterator2 + } else { + desc['@index'] = res.iterator1 + } + + delete el.attrsMap['v-for'] + addRawAttr(el, '[[repeat]]', desc) +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js new file mode 100644 index 00000000..d82ba632 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js @@ -0,0 +1,63 @@ +/* @flow */ + +import { addIfCondition } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +function hasConditionDirective (el: ASTElement): boolean { + for (const attr in el.attrsMap) { + if (/^v\-if|v\-else|v\-else\-if$/.test(attr)) { + return true + } + } + return false +} + +function getPreviousConditions (el: ASTElement): Array<string> { + const conditions = [] + if (el.parent && el.parent.children) { + for (let c = 0, n = el.parent.children.length; c < n; ++c) { + // $flow-disable-line + const ifConditions = el.parent.children[c].ifConditions + if (ifConditions) { + for (let i = 0, l = ifConditions.length; i < l; ++i) { + const condition = ifConditions[i] + if (condition && condition.exp) { + conditions.push(condition.exp) + } + } + } + } + } + return conditions +} + +export function preTransformVIf (el: ASTElement, options: WeexCompilerOptions) { + if (hasConditionDirective(el)) { + let exp + const ifExp = getAndRemoveAttr(el, 'v-if', true /* remove from attrsMap */) + const elseifExp = getAndRemoveAttr(el, 'v-else-if', true) + // don't need the value, but remove it to avoid being generated as a + // custom directive + getAndRemoveAttr(el, 'v-else', true) + if (ifExp) { + exp = ifExp + addIfCondition(el, { exp: ifExp, block: el }) + } else { + elseifExp && addIfCondition(el, { exp: elseifExp, block: el }) + const prevConditions = getPreviousConditions(el) + if (prevConditions.length) { + const prevMatch = prevConditions.join(' || ') + exp = elseifExp + ? `!(${prevMatch}) && (${elseifExp})` // v-else-if + : `!(${prevMatch})` // v-else + } else if (process.env.NODE_ENV !== 'production' && options.warn) { + options.warn( + `v-${elseifExp ? ('else-if="' + elseifExp + '"') : 'else'} ` + + `used on element <${el.tag}> without corresponding v-if.` + ) + return + } + } + addRawAttr(el, '[[match]]', exp) + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js new file mode 100644 index 00000000..b1ba262c --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js @@ -0,0 +1,25 @@ +/* @flow */ + +const inlineStatementRE = /^\s*([A-Za-z_$0-9\['\."\]]+)*\s*\(\s*(([A-Za-z_$0-9\['\."\]]+)?(\s*,\s*([A-Za-z_$0-9\['\."\]]+))*)\s*\)$/ + +function parseHandlerParams (handler: ASTElementHandler) { + const res = inlineStatementRE.exec(handler.value) + if (res && res[2]) { + handler.params = res[2].split(/\s*,\s*/) + } +} + +export function postTransformVOn (el: ASTElement, options: WeexCompilerOptions) { + const events: ASTElementHandlers | void = el.events + if (!events) { + return + } + for (const name in events) { + const handler: ASTElementHandler | Array<ASTElementHandler> = events[name] + if (Array.isArray(handler)) { + handler.map(fn => parseHandlerParams(fn)) + } else { + parseHandlerParams(handler) + } + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js new file mode 100644 index 00000000..baff838a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js @@ -0,0 +1,19 @@ +/* @flow */ + +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +function containVOnce (el: ASTElement): boolean { + for (const attr in el.attrsMap) { + if (/^v\-once$/i.test(attr)) { + return true + } + } + return false +} + +export function preTransformVOnce (el: ASTElement, options: WeexCompilerOptions) { + if (containVOnce(el)) { + getAndRemoveAttr(el, 'v-once', true) + addRawAttr(el, '[[once]]', true) + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/style.js b/node_modules/vue/src/platforms/weex/compiler/modules/style.js new file mode 100644 index 00000000..7bf5027f --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/style.js @@ -0,0 +1,86 @@ +/* @flow */ + +import { cached, camelize, isPlainObject } from 'shared/util' +import { parseText } from 'compiler/parser/text-parser' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +type StaticStyleResult = { + dynamic: boolean, + styleResult: string | Object | void +}; + +const normalize = cached(camelize) + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticStyle = getAndRemoveAttr(el, 'style') + const { dynamic, styleResult } = parseStaticStyle(staticStyle, options) + if (process.env.NODE_ENV !== 'production' && dynamic) { + warn( + `style="${String(staticStyle)}": ` + + 'Interpolation inside attributes has been deprecated. ' + + 'Use v-bind or the colon shorthand instead.' + ) + } + if (!dynamic && styleResult) { + // $flow-disable-line + el.staticStyle = styleResult + } + const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) + if (styleBinding) { + el.styleBinding = styleBinding + } else if (dynamic) { + // $flow-disable-line + el.styleBinding = styleResult + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticStyle) { + data += `staticStyle:${el.staticStyle},` + } + if (el.styleBinding) { + data += `style:${el.styleBinding},` + } + return data +} + +function parseStaticStyle (staticStyle: ?string, options: CompilerOptions): StaticStyleResult { + // "width: 200px; height: 200px;" -> {width: 200, height: 200} + // "width: 200px; height: {{y}}" -> {width: 200, height: y} + let dynamic = false + let styleResult = '' + if (typeof staticStyle === 'string') { + const styleList = staticStyle.trim().split(';').map(style => { + const result = style.trim().split(':') + if (result.length !== 2) { + return + } + const key = normalize(result[0].trim()) + const value = result[1].trim() + const dynamicValue = parseText(value, options.delimiters) + if (dynamicValue) { + dynamic = true + return key + ':' + dynamicValue.expression + } + return key + ':' + JSON.stringify(value) + }).filter(result => result) + if (styleList.length) { + styleResult = '{' + styleList.join(',') + '}' + } + } else if (isPlainObject(staticStyle)) { + styleResult = JSON.stringify(staticStyle) || '' + } + return { dynamic, styleResult } +} + +export default { + staticKeys: ['staticStyle'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/weex/entry-compiler.js b/node_modules/vue/src/platforms/weex/entry-compiler.js new file mode 100644 index 00000000..18d79ab7 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/entry-compiler.js @@ -0,0 +1 @@ +export { compile } from 'weex/compiler/index' diff --git a/node_modules/vue/src/platforms/weex/entry-framework.js b/node_modules/vue/src/platforms/weex/entry-framework.js new file mode 100644 index 00000000..a23775d4 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/entry-framework.js @@ -0,0 +1,187 @@ +/* @flow */ + +// this will be preserved during build +// $flow-disable-line +const VueFactory = require('./factory') + +const instanceOptions: { [key: string]: WeexInstanceOption } = {} + +/** + * Create instance context. + */ +export function createInstanceContext ( + instanceId: string, + runtimeContext: WeexRuntimeContext, + data: Object = {} +): WeexInstanceContext { + const weex: Weex = runtimeContext.weex + const instance: WeexInstanceOption = instanceOptions[instanceId] = { + instanceId, + config: weex.config, + document: weex.document, + data + } + + // Each instance has a independent `Vue` module instance + const Vue = instance.Vue = createVueModuleInstance(instanceId, weex) + + // DEPRECATED + const timerAPIs = getInstanceTimer(instanceId, weex.requireModule) + + const instanceContext = Object.assign({ Vue }, timerAPIs) + Object.freeze(instanceContext) + return instanceContext +} + +/** + * Destroy an instance with id. It will make sure all memory of + * this instance released and no more leaks. + */ +export function destroyInstance (instanceId: string): void { + const instance = instanceOptions[instanceId] + if (instance && instance.app instanceof instance.Vue) { + try { + instance.app.$destroy() + instance.document.destroy() + } catch (e) {} + delete instance.document + delete instance.app + } + delete instanceOptions[instanceId] +} + +/** + * Refresh an instance with id and new top-level component data. + * It will use `Vue.set` on all keys of the new data. So it's better + * define all possible meaningful keys when instance created. + */ +export function refreshInstance ( + instanceId: string, + data: Object +): Error | void { + const instance = instanceOptions[instanceId] + if (!instance || !(instance.app instanceof instance.Vue)) { + return new Error(`refreshInstance: instance ${instanceId} not found!`) + } + if (instance.Vue && instance.Vue.set) { + for (const key in data) { + instance.Vue.set(instance.app, key, data[key]) + } + } + // Finally `refreshFinish` signal needed. + instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, []) +} + +/** + * Create a fresh instance of Vue for each Weex instance. + */ +function createVueModuleInstance ( + instanceId: string, + weex: Weex +): GlobalAPI { + const exports = {} + VueFactory(exports, weex.document) + const Vue = exports.Vue + + const instance = instanceOptions[instanceId] + + // patch reserved tag detection to account for dynamically registered + // components + const weexRegex = /^weex:/i + const isReservedTag = Vue.config.isReservedTag || (() => false) + const isRuntimeComponent = Vue.config.isRuntimeComponent || (() => false) + Vue.config.isReservedTag = name => { + return (!isRuntimeComponent(name) && weex.supports(`@component/${name}`)) || + isReservedTag(name) || + weexRegex.test(name) + } + Vue.config.parsePlatformTagName = name => name.replace(weexRegex, '') + + // expose weex-specific info + Vue.prototype.$instanceId = instanceId + Vue.prototype.$document = instance.document + + // expose weex native module getter on subVue prototype so that + // vdom runtime modules can access native modules via vnode.context + Vue.prototype.$requireWeexModule = weex.requireModule + + // Hack `Vue` behavior to handle instance information and data + // before root component created. + Vue.mixin({ + beforeCreate () { + const options = this.$options + // root component (vm) + if (options.el) { + // set external data of instance + const dataOption = options.data + const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {} + options.data = Object.assign(internalData, instance.data) + // record instance by id + instance.app = this + } + }, + mounted () { + const options = this.$options + // root component (vm) + if (options.el && weex.document && instance.app === this) { + try { + // Send "createFinish" signal to native. + weex.document.taskCenter.send('dom', { action: 'createFinish' }, []) + } catch (e) {} + } + } + }) + + /** + * @deprecated Just instance variable `weex.config` + * Get instance config. + * @return {object} + */ + Vue.prototype.$getConfig = function () { + if (instance.app instanceof Vue) { + return instance.config + } + } + + return Vue +} + +/** + * DEPRECATED + * Generate HTML5 Timer APIs. An important point is that the callback + * will be converted into callback id when sent to native. So the + * framework can make sure no side effect of the callback happened after + * an instance destroyed. + */ +function getInstanceTimer ( + instanceId: string, + moduleGetter: Function +): Object { + const instance = instanceOptions[instanceId] + const timer = moduleGetter('timer') + const timerAPIs = { + setTimeout: (...args) => { + const handler = function () { + args[0](...args.slice(2)) + } + + timer.setTimeout(handler, args[1]) + return instance.document.taskCenter.callbackManager.lastCallbackId.toString() + }, + setInterval: (...args) => { + const handler = function () { + args[0](...args.slice(2)) + } + + timer.setInterval(handler, args[1]) + return instance.document.taskCenter.callbackManager.lastCallbackId.toString() + }, + clearTimeout: (n) => { + timer.clearTimeout(n) + }, + clearInterval: (n) => { + timer.clearInterval(n) + } + } + return timerAPIs +} diff --git a/node_modules/vue/src/platforms/weex/entry-runtime-factory.js b/node_modules/vue/src/platforms/weex/entry-runtime-factory.js new file mode 100644 index 00000000..166ddf2d --- /dev/null +++ b/node_modules/vue/src/platforms/weex/entry-runtime-factory.js @@ -0,0 +1,6 @@ +// this entry is built and wrapped with a factory function +// used to generate a fresh copy of Vue for every Weex instance. + +import Vue from './runtime/index' + +exports.Vue = Vue diff --git a/node_modules/vue/src/platforms/weex/runtime/components/index.js b/node_modules/vue/src/platforms/weex/runtime/components/index.js new file mode 100644 index 00000000..449db1ad --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/index.js @@ -0,0 +1,9 @@ +import Richtext from './richtext' +import Transition from './transition' +import TransitionGroup from './transition-group' + +export default { + Richtext, + Transition, + TransitionGroup +} diff --git a/node_modules/vue/src/platforms/weex/runtime/components/richtext.js b/node_modules/vue/src/platforms/weex/runtime/components/richtext.js new file mode 100644 index 00000000..a9164c7a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/richtext.js @@ -0,0 +1,82 @@ +/* @flow */ + +function getVNodeType (vnode: VNode): string { + if (!vnode.tag) { + return '' + } + return vnode.tag.replace(/vue\-component\-(\d+\-)?/, '') +} + +function isSimpleSpan (vnode: VNode): boolean { + return vnode.children && + vnode.children.length === 1 && + !vnode.children[0].tag +} + +function parseStyle (vnode: VNode): Object | void { + if (!vnode || !vnode.data) { + return + } + const { staticStyle, staticClass } = vnode.data + if (vnode.data.style || vnode.data.class || staticStyle || staticClass) { + const styles = Object.assign({}, staticStyle, vnode.data.style) + const cssMap = vnode.context.$options.style || {} + const classList = [].concat(staticClass, vnode.data.class) + classList.forEach(name => { + if (name && cssMap[name]) { + Object.assign(styles, cssMap[name]) + } + }) + return styles + } +} + +function convertVNodeChildren (children: Array<VNode>): Array<VNode> | void { + if (!children.length) { + return + } + + return children.map(vnode => { + const type: string = getVNodeType(vnode) + const props: Object = { type } + + // convert raw text node + if (!type) { + props.type = 'span' + props.attr = { + value: (vnode.text || '').trim() + } + } else { + props.style = parseStyle(vnode) + if (vnode.data) { + props.attr = vnode.data.attrs + if (vnode.data.on) { + props.events = vnode.data.on + } + } + if (type === 'span' && isSimpleSpan(vnode)) { + props.attr = props.attr || {} + props.attr.value = vnode.children[0].text.trim() + return props + } + } + + if (vnode.children && vnode.children.length) { + props.children = convertVNodeChildren(vnode.children) + } + + return props + }) +} + +export default { + name: 'richtext', + render (h: Function) { + return h('weex:richtext', { + on: this._events, + attrs: { + value: convertVNodeChildren(this.$options._renderChildren || []) + } + }) + } +} diff --git a/node_modules/vue/src/platforms/weex/runtime/components/transition-group.js b/node_modules/vue/src/platforms/weex/runtime/components/transition-group.js new file mode 100644 index 00000000..4a65965f --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/transition-group.js @@ -0,0 +1,148 @@ +import { warn, extend } from 'core/util/index' +import { transitionProps, extractTransitionData } from './transition' + +const props = extend({ + tag: String, + moveClass: String +}, transitionProps) + +delete props.mode + +export default { + props, + + created () { + const dom = this.$requireWeexModule('dom') + this.getPosition = el => new Promise((resolve, reject) => { + dom.getComponentRect(el.ref, res => { + if (!res.result) { + reject(new Error(`failed to get rect for element: ${el.tag}`)) + } else { + resolve(res.size) + } + }) + }) + + const animation = this.$requireWeexModule('animation') + this.animate = (el, options) => new Promise(resolve => { + animation.transition(el.ref, options, resolve) + }) + }, + + render (h) { + const tag = this.tag || this.$vnode.data.tag || 'span' + const map = Object.create(null) + const prevChildren = this.prevChildren = this.children + const rawChildren = this.$slots.default || [] + const children = this.children = [] + const transitionData = extractTransitionData(this) + + for (let i = 0; i < rawChildren.length; i++) { + const 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 if (process.env.NODE_ENV !== 'production') { + const opts = c.componentOptions + const name = opts + ? (opts.Ctor.options.name || opts.tag) + : c.tag + warn(`<transition-group> children must be keyed: <${name}>`) + } + } + } + + if (prevChildren) { + const kept = [] + const removed = [] + prevChildren.forEach(c => { + c.data.transition = transitionData + + // TODO: record before patch positions + + if (map[c.key]) { + kept.push(c) + } else { + removed.push(c) + } + }) + this.kept = h(tag, null, kept) + this.removed = removed + } + + return h(tag, null, children) + }, + + beforeUpdate () { + // force removing pass + this.__patch__( + this._vnode, + this.kept, + false, // hydrating + true // removeOnly (!important, avoids unnecessary moves) + ) + this._vnode = this.kept + }, + + updated () { + const children = this.prevChildren + const moveClass = this.moveClass || ((this.name || 'v') + '-move') + const moveData = children.length && this.getMoveData(children[0].context, moveClass) + if (!moveData) { + return + } + + // TODO: finish implementing move animations once + // we have access to sync getComponentRect() + + // children.forEach(callPendingCbs) + + // Promise.all(children.map(c => { + // const oldPos = c.data.pos + // const newPos = c.data.newPos + // const dx = oldPos.left - newPos.left + // const dy = oldPos.top - newPos.top + // if (dx || dy) { + // c.data.moved = true + // return this.animate(c.elm, { + // styles: { + // transform: `translate(${dx}px,${dy}px)` + // } + // }) + // } + // })).then(() => { + // children.forEach(c => { + // if (c.data.moved) { + // this.animate(c.elm, { + // styles: { + // transform: '' + // }, + // duration: moveData.duration || 0, + // delay: moveData.delay || 0, + // timingFunction: moveData.timingFunction || 'linear' + // }) + // } + // }) + // }) + }, + + methods: { + getMoveData (context, moveClass) { + const stylesheet = context.$options.style || {} + return stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][moveClass] + } + } +} + +// function callPendingCbs (c) { +// /* istanbul ignore if */ +// if (c.elm._moveCb) { +// c.elm._moveCb() +// } +// /* istanbul ignore if */ +// if (c.elm._enterCb) { +// c.elm._enterCb() +// } +// } diff --git a/node_modules/vue/src/platforms/weex/runtime/components/transition.js b/node_modules/vue/src/platforms/weex/runtime/components/transition.js new file mode 100644 index 00000000..20ba632d --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/transition.js @@ -0,0 +1,9 @@ +// reuse same transition component logic from web +export { + transitionProps, + extractTransitionData +} from 'web/runtime/components/transition' + +import Transition from 'web/runtime/components/transition' + +export default Transition diff --git a/node_modules/vue/src/platforms/weex/runtime/directives/index.js b/node_modules/vue/src/platforms/weex/runtime/directives/index.js new file mode 100755 index 00000000..efba7fa6 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/directives/index.js @@ -0,0 +1,2 @@ +export default { +} diff --git a/node_modules/vue/src/platforms/weex/runtime/index.js b/node_modules/vue/src/platforms/weex/runtime/index.js new file mode 100755 index 00000000..b8e09305 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/index.js @@ -0,0 +1,42 @@ +/* @flow */ + +import Vue from 'core/index' +import { patch } from 'weex/runtime/patch' +import { mountComponent } from 'core/instance/lifecycle' +import platformDirectives from 'weex/runtime/directives/index' +import platformComponents from 'weex/runtime/components/index' + +import { + query, + mustUseProp, + isReservedTag, + isRuntimeComponent, + isUnknownElement +} from 'weex/util/element' + +// install platform specific utils +Vue.config.mustUseProp = mustUseProp +Vue.config.isReservedTag = isReservedTag +Vue.config.isRuntimeComponent = isRuntimeComponent +Vue.config.isUnknownElement = isUnknownElement + +// install platform runtime directives and components +Vue.options.directives = platformDirectives +Vue.options.components = platformComponents + +// install platform patch function +Vue.prototype.__patch__ = patch + +// wrap mount +Vue.prototype.$mount = function ( + el?: any, + hydrating?: boolean +): Component { + return mountComponent( + this, + el && query(el, this.$document), + hydrating + ) +} + +export default Vue diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/attrs.js b/node_modules/vue/src/platforms/weex/runtime/modules/attrs.js new file mode 100755 index 00000000..1b6185ca --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/attrs.js @@ -0,0 +1,44 @@ +/* @flow */ + +import { extend } from 'shared/util' + +function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!oldVnode.data.attrs && !vnode.data.attrs) { + return + } + let key, cur, old + const elm = vnode.elm + const oldAttrs = oldVnode.data.attrs || {} + let attrs = vnode.data.attrs || {} + // clone observed objects, as the user probably wants to mutate it + if (attrs.__ob__) { + attrs = vnode.data.attrs = extend({}, attrs) + } + + const supportBatchUpdate = typeof elm.setAttrs === 'function' + const batchedAttrs = {} + for (key in attrs) { + cur = attrs[key] + old = oldAttrs[key] + if (old !== cur) { + supportBatchUpdate + ? (batchedAttrs[key] = cur) + : elm.setAttr(key, cur) + } + } + for (key in oldAttrs) { + if (attrs[key] == null) { + supportBatchUpdate + ? (batchedAttrs[key] = undefined) + : elm.setAttr(key) + } + } + if (supportBatchUpdate) { + elm.setAttrs(batchedAttrs) + } +} + +export default { + create: updateAttrs, + update: updateAttrs +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/class.js b/node_modules/vue/src/platforms/weex/runtime/modules/class.js new file mode 100755 index 00000000..029b9e9e --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/class.js @@ -0,0 +1,75 @@ +/* @flow */ + +import { extend } from 'shared/util' + +function updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) { + const el = vnode.elm + const ctx = vnode.context + + const data: VNodeData = vnode.data + const oldData: VNodeData = oldVnode.data + if (!data.staticClass && + !data.class && + (!oldData || (!oldData.staticClass && !oldData.class)) + ) { + return + } + + const oldClassList = [] + // unlike web, weex vnode staticClass is an Array + const oldStaticClass: any = oldData.staticClass + if (oldStaticClass) { + oldClassList.push.apply(oldClassList, oldStaticClass) + } + if (oldData.class) { + oldClassList.push.apply(oldClassList, oldData.class) + } + + const classList = [] + // unlike web, weex vnode staticClass is an Array + const staticClass: any = data.staticClass + if (staticClass) { + classList.push.apply(classList, staticClass) + } + if (data.class) { + classList.push.apply(classList, data.class) + } + + if (typeof el.setClassList === 'function') { + el.setClassList(classList) + } else { + const style = getStyle(oldClassList, classList, ctx) + if (typeof el.setStyles === 'function') { + el.setStyles(style) + } else { + for (const key in style) { + el.setStyle(key, style[key]) + } + } + } +} + +function getStyle (oldClassList: Array<string>, classList: Array<string>, ctx: Component): Object { + // style is a weex-only injected object + // compiled from <style> tags in weex files + const stylesheet: any = ctx.$options.style || {} + const result = {} + classList.forEach(name => { + const style = stylesheet[name] + extend(result, style) + }) + oldClassList.forEach(name => { + const style = stylesheet[name] + for (const key in style) { + if (!result.hasOwnProperty(key)) { + result[key] = '' + } + } + }) + return result +} + +export default { + create: updateClass, + update: updateClass +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/events.js b/node_modules/vue/src/platforms/weex/runtime/modules/events.js new file mode 100755 index 00000000..a3f9e25a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/events.js @@ -0,0 +1,57 @@ +/* @flow */ + +import { updateListeners } from 'core/vdom/helpers/update-listeners' + +let target: any + +function add ( + event: string, + handler: Function, + once: boolean, + capture: boolean, + passive?: boolean, + params?: Array<any> +) { + if (capture) { + console.log('Weex do not support event in bubble phase.') + return + } + if (once) { + const oldHandler = handler + const _target = target // save current target element in closure + handler = function (ev) { + const res = arguments.length === 1 + ? oldHandler(ev) + : oldHandler.apply(null, arguments) + if (res !== null) { + remove(event, null, null, _target) + } + } + } + target.addEvent(event, handler, params) +} + +function remove ( + event: string, + handler: any, + capture: any, + _target?: any +) { + (_target || target).removeEvent(event) +} + +function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!oldVnode.data.on && !vnode.data.on) { + return + } + const on = vnode.data.on || {} + const oldOn = oldVnode.data.on || {} + target = vnode.elm + updateListeners(on, oldOn, add, remove, vnode.context) + target = undefined +} + +export default { + create: updateDOMListeners, + update: updateDOMListeners +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/index.js b/node_modules/vue/src/platforms/weex/runtime/modules/index.js new file mode 100755 index 00000000..dd384484 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/index.js @@ -0,0 +1,13 @@ +import attrs from './attrs' +import klass from './class' +import events from './events' +import style from './style' +import transition from './transition' + +export default [ + attrs, + klass, + events, + style, + transition +] diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/style.js b/node_modules/vue/src/platforms/weex/runtime/modules/style.js new file mode 100755 index 00000000..bdabd17b --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/style.js @@ -0,0 +1,84 @@ +/* @flow */ + +import { extend, cached, camelize } from 'shared/util' + +const normalize = cached(camelize) + +function createStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!vnode.data.staticStyle) { + updateStyle(oldVnode, vnode) + return + } + const elm = vnode.elm + const staticStyle = vnode.data.staticStyle + const supportBatchUpdate = typeof elm.setStyles === 'function' + const batchedStyles = {} + for (const name in staticStyle) { + if (staticStyle[name]) { + supportBatchUpdate + ? (batchedStyles[normalize(name)] = staticStyle[name]) + : elm.setStyle(normalize(name), staticStyle[name]) + } + } + if (supportBatchUpdate) { + elm.setStyles(batchedStyles) + } + updateStyle(oldVnode, vnode) +} + +function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!oldVnode.data.style && !vnode.data.style) { + return + } + let cur, name + const elm = vnode.elm + const oldStyle: any = oldVnode.data.style || {} + let style: any = vnode.data.style || {} + + const needClone = style.__ob__ + + // handle array syntax + if (Array.isArray(style)) { + style = vnode.data.style = toObject(style) + } + + // clone the style for future updates, + // in case the user mutates the style object in-place. + if (needClone) { + style = vnode.data.style = extend({}, style) + } + + const supportBatchUpdate = typeof elm.setStyles === 'function' + const batchedStyles = {} + for (name in oldStyle) { + if (!style[name]) { + supportBatchUpdate + ? (batchedStyles[normalize(name)] = '') + : elm.setStyle(normalize(name), '') + } + } + for (name in style) { + cur = style[name] + supportBatchUpdate + ? (batchedStyles[normalize(name)] = cur) + : elm.setStyle(normalize(name), cur) + } + if (supportBatchUpdate) { + elm.setStyles(batchedStyles) + } +} + +function toObject (arr) { + const res = {} + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]) + } + } + return res +} + +export default { + create: createStyle, + update: updateStyle +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/transition.js b/node_modules/vue/src/platforms/weex/runtime/modules/transition.js new file mode 100644 index 00000000..e474c2b3 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/transition.js @@ -0,0 +1,270 @@ +import { warn } from 'core/util/debug' +import { extend, once, noop } from 'shared/util' +import { activeInstance } from 'core/instance/lifecycle' +import { resolveTransition } from 'web/runtime/transition-util' + +export default { + create: enter, + activate: enter, + remove: leave +} + +function enter (_, vnode) { + const el = vnode.elm + + // call leave callback now + if (el._leaveCb) { + el._leaveCb.cancelled = true + el._leaveCb() + } + + const data = resolveTransition(vnode.data.transition) + if (!data) { + return + } + + /* istanbul ignore if */ + if (el._enterCb) { + return + } + + const { + enterClass, + enterToClass, + enterActiveClass, + appearClass, + appearToClass, + appearActiveClass, + beforeEnter, + enter, + afterEnter, + enterCancelled, + beforeAppear, + appear, + afterAppear, + appearCancelled + } = data + + let context = activeInstance + let transitionNode = activeInstance.$vnode + while (transitionNode && transitionNode.parent) { + transitionNode = transitionNode.parent + context = transitionNode.context + } + + const isAppear = !context._isMounted || !vnode.isRootInsert + + if (isAppear && !appear && appear !== '') { + return + } + + const startClass = isAppear ? appearClass : enterClass + const toClass = isAppear ? appearToClass : enterToClass + const activeClass = isAppear ? appearActiveClass : enterActiveClass + const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter + const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter + const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter + const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled + + const userWantsControl = + enterHook && + // enterHook may be a bound method which exposes + // the length of original fn as _length + (enterHook._length || enterHook.length) > 1 + + const stylesheet = vnode.context.$options.style || {} + const startState = stylesheet[startClass] + const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {} + const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass, vnode.context) + const needAnimation = Object.keys(endState).length > 0 + + const cb = el._enterCb = once(() => { + if (cb.cancelled) { + enterCancelledHook && enterCancelledHook(el) + } else { + afterEnterHook && afterEnterHook(el) + } + el._enterCb = null + }) + + // We need to wait until the native element has been inserted, but currently + // there's no API to do that. So we have to wait "one frame" - not entirely + // sure if this is guaranteed to be enough (e.g. on slow devices?) + setTimeout(() => { + const parent = el.parentNode + const pendingNode = parent && parent._pending && parent._pending[vnode.key] + if (pendingNode && + pendingNode.context === vnode.context && + pendingNode.tag === vnode.tag && + pendingNode.elm._leaveCb + ) { + pendingNode.elm._leaveCb() + } + enterHook && enterHook(el, cb) + + if (needAnimation) { + const animation = vnode.context.$requireWeexModule('animation') + animation.transition(el.ref, { + styles: endState, + duration: transitionProperties.duration || 0, + delay: transitionProperties.delay || 0, + timingFunction: transitionProperties.timingFunction || 'linear' + }, userWantsControl ? noop : cb) + } else if (!userWantsControl) { + cb() + } + }, 16) + + // start enter transition + beforeEnterHook && beforeEnterHook(el) + + if (startState) { + if (typeof el.setStyles === 'function') { + el.setStyles(startState) + } else { + for (const key in startState) { + el.setStyle(key, startState[key]) + } + } + } + + if (!needAnimation && !userWantsControl) { + cb() + } +} + +function leave (vnode, rm) { + const el = vnode.elm + + // call enter callback now + if (el._enterCb) { + el._enterCb.cancelled = true + el._enterCb() + } + + const data = resolveTransition(vnode.data.transition) + if (!data) { + return rm() + } + + if (el._leaveCb) { + return + } + + const { + leaveClass, + leaveToClass, + leaveActiveClass, + beforeLeave, + leave, + afterLeave, + leaveCancelled, + delayLeave + } = data + + const userWantsControl = + leave && + // leave hook may be a bound method which exposes + // the length of original fn as _length + (leave._length || leave.length) > 1 + + const stylesheet = vnode.context.$options.style || {} + const startState = stylesheet[leaveClass] + const endState = stylesheet[leaveToClass] || stylesheet[leaveActiveClass] + const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][leaveActiveClass]) || {} + + const cb = el._leaveCb = once(() => { + if (el.parentNode && el.parentNode._pending) { + el.parentNode._pending[vnode.key] = null + } + if (cb.cancelled) { + leaveCancelled && leaveCancelled(el) + } else { + rm() + afterLeave && afterLeave(el) + } + el._leaveCb = null + }) + + if (delayLeave) { + delayLeave(performLeave) + } else { + performLeave() + } + + function performLeave () { + const animation = vnode.context.$requireWeexModule('animation') + // 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 (startState) { + animation.transition(el.ref, { + styles: startState + }, next) + } else { + next() + } + + function next () { + animation.transition(el.ref, { + styles: endState, + duration: transitionProperties.duration || 0, + delay: transitionProperties.delay || 0, + timingFunction: transitionProperties.timingFunction || 'linear' + }, userWantsControl ? noop : cb) + } + + leave && leave(el, cb) + if (!endState && !userWantsControl) { + cb() + } + } +} + +// determine the target animation style for an entering transition. +function getEnterTargetState (el, stylesheet, startClass, endClass, activeClass, vm) { + const targetState = {} + const startState = stylesheet[startClass] + const endState = stylesheet[endClass] + const activeState = stylesheet[activeClass] + // 1. fallback to element's default styling + if (startState) { + for (const key in startState) { + targetState[key] = el.style[key] + if ( + process.env.NODE_ENV !== 'production' && + targetState[key] == null && + (!activeState || activeState[key] == null) && + (!endState || endState[key] == null) + ) { + warn( + `transition property "${key}" is declared in enter starting class (.${startClass}), ` + + `but not declared anywhere in enter ending class (.${endClass}), ` + + `enter active cass (.${activeClass}) or the element's default styling. ` + + `Note in Weex, CSS properties need explicit values to be transitionable.` + ) + } + } + } + // 2. if state is mixed in active state, extract them while excluding + // transition properties + if (activeState) { + for (const key in activeState) { + if (key.indexOf('transition') !== 0) { + targetState[key] = activeState[key] + } + } + } + // 3. explicit endState has highest priority + if (endState) { + extend(targetState, endState) + } + return targetState +} diff --git a/node_modules/vue/src/platforms/weex/runtime/node-ops.js b/node_modules/vue/src/platforms/weex/runtime/node-ops.js new file mode 100755 index 00000000..0438ec5c --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/node-ops.js @@ -0,0 +1,91 @@ +/* @flow */ +declare var document: WeexDocument; + +import TextNode from 'weex/runtime/text-node' + +export const namespaceMap = {} + +export function createElement (tagName: string): WeexElement { + return document.createElement(tagName) +} + +export function createElementNS (namespace: string, tagName: string): WeexElement { + return document.createElement(namespace + ':' + tagName) +} + +export function createTextNode (text: string) { + return new TextNode(text) +} + +export function createComment (text: string) { + return document.createComment(text) +} + +export function insertBefore ( + node: WeexElement, + target: WeexElement, + before: WeexElement +) { + if (target.nodeType === 3) { + if (node.type === 'text') { + node.setAttr('value', target.text) + target.parentNode = node + } else { + const text = createElement('text') + text.setAttr('value', target.text) + node.insertBefore(text, before) + } + return + } + node.insertBefore(target, before) +} + +export function removeChild (node: WeexElement, child: WeexElement) { + if (child.nodeType === 3) { + node.setAttr('value', '') + return + } + node.removeChild(child) +} + +export function appendChild (node: WeexElement, child: WeexElement) { + if (child.nodeType === 3) { + if (node.type === 'text') { + node.setAttr('value', child.text) + child.parentNode = node + } else { + const text = createElement('text') + text.setAttr('value', child.text) + node.appendChild(text) + } + return + } + + node.appendChild(child) +} + +export function parentNode (node: WeexElement): WeexElement | void { + return node.parentNode +} + +export function nextSibling (node: WeexElement): WeexElement | void { + return node.nextSibling +} + +export function tagName (node: WeexElement): string { + return node.type +} + +export function setTextContent (node: WeexElement, text: string) { + if (node.parentNode) { + node.parentNode.setAttr('value', text) + } +} + +export function setAttribute (node: WeexElement, key: string, val: any) { + node.setAttr(key, val) +} + +export function setStyleScope (node: WeexElement, scopeId: string) { + node.setAttr('@styleScope', scopeId) +} diff --git a/node_modules/vue/src/platforms/weex/runtime/patch.js b/node_modules/vue/src/platforms/weex/runtime/patch.js new file mode 100644 index 00000000..5e70d143 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/patch.js @@ -0,0 +1,16 @@ +/* @flow */ + +import * as nodeOps from 'weex/runtime/node-ops' +import { createPatchFunction } from 'core/vdom/patch' +import baseModules from 'core/vdom/modules/index' +import platformModules from 'weex/runtime/modules/index' + +// the directive module should be applied last, after all +// built-in modules have been applied. +const modules = platformModules.concat(baseModules) + +export const patch: Function = createPatchFunction({ + nodeOps, + modules, + LONG_LIST_THRESHOLD: 10 +}) diff --git a/node_modules/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js b/node_modules/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js new file mode 100644 index 00000000..17e8c018 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js @@ -0,0 +1,34 @@ +/* @flow */ + +import { warn } from 'core/util/debug' +import { handleError } from 'core/util/error' +import { RECYCLE_LIST_MARKER } from 'weex/util/index' +import { createComponentInstanceForVnode } from 'core/vdom/create-component' +import { resolveVirtualComponent } from './virtual-component' + +export function isRecyclableComponent (vnode: VNodeWithData): boolean { + return vnode.data.attrs + ? (RECYCLE_LIST_MARKER in vnode.data.attrs) + : false +} + +export function renderRecyclableComponentTemplate (vnode: MountedComponentVNode): VNode { + // $flow-disable-line + delete vnode.data.attrs[RECYCLE_LIST_MARKER] + resolveVirtualComponent(vnode) + const vm = createComponentInstanceForVnode(vnode) + const render = (vm.$options: any)['@render'] + if (render) { + try { + return render.call(vm) + } catch (err) { + handleError(err, vm, `@render`) + } + } else { + warn( + `@render function not defined on component used in <recycle-list>. ` + + `Make sure to declare \`recyclable="true"\` on the component's template.`, + vm + ) + } +} diff --git a/node_modules/vue/src/platforms/weex/runtime/recycle-list/virtual-component.js b/node_modules/vue/src/platforms/weex/runtime/recycle-list/virtual-component.js new file mode 100644 index 00000000..e5498d26 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/recycle-list/virtual-component.js @@ -0,0 +1,136 @@ +/* @flow */ + +// https://github.com/Hanks10100/weex-native-directive/tree/master/component + +import { mergeOptions, isPlainObject, noop } from 'core/util/index' +import Watcher from 'core/observer/watcher' +import { initProxy } from 'core/instance/proxy' +import { initState, getData } from 'core/instance/state' +import { initRender } from 'core/instance/render' +import { initEvents } from 'core/instance/events' +import { initProvide, initInjections } from 'core/instance/inject' +import { initLifecycle, callHook } from 'core/instance/lifecycle' +import { initInternalComponent, resolveConstructorOptions } from 'core/instance/init' +import { registerComponentHook, updateComponentData } from '../../util/index' + +let uid = 0 + +// override Vue.prototype._init +function initVirtualComponent (options: Object = {}) { + const vm: Component = this + const componentId = options.componentId + + // virtual component uid + vm._uid = `virtual-component-${uid++}` + + // a flag to avoid this being observed + vm._isVue = true + // merge options + if (options && options._isComponent) { + // optimize internal component instantiation + // since dynamic options merging is pretty slow, and none of the + // internal component options needs special treatment. + initInternalComponent(vm, options) + } else { + vm.$options = mergeOptions( + resolveConstructorOptions(vm.constructor), + options || {}, + vm + ) + } + + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + initProxy(vm) + } else { + vm._renderProxy = vm + } + + vm._self = vm + initLifecycle(vm) + initEvents(vm) + initRender(vm) + callHook(vm, 'beforeCreate') + initInjections(vm) // resolve injections before data/props + initState(vm) + initProvide(vm) // resolve provide after data/props + callHook(vm, 'created') + + // send initial data to native + const data = vm.$options.data + const params = typeof data === 'function' + ? getData(data, vm) + : data || {} + if (isPlainObject(params)) { + updateComponentData(componentId, params) + } + + registerComponentHook(componentId, 'lifecycle', 'attach', () => { + callHook(vm, 'beforeMount') + + const updateComponent = () => { + vm._update(vm._vnode, false) + } + new Watcher(vm, updateComponent, noop, null, true) + + vm._isMounted = true + callHook(vm, 'mounted') + }) + + registerComponentHook(componentId, 'lifecycle', 'detach', () => { + vm.$destroy() + }) +} + +// override Vue.prototype._update +function updateVirtualComponent (vnode?: VNode) { + const vm: Component = this + const componentId = vm.$options.componentId + if (vm._isMounted) { + callHook(vm, 'beforeUpdate') + } + vm._vnode = vnode + if (vm._isMounted && componentId) { + // TODO: data should be filtered and without bindings + const data = Object.assign({}, vm._data) + updateComponentData(componentId, data, () => { + callHook(vm, 'updated') + }) + } +} + +// listening on native callback +export function resolveVirtualComponent (vnode: MountedComponentVNode): VNode { + const BaseCtor = vnode.componentOptions.Ctor + const VirtualComponent = BaseCtor.extend({}) + const cid = VirtualComponent.cid + VirtualComponent.prototype._init = initVirtualComponent + VirtualComponent.prototype._update = updateVirtualComponent + + vnode.componentOptions.Ctor = BaseCtor.extend({ + beforeCreate () { + // const vm: Component = this + + // TODO: listen on all events and dispatch them to the + // corresponding virtual components according to the componentId. + // vm._virtualComponents = {} + const createVirtualComponent = (componentId, propsData) => { + // create virtual component + // const subVm = + new VirtualComponent({ + componentId, + propsData + }) + // if (vm._virtualComponents) { + // vm._virtualComponents[componentId] = subVm + // } + } + + registerComponentHook(cid, 'lifecycle', 'create', createVirtualComponent) + }, + beforeDestroy () { + delete this._virtualComponents + } + }) +} + diff --git a/node_modules/vue/src/platforms/weex/runtime/text-node.js b/node_modules/vue/src/platforms/weex/runtime/text-node.js new file mode 100644 index 00000000..0720a05a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/text-node.js @@ -0,0 +1,9 @@ +let latestNodeId = 1 + +export default function TextNode (text) { + this.instanceId = '' + this.nodeId = latestNodeId++ + this.parentNode = null + this.nodeType = 3 + this.text = text +} diff --git a/node_modules/vue/src/platforms/weex/util/element.js b/node_modules/vue/src/platforms/weex/util/element.js new file mode 100644 index 00000000..97e97feb --- /dev/null +++ b/node_modules/vue/src/platforms/weex/util/element.js @@ -0,0 +1,52 @@ +/* @flow */ + +// These util functions are split into its own file because Rollup cannot drop +// makeMap() due to potential side effects, so these variables end up +// bloating the web builds. + +import { makeMap } from 'shared/util' + +export const isReservedTag = makeMap( + 'template,script,style,element,content,slot,link,meta,svg,view,' + + 'a,div,img,image,text,span,input,switch,textarea,spinner,select,' + + 'slider,slider-neighbor,indicator,canvas,' + + 'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' + + 'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown', + true +) + +// Elements that you can, intentionally, leave open (and which close themselves) +// more flexible than web +export const canBeLeftOpenTag = makeMap( + 'web,spinner,switch,video,textarea,canvas,' + + 'indicator,marquee,countdown', + true +) + +export const isRuntimeComponent = makeMap( + 'richtext,transition,transition-group', + true +) + +export const isUnaryTag = makeMap( + 'embed,img,image,input,link,meta', + true +) + +export function mustUseProp (tag: string, type: ?string, name: string): boolean { + return false +} + +export function getTagNamespace (tag?: string): string | void { } + +export function isUnknownElement (tag?: string): boolean { + return false +} + +export function query (el: string | Element, document: Object) { + // document is injected by weex factory wrapper + const placeholder = document.createComment('root') + placeholder.hasAttribute = placeholder.removeAttribute = function () {} // hack for patch + document.documentElement.appendChild(placeholder) + return placeholder +} diff --git a/node_modules/vue/src/platforms/weex/util/index.js b/node_modules/vue/src/platforms/weex/util/index.js new file mode 100755 index 00000000..8181d70e --- /dev/null +++ b/node_modules/vue/src/platforms/weex/util/index.js @@ -0,0 +1,40 @@ +/* @flow */ +declare var document: WeexDocument; + +import { warn } from 'core/util/index' + +export const RECYCLE_LIST_MARKER = '@inRecycleList' + +// Register the component hook to weex native render engine. +// The hook will be triggered by native, not javascript. +export function registerComponentHook ( + componentId: string, + type: string, // hook type, could be "lifecycle" or "instance" + hook: string, // hook name + fn: Function +) { + if (!document || !document.taskCenter) { + warn(`Can't find available "document" or "taskCenter".`) + return + } + if (typeof document.taskCenter.registerHook === 'function') { + return document.taskCenter.registerHook(componentId, type, hook, fn) + } + warn(`Failed to register component hook "${type}@${hook}#${componentId}".`) +} + +// Updates the state of the component to weex native render engine. +export function updateComponentData ( + componentId: string, + newData: Object | void, + callback?: Function +) { + if (!document || !document.taskCenter) { + warn(`Can't find available "document" or "taskCenter".`) + return + } + if (typeof document.taskCenter.updateData === 'function') { + return document.taskCenter.updateData(componentId, newData, callback) + } + warn(`Failed to update component data (${componentId}).`) +} diff --git a/node_modules/vue/src/platforms/weex/util/parser.js b/node_modules/vue/src/platforms/weex/util/parser.js new file mode 100644 index 00000000..081908e8 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/util/parser.js @@ -0,0 +1,60 @@ +/* @flow */ + +// import { warn } from 'core/util/index' + +// this will be preserved during build +// $flow-disable-line +const acorn = require('acorn') // $flow-disable-line +const walk = require('acorn/dist/walk') // $flow-disable-line +const escodegen = require('escodegen') + +export function nodeToBinding (node: Object): any { + switch (node.type) { + case 'Literal': return node.value + case 'Identifier': + case 'UnaryExpression': + case 'BinaryExpression': + case 'LogicalExpression': + case 'ConditionalExpression': + case 'MemberExpression': return { '@binding': escodegen.generate(node) } + case 'ArrayExpression': return node.elements.map(_ => nodeToBinding(_)) + case 'ObjectExpression': { + const object = {} + node.properties.forEach(prop => { + if (!prop.key || prop.key.type !== 'Identifier') { + return + } + const key = escodegen.generate(prop.key) + const value = nodeToBinding(prop.value) + if (key && value) { + object[key] = value + } + }) + return object + } + default: { + // warn(`Not support ${node.type}: "${escodegen.generate(node)}"`) + return '' + } + } +} + +export function generateBinding (exp: ?string): any { + if (exp && typeof exp === 'string') { + let ast = null + try { + ast = acorn.parse(`(${exp})`) + } catch (e) { + // warn(`Failed to parse the expression: "${exp}"`) + return '' + } + + let output = '' + walk.simple(ast, { + Expression (node) { + output = nodeToBinding(node) + } + }) + return output + } +} |
