diff --git a/dist/legacy/dev/yox.esm.js b/dist/legacy/dev/yox.esm.js index d820430c..3c9147bc 100644 --- a/dist/legacy/dev/yox.esm.js +++ b/dist/legacy/dev/yox.esm.js @@ -1,5 +1,5 @@ /** - * yox.js v1.0.0-alpha.89 + * yox.js v1.0.0-alpha.90 * (c) 2017-2019 musicode * Released under the MIT License. */ @@ -234,6 +234,9 @@ class CustomEvent { * 可以传事件名称,也可以传原生事件对象 */ constructor(type, originalEvent) { + // 这里不设置命名空间 + // 因为有没有命名空间取决于 Emitter 的构造函数有没有传 true + // CustomEvent 自己无法决定 this.type = type; this.phase = CustomEvent.PHASE_CURRENT; if (originalEvent) { @@ -1065,7 +1068,7 @@ class Emitter { * @param filter 自定义过滤器 */ fire(type, args, filter) { - let instance = this, namespace = string(type) ? instance.parse(type) : type, list = instance.listeners[namespace.name], isComplete = TRUE; + let instance = this, namespace = string(type) ? instance.parse(type) : type, list = instance.listeners[namespace.type], isComplete = TRUE; if (list) { // 避免遍历过程中,数组发生变化,比如增删了 list = copy(list); @@ -1101,7 +1104,7 @@ class Emitter { options.num = options.num ? (options.num + 1) : 1; // 注册的 listener 可以指定最大执行次数 if (options.num === options.max) { - instance.off(namespace.key, options.fn); + instance.off(namespace, options.fn); } // 如果没有返回 false,而是调用了 event.stop 也算是返回 false if (event) { @@ -1126,13 +1129,13 @@ class Emitter { * @param listener */ on(type, listener) { - const instance = this, { listeners } = instance, options = func(listener) + const instance = this, listeners = instance.listeners, options = func(listener) ? { fn: listener } : listener; if (object(options) && func(options.fn)) { - const { name, ns } = instance.parse(type); - options.ns = ns; - push(listeners[name] || (listeners[name] = []), options); + const namespace = string(type) ? instance.parse(type) : type; + options.ns = namespace.ns; + push(listeners[namespace.type] || (listeners[namespace.type] = []), options); } else { fatal(`emitter.on(type, listener) invoke failed:\n\n"listener" is expected to be a Function or an EmitterOptions.\n`); @@ -1145,9 +1148,9 @@ class Emitter { * @param listener */ off(type, listener) { - const instance = this, { listeners } = instance; + const instance = this, listeners = instance.listeners; if (type) { - const { name, ns } = instance.parse(type), matchListener = createMatchListener(listener), each$1 = function (list, name) { + const namespace = string(type) ? instance.parse(type) : type, name = namespace.type, ns = namespace.ns, matchListener = createMatchListener(listener), each$1 = function (list, name) { each(list, function (options, index) { if (matchListener(options) && matchNamespace(ns, options)) { list.splice(index, 1); @@ -1192,7 +1195,7 @@ class Emitter { * @param listener */ has(type, listener) { - let instance = this, { listeners } = instance, { name, ns } = instance.parse(type), result = TRUE, matchListener = createMatchListener(listener), each$1 = function (list) { + let instance = this, listeners = instance.listeners, namespace = string(type) ? instance.parse(type) : type, name = namespace.type, ns = namespace.ns, result = TRUE, matchListener = createMatchListener(listener), each$1 = function (list) { each(list, function (options) { if (matchListener(options) && matchNamespace(ns, options)) { return result = FALSE; @@ -1216,15 +1219,17 @@ class Emitter { * @param type */ parse(type) { + // 这里 ns 必须为字符串 + // 用于区分 event 对象是否已完成命名空间的解析 const result = { - key: type, - name: type, + type, ns: EMPTY_STRING, }; + // 是否开启命名空间 if (this.ns) { const index = indexOf$1(type, RAW_DOT); if (index >= 0) { - result.name = slice(type, 0, index); + result.type = slice(type, 0, index); result.ns = slice(type, index + 1); } } @@ -6946,7 +6951,15 @@ class Yox { // 外部为了使用方便,fire(type) 或 fire(type, data) 就行了 // 内部为了保持格式统一 // 需要转成 Event,这样还能知道 target 是哪个组件 - let instance = this, { $emitter, $parent, $children } = instance, event = type instanceof CustomEvent ? type : new CustomEvent(type), namespace = event.ns || (event.ns = $emitter.parse(event.type)), args = [event], isComplete; + let instance = this, { $emitter, $parent, $children } = instance, event = type instanceof CustomEvent ? type : new CustomEvent(type), args = [event], isComplete; + // 创建完 CustomEvent,如果没有人为操作 + // 它的 ns 为 undefined + // 这里先解析出命名空间,避免每次 fire 都要解析 + if (isUndef(event.ns)) { + const namespace = $emitter.parse(event.type); + event.type = namespace.type; + event.ns = namespace.ns; + } // 告诉外部是谁发出的事件 if (!event.target) { event.target = instance; @@ -6961,11 +6974,11 @@ class Yox { // 如果手动 fire 带上了事件命名空间 // 则命名空间不能是 native,因为 native 有特殊用处 { - if (namespace.ns === MODIFER_NATIVE) { - error(`"${event.type}": The namespace "${MODIFER_NATIVE}" is not permitted.`); + if (event.ns === MODIFER_NATIVE) { + error(`The namespace "${MODIFER_NATIVE}" is not permitted.`); } } - isComplete = $emitter.fire(namespace, args); + isComplete = $emitter.fire(event, args); if (isComplete) { if (downward) { if ($children) { @@ -7322,7 +7335,7 @@ class Yox { /** * core 版本 */ -Yox.version = "1.0.0-alpha.89"; +Yox.version = "1.0.0-alpha.90"; /** * 方便外部共用的通用逻辑,特别是写插件,减少重复代码 */ diff --git a/dist/legacy/dev/yox.esm.js.map b/dist/legacy/dev/yox.esm.js.map index 573bc225..da21c2a0 100644 --- a/dist/legacy/dev/yox.esm.js.map +++ b/dist/legacy/dev/yox.esm.js.map @@ -1 +1 @@ -{"version":3,"file":"yox.esm.js","sources":["../../../../yox-config/src/config.ts","../../../../yox-common/src/util/env.ts","../../../../yox-common/src/function/isDef.ts","../../../../yox-common/src/function/isUndef.ts","../../../../yox-common/src/util/is.ts","../../../../yox-common/src/function/execute.ts","../../../../yox-common/src/util/CustomEvent.ts","../../../../yox-common/src/util/array.ts","../../../../yox-common/src/util/string.ts","../../../../yox-common/src/util/keypath.ts","../../../../yox-common/src/util/holder.ts","../../../../yox-common/src/util/object.ts","../../../../yox-common/src/function/toString.ts","../../../../yox-common/src/util/logger.ts","../../../../yox-common/src/util/Emitter.ts","../../../../yox-common/src/function/isNative.ts","../../../../yox-common/src/function/nextTick.ts","../../../../yox-common/src/util/NextTask.ts","../../../../yox-common/src/function/guid.ts","../../../../yox-snabbdom/src/field.ts","../../../../yox-snabbdom/src/nativeAttr.ts","../../../../yox-snabbdom/src/nativeProp.ts","../../../../yox-snabbdom/src/directive.ts","../../../../yox-snabbdom/src/component.ts","../../../../yox-snabbdom/src/snabbdom.ts","../../../../yox-template-compiler/src/nodeType.ts","../../../../yox-template-compiler/src/helper.ts","../../../../yox-template-compiler/src/creator.ts","../../../../yox-template-compiler/src/platform/web.ts","../../../../yox-common/src/function/toNumber.ts","../../../../yox-expression-compiler/src/nodeType.ts","../../../../yox-expression-compiler/src/creator.ts","../../../../yox-expression-compiler/src/interpreter.ts","../../../../yox-expression-compiler/src/compiler.ts","../../../../yox-template-compiler/src/compiler.ts","../../../../yox-common/src/util/generator.ts","../../../../yox-expression-compiler/src/generator.ts","../../../../yox-template-compiler/src/generator.ts","../../../../yox-template-compiler/src/renderer.ts","../../../../yox-dom/src/dom.ts","../../../../yox-observer/src/Computed.ts","../../../../yox-observer/src/function/matchBest.ts","../../../../yox-observer/src/function/readValue.ts","../../../../yox-observer/src/function/diffString.ts","../../../../yox-observer/src/function/diffArray.ts","../../../../yox-observer/src/function/diffObject.ts","../../../../yox-observer/src/function/diffRecursion.ts","../../../../yox-observer/src/function/diffWatcher.ts","../../../../yox-observer/src/function/filterWatcher.ts","../../../../yox-observer/src/function/formatWatcherOptions.ts","../../../../yox-observer/src/Observer.ts","../../../../yox-common/src/function/debounce.ts","../../../src/directive/event.ts","../../../src/directive/model.ts","../../../src/directive/binding.ts","../../../src/Yox.ts"],"sourcesContent":["export const SYNTAX_IF = '#if'\nexport const SYNTAX_ELSE = 'else'\nexport const SYNTAX_ELSE_IF = 'else if'\nexport const SYNTAX_EACH = '#each'\nexport const SYNTAX_PARTIAL = '#partial'\nexport const SYNTAX_IMPORT = '>'\nexport const SYNTAX_SPREAD = '...'\nexport const SYNTAX_COMMENT = /^!\\s/\n\nexport const SLOT_DATA_PREFIX = '$slot_'\nexport const SLOT_NAME_DEFAULT = 'children'\n\nexport const HINT_STRING = 1\nexport const HINT_NUMBER = 2\nexport const HINT_BOOLEAN = 3\n\nexport const DIRECTIVE_ON = 'on'\nexport const DIRECTIVE_LAZY = 'lazy'\nexport const DIRECTIVE_MODEL = 'model'\nexport const DIRECTIVE_EVENT = 'event'\nexport const DIRECTIVE_BINDING = 'binding'\nexport const DIRECTIVE_CUSTOM = 'o'\n\nexport const MODIFER_NATIVE = 'native'\n\nexport const MODEL_PROP_DEFAULT = 'value'\n\nexport const NAMESPACE_HOOK = '.hook'\n\nexport const HOOK_BEFORE_CREATE = 'beforeCreate'\nexport const HOOK_AFTER_CREATE = 'afterCreate'\nexport const HOOK_BEFORE_MOUNT = 'beforeMount'\nexport const HOOK_AFTER_MOUNT = 'afterMount'\nexport const HOOK_BEFORE_UPDATE = 'beforeUpdate'\nexport const HOOK_AFTER_UPDATE = 'afterUpdate'\nexport const HOOK_BEFORE_DESTROY = 'beforeDestroy'\nexport const HOOK_AFTER_DESTROY = 'afterDestroy'\n\n// 路由钩子\nexport const HOOK_BEFORE_ROUTE_ENTER = 'beforeRouteEnter'\nexport const HOOK_AFTER_ROUTE_ENTER = 'afterRouteEnter'\nexport const HOOK_BEFORE_ROUTE_UPDATE = 'beforeRouteUpdate'\nexport const HOOK_AFTER_ROUTE_UPDATE = 'afterRouteUpdate'\nexport const HOOK_BEFORE_ROUTE_LEAVE = 'beforeRouteLeave'\nexport const HOOK_AFTER_ROUTE_LEAVE = 'afterRouteLeave'\n","/**\n * 为了压缩,定义的常量\n */\nexport const TRUE = true\nexport const FALSE = false\nexport const NULL = null\nexport const UNDEFINED = void 0\nexport const MINUS_ONE = -1\n\nexport const RAW_TRUE = 'true'\nexport const RAW_FALSE = 'false'\nexport const RAW_NULL = 'null'\nexport const RAW_UNDEFINED = 'undefined'\n\nexport const RAW_KEY = 'key'\nexport const RAW_REF = 'ref'\nexport const RAW_SLOT = 'slot'\nexport const RAW_NAME = 'name'\n\nexport const RAW_FILTER = 'filter'\nexport const RAW_PARTIAL = 'partial'\nexport const RAW_COMPONENT = 'component'\nexport const RAW_DIRECTIVE = 'directive'\nexport const RAW_TRANSITION = 'transition'\n\nexport const RAW_THIS = 'this'\nexport const RAW_VALUE = 'value'\nexport const RAW_LENGTH = 'length'\nexport const RAW_FUNCTION = 'function'\nexport const RAW_TEMPLATE = 'template'\nexport const RAW_WILDCARD = '*'\nexport const RAW_DOT = '.'\n\nexport const KEYPATH_PARENT = '..'\nexport const KEYPATH_CURRENT = RAW_THIS\n\n/**\n * Single instance for window in browser\n */\nexport const WINDOW = typeof window !== RAW_UNDEFINED ? window : UNDEFINED\n\n/**\n * Single instance for document in browser\n */\nexport const DOCUMENT = typeof document !== RAW_UNDEFINED ? document : UNDEFINED\n\n/**\n * Single instance for global in nodejs or browser\n */\nexport const GLOBAL = typeof global !== RAW_UNDEFINED ? global : WINDOW\n\n/**\n * tap 事件\n *\n * 非常有用的抽象事件,比如 pc 端是 click 事件,移动端是 touchend 事件\n *\n * 这样只需 on-tap=\"handler\" 就可以完美兼容各端\n *\n * 框架未实现此事件,通过 Yox.dom.addSpecialEvent 提供给外部扩展\n *\n */\nexport const EVENT_TAP = 'tap'\n\n/**\n * 点击事件\n */\nexport const EVENT_CLICK = 'click'\n\n/**\n * 输入事件\n */\nexport const EVENT_INPUT = 'input'\n\n/**\n * 变化事件\n */\nexport const EVENT_CHANGE = 'change'\n\n/**\n * 唯一内置的特殊事件:model\n */\nexport const EVENT_MODEL = 'model'\n\n/**\n * Single instance for noop function\n */\nexport const EMPTY_FUNCTION = function () {\n /** yox */\n}\n\n/**\n * 空对象,很多地方会用到,比如 `a || EMPTY_OBJECT` 确保是个对象\n */\nexport const EMPTY_OBJECT = Object.freeze({})\n\n/**\n * 空数组\n */\nexport const EMPTY_ARRAY = Object.freeze([])\n\n/**\n * 空字符串\n */\nexport const EMPTY_STRING = ''\n","import * as env from '../util/env'\n\nexport default function (target: any): boolean {\n return target !== env.UNDEFINED\n}\n","import * as env from '../util/env'\n\nexport default function (target: any): boolean {\n return target === env.UNDEFINED\n}\n","import * as env from './env'\n\n/**\n * Check if value is a function.\n *\n * @param value\n * @return\n */\nexport function func(value: any): boolean {\n return typeof value === env.RAW_FUNCTION\n}\n\n/**\n * Check if value is an array.\n *\n * @param value\n * @return\n */\nexport function array(value: any): boolean {\n return Array.isArray(value)\n}\n\n/**\n * Check if value is an object.\n *\n * @param value\n * @return\n */\nexport function object(value: any): boolean {\n // 低版本 IE 会把 null 当作 object\n return value !== env.NULL && typeof value === 'object'\n}\n\n/**\n * Check if value is a string.\n *\n * @param value\n * @return\n */\nexport function string(value: any): boolean {\n return typeof value === 'string'\n}\n\n/**\n * Check if value is a number.\n *\n * @param value\n * @return\n */\nexport function number(value: any): boolean {\n return typeof value === 'number'\n}\n\n/**\n * Check if value is boolean.\n *\n * @param value\n * @return\n */\nexport function boolean(value: any): boolean {\n return typeof value === 'boolean'\n}\n\n/**\n * Check if value is numeric.\n *\n * @param value\n * @return\n */\nexport function numeric(value: any): boolean {\n return number(value)\n || (string(value) && !isNaN(parseFloat(value)) && isFinite(value))\n}\n","import isDef from './isDef'\nimport * as is from '../util/is'\n\n/**\n * 任性地执行一个函数,不管它有没有、是不是\n *\n * @param fn 调用的函数\n * @param context 执行函数时的 this 指向\n * @param args 调用函数的参数,多参数时传入数组\n * @return 调用函数的返回值\n */\nexport default function (fn: any, context?: any, args?: any): any {\n if (is.func(fn)) {\n return is.array(args)\n ? fn.apply(context, args)\n : isDef(context)\n ? fn.call(context, args)\n : isDef(args)\n ? fn(args)\n : fn()\n }\n}\n","import {\n Namespace,\n} from '../../../yox-type/src/type'\n\nimport {\n YoxInterface,\n} from '../../../yox-type/src/yox'\n\nimport {\n CustomEventInterface,\n} from '../../../yox-type/src/emitter'\n\nimport * as env from './env'\n\nexport default class CustomEvent implements CustomEventInterface {\n\n public static PHASE_CURRENT = 0\n\n public static PHASE_UPWARD = 1\n\n public static PHASE_DOWNWARD = env.MINUS_ONE\n\n // 事件名称\n type: string\n\n // 事件当前阶段\n phase: number\n\n // 事件命名空间信息\n ns?: Namespace\n\n // 哪个组件发出的事件\n target?: YoxInterface\n\n // 原始事件,比如 DOM 事件\n originalEvent?: CustomEventInterface | Event\n\n // 是否已阻止事件的默认行为\n isPrevented?: true\n\n // 是否已停止事件冒泡\n isStoped?: true\n\n // 处理当前事件的监听器,方便外部获取 listener 进行解绑\n listener?: Function\n\n /**\n * 构造函数\n *\n * 可以传事件名称,也可以传原生事件对象\n */\n constructor(type: string, originalEvent?: CustomEventInterface | Event) {\n this.type = type\n this.phase = CustomEvent.PHASE_CURRENT\n if (originalEvent) {\n this.originalEvent = originalEvent\n }\n }\n\n /**\n * 阻止事件的默认行为\n */\n preventDefault(): this {\n const instance = this\n if (!instance.isPrevented) {\n const { originalEvent } = instance\n if (originalEvent) {\n originalEvent.preventDefault()\n }\n instance.isPrevented = env.TRUE\n }\n return instance\n }\n\n /**\n * 停止事件广播\n */\n stopPropagation(): this {\n const instance = this\n if (!instance.isStoped) {\n const { originalEvent } = instance\n if (originalEvent) {\n originalEvent.stopPropagation()\n }\n instance.isStoped = env.TRUE\n }\n return instance\n }\n\n prevent(): this {\n return this.preventDefault()\n }\n\n stop(): this {\n return this.stopPropagation()\n }\n\n}\n","import * as is from './is'\nimport * as env from './env'\nimport execute from '../function/execute'\n\n/**\n * 遍历数组\n *\n * @param array\n * @param callback 返回 false 可停止遍历\n * @param reversed 是否逆序遍历\n */\nexport function each(\n array: T[],\n callback: (item: T, index: number) => boolean | void,\n reversed?: boolean\n): void {\n const { length } = array\n if (length) {\n if (reversed) {\n for (let i = length - 1; i >= 0; i--) {\n if (callback(array[i], i) === env.FALSE) {\n break\n }\n }\n }\n else {\n for (let i = 0; i < length; i++) {\n if (callback(array[i], i) === env.FALSE) {\n break\n }\n }\n }\n }\n}\n\nfunction nativePush(array: T[], item: T) {\n array[array.length] = item\n}\n\nfunction nativeUnshift(array: T[], item: T) {\n array.unshift(item)\n}\n\n/**\n * 添加\n *\n * @param array\n * @param value\n * @param action\n */\nfunction addItem(array: T[], value: T | T[], action: Function) {\n if (is.array(value)) {\n each(\n value as T[],\n function (item: T) {\n action(array, item)\n }\n )\n }\n else {\n action(array, value)\n }\n}\n\n/**\n * 往后加\n *\n * @param array\n * @param target\n */\nexport function push(array: T[], target: T | T[]): void {\n addItem(array, target, nativePush)\n}\n\n/**\n * 往前加\n *\n * @param array\n * @param target\n */\nexport function unshift(array: T[], target: T | T[]): void {\n addItem(array, target, nativeUnshift)\n}\n\n/**\n * 数组项在数组中的位置\n *\n * @param array 数组\n * @param target 数组项\n * @param strict 是否全等判断,默认是全等\n * @return 如果未找到,返回 -1\n */\nexport function indexOf(array: T[], target: T, strict?: boolean): number {\n let result = env.MINUS_ONE\n each(\n array,\n function (item: any, index: number) {\n if (strict === env.FALSE ? item == target : item === target) {\n result = index\n return env.FALSE\n }\n }\n )\n return result\n}\n\n/**\n * 获取数组最后一项\n *\n * @param array 数组\n * @return\n */\nexport function last(array: T[]): T | void {\n const { length } = array\n if (length > 0) {\n return array[length - 1]\n }\n}\n\n/**\n * 弹出数组最后一项\n *\n * 项目里用的太多,仅用于节省字符...\n *\n * @param array 数组\n * @return 弹出的数组项\n */\nexport function pop(array: T[]): T | void {\n const { length } = array\n if (length > 0) {\n return array.pop()\n }\n}\n\n/**\n * 删除数组项\n *\n * @param array 数组\n * @param item 待删除项\n * @param strict 是否全等判断,默认是全等\n * @return 删除的数量\n */\nexport function remove(array: T[], target: T, strict?: boolean): number {\n let result = 0\n each(\n array,\n function (item: T, index: number) {\n if (strict === env.FALSE ? item == target : item === target) {\n array.splice(index, 1)\n result++\n }\n },\n env.TRUE\n )\n return result\n}\n\n/**\n * 数组是否包含 item\n *\n * @param array 数组\n * @param target 可能包含的数组项\n * @param strict 是否全等判断,默认是全等\n * @return\n */\nexport function has(array: T[], target: T, strict?: boolean): boolean {\n return indexOf(array, target, strict) >= 0\n}\n\n/**\n * 把类数组转成数组\n *\n * @param array 类数组\n * @return\n */\nexport function toArray(array: T[] | ArrayLike): T[] {\n return is.array(array)\n ? array\n : execute(env.EMPTY_ARRAY.slice, array)\n}\n\n/**\n * 把数组转成对象\n *\n * @param array 数组\n * @param key 数组项包含的字段名称,如果数组项是基本类型,可不传\n * @param value\n * @return\n */\nexport function toObject(array: any[], key?: string | null, value?: any): object {\n let result = {}\n each(\n array,\n function (item: any) {\n result[key ? item[key] : item] = value || item\n }\n )\n return result\n}\n\n/**\n * 把数组合并成字符串\n *\n * @param array\n * @param separator\n * @return\n */\nexport function join(array: string[], separator: string): string {\n return array.join(separator)\n}\n\n/**\n * 用于判断长度大于 0 的数组\n *\n * @param array\n * @return\n */\nexport function falsy(array: any): boolean {\n return !is.array(array) || !array.length\n}\n","import * as is from './is'\nimport * as env from './env'\n\nimport isDef from '../function/isDef'\n\nconst camelizePattern = /-([a-z])/gi,\n\nhyphenatePattern = /\\B([A-Z])/g,\n\ncapitalizePattern = /^[a-z]/,\n\ncamelizeCache: Record = {},\n\nhyphenateCache: Record = {},\n\ncapitalizeCache: Record = {}\n\n/**\n * 连字符转成驼峰\n *\n * @param str\n * @return 驼峰格式的字符串\n */\nexport function camelize(str: string): string {\n if (!camelizeCache[str]) {\n camelizeCache[str] = str.replace(\n camelizePattern,\n function ($0, $1) {\n return upper($1)\n }\n )\n }\n return camelizeCache[str]\n}\n\n/**\n * 驼峰转成连字符\n *\n * @param str\n * @return 连字符格式的字符串\n */\nexport function hyphenate(str: string): string {\n if (!hyphenateCache[str]) {\n hyphenateCache[str] = str.replace(\n hyphenatePattern,\n function ($0, $1) {\n return '-' + lower($1)\n }\n )\n }\n return hyphenateCache[str]\n}\n\n/**\n * 首字母大写\n *\n * @param str\n * @return\n */\nexport function capitalize(str: string): string {\n if (!capitalizeCache[str]) {\n capitalizeCache[str] = str.replace(\n capitalizePattern,\n upper\n )\n }\n return capitalizeCache[str]\n}\n\n/**\n * 清除两侧空白符\n *\n * @param str\n * @return 清除两侧空白符的字符串\n */\nexport function trim(str: any): string {\n return falsy(str)\n ? env.EMPTY_STRING\n : str.trim()\n}\n\n/**\n * 截取字符串\n *\n * @param str\n * @param start\n * @param end\n * @return\n */\nexport function slice(str: string, start: number, end?: number): string {\n return is.number(end)\n ? start === end\n ? env.EMPTY_STRING\n : str.slice(start, end)\n : str.slice(start)\n}\n\n/**\n * 获取子串的起始位置\n *\n * @param str\n * @param part\n * @param start\n * @return\n */\nexport function indexOf(str: string, part: string, start?: number): number {\n return str.indexOf(part, isDef(start) ? start : 0)\n}\n\n/**\n * 获取子串的起始位置\n *\n * @param str\n * @param part\n * @param end\n * @return\n */\nexport function lastIndexOf(str: string, part: string, end?: number): number {\n return str.lastIndexOf(part, isDef(end) ? end : str.length)\n}\n\n/**\n * str 是否以 part 开头\n *\n * @param str\n * @param part\n * @return\n */\nexport function startsWith(str: string, part: string): boolean {\n return indexOf(str, part) === 0\n}\n\n/**\n * str 是否以 part 结束\n *\n * @param str\n * @param part\n * @return\n */\nexport function endsWith(str: string, part: string): boolean {\n const offset = str.length - part.length\n return offset >= 0 && lastIndexOf(str, part) === offset\n}\n\n/**\n * 获取某个位置的字符\n */\nexport function charAt(str: string, index?: number): string {\n return str.charAt(index || 0)\n}\n\n/**\n * 获取某个位置的字符编码\n */\nexport function codeAt(str: string, index?: number): number {\n return str.charCodeAt(index || 0)\n}\n\n/**\n * 大写格式\n */\nexport function upper(str: string): string {\n return str.toUpperCase()\n}\n\n/**\n * 小写格式\n */\nexport function lower(str: string): string {\n return str.toLowerCase()\n}\n\n/**\n * str 是否包含 part\n *\n * @param str\n * @param part\n * @return 是否包含\n */\nexport function has(str: string, part: string): boolean {\n return indexOf(str, part) >= 0\n}\n\n/**\n * 判断长度大于 0 的字符串\n *\n * @param str\n * @return\n */\nexport function falsy(str: any): boolean {\n return !is.string(str) || !str.length\n}\n","import * as env from './env'\nimport * as string from './string'\n\nimport isDef from '../function/isDef'\n\nconst dotPattern = /\\./g,\n\nasteriskPattern = /\\*/g,\n\ndoubleAsteriskPattern = /\\*\\*/g,\n\nsplitCache: Record = {},\n\npatternCache: Record = {}\n\n/**\n * 判断 keypath 是否以 prefix 开头,如果是,返回匹配上的前缀长度,否则返回 -1\n *\n * @param keypath\n * @param prefix\n * @return\n */\nexport function match(keypath: string, prefix: string): number {\n if (keypath === prefix) {\n return prefix.length\n }\n prefix += env.RAW_DOT\n return string.startsWith(keypath, prefix)\n ? prefix.length\n : env.MINUS_ONE\n}\n\n/**\n * 遍历 keypath 的每个部分\n *\n * @param keypath\n * @param callback 返回 false 可中断遍历\n */\nexport function each(keypath: string, callback: (key: string, isLast: boolean) => boolean | void) {\n // 判断字符串是因为 keypath 有可能是 toString\n // 而 splitCache.toString 是个函数\n const list = isDef(splitCache[keypath])\n ? splitCache[keypath]\n : (splitCache[keypath] = keypath.split(env.RAW_DOT))\n\n for (let i = 0, lastIndex = list.length - 1; i <= lastIndex; i++) {\n if (callback(list[i], i === lastIndex) === env.FALSE) {\n break\n }\n }\n}\n\n/**\n * 遍历 keypath 的每个部分\n *\n * @param keypath1\n * @param keypath2\n */\nexport function join(keypath1: string, keypath2: string): string {\n return keypath1 && keypath2\n ? keypath1 + env.RAW_DOT + keypath2\n : keypath1 || keypath2\n}\n\n/**\n * 是否模糊匹配\n *\n * @param keypath\n */\nexport function isFuzzy(keypath: string): boolean {\n return string.has(keypath, env.RAW_WILDCARD)\n}\n\n/**\n * 模糊匹配 keypath\n *\n * @param keypath\n * @param pattern\n */\nexport function matchFuzzy(keypath: string, pattern: string): string | void {\n let cache = patternCache[pattern]\n if (!cache) {\n const str = pattern\n .replace(dotPattern, '\\\\.')\n .replace(asteriskPattern, '(\\\\w+)')\n .replace(doubleAsteriskPattern, '([\\.\\\\w]+?)')\n cache = patternCache[pattern] = new RegExp(`^${str}$`)\n }\n const result = keypath.match(cache)\n if (result) {\n return result[1]\n }\n}","import { ValueHolder } from '../../../yox-type/src/type'\nimport * as env from './env'\n\n/**\n * 全局 value holder,避免频繁的创建临时对象\n */\nconst holder: ValueHolder = {\n value: env.UNDEFINED\n}\n\nexport default holder","import {\n Data,\n ValueHolder\n} from '../../../yox-type/src/type'\n\nimport * as is from './is'\nimport * as env from './env'\nimport * as array from './array'\nimport * as keypathUtil from './keypath'\n\nimport holder from './holder'\n\nimport isDef from '../function/isDef'\n\n/**\n * 获取对象的 key 的数组\n *\n * @param object\n * @return\n */\nexport function keys(object: Data): string[] {\n return Object.keys(object)\n}\n\nfunction sortKeyByAsc(a: string, b: string): number {\n return a.length - b.length\n}\n\nfunction sortKeyByDesc(a: string, b: string): number {\n return b.length - a.length\n}\n\n/**\n * 排序对象的 key\n *\n * @param object\n * @param desc 是否逆序,默认从小到大排序\n * @return\n */\nexport function sort(object: Data, desc?: boolean): string[] {\n return keys(object).sort(\n desc ? sortKeyByDesc : sortKeyByAsc\n )\n}\n\n/**\n * 遍历对象\n *\n * @param object\n * @param callback 返回 false 可停止遍历\n */\nexport function each(object: Data, callback: (value: any, key: string) => boolean | void): void {\n for (let key in object) {\n if (callback(object[key], key) === env.FALSE) {\n break\n }\n }\n}\n\n/**\n * 清空对象所有的键值对\n *\n * @param object\n */\nexport function clear(object: Data): void {\n each(\n object,\n function (_, key) {\n delete object[key]\n }\n )\n}\n\n/**\n * 扩展对象\n *\n * @return\n */\nexport function extend(original: Data, object: Data): Data {\n each(\n object,\n function (value, key) {\n original[key] = value\n }\n )\n return original\n}\n\n/**\n * 合并对象\n *\n * @return\n */\nexport function merge(object1: Data | void, object2: Data | void): Data | void {\n return object1 && object2\n ? extend(extend({}, object1), object2)\n : object1 || object2\n}\n\n/**\n * 拷贝对象\n *\n * @param object\n * @param deep 是否需要深拷贝\n * @return\n */\nexport function copy(object: any, deep?: boolean): any {\n let result = object\n if (is.array(object)) {\n if (deep) {\n result = []\n array.each(\n object,\n function (item, index) {\n result[index] = copy(item, deep)\n }\n )\n }\n else {\n result = object.slice()\n }\n }\n else if (is.object(object)) {\n result = {}\n each(\n object,\n function (value, key) {\n result[key] = deep ? copy(value, deep) : value\n }\n )\n }\n return result\n}\n\n/**\n * 从对象中查找一个 keypath\n *\n * 返回值是空时,表示没找到值\n *\n * @param object\n * @param keypath\n * @return\n */\nexport function get(object: any, keypath: string): ValueHolder | undefined {\n\n keypathUtil.each(\n keypath,\n function (key, isLast) {\n\n if (object != env.NULL) {\n\n // 先直接取值\n let value = object[key],\n\n // 紧接着判断值是否存在\n // 下面会处理计算属性的值,不能在它后面设置 hasValue\n hasValue = isDef(value)\n\n // 如果是计算属性,取计算属性的值\n if (value && is.func(value.get)) {\n value = value.get()\n }\n\n if (isLast) {\n if (hasValue) {\n holder.value = value\n object = holder\n }\n else {\n object = env.UNDEFINED\n }\n }\n else {\n object = value\n }\n }\n else {\n object = env.UNDEFINED\n return env.FALSE\n }\n\n }\n )\n\n return object\n\n}\n\n/**\n * 为对象设置一个键值对\n *\n * @param object\n * @param keypath\n * @param value\n * @param autofill 是否自动填充不存在的对象,默认自动填充\n */\nexport function set(object: Data, keypath: string, value: any, autofill?: boolean): void {\n keypathUtil.each(\n keypath,\n function (key, isLast) {\n if (isLast) {\n object[key] = value\n }\n else if (object[key]) {\n object = object[key]\n }\n else if (autofill) {\n object = object[key] = {}\n }\n else {\n return env.FALSE\n }\n }\n )\n}\n\n/**\n * 对象是否包含某个 key\n *\n * @param object\n * @param key\n * @return\n */\nexport function has(object: Data, key: string | number): boolean {\n // 不用 hasOwnProperty,性能差\n return isDef(object[key])\n}\n\n/**\n * 是否是空对象\n *\n * @param object\n * @return\n */\nexport function falsy(object: any): boolean {\n return !is.object(object)\n || is.array(object)\n || !keys(object).length\n}","import * as env from '../util/env'\nimport isDef from './isDef'\n\nexport default function (target: any, defaultValue?: string): string {\n return target != env.NULL && target.toString\n ? target.toString()\n : isDef(defaultValue)\n ? defaultValue as string\n : env.EMPTY_STRING\n}\n","import * as env from './env'\nimport toString from '../function/toString'\n\nexport const DEBUG = 1\nexport const INFO = 2\nexport const WARN = 3\nexport const ERROR = 4\nexport const FATAL = 5\n\n/**\n * 是否有原生的日志特性,没有必要单独实现\n */\nconst nativeConsole: Console | null = typeof console !== env.RAW_UNDEFINED ? console : env.NULL,\n\n/**\n * 当前是否是源码调试,如果开启了代码压缩,empty function 里的注释会被干掉\n */\ndefaultLogLevel = /yox/.test(toString(env.EMPTY_FUNCTION)) ? DEBUG : WARN,\n\n/**\n * console 样式前缀\n * ie 和 edge 不支持 console.log 样式\n */\nstylePrefix = env.WINDOW && /edge|msie|trident/i.test(env.WINDOW.navigator.userAgent)\n ? env.EMPTY_STRING\n : '%c',\n\n/**\n * 日志打印函数\n */\nprintLog = nativeConsole\n ? stylePrefix\n ? function (tag: string, msg: string, style: string) {\n nativeConsole.log(stylePrefix + tag, style, msg)\n }\n : function (tag: string, msg: string) {\n nativeConsole.log(tag, msg)\n }\n : env.EMPTY_FUNCTION\n\n/**\n * 全局调试开关\n */\nfunction getLogLevel() {\n if (env.GLOBAL) {\n const logLevel = env.GLOBAL['YOX_LOG_LEVEL']\n if (logLevel >= DEBUG && logLevel <= FATAL) {\n return logLevel as number\n }\n }\n return defaultLogLevel\n}\n\nfunction getStyle(backgroundColor: string) {\n return `background-color:${backgroundColor};border-radius:12px;color:#fff;font-size:10px;padding:3px 6px;`\n}\n\n/**\n * 打印 debug 日志\n *\n * @param msg\n */\nexport function debug(msg: string, tag?: string): void {\n if (getLogLevel() <= DEBUG) {\n printLog(tag || 'Yox debug', msg, getStyle('#999'))\n }\n}\n\n/**\n * 打印 info 日志\n *\n * @param msg\n */\nexport function info(msg: string, tag?: string): void {\n if (getLogLevel() <= INFO) {\n printLog(tag || 'Yox info', msg, getStyle('#2db7f5'))\n }\n}\n\n/**\n * 打印 warn 日志\n *\n * @param msg\n */\nexport function warn(msg: string, tag?: string): void {\n if (getLogLevel() <= WARN) {\n printLog(tag || 'Yox warn', msg, getStyle('#f90'))\n }\n}\n\n/**\n * 打印 error 日志\n *\n * @param msg\n */\nexport function error(msg: string, tag?: string): void {\n if (getLogLevel() <= ERROR) {\n printLog(tag || 'Yox error', msg, getStyle('#ed4014'))\n }\n}\n\n/**\n * 致命错误,中断程序\n *\n * @param msg\n */\nexport function fatal(msg: string, tag?: string): void {\n if (getLogLevel() <= FATAL) {\n throw new Error(`[${tag || 'Yox fatal'}]: ${msg}`)\n }\n}\n","import {\n Namespace,\n NativeListener,\n} from '../../../yox-type/src/type'\n\nimport {\n EmitterOptions,\n} from '../../../yox-type/src/options'\n\nimport {\n EmitterInterface,\n} from '../../../yox-type/src/emitter'\n\nimport execute from '../function/execute'\n\nimport * as is from './is'\nimport * as env from './env'\nimport * as array from './array'\nimport * as object from './object'\nimport * as string from './string'\nimport * as logger from './logger'\n\nimport CustomEvent from './CustomEvent'\n\nexport default class Emitter implements EmitterInterface {\n\n /**\n * 是否开启命名空间\n */\n ns: boolean\n\n /**\n * 已注册的事件监听\n */\n listeners: Record\n\n /**\n * 原生事件监听,一个事件对应一个 listener\n */\n nativeListeners?: Record\n\n constructor(ns?: boolean) {\n this.ns = ns || env.FALSE\n this.listeners = {}\n }\n\n /**\n * 发射事件\n *\n * @param type 事件名称或命名空间\n * @param args 事件处理函数的参数列表\n * @param filter 自定义过滤器\n */\n fire(\n type: string | Namespace,\n args: any[] | void,\n filter?: (\n namespace: Namespace,\n args: any[] | void,\n options: EmitterOptions\n ) => boolean | void\n ): boolean {\n\n let instance = this,\n\n namespace = is.string(type) ? instance.parse(type as string) : type as Namespace,\n\n list = instance.listeners[namespace.name],\n\n isComplete = env.TRUE\n\n if (list) {\n\n // 避免遍历过程中,数组发生变化,比如增删了\n list = object.copy(list)\n\n // 判断是否是发射事件\n // 如果 args 的第一个参数是 CustomEvent 类型,表示发射事件\n // 因为事件处理函数的参数列表是 (event, data)\n const event = args && args[0] instanceof CustomEvent\n ? args[0] as CustomEvent\n : env.UNDEFINED\n\n array.each(\n list,\n function (options) {\n\n // 命名空间不匹配\n if (!matchNamespace(namespace.ns, options)\n // 在 fire 过程中被移除了\n || !array.has(list, options)\n // 传了 filter,则用 filter 判断是否过滤此 options\n || (filter && !filter(namespace, args, options))\n ) {\n return\n }\n\n // 为 event 对象加上当前正在处理的 listener\n // 这样方便业务层移除事件绑定\n // 比如 on('xx', function) 这样定义了匿名 listener\n // 在这个 listener 里面获取不到当前 listener 的引用\n // 为了能引用到,有时候会先定义 var listener = function\n // 然后再 on('xx', listener) 这样其实是没有必要的\n if (event) {\n event.listener = options.fn\n }\n\n let result = execute(options.fn, options.ctx, args)\n\n if (event) {\n event.listener = env.UNDEFINED\n }\n\n // 执行次数\n options.num = options.num ? (options.num + 1) : 1\n\n // 注册的 listener 可以指定最大执行次数\n if (options.num === options.max) {\n instance.off(namespace.key, options.fn)\n }\n\n // 如果没有返回 false,而是调用了 event.stop 也算是返回 false\n if (event) {\n if (result === env.FALSE) {\n event.prevent().stop()\n }\n else if (event.isStoped) {\n result = env.FALSE\n }\n }\n\n if (result === env.FALSE) {\n return isComplete = env.FALSE\n }\n }\n )\n\n }\n\n return isComplete\n\n }\n\n /**\n * 注册监听\n *\n * @param type\n * @param listener\n */\n on(\n type: string,\n listener: Function | EmitterOptions\n ): void {\n\n const instance = this,\n\n { listeners } = instance,\n\n options: EmitterOptions = is.func(listener)\n ? { fn: listener as Function }\n : listener as EmitterOptions\n\n if (is.object(options) && is.func(options.fn)) {\n const { name, ns } = instance.parse(type)\n options.ns = ns\n array.push(\n listeners[name] || (listeners[name] = []),\n options\n )\n }\n else if (process.env.NODE_ENV === 'development') {\n logger.fatal(`emitter.on(type, listener) invoke failed:\\n\\n\"listener\" is expected to be a Function or an EmitterOptions.\\n`)\n }\n\n }\n\n /**\n * 取消监听\n *\n * @param type\n * @param listener\n */\n off(\n type?: string,\n listener?: Function\n ): void {\n\n const instance = this,\n\n { listeners } = instance\n\n if (type) {\n\n const { name, ns } = instance.parse(type),\n\n matchListener = createMatchListener(listener),\n\n each = function (list: EmitterOptions[], name: string) {\n array.each(\n list,\n function (options, index) {\n if (matchListener(options) && matchNamespace(ns, options)) {\n list.splice(index, 1)\n }\n },\n env.TRUE\n )\n if (!list.length) {\n delete listeners[name]\n }\n }\n\n if (name) {\n if (listeners[name]) {\n each(listeners[name], name)\n }\n }\n else if (ns) {\n object.each(listeners, each)\n }\n\n // 在开发阶段进行警告,比如传了 listener 进来,listener 是个空值\n // 但你不知道它是空值\n if (process.env.NODE_ENV === 'development') {\n if (arguments.length > 1 && listener == env.NULL) {\n logger.warn(`emitter.off(type, listener) is invoked, but \"listener\" is ${listener}.`)\n }\n }\n\n }\n else {\n // 清空\n instance.listeners = {}\n // 在开发阶段进行警告,比如传了 type 进来,type 是个空值\n // 但你不知道它是空值\n if (process.env.NODE_ENV === 'development') {\n if (arguments.length > 0) {\n logger.warn(`emitter.off(type) is invoked, but \"type\" is ${type}.`)\n }\n }\n }\n\n }\n\n /**\n * 是否已监听某个事件\n *\n * @param type\n * @param listener\n */\n has(\n type: string,\n listener?: Function\n ): boolean {\n\n let instance = this,\n\n { listeners } = instance,\n\n { name, ns } = instance.parse(type),\n\n result = env.TRUE,\n\n matchListener = createMatchListener(listener),\n\n each = function (list: EmitterOptions[]) {\n array.each(\n list,\n function (options) {\n if (matchListener(options) && matchNamespace(ns, options)) {\n return result = env.FALSE\n }\n }\n )\n return result\n }\n\n if (name) {\n if (listeners[name]) {\n each(listeners[name])\n }\n }\n else if (ns) {\n object.each(listeners, each)\n }\n\n return !result\n\n }\n\n /**\n * 把事件类型解析成命名空间格式\n *\n * @param type\n */\n parse(type: string): Namespace {\n\n const result = {\n key: type,\n name: type,\n ns: env.EMPTY_STRING,\n }\n\n if (this.ns) {\n const index = string.indexOf(type, env.RAW_DOT)\n if (index >= 0) {\n result.name = string.slice(type, 0, index)\n result.ns = string.slice(type, index + 1)\n }\n }\n\n return result\n\n }\n\n}\n\nfunction matchTrue() {\n return env.TRUE\n}\n\n/**\n * 外部会传入 Function 或 EmitterOptions 或 空\n *\n * 这里根据传入值的不同类型,创建不同的判断函数\n *\n * 如果传入的是 EmitterOptions,则全等判断\n *\n * 如果传入的是 Function,则判断函数是否全等\n *\n * 如果传入的是空,则直接返回 true\n *\n * @param listener\n */\nfunction createMatchListener(listener: Function | void): (options: EmitterOptions) => boolean {\n return is.func(listener)\n ? function (options: EmitterOptions) {\n return listener === options.fn\n }\n : matchTrue\n}\n\n/**\n * 判断 options 是否能匹配命名空间\n *\n * 如果 namespace 和 options.ns 都不为空,则需完全匹配\n *\n * 如果他们两个其中任何一个为空,则不判断命名空间\n *\n * @param namespace\n * @param options\n */\nfunction matchNamespace(namespace: string, options: EmitterOptions): boolean {\n const { ns } = options\n return ns && namespace\n ? ns === namespace\n : env.TRUE\n}","import * as is from '../util/is'\nimport toString from './toString'\n\nexport default function (target: any): boolean {\n return is.func(target) && /native code/.test(toString(target))\n}","import * as env from '../util/env'\nimport isNative from './isNative'\n\ndeclare const setImmediate: Function\n\nlet nextTick: Function\n\n// IE (10+) 和 node\nif (typeof setImmediate === env.RAW_FUNCTION && isNative(setImmediate)) {\n nextTick = setImmediate\n}\n// 用 MessageChannel 去做 setImmediate 的 polyfill\n// 原理是将新的 message 事件加入到原有的 dom events 之后\n// 兼容性 IE10+ 和其他标准浏览器\nif (typeof MessageChannel === env.RAW_FUNCTION && isNative(MessageChannel)) {\n nextTick = function (fn: any) {\n const channel = new MessageChannel()\n channel.port1.onmessage = fn\n channel.port2.postMessage(1)\n }\n}\nelse {\n nextTick = setTimeout\n}\n\nexport default nextTick\n","import {\n Task,\n} from '../../../yox-type/src/type'\n\nimport {\n NextTaskInterface,\n} from '../../../yox-type/src/yox'\n\nimport * as array from './array'\nimport execute from '../function/execute'\nimport nextTick from '../function/nextTick'\n\nlet shared: NextTask | void\n\nexport default class NextTask implements NextTaskInterface {\n\n /**\n * 全局单例\n */\n public static shared(): NextTask {\n return shared || (shared = new NextTask())\n }\n\n /**\n * 异步队列\n */\n tasks: Task[]\n\n constructor() {\n this.tasks = []\n }\n\n /**\n * 在队尾添加异步任务\n */\n append(func: Function, context?: any): void {\n const instance = this, { tasks } = instance\n array.push(\n tasks,\n {\n fn: func,\n ctx: context\n }\n )\n if (tasks.length === 1) {\n nextTick(\n function () {\n instance.run()\n }\n )\n }\n }\n\n /**\n * 在队首添加异步任务\n */\n prepend(func: Function, context?: any): void {\n const instance = this, { tasks } = instance\n array.unshift(\n tasks,\n {\n fn: func,\n ctx: context\n }\n )\n if (tasks.length === 1) {\n nextTick(\n function () {\n instance.run()\n }\n )\n }\n }\n\n /**\n * 清空异步队列\n */\n clear(): void {\n this.tasks.length = 0\n }\n\n /**\n * 立即执行异步任务,并清空队列\n */\n run(): void {\n const { tasks } = this\n if (tasks.length) {\n this.tasks = []\n array.each(\n tasks,\n function (task) {\n execute(task.fn, task.ctx)\n }\n )\n }\n }\n\n}\n","let guid = 0\n\nexport default function (): number {\n return ++guid\n}\n","// vnode.data 内部使用的几个字段\n\nexport const ID = '$id'\n\nexport const VNODE = '$vnode'\n\nexport const LOADING = '$loading'\n\nexport const COMPONENT = '$component'\n\nexport const LEAVING = '$leaving'","import {\n VNode,\n Attribute,\n} from '../../yox-type/src/vnode'\n\nimport * as env from '../../yox-common/src/util/env'\nimport * as object from '../../yox-common/src/util/object'\n\nexport function update(api: any, vnode: VNode, oldVnode?: VNode) {\n\n const { node, nativeAttrs } = vnode,\n\n oldNativeAttrs = oldVnode && oldVnode.nativeAttrs\n\n if (nativeAttrs || oldNativeAttrs) {\n\n const newValue = nativeAttrs || env.EMPTY_OBJECT,\n\n oldValue = oldNativeAttrs || env.EMPTY_OBJECT\n\n object.each(\n newValue,\n function (attr: Attribute, name: string) {\n if (!oldValue[name]\n || attr.value !== oldValue[name].value\n ) {\n api.attr(node, name, attr.value)\n }\n }\n )\n\n object.each(\n oldValue,\n function (_: Attribute, name: string) {\n if (!newValue[name]) {\n api.removeAttr(node, name)\n }\n }\n )\n\n }\n\n}\n","import {\n VNode,\n Property\n} from '../../yox-type/src/vnode'\n\nimport * as env from '../../yox-common/src/util/env'\nimport * as object from '../../yox-common/src/util/object'\n\nexport function update(api: any, vnode: VNode, oldVnode?: VNode) {\n\n const { node, nativeProps } = vnode,\n\n oldNativeProps = oldVnode && oldVnode.nativeProps\n\n if (nativeProps || oldNativeProps) {\n\n const newValue = nativeProps || env.EMPTY_OBJECT,\n\n oldValue = oldNativeProps || env.EMPTY_OBJECT\n\n object.each(\n newValue,\n function (prop: Property, name: string) {\n if (!oldValue[name]\n || prop.value !== oldValue[name].value\n ) {\n api.prop(node, name, prop.value)\n }\n }\n )\n\n object.each(\n oldValue,\n function (prop: Property, name: string) {\n if (!newValue[name]) {\n api.removeProp(node, name, prop.hint)\n }\n }\n )\n\n }\n\n}","import {\n VNode,\n Directive,\n} from '../../yox-type/src/vnode'\n\nimport * as env from '../../yox-common/src/util/env'\nimport * as object from '../../yox-common/src/util/object'\n\nimport * as field from './field'\n\nexport function update(vnode: VNode, oldVnode?: VNode) {\n\n const { data, directives } = vnode,\n\n oldDirectives = oldVnode && oldVnode.directives\n\n if (directives || oldDirectives) {\n\n const node = data[field.COMPONENT] || vnode.node,\n\n isKeypathChange = oldVnode && vnode.keypath !== oldVnode.keypath,\n\n newValue = directives || env.EMPTY_OBJECT,\n\n oldValue = oldDirectives || env.EMPTY_OBJECT\n\n object.each(\n newValue,\n function (directive: Directive, name: string) {\n const { once, bind, unbind } = directive.hooks\n if (!oldValue[name]) {\n bind(node, directive, vnode)\n }\n else if (once\n || directive.value !== oldValue[name].value\n || isKeypathChange\n ) {\n if (unbind) {\n unbind(node, oldValue[name], oldVnode as VNode)\n }\n bind(node, directive, vnode)\n }\n }\n )\n\n object.each(\n oldValue,\n function (directive: Directive, name: string) {\n if (!newValue[name]) {\n const { unbind } = directive.hooks\n if (unbind) {\n unbind(node, directive, oldVnode as VNode)\n }\n }\n }\n )\n\n }\n\n}\n\nexport function remove(vnode: VNode) {\n const { directives } = vnode\n if (directives) {\n const node = vnode.data[field.COMPONENT] || vnode.node\n object.each(\n directives,\n function (directive: Directive) {\n const { unbind } = directive.hooks\n if (unbind) {\n unbind(node, directive, vnode)\n }\n }\n )\n }\n}\n","import {\n VNode,\n} from '../../yox-type/src/vnode'\n\nimport {\n DIRECTIVE_MODEL\n} from '../../yox-config/src/config'\n\nimport * as object from '../../yox-common/src/util/object'\n\nimport * as field from './field'\n\nexport function update(vnode: VNode, oldVnode?: VNode) {\n\n let { data, ref, props, slots, directives, context } = vnode, node: any\n\n if (vnode.isComponent) {\n node = data[field.COMPONENT]\n // 更新时才要 set\n // 因为初始化时,所有这些都经过构造函数完成了\n if (oldVnode) {\n\n const model = directives && directives[DIRECTIVE_MODEL]\n if (model) {\n if (!props) {\n props = {}\n }\n props[node.$model] = model.value\n }\n\n if (process.env.NODE_ENV === 'development') {\n if (props) {\n object.each(\n props,\n function (value, key) {\n node.checkProp(key, value)\n }\n )\n }\n }\n\n const result = object.merge(props, slots)\n if (result) {\n node.forceUpdate(result)\n }\n }\n }\n else {\n node = vnode.node\n }\n\n if (ref) {\n const refs = context.$refs\n if (refs) {\n refs[ref] = node\n }\n }\n\n}\n","import {\n Data,\n} from '../../yox-type/src/type'\n\nimport {\n DomApi,\n} from '../../yox-type/src/api'\n\nimport {\n VNode,\n} from '../../yox-type/src/vnode'\n\nimport {\n ComponentOptions,\n} from '../../yox-type/src/options'\n\nimport {\n YoxInterface,\n} from '../../yox-type/src/yox'\n\nimport * as is from '../../yox-common/src/util/is'\nimport * as env from '../../yox-common/src/util/env'\nimport * as array from '../../yox-common/src/util/array'\nimport * as object from '../../yox-common/src/util/object'\nimport * as logger from '../../yox-common/src/util/logger'\n\nimport guid from '../../yox-common/src/function/guid'\nimport isDef from '../../yox-common/src/function/isDef'\nimport execute from '../../yox-common/src/function/execute'\n\nimport * as field from './field'\n\nimport * as nativeAttr from './nativeAttr'\nimport * as nativeProp from './nativeProp'\nimport * as directive from './directive'\nimport * as component from './component'\n\nfunction isPatchable(vnode: VNode, oldVnode: VNode): boolean {\n return vnode.tag === oldVnode.tag\n && vnode.key === oldVnode.key\n}\n\nfunction createKeyToIndex(vnodes: (VNode | void)[], startIndex: number, endIndex: number): Record {\n\n let result: Record | void,\n\n vnode: VNode | void,\n\n key: string | void\n\n while (startIndex <= endIndex) {\n vnode = vnodes[startIndex]\n if (vnode && (key = vnode.key)) {\n if (!result) {\n result = {}\n }\n result[key] = startIndex\n }\n startIndex++\n }\n\n return result || env.EMPTY_OBJECT\n\n}\n\nfunction insertBefore(api: DomApi, parentNode: Node, node: Node, referenceNode: Node | void) {\n if (referenceNode) {\n api.before(parentNode, node, referenceNode)\n }\n else {\n api.append(parentNode, node)\n }\n}\n\nfunction createComponent(vnode: VNode, options: ComponentOptions) {\n\n const child = (vnode.parent || vnode.context).createComponent(options, vnode)\n\n vnode.data[field.COMPONENT] = child\n vnode.data[field.LOADING] = env.FALSE\n\n component.update(vnode)\n directive.update(vnode)\n\n return child\n\n}\n\nfunction createData(): Data {\n const data = {}\n data[field.ID] = guid()\n return data\n}\n\nfunction createVnode(api: DomApi, vnode: VNode) {\n\n let { tag, node, data, isComponent, isComment, isText, isStyle, isOption, children, text, html, context } = vnode\n\n if (node && data) {\n return\n }\n\n data = createData()\n\n vnode.data = data\n\n if (isText) {\n vnode.node = api.createText(text as string)\n return\n }\n\n if (isComment) {\n vnode.node = api.createComment(text as string)\n return\n }\n\n if (isComponent) {\n\n let componentOptions: ComponentOptions | undefined = env.UNDEFINED\n\n // 动态组件,tag 可能为空\n if (tag) {\n context.loadComponent(\n tag,\n function (options: ComponentOptions) {\n if (object.has(data, field.LOADING)) {\n // 异步组件\n if (data[field.LOADING]) {\n // 尝试使用最新的 vnode\n if (data[field.VNODE]) {\n vnode = data[field.VNODE]\n // 用完就删掉\n delete data[field.VNODE]\n }\n enterVnode(\n vnode,\n createComponent(vnode, options)\n )\n }\n }\n // 同步组件\n else {\n componentOptions = options\n }\n }\n )\n }\n\n // 不论是同步还是异步组件,都需要一个占位元素\n vnode.node = api.createComment(env.RAW_COMPONENT)\n\n if (componentOptions) {\n createComponent(vnode, componentOptions as ComponentOptions)\n }\n else {\n data[field.LOADING] = env.TRUE\n }\n\n }\n else {\n\n node = vnode.node = api.createElement(vnode.tag as string, vnode.isSvg)\n\n if (children) {\n addVnodes(api, node, children)\n }\n else if (text) {\n api.text(node as Element, text, isStyle, isOption)\n }\n else if (html) {\n api.html(node as Element, html, isStyle, isOption)\n }\n\n nativeAttr.update(api, vnode)\n nativeProp.update(api, vnode)\n component.update(vnode)\n directive.update(vnode)\n\n }\n}\n\nfunction addVnodes(api: DomApi, parentNode: Node, vnodes: VNode[], startIndex?: number, endIndex?: number, before?: VNode) {\n let vnode: VNode, start = startIndex || 0, end = isDef(endIndex) ? endIndex as number : vnodes.length - 1\n while (start <= end) {\n vnode = vnodes[start]\n createVnode(api, vnode)\n insertVnode(api, parentNode, vnode, before)\n start++\n }\n}\n\nfunction insertVnode(api: DomApi, parentNode: Node, vnode: VNode, before?: VNode) {\n\n const { node, data, context } = vnode,\n\n hasParent = api.parent(node)\n\n // 这里不调用 insertBefore,避免判断两次\n if (before) {\n api.before(parentNode, node, before.node)\n }\n else {\n api.append(parentNode, node)\n }\n\n // 普通元素和组件的占位节点都会走到这里\n // 但是占位节点不用 enter,而是等组件加载回来之后再调 enter\n if (!hasParent) {\n let enter: Function | void = env.UNDEFINED\n if (vnode.isComponent) {\n const component = data[field.COMPONENT]\n if (component) {\n enter = function () {\n enterVnode(vnode, component)\n }\n }\n }\n else if (!vnode.isStatic && !vnode.isText && !vnode.isComment) {\n enter = function () {\n enterVnode(vnode)\n }\n }\n if (enter) {\n // 执行到这时,组件还没有挂载到 DOM 树\n // 如果此时直接触发 enter,外部还需要做多余的工作,比如 setTimeout\n // 索性这里直接等挂载到 DOM 数之后再触发\n // 注意:YoxInterface 没有声明 $observer,因为不想让外部访问,\n // 但是这里要用一次,所以加了 as any\n (context as any).$observer.nextTask.prepend(enter)\n }\n }\n\n}\n\nfunction removeVnodes(api: DomApi, parentNode: Node, vnodes: (VNode | void)[], startIndex?: number, endIndex?: number) {\n let vnode: VNode | void, start = startIndex || 0, end = isDef(endIndex) ? endIndex as number : vnodes.length - 1\n while (start <= end) {\n vnode = vnodes[start]\n if (vnode) {\n removeVnode(api, parentNode, vnode)\n }\n start++\n }\n}\n\nfunction removeVnode(api: DomApi, parentNode: Node, vnode: VNode) {\n const { node } = vnode\n if (vnode.isStatic || vnode.isText || vnode.isComment) {\n api.remove(parentNode, node)\n }\n else {\n\n let done = function () {\n destroyVnode(api, vnode)\n api.remove(parentNode, node)\n },\n\n component: YoxInterface | void\n\n if (vnode.isComponent) {\n component = vnode.data[field.COMPONENT]\n // 异步组件,还没加载成功就被删除了\n if (!component) {\n done()\n return\n }\n }\n\n leaveVnode(vnode, component, done)\n\n }\n}\n\nfunction destroyVnode(api: DomApi, vnode: VNode) {\n\n /**\n * 如果一个子组件的模板是这样写的:\n *\n *
\n * {{#if visible}}\n * \n * {{/if}}\n *
\n *\n * 当 visible 从 true 变为 false 时,不能销毁 slot 导入的任何 vnode\n * 不论是组件或是元素,都不能销毁,只能简单的 remove,\n * 否则子组件下一次展现它们时,会出问题\n */\n\n const { data, children, parent, slot } = vnode\n\n // 销毁插槽组件\n\n // 如果宿主组件正在销毁,$vnode 属性会在调 destroy() 之前被删除\n // 这里表示的是宿主组件还没被销毁\n // 如果宿主组件被销毁了,则它的一切都要进行销毁\n if (slot && parent && parent.$vnode) {\n // 如果更新时,父组件没有传入该 slot,则子组件需要销毁该 slot\n const slots = parent.get(slot)\n // slots 要么没有,要么是数组,不可能是别的\n if (slots && array.has(slots, vnode)) {\n return\n }\n }\n\n if (vnode.isComponent) {\n const component = data[field.COMPONENT]\n if (component) {\n directive.remove(vnode)\n component.destroy()\n }\n else [\n data[field.LOADING] = env.FALSE\n ]\n }\n else {\n directive.remove(vnode)\n if (children) {\n array.each(\n children,\n function (child) {\n destroyVnode(api, child)\n }\n )\n }\n }\n\n}\n\n/**\n * vnode 触发 enter hook 时,外部一般会做一些淡入动画\n */\nfunction enterVnode(vnode: VNode, component: YoxInterface | void) {\n // 如果组件根元素和组件本身都写了 transition\n // 优先用外面定义的\n // 因为这明确是在覆盖配置\n let { data, transition } = vnode\n if (component && !transition) {\n // 再看组件根元素是否有 transition\n transition = (component.$vnode as VNode).transition\n }\n execute(data[field.LEAVING])\n if (transition) {\n const { enter } = transition\n if (enter) {\n enter(\n vnode.node as HTMLElement\n )\n return\n }\n }\n}\n\n/**\n * vnode 触发 leave hook 时,外部一般会做一些淡出动画\n * 动画结束后才能移除节点,否则无法产生动画\n * 这里由外部调用 done 来通知内部动画结束\n */\nfunction leaveVnode(vnode: VNode, component: YoxInterface | void, done: () => void) {\n // 如果组件根元素和组件本身都写了 transition\n // 优先用外面定义的\n // 因为这明确是在覆盖配置\n let { data, transition } = vnode\n if (component && !transition) {\n // 再看组件根元素是否有 transition\n transition = (component.$vnode as VNode).transition\n }\n if (transition) {\n const { leave } = transition\n if (leave) {\n leave(\n vnode.node as HTMLElement,\n data[field.LEAVING] = function () {\n if (data[field.LEAVING]) {\n done()\n data[field.LEAVING] = env.UNDEFINED\n }\n }\n )\n return\n }\n }\n // 如果没有淡出动画,直接结束\n done()\n}\n\nfunction updateChildren(api: DomApi, parentNode: Node, children: VNode[], oldChildren: (VNode | void)[]) {\n\n let startIndex = 0,\n endIndex = children.length - 1,\n startVnode = children[startIndex],\n endVnode = children[endIndex],\n\n oldStartIndex = 0,\n oldEndIndex = oldChildren.length - 1,\n oldStartVnode = oldChildren[oldStartIndex],\n oldEndVnode = oldChildren[oldEndIndex],\n\n oldKeyToIndex: Record | void,\n oldIndex: number | void\n\n while (oldStartIndex <= oldEndIndex && startIndex <= endIndex) {\n\n // 下面有设为 UNDEFINED 的逻辑\n if (!startVnode) {\n startVnode = children[++startIndex];\n }\n else if (!endVnode) {\n endVnode = children[--endIndex];\n }\n else if (!oldStartVnode) {\n oldStartVnode = oldChildren[++oldStartIndex]\n }\n else if (!oldEndVnode) {\n oldEndVnode = oldChildren[--oldEndIndex]\n }\n\n // 从头到尾比较,位置相同且值得 patch\n else if (isPatchable(startVnode, oldStartVnode)) {\n patch(api, startVnode, oldStartVnode)\n startVnode = children[++startIndex]\n oldStartVnode = oldChildren[++oldStartIndex]\n }\n\n // 从尾到头比较,位置相同且值得 patch\n else if (isPatchable(endVnode, oldEndVnode)) {\n patch(api, endVnode, oldEndVnode)\n endVnode = children[--endIndex]\n oldEndVnode = oldChildren[--oldEndIndex]\n }\n\n // 比较完两侧的节点,剩下就是 位置发生改变的节点 和 全新的节点\n\n // 当 endVnode 和 oldStartVnode 值得 patch\n // 说明元素被移到右边了\n else if (isPatchable(endVnode, oldStartVnode)) {\n patch(api, endVnode, oldStartVnode)\n insertBefore(\n api,\n parentNode,\n oldStartVnode.node,\n api.next(oldEndVnode.node)\n )\n endVnode = children[--endIndex]\n oldStartVnode = oldChildren[++oldStartIndex]\n }\n\n // 当 oldEndVnode 和 startVnode 值得 patch\n // 说明元素被移到左边了\n else if (isPatchable(startVnode, oldEndVnode)) {\n patch(api, startVnode, oldEndVnode)\n insertBefore(\n api,\n parentNode,\n oldEndVnode.node,\n oldStartVnode.node\n )\n startVnode = children[++startIndex]\n oldEndVnode = oldChildren[--oldEndIndex]\n }\n\n // 尝试同级元素的 key\n else {\n\n if (!oldKeyToIndex) {\n oldKeyToIndex = createKeyToIndex(oldChildren, oldStartIndex, oldEndIndex)\n }\n\n // 新节点之前的位置\n oldIndex = startVnode.key\n ? oldKeyToIndex[startVnode.key]\n : env.UNDEFINED\n\n // 移动元素\n if (isDef(oldIndex)) {\n patch(api, startVnode, oldChildren[oldIndex as number] as VNode)\n oldChildren[oldIndex as number] = env.UNDEFINED\n }\n // 新元素\n else {\n createVnode(api, startVnode)\n }\n\n insertVnode(api, parentNode, startVnode, oldStartVnode)\n\n startVnode = children[++startIndex]\n\n }\n }\n\n if (oldStartIndex > oldEndIndex) {\n addVnodes(\n api,\n parentNode,\n children,\n startIndex,\n endIndex,\n children[endIndex + 1]\n )\n }\n else if (startIndex > endIndex) {\n removeVnodes(\n api,\n parentNode,\n oldChildren,\n oldStartIndex,\n oldEndIndex\n )\n }\n}\n\nexport function patch(api: DomApi, vnode: VNode, oldVnode: VNode) {\n\n if (vnode === oldVnode) {\n return\n }\n\n const { node, data } = oldVnode\n\n // 如果不能 patch,则删除重建\n if (!isPatchable(vnode, oldVnode)) {\n // 同步加载的组件,初始化时不会传入占位节点\n // 它内部会自动生成一个注释节点,当它的根 vnode 和注释节点对比时,必然无法 patch\n // 于是走进此分支,为新组件创建一个 DOM 节点,然后继续 createComponent 后面的流程\n const parentNode = api.parent(node)\n createVnode(api, vnode)\n if (parentNode) {\n insertVnode(api, parentNode, vnode, oldVnode)\n removeVnode(api, parentNode, oldVnode)\n }\n return\n }\n\n vnode.node = node\n vnode.data = data\n\n // 组件正在异步加载,更新为最新的 vnode\n // 当异步加载完成时才能用上最新的 vnode\n if (oldVnode.isComponent && data[field.LOADING]) {\n data[field.VNODE] = vnode\n return\n }\n\n // 两棵静态子树就别折腾了\n if (vnode.isStatic && oldVnode.isStatic) {\n return\n }\n\n nativeAttr.update(api, vnode, oldVnode)\n nativeProp.update(api, vnode, oldVnode)\n component.update(vnode, oldVnode)\n directive.update(vnode, oldVnode)\n\n const { text, html, children, isStyle, isOption } = vnode,\n\n oldText = oldVnode.text,\n oldHtml = oldVnode.html,\n oldChildren = oldVnode.children\n\n if (is.string(text)) {\n if (text !== oldText) {\n api.text(node, text, isStyle, isOption)\n }\n }\n else if (is.string(html)) {\n if (html !== oldHtml) {\n api.html(node as Element, html, isStyle, isOption)\n }\n }\n // 两个都有需要 diff\n else if (children && oldChildren) {\n if (children !== oldChildren) {\n updateChildren(api, node, children, oldChildren)\n }\n }\n // 有新的没旧的 - 新增节点\n else if (children) {\n if (is.string(oldText) || is.string(oldHtml)) {\n api.text(node, env.EMPTY_STRING, isStyle)\n }\n addVnodes(api, node, children)\n }\n // 有旧的没新的 - 删除节点\n else if (oldChildren) {\n removeVnodes(api, node, oldChildren)\n }\n // 有旧的 text 没有新的 text\n else if (is.string(oldText) || is.string(oldHtml)) {\n api.text(node, env.EMPTY_STRING, isStyle)\n }\n\n}\n\nexport function create(api: DomApi, node: Node, context: YoxInterface, keypath: string): VNode {\n return {\n tag: api.tag(node),\n data: createData(),\n node,\n context,\n keypath,\n }\n}\n\nexport function destroy(api: DomApi, vnode: VNode, isRemove?: boolean) {\n if (isRemove) {\n const parentNode = api.parent(vnode.node)\n if (parentNode) {\n removeVnode(api, parentNode, vnode)\n }\n else if (process.env.NODE_ENV === 'development') {\n logger.fatal(`The vnode can't be destroyed without a parent node.`)\n }\n }\n else {\n destroyVnode(api, vnode)\n }\n}\n","/**\n * 元素 节点\n */\nexport const ELEMENT = 1\n\n/**\n * 属性 节点\n */\nexport const ATTRIBUTE = 2\n\n/**\n * 指令 节点\n */\nexport const DIRECTIVE = 3\n\n/**\n * 属性 节点\n */\nexport const PROPERTY = 4\n\n/**\n * 文本 节点\n */\nexport const TEXT = 5\n\n/**\n * if 节点\n */\nexport const IF = 6\n\n/**\n * else if 节点\n */\nexport const ELSE_IF = 7\n\n/**\n * else 节点\n */\nexport const ELSE = 8\n\n/**\n * each 节点\n */\nexport const EACH = 9\n\n/**\n * partial 节点\n */\nexport const PARTIAL = 10\n\n/**\n * import 节点\n */\nexport const IMPORT = 11\n\n/**\n * 表达式 节点\n */\nexport const EXPRESSION = 12\n\n/**\n * 延展操作 节点\n */\nexport const SPREAD = 13\n","import * as env from '../../yox-common/src/util/env'\n\nimport * as nodeType from './nodeType'\n\n// 特殊标签\nexport const specialTags = {}\n// 特殊属性\nexport const specialAttrs = {}\n// 名称 -> 类型的映射\nexport const name2Type = {}\n\nspecialTags[env.RAW_SLOT] =\nspecialTags[env.RAW_TEMPLATE] =\n\nspecialAttrs[env.RAW_KEY] =\nspecialAttrs[env.RAW_REF] =\nspecialAttrs[env.RAW_SLOT] = env.TRUE\n\nname2Type['if'] = nodeType.IF\nname2Type['each'] = nodeType.EACH\nname2Type['partial'] = nodeType.PARTIAL\n\n","import {\n PropertyHint,\n} from '../../yox-type/src/type'\n\nimport * as env from '../../yox-common/src/util/env'\nimport * as keypathUtil from '../../yox-common/src/util/keypath'\n\nimport ExpressionNode from '../../yox-expression-compiler/src/node/Node'\n\nimport * as nodeType from './nodeType'\n\nimport Node from './node/Node'\nimport Attribute from './node/Attribute'\nimport Directive from './node/Directive'\nimport Property from './node/Property'\nimport Each from './node/Each'\nimport Element from './node/Element'\nimport Else from './node/Else'\nimport ElseIf from './node/ElseIf'\nimport Expression from './node/Expression'\nimport If from './node/If'\nimport Import from './node/Import'\nimport Partial from './node/Partial'\nimport Spread from './node/Spread'\nimport Text from './node/Text'\n\nexport function createAttribute(name: string): Attribute {\n return {\n type: nodeType.ATTRIBUTE,\n isStatic: env.TRUE,\n name,\n }\n}\n\nexport function createDirective(name: string, ns: string, modifier?: string): Directive {\n return {\n type: nodeType.DIRECTIVE,\n ns,\n name,\n key: keypathUtil.join(ns, name),\n modifier,\n }\n}\n\nexport function createProperty(name: string, hint: PropertyHint, value?: string | number | boolean, expr?: ExpressionNode, children?: Node[]): Property {\n return {\n type: nodeType.PROPERTY,\n isStatic: env.TRUE,\n name,\n hint,\n value,\n expr,\n children,\n }\n}\n\nexport function createEach(from: ExpressionNode, to: ExpressionNode | void, equal: boolean, index: string): Each {\n return {\n type: nodeType.EACH,\n from,\n to,\n equal,\n index,\n isComplex: env.TRUE,\n }\n}\n\nexport function createElement(tag: string, isSvg: boolean, isStyle: boolean, isComponent: boolean): Element {\n return {\n type: nodeType.ELEMENT,\n tag,\n isSvg,\n isStyle,\n // 只有