chore(omi-kbone): use latest version of kbone

This commit is contained in:
dntzhang 2020-02-21 07:46:47 +08:00
parent b57e0546b0
commit 4602482bc3
85 changed files with 1410 additions and 300 deletions

View File

@ -50,13 +50,13 @@ module.exports = {
}, {
name: 'hoverStartTime',
get(domNode) {
const value = parseInt(domNode.getAttribute('hover-start-time'), 10)
const value = parseFloat(domNode.getAttribute('hover-start-time'))
return !isNaN(value) ? value : 20
},
}, {
name: 'hoverStayTime',
get(domNode) {
const value = parseInt(domNode.getAttribute('hover-stay-time'), 10)
const value = parseFloat(domNode.getAttribute('hover-stay-time'))
return !isNaN(value) ? value : 70
},
}, {
@ -94,6 +94,11 @@ module.exports = {
get(domNode) {
return !!domNode.getAttribute('show-message-card')
},
}, {
name: 'businessId',
get(domNode) {
return domNode.getAttribute('business-id') || ''
},
}],
handles: {
onButtonGetUserInfo(evt) {

View File

@ -0,0 +1,27 @@
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/form.html
*
* 可以认为下述 form 组件的属性和事件是没有用的因为 button 组件会被封装到自定义组件内
*/
module.exports = {
properties: [{
name: 'reportSubmit',
get(domNode) {
return !!domNode.getAttribute('report-submit')
},
}, {
name: 'reportSubmitTimeout',
get(domNode) {
return +domNode.getAttribute('report-submit-timeout') || 0
},
}],
handles: {
onFormSubmit(evt) {
this.callSimpleEvent('submit', evt)
},
onFormReset(evt) {
this.callSimpleEvent('reset', evt)
},
},
}

View File

@ -47,7 +47,7 @@ module.exports = {
}, {
name: 'maxlength',
get(domNode) {
const value = parseInt(domNode.maxlength, 10)
const value = parseFloat(domNode.maxlength)
return !isNaN(value) ? value : 140
},
}, {
@ -78,19 +78,19 @@ module.exports = {
}, {
name: 'cursor',
get(domNode) {
const value = parseInt(domNode.getAttribute('cursor'), 10)
const value = parseFloat(domNode.getAttribute('cursor'))
return !isNaN(value) ? value : -1
},
}, {
name: 'selectionStart',
get(domNode) {
const value = parseInt(domNode.getAttribute('selection-start'), 10)
const value = parseFloat(domNode.getAttribute('selection-start'))
return !isNaN(value) ? value : -1
},
}, {
name: 'selectionEnd',
get(domNode) {
const value = parseInt(domNode.getAttribute('selection-end'), 10)
const value = parseFloat(domNode.getAttribute('selection-end'))
return !isNaN(value) ? value : -1
},
}, {
@ -114,11 +114,13 @@ module.exports = {
onInputInput(evt) {
if (!this.domNode) return
this.domNode.value = evt.detail.value
const value = '' + evt.detail.value
this.domNode.setAttribute('value', value)
this.callEvent('input', evt)
},
onInputFocus(evt) {
this._inputOldValue = this.domNode.value
this.callSimpleEvent('focus', evt)
},
@ -126,6 +128,10 @@ module.exports = {
if (!this.domNode) return
this.domNode.setAttribute('focus', false)
if (this._inputOldValue !== undefined && this.domNode.value !== this._inputOldValue) {
this._inputOldValue = undefined
this.callEvent('change', evt)
}
this.callSimpleEvent('blur', evt)
},
@ -145,25 +151,27 @@ module.exports = {
const otherDomNodes = window.document.querySelectorAll(`input[name=${name}]`) || []
if (value === domNode.value) {
domNode.checked = true
domNode.setAttribute('checked', true)
for (const otherDomNode of otherDomNodes) {
if (otherDomNode.type === 'radio' && otherDomNode !== domNode) {
otherDomNode.checked = false
otherDomNode.setAttribute('checked', false)
}
}
}
this.callSimpleEvent('change', evt)
this.callEvent('input', evt)
this.callEvent('change', evt)
},
onCheckboxChange(evt) {
const domNode = this.domNode
const value = evt.detail.value || []
if (value.indexOf(domNode.value) >= 0) {
domNode.checked = true
domNode.setAttribute('checked', true)
} else {
domNode.checked = false
domNode.setAttribute('checked', false)
}
this.callSimpleEvent('change', evt)
this.callEvent('input', evt)
this.callEvent('change', evt)
},
},
}

View File

@ -48,13 +48,13 @@ module.exports = {
}, {
name: 'minCache',
get(domNode) {
const value = parseInt(domNode.getAttribute('min-cache'), 10)
const value = parseFloat(domNode.getAttribute('min-cache'))
return !isNaN(value) ? value : 1
},
}, {
name: 'maxCache',
get(domNode) {
const value = parseInt(domNode.getAttribute('max-cache'), 10)
const value = parseFloat(domNode.getAttribute('max-cache'))
return !isNaN(value) ? value : 3
},
}, {

View File

@ -66,13 +66,13 @@ module.exports = {
}, {
name: 'minBitrate',
get(domNode) {
const value = parseInt(domNode.getAttribute('min-bitrate'), 10)
const value = parseFloat(domNode.getAttribute('min-bitrate'))
return !isNaN(value) ? value : 200
},
}, {
name: 'maxBitrate',
get(domNode) {
const value = parseInt(domNode.getAttribute('max-bitrate'), 10)
const value = parseFloat(domNode.getAttribute('max-bitrate'))
return !isNaN(value) ? value : 1000
},
}, {

View File

@ -5,19 +5,19 @@ module.exports = {
properties: [{
name: 'longitude',
get(domNode) {
const value = parseInt(domNode.getAttribute('longitude'), 10)
const value = parseFloat(domNode.getAttribute('longitude'))
return !isNaN(value) ? value : 39.92
},
}, {
name: 'latitude',
get(domNode) {
const value = parseInt(domNode.getAttribute('latitude'), 10)
const value = parseFloat(domNode.getAttribute('latitude'))
return !isNaN(value) ? value : 116.46
},
}, {
name: 'scale',
get(domNode) {
const value = parseInt(domNode.getAttribute('scale'), 10)
const value = parseFloat(domNode.getAttribute('scale'))
return !isNaN(value) ? value : 16
},
}, {
@ -69,7 +69,7 @@ module.exports = {
}, {
name: 'layerStyle',
get(domNode) {
const value = parseInt(domNode.getAttribute('layer-style'), 10)
const value = parseFloat(domNode.getAttribute('layer-style'))
return !isNaN(value) ? value : 1
},
}, {

View File

@ -0,0 +1,12 @@
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/movable-area.html
*/
module.exports = {
properties: [{
name: 'scaleArea',
get(domNode) {
return !!domNode.getAttribute('scale-area')
},
}],
handles: {},
}

View File

@ -0,0 +1,125 @@
const mp = require('miniprogram-render')
const {
cache,
} = mp.$$adapter
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/movable-view.html
*/
module.exports = {
properties: [{
name: 'direction',
get(domNode) {
return domNode.getAttribute('direction') || 'none'
},
}, {
name: 'inertia',
get(domNode) {
return !!domNode.getAttribute('inertia')
},
}, {
name: 'outOfBounds',
get(domNode) {
return !!domNode.getAttribute('out-of-bounds')
},
}, {
name: 'x',
get(domNode) {
return +domNode.getAttribute('x') || 0
},
}, {
name: 'y',
get(domNode) {
return +domNode.getAttribute('y') || 0
},
}, {
name: 'damping',
get(domNode) {
const value = parseFloat(domNode.getAttribute('damping'))
return !isNaN(value) ? value : 20
},
}, {
name: 'friction',
get(domNode) {
const value = parseFloat(domNode.getAttribute('friction'))
return !isNaN(value) ? value : 2
},
}, {
name: 'disabled',
get(domNode) {
return !!domNode.getAttribute('disabled')
},
}, {
name: 'scale',
get(domNode) {
return !!domNode.getAttribute('scale')
},
}, {
name: 'scaleMin',
get(domNode) {
const value = parseFloat(domNode.getAttribute('scale-min'))
return !isNaN(value) ? value : 0.5
},
}, {
name: 'scaleMax',
get(domNode) {
const value = parseFloat(domNode.getAttribute('scale-max'))
return !isNaN(value) ? value : 10
},
}, {
name: 'scaleValue',
get(domNode) {
const value = parseFloat(domNode.getAttribute('scale-value'))
return !isNaN(value) ? value : 1
},
}, {
name: 'animation',
get(domNode) {
const value = domNode.getAttribute('animation')
return value !== undefined ? !!value : true
},
}],
handles: {
onMovableViewChange(evt) {
const nodeId = evt.currentTarget.dataset.privateNodeId
const domNode = cache.getNode(this.pageId, nodeId)
if (!domNode) return
domNode.$$setAttributeWithoutUpdate('x', evt.detail.x)
domNode.$$setAttributeWithoutUpdate('y', evt.detail.y)
this.callSimpleEvent('change', evt, domNode)
},
onMovableViewScale(evt) {
const nodeId = evt.currentTarget.dataset.privateNodeId
const domNode = cache.getNode(this.pageId, nodeId)
if (!domNode) return
domNode.$$setAttributeWithoutUpdate('x', evt.detail.x)
domNode.$$setAttributeWithoutUpdate('y', evt.detail.y)
domNode.$$setAttributeWithoutUpdate('scale-value', evt.detail.scale)
this.callSimpleEvent('scale', evt, domNode)
},
onMovableViewHtouchmove(evt) {
const nodeId = evt.currentTarget.dataset.privateNodeId
const domNode = cache.getNode(this.pageId, nodeId)
if (!domNode) return
this.callSimpleEvent('htouchmove', evt, domNode)
},
onMovableViewVtouchmove(evt) {
const nodeId = evt.currentTarget.dataset.privateNodeId
const domNode = cache.getNode(this.pageId, nodeId)
if (!domNode) return
this.callSimpleEvent('vtouchmove', evt, domNode)
},
},
}

View File

@ -20,7 +20,7 @@ module.exports = {
}, {
name: 'delta',
get(domNode) {
const value = parseInt(domNode.getAttribute('delta'), 10)
const value = parseFloat(domNode.getAttribute('delta'))
return !isNaN(value) ? value : 1
},
}, {
@ -56,13 +56,13 @@ module.exports = {
}, {
name: 'hoverStartTime',
get(domNode) {
const value = parseInt(domNode.getAttribute('hover-start-time'), 10)
const value = parseFloat(domNode.getAttribute('hover-start-time'))
return !isNaN(value) ? value : 50
},
}, {
name: 'hoverStayTime',
get(domNode) {
const value = parseInt(domNode.getAttribute('hover-stay-time'), 10)
const value = parseFloat(domNode.getAttribute('hover-stay-time'))
return !isNaN(value) ? value : 600
},
}],

View File

@ -0,0 +1,7 @@
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/picker-view-column.html
*/
module.exports = {
properties: [],
handles: {},
}

View File

@ -0,0 +1,48 @@
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/picker-view.html
*/
module.exports = {
properties: [{
name: 'value',
get(domNode) {
const value = domNode.getAttribute('value')
return value !== undefined ? value : []
},
}, {
name: 'indicatorStyle',
get(domNode) {
return domNode.getAttribute('indicator-style') || ''
},
}, {
name: 'indicatorClass',
get(domNode) {
return domNode.getAttribute('indicator-class') || ''
},
}, {
name: 'maskStyle',
get(domNode) {
return domNode.getAttribute('mask-style') || ''
},
}, {
name: 'maskClass',
get(domNode) {
return domNode.getAttribute('mask-class') || ''
},
}],
handles: {
onPickerViewChange(evt) {
if (!this.domNode) return
this.domNode.$$setAttributeWithoutUpdate('value', evt.detail.value)
this.callSimpleEvent('change', evt)
},
onPickerViewPickstart(evt) {
this.callSimpleEvent('pickstart', evt)
},
onPickerViewPickend(evt) {
this.callSimpleEvent('pickend', evt)
},
},
}

View File

@ -15,26 +15,22 @@ module.exports = {
}, {
name: 'upperThreshold',
get(domNode) {
const value = parseInt(domNode.getAttribute('upper-threshold'), 10)
return !isNaN(value) ? value : 50
return domNode.getAttribute('upper-threshold') || '50'
},
}, {
name: 'lowerThreshold',
get(domNode) {
const value = parseInt(domNode.getAttribute('lower-threshold'), 10)
return !isNaN(value) ? value : 50
return domNode.getAttribute('lower-threshold') || '50'
},
}, {
name: 'scrollTop',
get(domNode) {
const value = parseInt(domNode.getAttribute('scroll-top'), 10)
return !isNaN(value) ? value : ''
return domNode.getAttribute('scroll-top') || ''
},
}, {
name: 'scrollLeft',
get(domNode) {
const value = parseInt(domNode.getAttribute('scroll-left'), 10)
return !isNaN(value) ? value : ''
return domNode.getAttribute('scroll-left') || ''
},
}, {
name: 'scrollIntoView',

View File

@ -10,13 +10,13 @@ module.exports = {
}, {
name: 'max',
get(domNode) {
const value = parseInt(domNode.getAttribute('max'), 10)
const value = parseFloat(domNode.getAttribute('max'))
return !isNaN(value) ? value : 100
},
}, {
name: 'step',
get(domNode) {
const value = parseInt(domNode.getAttribute('step'), 10)
const value = parseFloat(domNode.getAttribute('step'))
return !isNaN(value) ? value : 1
},
}, {
@ -52,7 +52,7 @@ module.exports = {
}, {
name: 'blockSize',
get(domNode) {
const value = parseInt(domNode.getAttribute('block-size'), 10)
const value = parseFloat(domNode.getAttribute('block-size'))
return !isNaN(value) ? value : 28
},
}, {
@ -68,6 +68,9 @@ module.exports = {
}],
handles: {
onSliderChange(evt) {
if (!this.domNode) return
this.domNode.$$setAttributeWithoutUpdate('value', evt.detail.value)
this.callSimpleEvent('change', evt)
},

View File

@ -0,0 +1,12 @@
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/swiper-item.html
*/
module.exports = {
properties: [{
name: 'itemId',
get(domNode) {
return domNode.getAttribute('item-id') || ''
},
}],
handles: {},
}

View File

@ -0,0 +1,95 @@
/**
* https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html
*/
module.exports = {
properties: [{
name: 'indicatorDots',
get(domNode) {
return !!domNode.getAttribute('indicator-dots')
},
}, {
name: 'indicatorColor',
get(domNode) {
return domNode.getAttribute('indicator-color') || 'rgba(0, 0, 0, .3)'
},
}, {
name: 'indicatorActiveColor',
get(domNode) {
return domNode.getAttribute('indicator-active-color') || '#000000'
},
}, {
name: 'autoplay',
get(domNode) {
return !!domNode.getAttribute('autoplay')
},
}, {
name: 'current',
get(domNode) {
return +domNode.getAttribute('current') || 0
},
}, {
name: 'interval',
get(domNode) {
const value = parseFloat(domNode.getAttribute('interval'))
return !isNaN(value) ? value : 5000
},
}, {
name: 'duration',
get(domNode) {
const value = parseFloat(domNode.getAttribute('duration'))
return !isNaN(value) ? value : 500
},
}, {
name: 'circular',
get(domNode) {
return !!domNode.getAttribute('circular')
},
}, {
name: 'vertical',
get(domNode) {
return !!domNode.getAttribute('vertical')
},
}, {
name: 'previousMargin',
get(domNode) {
return domNode.getAttribute('previous-margin') || '0px'
},
}, {
name: 'nextMargin',
get(domNode) {
return domNode.getAttribute('next-margin') || '0px'
},
}, {
name: 'displayMultipleItems',
get(domNode) {
const value = parseFloat(domNode.getAttribute('display-multiple-items'))
return !isNaN(value) ? value : 1
},
}, {
name: 'skipHiddenItemLayout',
get(domNode) {
return !!domNode.getAttribute('skip-hidden-item-layout')
},
}, {
name: 'easingFunction',
get(domNode) {
return domNode.getAttribute('easing-function') || 'default'
},
}],
handles: {
onSwiperChange(evt) {
if (!this.domNode) return
this.domNode.$$setAttributeWithoutUpdate('current', evt.detail.current)
this.callSimpleEvent('change', evt)
},
onSwiperTransition(evt) {
this.callSimpleEvent('transition', evt)
},
onSwiperAnimationfinish(evt) {
this.callSimpleEvent('animationfinish', evt)
},
},
}

View File

@ -30,7 +30,7 @@ module.exports = {
}, {
name: 'maxlength',
get(domNode) {
const value = parseInt(domNode.maxlength, 10)
const value = parseFloat(domNode.maxlength)
return !isNaN(value) ? value : 140
}
}, {
@ -61,7 +61,7 @@ module.exports = {
}, {
name: 'cursor',
get(domNode) {
const value = parseInt(domNode.getAttribute('cursor'), 10)
const value = parseFloat(domNode.getAttribute('cursor'))
return !isNaN(value) ? value : -1
},
}, {
@ -73,13 +73,13 @@ module.exports = {
}, {
name: 'selectionStart',
get(domNode) {
const value = parseInt(domNode.getAttribute('selection-start'), 10)
const value = parseFloat(domNode.getAttribute('selection-start'))
return !isNaN(value) ? value : -1
},
}, {
name: 'selectionEnd',
get(domNode) {
const value = parseInt(domNode.getAttribute('selection-end'), 10)
const value = parseFloat(domNode.getAttribute('selection-end'))
return !isNaN(value) ? value : -1
},
}, {
@ -91,6 +91,7 @@ module.exports = {
}],
handles: {
onTextareaFocus(evt) {
this._textareaOldValue = this.domNode.value
this.callSimpleEvent('focus', evt)
},
@ -98,6 +99,10 @@ module.exports = {
if (!this.domNode) return
this.domNode.setAttribute('focus', false)
if (this._textareaOldValue !== undefined && this.domNode.value !== this._textareaOldValue) {
this._textareaOldValue = undefined
this.callEvent('change', evt)
}
this.callSimpleEvent('blur', evt)
},
@ -108,7 +113,8 @@ module.exports = {
onTextareaInput(evt) {
if (!this.domNode) return
this.domNode.value = evt.detail.value
const value = '' + evt.detail.value
this.domNode.setAttribute('value', value)
this.callEvent('input', evt)
},

View File

@ -64,7 +64,7 @@ module.exports = {
}, {
name: 'direction',
get(domNode) {
const value = parseInt(domNode.getAttribute('direction'), 10)
const value = parseFloat(domNode.getAttribute('direction'))
return !isNaN(value) ? value : -1
},
}, {

View File

@ -15,13 +15,13 @@ module.exports = {
}, {
name: 'hoverStartTime',
get(domNode) {
const value = parseInt(domNode.getAttribute('hover-start-time'), 10)
const value = parseFloat(domNode.getAttribute('hover-start-time'))
return !isNaN(value) ? value : 50
},
}, {
name: 'hoverStayTime',
get(domNode) {
const value = parseInt(domNode.getAttribute('hover-stay-time'), 10)
const value = parseFloat(domNode.getAttribute('hover-stay-time'))
return !isNaN(value) ? value : 400
},
}],

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -28,6 +28,7 @@ Component({
},
data: {
wxCompName: '', // 需要渲染的内置组件名
wxCustomCompName: '', // 需要渲染的自定义组件名
innerChildNodes: [], // 内置组件的孩子节点
childNodes: [], // 孩子节点
},
@ -51,6 +52,7 @@ Component({
// 记录 dom
this.domNode = cache.getNode(pageId, nodeId)
if (!this.domNode) return
// TODO为了兼容基础库的一个 bug暂且如此实现
if (this.domNode.tagName === 'CANVAS') this.domNode._wxComponent = this
@ -72,12 +74,12 @@ Component({
// 初始化孩子节点
const childNodes = _.filterNodes(this.domNode, DOM_SUB_TREE_LEVEL - 1)
const dataChildNodes = _.dealWithLeafAndSimple(childNodes, this.onChildNodesUpdate)
if (data.wxCompName) {
// 内置组件
if (data.wxCompName || data.wxCustomCompName) {
// 内置组件/自定义组件
data.innerChildNodes = dataChildNodes
data.childNodes = []
} else {
// 非内置组件
// 普通标签
data.innerChildNodes = []
data.childNodes = dataChildNodes
}
@ -101,15 +103,16 @@ Component({
// 儿子节点有变化
const childNodes = _.filterNodes(this.domNode, DOM_SUB_TREE_LEVEL - 1)
if (_.checkDiffChildNodes(childNodes, this.data.childNodes)) {
const oldChildNodes = this.data.wxCompName || this.data.wxCustomCompName ? this.data.innerChildNodes : this.data.childNodes
if (_.checkDiffChildNodes(childNodes, oldChildNodes)) {
const dataChildNodes = _.dealWithLeafAndSimple(childNodes, this.onChildNodesUpdate)
const newData = {}
if (this.data.wxCompName) {
// 内置组件
if (this.data.wxCompName || this.data.wxCustomCompName) {
// 内置组件/自定义组件
newData.innerChildNodes = dataChildNodes
newData.childNodes = []
} else {
// 非内置组件
// 普通标签
newData.innerChildNodes = []
newData.childNodes = dataChildNodes
}
@ -143,10 +146,15 @@ Component({
const tagName = domNode.tagName
if (tagName === 'WX-COMPONENT') {
// 无可替换 html 标签
// 内置组件
if (data.wxCompName !== domNode.behavior) newData.wxCompName = domNode.behavior
const wxCompName = wxCompNameMap[domNode.behavior]
if (wxCompName) _.checkComponentAttr(wxCompName, domNode, newData, data)
} else if (tagName === 'WX-CUSTOM-COMPONENT') {
// 自定义组件
if (data.wxCustomCompName !== domNode.behavior) newData.wxCustomCompName = domNode.behavior
if (data.nodeId !== this.nodeId) data.nodeId = this.nodeId
if (data.pageId !== this.pageId) data.pageId = this.pageId
} else if (NOT_SUPPORT.indexOf(tagName) >= 0) {
// 不支持标签
newData.wxCompName = 'not-support'
@ -207,17 +215,17 @@ Component({
const type = targetDomNode.type
if (type === 'radio') {
targetDomNode.checked = true
targetDomNode.setAttribute('checked', true)
const name = targetDomNode.name
const otherDomNodes = window.document.querySelectorAll(`input[name=${name}]`) || []
for (const otherDomNode of otherDomNodes) {
if (otherDomNode.type === 'radio' && otherDomNode !== targetDomNode) {
otherDomNode.checked = false
otherDomNode.setAttribute('checked', false)
}
}
this.callSimpleEvent('change', {detail: {value: targetDomNode.value}}, targetDomNode)
} else if (type === 'checkbox') {
targetDomNode.checked = !targetDomNode.checked
targetDomNode.setAttribute('checked', !targetDomNode.checked)
this.callSimpleEvent('change', {detail: {value: targetDomNode.checked ? [targetDomNode.value] : []}}, targetDomNode)
} else {
targetDomNode.focus()
@ -232,6 +240,60 @@ Component({
this.callSimpleEvent('change', {detail: {value: checked}}, targetDomNode)
}
}
} else if ((domNode.tagName === 'BUTTON' || (domNode.tagName === 'WX-COMPONENT' && domNode.behavior === 'button')) && evt.type === 'click' && !isCapture) {
// 处理 button 点击
const type = domNode.tagName === 'BUTTON' ? domNode.getAttribute('type') : domNode.getAttribute('form-type')
const formAttr = domNode.getAttribute('form')
const form = formAttr ? window.document.getElementById('formAttr') : _.findParentNode(domNode, 'FORM')
if (!form) return
if (type !== 'submit' && type !== 'reset') return
const inputList = form.querySelectorAll('input[name]')
const textareaList = form.querySelectorAll('textarea[name]')
const switchList = form.querySelectorAll('wx-component[behavior=switch]').filter(item => !!item.getAttribute('name'))
const sliderList = form.querySelectorAll('wx-component[behavior=slider]').filter(item => !!item.getAttribute('name'))
const pickerList = form.querySelectorAll('wx-component[behavior=picker]').filter(item => !!item.getAttribute('name'))
if (type === 'submit') {
const formData = {}
if (inputList.length) {
inputList.forEach(item => {
if (item.type === 'radio') {
if (item.checked) formData[item.name] = item.value
} else if (item.type === 'checkbox') {
formData[item.name] = formData[item.name] || []
if (item.checked) formData[item.name].push(item.value)
} else {
formData[item.name] = item.value
}
})
}
if (textareaList.length) textareaList.forEach(item => formData[item.getAttribute('name')] = item.value)
if (switchList.length) switchList.forEach(item => formData[item.getAttribute('name')] = !!item.getAttribute('checked'))
if (sliderList.length) sliderList.forEach(item => formData[item.getAttribute('name')] = +item.getAttribute('value') || 0)
if (pickerList.length) pickerList.forEach(item => formData[item.getAttribute('name')] = item.getAttribute('value'))
this.callSimpleEvent('submit', {detail: {value: formData}, extra: {$$from: 'button'}}, form)
} else if (type === 'reset') {
if (inputList.length) {
inputList.forEach(item => {
if (item.type === 'radio') {
item.setAttribute('checked', false)
} else if (item.type === 'checkbox') {
item.setAttribute('checked', false)
} else {
item.setAttribute('value', '')
}
})
}
if (textareaList.length) textareaList.forEach(item => item.setAttribute('value', ''))
if (switchList.length) switchList.forEach(item => item.setAttribute('checked', undefined))
if (sliderList.length) sliderList.forEach(item => item.setAttribute('value', undefined))
if (pickerList.length) pickerList.forEach(item => item.setAttribute('value', undefined))
this.callSimpleEvent('reset', {extra: {$$from: 'button'}}, form)
}
}
}, 0)
})

View File

@ -2,5 +2,10 @@
"component": true,
"usingComponents": {
"element": "./index"
},
"componentGenerics": {
"custom-component": {
"default": "./custom-component/index"
}
}
}

View File

@ -6,6 +6,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
src="{{src}}"
bindload="onCoverImageLoad"
binderror="onCoverImageError"
@ -15,13 +16,55 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
scroll-top="{{scrollTop}}"
><template is="subtree-cover" data="{{childNodes: innerChildNodes}}"/></cover-view>
<movable-area
wx:elif="{{wxCompName === 'movable-area'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
scale-area="{{scaleArea}}"
><block wx:for="{{innerChildNodes}}" wx:key="nodeId">
<movable-view
wx:if="{{item.type === 'element' && item.compName === 'movable-view'}}"
data-private-node-id="{{item.nodeId}}"
data-private-page-id="{{item.pageId}}"
id="{{item.id}}"
class="{{item.class || ''}}"
style="{{item.style || ''}}"
bindtouchstart="onTouchStart"
bindtouchmove="onTouchMove"
bindtouchend="onTouchEnd"
bindtouchcancel="onTouchCancel"
bindtap="onTap"
hidden="{{item.extra.hidden}}"
direction="{{item.extra.direction}}"
inertia="{{item.extra.inertia}}"
out-of-bounds="{{item.extra.outOfBounds}}"
x="{{item.extra.x}}"
y="{{item.extra.y}}"
damping="{{item.extra.damping}}"
friction="{{item.extra.friction}}"
disabled="{{item.extra.disabled}}"
scale="{{item.extra.scale}}"
scale-min="{{item.extra.scaleMin}}"
scale-max="{{item.extra.scaleMax}}"
scale-value="{{item.extra.scaleValue}}"
animation="{{item.extra.animation}}"
bindchange="onMovableViewChange"
bindscale="onMovableViewScale"
bindhtouchmove="onMovableViewHtouchmove"
bindvtouchmove="onMovableViewVtouchmove"
><template is="subtree" data="{{childNodes: item.childNodes, inCover}}"/></movable-view>
</block></movable-area>
<scroll-view
wx:elif="{{wxCompName === 'scroll-view'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
scroll-x="{{scrollX}}"
scroll-y="{{scrollY}}"
upper-threshold="{{upperThreshold}}"
@ -36,11 +79,52 @@
bindscrolltolower="onScrollViewScrolltolower"
bindscroll="onScrollViewScroll"
><template is="subtree" data="{{childNodes: innerChildNodes, inCover}}"/></scroll-view>
<swiper
wx:elif="{{wxCompName === 'swiper'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
indicator-dots="{{indicatorDots}}"
indicator-color="{{indicatorColor}}"
indicator-active-color="{{indicatorActiveColor}}"
autoplay="{{autoplay}}"
current="{{current}}"
interval="{{interval}}"
duration="{{duration}}"
circular="{{circular}}"
vertical="{{vertical}}"
previous-margin="{{previousMargin}}"
next-margin="{{nextMargin}}"
display-multiple-items="{{displayMultipleItems}}"
skip-hidden-item-layout="{{skipHiddenItemLayout}}"
easing-function="{{easingFunction}}"
bindchange="onSwiperChange"
bindtransition="onSwiperTransition"
bindanimationfinish="onSwiperAnimationfinish"
><block wx:for="{{innerChildNodes}}" wx:key="nodeId">
<!-- swiper-item 无法设置 style不然会覆盖内置组件自己的滚动样式 -->
<swiper-item
wx:if="{{item.type === 'element' && item.compName === 'swiper-item'}}"
data-private-node-id="{{item.nodeId}}"
data-private-page-id="{{item.pageId}}"
id="{{item.id}}"
class="{{item.class || ''}}"
bindtouchstart="onTouchStart"
bindtouchmove="onTouchMove"
bindtouchend="onTouchEnd"
bindtouchcancel="onTouchCancel"
bindtap="onTap"
hidden="{{item.extra.hidden}}"
item-id="{{item.extra.itemId}}"
><template is="subtree" data="{{childNodes: item.childNodes, inCover}}"/></swiper-item>
</block></swiper>
<view
wx:elif="{{wxCompName === 'view'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
hover-class="{{hoverClass}}"
hover-stop-propagation="{{hoverStopPropagation}}"
hover-start-time="{{hoverStayTime}}"
@ -52,6 +136,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
type="{{type}}"
size="{{size}}"
color="{{color}}"
@ -61,6 +146,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
percent="{{percent}}"
show-info="{{showInfo}}"
border-radius="{{borderRadius}}"
@ -78,6 +164,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
selectable="{{selectable}}"
space="{{space}}"
decode="{{decode}}"
@ -88,6 +175,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
size="{{size}}"
type="{{type}}"
plain="{{plain}}"
@ -106,6 +194,7 @@
send-message-img="{{sendMessageImg}}"
app-parameter="{{appParameter}}"
show-message-card="{{showMessageCard}}"
business-id="{{businessId}}"
bindgetuserinfo="onButtonGetUserInfo"
bindcontact="onButtonContact"
bindgetphonenumber="onButtonGetPhoneNumber"
@ -118,6 +207,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
read-only="{{readOnly}}"
placeholder="{{placeholder}}"
show-img-size="{{showImgSize}}"
@ -129,11 +219,23 @@
bindinput="onEditorInput"
bindstatuschange="onEditorStatusChange"
></editor>
<form
wx:elif="{{wxCompName === 'form'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
report-submit="{{reportSubmit}}"
report-submit-timeout="{{reportSubmitTimeout}}"
bindsubmit="onFormSubmit"
bindreset="onFormReset"
><template is="subtree" data="{{childNodes: innerChildNodes, inCover}}"/></form>
<block wx:elif="{{wxCompName === 'input'}}">
<radio-group wx:if="{{type === 'radio'}}" bindchange="onRadioChange"><radio
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
value="{{value}}"
disabled="{{disabled}}"
checked="{{checked}}"
@ -143,16 +245,18 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
value="{{value}}"
disabled="{{disabled}}"
checked="{{checked}}"
color="{{color}}"
><template is="subtree" data="{{childNodes: innerChildNodes, inCover}}"/></checkbox></checkbox-group>
<input
wx:else
wx:elif="{{type !== 'hidden'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
value="{{value}}"
type="{{type}}"
password="{{password}}"
@ -182,6 +286,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
mode="{{mode}}"
disabled="{{disabled}}"
range="{{range}}"
@ -195,11 +300,42 @@
bindcolumnchange="onPickerColumnChange"
bindcancel="onPickerCancel"
><template is="subtree" data="{{childNodes: innerChildNodes, inCover}}"/></picker>
<picker-view
wx:elif="{{wxCompName === 'picker-view'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
value="{{value}}"
indicator-style="{{indicatorStyle}}"
indicator-class="{{indicatorClass}}"
mask-style="{{maskStyle}}"
mask-class="{{maskClass}}"
bindchange="onPickerViewChange"
bindpickstart="onPickerViewPickstart"
bindpickend="onPickerViewPickend"
><block wx:for="{{innerChildNodes}}" wx:key="nodeId">
<picker-view-column
wx:if="{{item.type === 'element' && item.compName === 'picker-view-column'}}"
data-private-node-id="{{item.nodeId}}"
data-private-page-id="{{item.pageId}}"
id="{{item.id}}"
class="{{item.class || ''}}"
style="{{item.style || ''}}"
bindtouchstart="onTouchStart"
bindtouchmove="onTouchMove"
bindtouchend="onTouchEnd"
bindtouchcancel="onTouchCancel"
bindtap="onTap"
hidden="{{item.extra.hidden}}"
><template is="subtree" data="{{childNodes: item.childNodes, inCover}}"/></picker-view-column>
</block></picker-view>
<slider
wx:elif="{{wxCompName === 'slider'}}"
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
min="{{min}}"
max="{{max}}"
step="{{step}}"
@ -220,6 +356,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
checked="{{checked}}"
disabled="{{disabled}}"
type="{{type}}"
@ -231,6 +368,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
value="{{value}}"
placeholder="{{placeholder}}"
placeholder-style="{{placeholderStyle}}"
@ -260,6 +398,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
target="{{target}}"
url="{{url}}"
open-type="{{openType}}"
@ -282,6 +421,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
mode="{{mode}}"
device-position="{{devicePosition}}"
flash="{{flash}}"
@ -296,6 +436,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
rendering-mode="{{renderingMode}}"
src="{{src}}"
mode="{{mode}}"
@ -309,6 +450,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
src="{{src}}"
mode="{{mode}}"
autoplay="{{autoplay}}"
@ -330,6 +472,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
url="{{url}}"
mode="{{mode}}"
autopush="{{autopush}}"
@ -360,6 +503,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
src="{{src}}"
duration="{{duration}}"
controls="{{controls}}"
@ -401,6 +545,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
longitude="{{longitude}}"
latitude="{{latitude}}"
scale="{{scale}}"
@ -437,6 +582,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
type="{{type}}"
canvas-id="{{canvasId}}"
disable-scroll="{{disableScroll}}"
@ -453,6 +599,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
unit-id="{{unitId}}"
ad-intervals="{{adIntervals}}"
bindload="onAdLoad"
@ -464,6 +611,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
bindload="onOfficialAccountLoad"
binderror="onOfficialAccountError"
></official-account>
@ -472,6 +620,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
type="{{type}}"
open-gid="{{openGid}}"
lang="{{lang}}"
@ -481,6 +630,7 @@
id="{{id}}"
class="{{class}}"
style="{{style}}"
hidden="{{hidden}}"
src="{{src}}"
bindmessage="onWebviewMessage"
bindload="onWebviewLoad"
@ -490,5 +640,12 @@
<block
wx:elif="{{wxCompName === 'not-support'}}"
>{{content}}</block>
<!-- 自定义组件 -->
<custom-component
wx:elif="{{!!wxCustomCompName}}"
name="{{wxCustomCompName}}"
data-private-node-id="{{nodeId}}"
data-private-page-id="{{pageId}}"
><template is="subtree" data="{{childNodes: innerChildNodes, inCover}}"/></custom-component>
<!-- 子节点 -->
<template is="subtree" data="{{childNodes, inCover}}"/>
<template wx:else is="subtree" data="{{childNodes, inCover}}"/>

View File

@ -1,35 +0,0 @@
/* 去除 button 组件默认样式 */
button {
position: relative;
display: block;
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
box-sizing: border-box;
font-size: inherit;
text-align: inherit;
text-decoration: inherit;
line-height: inherit;
border-radius: inherit;
-webkit-tap-highlight-color: inherit;
overflow: hidden;
color: inherit;
background-color: inherit;
}
button:after {
content: "";
width: auto;
height: auto;
position: static;
top: 0;
left: 0;
border: none;
-webkit-transform: none;
transform: none;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
box-sizing: border-box;
border-radius: 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,10 +8,18 @@ const wxComponentMap = {
wxCompName: 'cover-view',
config: require('../component/cover-view'),
},
'movable-area': {
wxCompName: 'movable-area',
config: require('../component/movable-area'),
},
'scroll-view': {
wxCompName: 'scroll-view',
config: require('../component/scroll-view'),
},
swiper: {
wxCompName: 'swiper',
config: require('../component/swiper'),
},
view: {
wxCompName: 'view',
config: require('../component/view'),
@ -38,6 +46,10 @@ const wxComponentMap = {
wxCompName: 'editor',
config: require('../component/editor'),
},
form: {
wxCompName: 'form',
config: require('../component/form'),
},
INPUT: {
wxCompName: 'input',
config: require('../component/input'),
@ -46,6 +58,10 @@ const wxComponentMap = {
wxCompName: 'picker',
config: require('../component/picker'),
},
'picker-view': {
wxCompName: 'picker-view',
config: require('../component/picker-view'),
},
slider: {
wxCompName: 'slider',
config: require('../component/slider'),
@ -113,6 +129,12 @@ const wxComponentMap = {
},
}
const wxSubComponentMap = {
'movable-view': require('../component/movable-view'),
'swiper-item': require('../component/swiper-item'),
'picker-view-column': require('../component/picker-view-column'),
}
const wxComponentKeys = Object.keys(wxComponentMap)
const wxCompNameMap = {}
const properties = {}
@ -124,9 +146,14 @@ wxComponentKeys.forEach(key => {
properties[wxCompName] = config.properties
Object.assign(handles, config.handles)
})
Object.keys(wxSubComponentMap).forEach(key => {
const config = wxSubComponentMap[key]
Object.assign(handles, config.handles)
})
module.exports = {
wxCompNameMap,
properties,
handles,
wxSubComponentMap,
}

View File

@ -4,12 +4,14 @@ const component = require('./component')
const {
Event,
EventTarget,
} = mp.$$adapter
const {
NOT_SUPPORT,
} = _
const {
wxCompNameMap
wxCompNameMap,
handles,
} = component
module.exports = {
@ -21,11 +23,16 @@ module.exports = {
const tagName = domNode.tagName
if (tagName === 'WX-COMPONENT') {
// 无可替换 html 标签
// 内置组件
data.wxCompName = domNode.behavior
const wxCompName = wxCompNameMap[data.wxCompName]
if (wxCompName) _.checkComponentAttr(wxCompName, domNode, data)
else console.warn(`value "${data.wxCompName}" is not supported for wx-component's behavior`)
} else if (tagName === 'WX-CUSTOM-COMPONENT') {
// 自定义组件
data.wxCustomCompName = domNode.behavior
data.nodeId = this.nodeId
data.pageId = this.pageId
} else if (NOT_SUPPORT.indexOf(tagName) >= 0) {
// 不支持标签
data.wxCompName = 'not-support'
@ -38,22 +45,23 @@ module.exports = {
},
/**
* 触发简单节点事件
* 触发简单节点事件不做冒泡处理
*/
callSimpleEvent(eventName, evt, domNode) {
domNode = domNode || this.domNode
if (!domNode) return
domNode.$$trigger(eventName, {
event: new Event({
name: eventName,
target: domNode,
eventPhase: Event.AT_TARGET,
detail: evt && evt.detail,
}),
currentTarget: domNode,
})
EventTarget.$$process(domNode, new Event({
touches: evt.touches,
changedTouches: evt.changedTouches,
name: eventName,
target: domNode,
eventPhase: Event.AT_TARGET,
detail: evt && evt.detail,
$$extra: evt && evt.extra,
bubbles: false,
}))
},
...component.handles,
...handles,
}

View File

@ -1,14 +1,20 @@
const mp = require('miniprogram-render')
const initData = require('./init-data')
const component = require('./component')
const {
cache,
tool,
} = mp.$$adapter
const ELEMENT_DIFF_KEYS = ['nodeId', 'pageId', 'tagName', 'compName', 'id', 'class', 'style', 'src', 'mode', 'lazyLoad', 'showMenuByLongpress', 'isImage', 'isLeaf', 'isSimple', 'content']
const {
wxSubComponentMap,
} = component
const ELEMENT_DIFF_KEYS = ['nodeId', 'pageId', 'tagName', 'compName', 'id', 'class', 'style', 'src', 'mode', 'lazyLoad', 'showMenuByLongpress', 'isImage', 'isLeaf', 'isSimple', 'content', 'extra']
const TEXT_NODE_DIFF_KEYS = ['nodeId', 'pageId', 'content']
const NEET_SPLIT_CLASS_STYLE_FROM_CUSTOM_ELEMENT = ['INPUT', 'TEXTAREA', 'VIDEO', 'CANVAS', 'WX-COMPONENT'] // 需要分离 class 和 style 的节点
const NEET_SPLIT_CLASS_STYLE_FROM_CUSTOM_ELEMENT = ['INPUT', 'TEXTAREA', 'VIDEO', 'CANVAS', 'WX-COMPONENT', 'WX-CUSTOM-COMPONENT'] // 需要分离 class 和 style 的节点
const NEET_BEHAVIOR_NORMAL_CUSTOM_ELEMENT = ['swiper-item', 'movable-view', 'picker-view-column']
const NEET_RENDER_TO_CUSTOM_ELEMENT = ['IFRAME', ...NEET_SPLIT_CLASS_STYLE_FROM_CUSTOM_ELEMENT] // 必须渲染成自定义组件的节点
const NOT_SUPPORT = ['IFRAME']
@ -30,10 +36,32 @@ function filterNodes(domNode, level) {
domInfo.class = `h5-${domInfo.tagName} node-${domInfo.nodeId} ${domInfo.class || ''}` // 增加默认 class
domInfo.domNode = child
// 特殊节点不需要处理 id 和样式
// 特殊节点
if (NEET_SPLIT_CLASS_STYLE_FROM_CUSTOM_ELEMENT.indexOf(child.tagName) >= 0) {
if (domInfo.tagName === 'wx-component' && NEET_BEHAVIOR_NORMAL_CUSTOM_ELEMENT.indexOf(child.behavior) !== -1) {
// 特殊内置组件,强制作为某内置组件的子组件,需要直接在当前模板渲染
domInfo.compName = child.behavior
domInfo.extra = {
hidden: child.getAttribute('hidden') || false,
}
// 补充该内置组件的属性
const {properties} = wxSubComponentMap[child.behavior] || {}
if (properties && properties.length) {
properties.forEach(({name, get}) => {
domInfo.extra[name] = get(child)
})
}
if (child.childNodes.length && level > 0) {
domInfo.childNodes = filterNodes(child, level - 1)
}
return domInfo
}
// 不需要处理 id 和样式
domInfo.class = `h5-${domInfo.tagName} ${domInfo.tagName === 'wx-component' ? 'wx-' + child.behavior : ''}`
domInfo.id = ''
domInfo.class = `h5-${domInfo.tagName}`
domInfo.style = ''
}
@ -68,6 +96,18 @@ function filterNodes(domNode, level) {
}).filter(child => !!child)
}
/**
* 判断两值是否相等
*/
function isEqual(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
// 值为数值,需要考虑精度
return parseInt(a * 1000, 10) === parseInt(b * 1000, 10)
}
return a === b
}
/**
* 比较新旧子节点是否不同
*/
@ -83,7 +123,19 @@ function checkDiffChildNodes(newChildNodes, oldChildNodes) {
const keys = newChild.type === 'element' ? ELEMENT_DIFF_KEYS : TEXT_NODE_DIFF_KEYS
for (const key of keys) {
if (newChild[key] !== oldChild[key]) return true
const newValue = newChild[key]
const oldValue = oldChild[key]
if (typeof newValue === 'object') {
// 值为对象,则判断对象顶层值是否有变化
if (typeof oldValue !== 'object') return true
const objectKeys = Object.keys(newValue)
for (const objectKey of objectKeys) {
if (newValue[objectKey] !== oldValue[objectKey]) return true
}
} else if (!isEqual(newValue, oldValue)) {
return true
}
}
// 比较孙子后辈节点
@ -109,17 +161,19 @@ function checkComponentAttr(name, domNode, destData, oldData) {
if (attrs && attrs.length) {
for (const {name, get} of attrs) {
const newValue = get(domNode)
if (!oldData || oldData[name] !== newValue) destData[name] = newValue
if (!oldData || !isEqual(newValue, oldData[name])) destData[name] = newValue
}
}
// 补充 id、class 和 style
// 补充 id、class、style 和 hidden
const newId = domNode.id
if (!oldData || oldData.id !== newId) destData.id = newId
const newClass = `wx-comp-${name} node-${domNode.$$nodeId} ${domNode.className || ''}`
if (!oldData || oldData.class !== newClass) destData.class = newClass
const newStyle = domNode.style.cssText
if (!oldData || oldData.style !== newStyle) destData.style = newStyle
const newHidden = domNode.getAttribute('hidden') || false
if (!oldData || oldData.hidden !== newHidden) destData.hidden = newHidden
}
/**
@ -163,6 +217,28 @@ function checkEventAccessDomNode(evt, domNode, dest) {
return false
}
/**
* 查找最近的符合条件的祖先节点
*/
function findParentNode(domNode, tagName) {
const checkParentNode = (parentNode, tagName) => {
if (!parentNode) return false
if (parentNode.tagName === tagName) return true
if (parentNode.tagName === 'WX-COMPONENT' && parentNode.behavior === tagName.toLowerCase()) return true
return false
}
let parentNode = domNode.parentNode
if (checkParentNode(parentNode, tagName)) return parentNode
while (parentNode && parentNode.tagName !== tagName) {
parentNode = parentNode.parentNode
if (checkParentNode(parentNode, tagName)) return parentNode
}
return null
}
module.exports = {
NOT_SUPPORT,
filterNodes,
@ -170,4 +246,5 @@ module.exports = {
checkComponentAttr,
dealWithLeafAndSimple,
checkEventAccessDomNode,
findParentNode,
}

View File

@ -1,8 +1,19 @@
const Location = require('./location')
const cache = require('../util/cache')
class Cookie {
constructor() {
this.$_map = {} // 三维数组domain - path - key
constructor(pageName) {
const config = cache.getConfig()
const runtime = config.runtime || {}
this.cookieStore = runtime.cookieStore
this.$_pageName = pageName
if (this.cookieStore !== 'storage' && this.cookieStore !== 'memory') {
// 需要全局共享
this.$_map = cache.getCookie()
} else {
this.$_map = {} // 三维数组domain - path - key
}
}
static parse(cookieStr) {
@ -151,6 +162,15 @@ class Cookie {
// 存在旧 cookie且被设置为已过期
delete map[cookieDomain][cookiePath][cookieKey]
}
// 持久化 cookie
if (this.cookieStore !== 'memory' && this.cookieStore !== 'globalmemory') {
const key = this.cookieStore === 'storage' ? `PAGE_COOKIE_${this.$_pageName}` : 'PAGE_COOKIE'
wx.setStorage({
key,
data: this.serialize(),
})
}
}
/**
@ -213,6 +233,54 @@ class Cookie {
.map(cookie => `${cookie.key}=${cookie.value}`)
.join('; ')
}
/**
* 序列化
*/
serialize() {
try {
return JSON.stringify(this.$_map)
} catch (err) {
console.log('cannot serialize the cookie')
return ''
}
}
/**
* 反序列化
*/
deserialize(str) {
let map = {}
try {
map = JSON.parse(str)
} catch (err) {
console.log('cannot deserialize the cookie')
map = {}
}
// 合并 cookie
const domainList = Object.keys(map)
for (const domainItem of domainList) {
const domainMap = map[domainItem] || {}
const pathList = Object.keys(domainMap)
for (const pathItem of pathList) {
const pathMap = map[domainItem][pathItem] || {}
Object.keys(pathMap).forEach(key => {
const cookie = pathMap[key]
if (!cookie) return
// 已存在则不覆盖
if (!this.$_map[domainItem]) this.$_map[domainItem] = {}
if (!this.$_map[domainItem][pathItem]) this.$_map[domainItem][pathItem] = {}
if (!this.$_map[domainItem][pathItem][key]) this.$_map[domainItem][pathItem][key] = cookie
})
}
}
}
}
module.exports = Cookie

View File

@ -156,10 +156,21 @@ class Location extends EventTarget {
param = '?' + param.join('&')
wx.redirectTo({
url: `${matchRoute}${param}`
const callMethod = window.$$miniprogram.isTabBarPage(matchRoute) ? 'switchTab' : 'redirectTo'
wx[callMethod]({
url: `${matchRoute}${param}`,
})
if (callMethod === 'switchTab') {
// switchTab 不会销毁页面实例,所以也需要恢复成原状
this.$_protocol = oldValues.protocol
this.$_hostname = oldValues.hostname
this.$_port = oldValues.port
this.$_pathname = oldValues.pathname
this.$_search = oldValues.search
this.$_hash = oldValues.hash
}
return true
} else {
const jumpUrl = this.href
@ -214,8 +225,9 @@ class Location extends EventTarget {
param = '?' + param.join('&')
wx.navigateTo({
url: `${matchRoute}${param}`
const callMethod = window.$$miniprogram.isTabBarPage(matchRoute) ? 'switchTab' : 'navigateTo'
wx[callMethod]({
url: `${matchRoute}${param}`,
})
} else {
window.$$trigger('pagenotfound', {
@ -538,14 +550,16 @@ class Location extends EventTarget {
}
reload() {
const window = cache.getWindow(this.$_pageId)
let param = ['type=jump', `targeturl=${encodeURIComponent(this.href)}`]
if (this.$_search) param.push(`search=${encodeURIComponent(this.$_search)}`)
if (this.$_hash) param.push(`hash=${encodeURIComponent(this.$_hash)}`)
param = '?' + param.join('&')
wx.redirectTo({
url: `${this.$_pageRoute}${param}`
const callMethod = window.$$miniprogram.isTabBarPage(this.$_pageRoute) ? 'switchTab' : 'redirectTo'
wx[callMethod]({
url: `${this.$_pageRoute}${param}`,
})
}

View File

@ -26,7 +26,7 @@ class Miniprogram {
init(url) {
if (typeof url === 'string') this.$_pageUrl = url // 设置真实 url
const {
origin, entry, router, runtime
origin, entry, router, runtime = {}
} = cache.getConfig()
const subpackagesMap = runtime.subpackagesMap || {}
@ -79,6 +79,17 @@ class Miniprogram {
return null
}
/**
* 判断是否 tabBar 页面
*/
isTabBarPage(pageRoute) {
const {
runtime = {}
} = cache.getConfig()
const tabBarMap = runtime.tabBarMap || {}
return !!tabBarMap[pageRoute]
}
}
module.exports = Miniprogram

View File

@ -14,6 +14,7 @@ const Video = require('./node/element/video')
const Canvas = require('./node/element/canvas')
const NotSupport = require('./node/element/not-support')
const WxComponent = require('./node/element/wx-component')
const WxCustomComponent = require('./node/element/wx-custom-component')
const Cookie = require('./bom/cookie')
const CONSTRUCTOR_MAP = {
@ -37,6 +38,7 @@ const WX_COMPONENT_LIST = [
'ad', 'official-account', 'open-data', 'web-view'
]
WX_COMPONENT_LIST.forEach(name => WX_COMPONENT_MAP[name] = name)
let WX_CUSTOM_COMPONENT_MAP = {}
/**
* 判断是否是内置组件
@ -54,6 +56,15 @@ class Document extends EventTarget {
constructor(pageId, nodeIdMap) {
super()
const config = cache.getConfig()
const runtime = config.runtime || {}
const cookieStore = runtime.cookieStore
WX_CUSTOM_COMPONENT_MAP = runtime.usingComponents || {}
this.$_pageId = pageId
const pageRoute = tool.getPageRoute(pageId)
const pageName = tool.getPageName(pageRoute)
// 用于封装特殊标签和对应构造器
const that = this
this.$_imageConstructor = function HTMLImageElement(width, height) {
@ -75,7 +86,7 @@ class Document extends EventTarget {
nodeId: 'e-body',
children: [],
}, nodeIdMap, this)
this.$_cookie = new Cookie()
this.$_cookie = new Cookie(pageName)
this.$_config = null
// documentElement
@ -93,6 +104,17 @@ class Document extends EventTarget {
// 更新 body 的 parentNode
this.$_tree.root.$$updateParent(this.$_node)
// 持久化 cookie
if (cookieStore !== 'memory' && cookieStore !== 'globalmemory') {
try {
const key = cookieStore === 'storage' ? `PAGE_COOKIE_${pageName}` : 'PAGE_COOKIE'
const cookie = wx.getStorageSync(key)
if (cookie) this.$$cookieInstance.deserialize(cookie)
} catch (err) {
// ignore
}
}
}
/**
@ -113,6 +135,13 @@ class Document extends EventTarget {
return this.$_cookie.getCookie(this.URL, true)
}
/**
* 获取 cookie 实例
*/
get $$cookieInstance() {
return this.$_cookie
}
/**
* 创建内置组件的时候是否支持不用前缀写法
*/
@ -147,6 +176,12 @@ class Document extends EventTarget {
options.attrs = options.attrs || {}
options.attrs.behavior = wxComponentName
return WxComponent.$$create(options, tree)
} else if (WX_CUSTOM_COMPONENT_MAP[originTagName]) {
// 自定义组件的特殊写法,转成 wx-custom-component 节点
options.tagName = 'wx-custom-component'
options.attrs = options.attrs || {}
options.componentName = originTagName
return WxCustomComponent.$$create(options, tree)
} else if (!tool.isTagNameSupport(tagName)) {
return NotSupport.$$create(options, tree)
} else {
@ -168,6 +203,37 @@ class Document extends EventTarget {
return Comment.$$create(options, tree || this.$_tree)
}
/**
* 处理 Set-Cookie 头串
*/
$$setCookie(str) {
if (str && typeof str === 'string') {
let start = 0
let startSplit = 0
let nextSplit = str.indexOf(',', startSplit)
const cookies = []
while (nextSplit >= 0) {
const lastSplitStr = str.substring(start, nextSplit)
const splitStr = str.substr(nextSplit)
if (/^,\s*([^,=;\x00-\x1F]+)=([^;\n\r\0\x00-\x1F]*).*/.test(splitStr)) {
// 分割成功,则上一片是完整 cookie
cookies.push(lastSplitStr)
start = nextSplit + 1
}
startSplit = nextSplit + 1
nextSplit = str.indexOf(',', startSplit)
}
// 塞入最后一片 cookie
cookies.push(str.substr(start))
cookies.forEach(cookie => this.cookie = cookie)
}
}
/**
* 对外属性和方法
*/
@ -229,6 +295,12 @@ class Document extends EventTarget {
return this.$_tree.getByClassName(className)
}
getElementsByName(name) {
if (typeof name !== 'string') return []
return this.$_tree.query(`*[name=${name}]`)
}
querySelector(selector) {
if (typeof selector !== 'string') return

View File

@ -27,11 +27,15 @@ class EventTarget {
* 初始化实例
*/
$$init() {
// 补充实例的属性,用于 'xxx' in XXX 判断
// 补充实例的属性,用于 'xxx' in XXX 判断
this.ontouchstart = null
this.ontouchmove = null
this.ontouchend = null
this.ontouchcancel = null
this.oninput = null
this.onfocus = null
this.onblur = null
this.onchange = null
this.$_miniprogramEvent = null // 记录已触发的小程序事件
this.$_eventHandlerMap = null
@ -68,8 +72,8 @@ class EventTarget {
static $$process(target, eventName, miniprogramEvent, extra, callback) {
let event
if (eventName instanceof CustomEvent) {
// 传入的是自定义事件
if (eventName instanceof CustomEvent || eventName instanceof Event) {
// 传入的是事件对象
event = eventName
eventName = event.type
}
@ -102,23 +106,21 @@ class EventTarget {
})
}
if (event.bubbles) {
// 捕获
for (let i = path.length - 1; i >= 0; i--) {
const currentTarget = path[i]
// 捕获
for (let i = path.length - 1; i >= 0; i--) {
const currentTarget = path[i]
if (!event.$$canBubble) break // 判定冒泡是否结束
if (currentTarget === target) continue
if (!event.$$canBubble) break // 判定冒泡是否结束
if (currentTarget === target) continue
event.$$setCurrentTarget(currentTarget)
event.$$setEventPhase(Event.CAPTURING_PHASE)
event.$$setCurrentTarget(currentTarget)
event.$$setEventPhase(Event.CAPTURING_PHASE)
currentTarget.$$trigger(eventName, {
event,
isCapture: true,
})
if (callback) callback(currentTarget, event, true)
}
currentTarget.$$trigger(eventName, {
event,
isCapture: true,
})
if (callback) callback(currentTarget, event, true)
}
// 目标
@ -130,12 +132,14 @@ class EventTarget {
target.$$trigger(eventName, {
event,
isCapture: true,
isTarget: true,
})
if (callback) callback(target, event, true)
target.$$trigger(eventName, {
event,
isCapture: false,
isTarget: true,
})
if (callback) callback(target, event, false)
}
@ -160,6 +164,8 @@ class EventTarget {
// 重置事件
event.$$setCurrentTarget(null)
event.$$setEventPhase(Event.NONE)
return event
}
/**
@ -187,19 +193,33 @@ class EventTarget {
/**
* 触发节点事件
*/
$$trigger(eventName, {event, isCapture} = {}) {
$$trigger(eventName, {
event, args = [], isCapture, isTarget
} = {}) {
eventName = eventName.toLowerCase()
const handlers = this.$_getHandlers(eventName, isCapture)
const onEventName = `on${eventName}`
if (typeof this[onEventName] === 'function') {
if ((!isCapture || !isTarget) && typeof this[onEventName] === 'function') {
// 触发 onXXX 绑定的事件
this[onEventName].call(this || null, event)
if (event && event.$$immediateStop) return
try {
this[onEventName].call(this || null, event, ...args)
} catch (err) {
console.error(err)
}
}
if (handlers && handlers.length) {
// 触发 addEventListener 绑定的事件
handlers.forEach(handler => handler.call(this || null, event))
handlers.forEach(handler => {
if (event && event.$$immediateStop) return
try {
handler.call(this || null, event, ...args)
} catch (err) {
console.error(err)
}
})
}
}

View File

@ -21,6 +21,7 @@ class Event {
this.$_currentTarget = options.currentTarget || options.target
this.$_eventPhase = options.eventPhase || Event.NONE
this.$_detail = options.detail || null
this.$_immediateStop = false
this.$_canBubble = true
this.$_bubbles = options.bubbles || false
this.$_touches = null
@ -41,14 +42,26 @@ class Event {
this.$_touches = options.touches.map(touch => ({...touch, target: options.target}))
this.$$checkTargetTouches()
} else if (options.touches) {
this.$_touches = []
this.$_targetTouches = []
}
// 处理 changedTouches
if (options.changedTouches && options.changedTouches.length) {
this.$_changedTouches = options.changedTouches.map(touch => ({...touch, target: options.target}))
} else if (options.changedTouches) {
this.$_changedTouches = []
}
}
/**
* 返回事件是否立即停止
*/
get $$immediateStop() {
return this.$_immediateStop
}
/**
* 返回事件时否还可以冒泡
*/
@ -149,6 +162,13 @@ class Event {
this.$_canBubble = false
}
stopImmediatePropagation() {
if (this.eventPhase === Event.NONE) return
this.$_immediateStop = true
this.$_canBubble = false
}
initEvent(name = '', bubbles) {
if (typeof name !== 'string') return

View File

@ -80,7 +80,7 @@ class Attribute {
if (name === 'id') {
map.id = value
} else if (name === 'class') {
} else if (name === 'class' || (element.tagName === 'WX-COMPONENT' && name === 'className')) {
element.className = value
} else if (name === 'style') {
element.style.cssText = value

View File

@ -48,6 +48,15 @@ class Element extends Node {
this.$_attrs = null
this.$_initAttrs(options.attrs)
// 补充实例的属性,用于 'xxx' in XXX 判断
this.onclick = null
this.ontouchstart = null
this.ontouchmove = null
this.ontouchend = null
this.ontouchcancel = null
this.onload = null
this.onerror = null
}
/**
@ -327,7 +336,7 @@ class Element extends Node {
* 调用 cloneNode 接口时用于处理额外的属性
*/
$$dealWithAttrsForCloneNode() {
// 具体实现逻辑由子类实现
// 具体实现逻辑由子类实现
return {}
}
@ -369,6 +378,25 @@ class Element extends Node {
})
}
/**
* 获取对应节点的 NodesRef 对象
* https://developers.weixin.qq.com/miniprogram/dev/api/wxml/NodesRef.html
*/
$$getNodesRef() {
tool.flushThrottleCache() // 先清空 setData
const window = cache.getWindow(this.$_pageId)
return new Promise((resolve, reject) => {
if (!window) reject()
if (this.tagName === 'CANVAS') {
// TODO为了兼容基础库的一个 bug暂且如此实现
resolve(wx.createSelectorQuery().in(this._wxComponent).select(`.node-${this.$_nodeId}`))
} else {
resolve(window.$$createSelectorQuery().select(`.miniprogram-root >>> .node-${this.$_nodeId}`))
}
})
}
/**
* 设置属性但不触发更新
*/
@ -578,12 +606,6 @@ class Element extends Node {
set textContent(text) {
text = '' + text
// 空串不新增 textNode 节点
if (!text) return
const nodeId = `b-${tool.getId()}` // 运行时生成,使用 b- 前缀
const child = this.ownerDocument.$$createTextNode({content: text, nodeId})
// 删除所有子节点
this.$_children.forEach(node => {
node.$$updateParent(null)
@ -593,6 +615,12 @@ class Element extends Node {
})
this.$_children.length = 0
// 空串不新增 textNode 节点
if (!text) return
const nodeId = `b-${tool.getId()}` // 运行时生成,使用 b- 前缀
const child = this.ownerDocument.$$createTextNode({content: text, nodeId})
this.appendChild(child)
}
@ -864,6 +892,30 @@ class Element extends Node {
return this.$_attrs.remove(name)
}
setAttributeNS(namespace, name, value) {
// 不支持 namespace使用 setAttribute 来兼容
console.warn(`namespace ${namespace} is not supported`)
this.setAttribute(name, value)
}
getAttributeNS(namespace, name) {
// 不支持 namespace使用 setAttribute 来兼容
console.warn(`namespace ${namespace} is not supported`)
return this.getAttribute(name)
}
hasAttributeNS(namespace, name) {
// 不支持 namespace使用 setAttribute 来兼容
console.warn(`namespace ${namespace} is not supported`)
return this.hasAttribute(name)
}
removeAttributeNS(namespace, name) {
// 不支持 namespace使用 setAttribute 来兼容
console.warn(`namespace ${namespace} is not supported`)
return this.removeAttribute(name)
}
contains(otherElement) {
const stack = []
let checkElement = this

View File

@ -103,7 +103,7 @@ class HTMLInputElement extends Element {
}
get type() {
return this.$_attrs.get('type')
return this.$_attrs.get('type') || 'text'
}
set type(value) {

View File

@ -94,7 +94,7 @@ class HTMLTextAreaElement extends Element {
* 对外属性和方法
*/
get type() {
return this.$_attrs.get('type')
return this.$_attrs.get('type') || 'textarea'
}
set type(value) {

View File

@ -41,6 +41,12 @@ class WxComponent extends Element {
get behavior() {
return this.$_attrs.get('behavior') || ''
}
set behavior(value) {
if (typeof value !== 'string') return
this.$_attrs.set('behavior', value)
}
}
module.exports = WxComponent

View File

@ -0,0 +1,64 @@
const Element = require('../element')
const Pool = require('../../util/pool')
const cache = require('../../util/cache')
const pool = new Pool()
class WxCustomComponent extends Element {
/**
* 创建实例
*/
static $$create(options, tree) {
const config = cache.getConfig()
if (config.optimization.elementMultiplexing) {
// 复用 element 节点
const instance = pool.get()
if (instance) {
instance.$$init(options, tree)
return instance
}
}
return new WxCustomComponent(options, tree)
}
/**
* 覆写父类的 $$init 方法
*/
$$init(options, tree) {
this.$_behavior = options.componentName
super.$$init(options, tree)
}
/**
* 覆写父类的 $$destroy 方法
*/
$$destroy() {
super.$$destroy()
this.$_behavior = null
}
/**
* 覆写父类的回收实例方法
*/
$$recycle() {
this.$$destroy()
const config = cache.getConfig()
if (config.optimization.elementMultiplexing) {
// 复用 element 节点
pool.add(this)
}
}
get behavior() {
return this.$_behavior
}
}
module.exports = WxCustomComponent

View File

@ -115,6 +115,12 @@ class Node extends EventTarget {
hasChildNodes() {
return false
}
remove() {
if (!this.parentNode || !this.parentNode.removeChild) return this
return this.parentNode.removeChild(this)
}
}
// 静态属性

View File

@ -21,5 +21,7 @@ module.exports = [
'wordBreak', 'wordSpacing', 'wordWrap', 'lineHeight', 'letterSpacing', 'whiteSpace', 'userSelect',
'visibility', 'opacity', 'zIndex', 'zoom', 'overflow', 'overflowX', 'overflowY',
'boxShadow', 'boxSizing', 'content', 'cursor', 'direction', 'listStyle', 'objectFit', 'pointerEvents', 'resize', 'verticalAlign', 'willChange', 'clip', 'clipPath', 'fill'
'boxShadow', 'boxSizing', 'content', 'cursor', 'direction', 'listStyle', 'objectFit', 'pointerEvents', 'resize', 'verticalAlign', 'willChange', 'clip', 'clipPath', 'fill',
'touchAction', 'WebkitAppearance'
]

View File

@ -108,6 +108,14 @@ class TextNode extends Node {
this.$_triggerParentUpdate()
}
get data() {
return this.textContent
}
set data(value) {
this.textContent = value
}
cloneNode() {
return this.ownerDocument.$$createTextNode({
content: this.$_content,

View File

@ -115,7 +115,7 @@ function checkHit(node, rule) {
// 属性选择器
if (attr) {
for (const {name, opr, val} of attr) {
const nodeVal = node[name]
const nodeVal = node[name] || node.getAttribute(name)
if (nodeVal === undefined) return false
if (opr) {

View File

@ -6,22 +6,21 @@ const QuerySelector = require('./query-selector')
function walkDomTree(node, cache) {
const tagMap = cache.tagMap = cache.tagMap || {}
const classMap = cache.classMap = cache.classMap || {}
const {tagName, classList} = node
// 标签
tagMap[tagName] = tagMap[tagName] || []
tagMap[tagName].push(node)
// 类
for (const className of classList) {
classMap[className] = classMap[className] || []
classMap[className].push(node)
}
const children = node.children || []
for (const child of children) {
const {tagName, classList} = child
// 标签
tagMap[tagName] = tagMap[tagName] || []
tagMap[tagName].push(child)
// 类
for (const className of classList) {
classMap[className] = classMap[className] || []
classMap[className].push(child)
}
// 递归遍历
walkDomTree(child, cache)
}

View File

@ -1,5 +1,6 @@
const pageMap = {}
let configCache = {}
const cookieCache = {}
/**
* 初始化
@ -74,6 +75,13 @@ function getConfig() {
return configCache
}
/**
* 获取全局 cookie
*/
function getCookie() {
return cookieCache
}
module.exports = {
init,
destroy,
@ -83,4 +91,5 @@ module.exports = {
getNode,
setConfig,
getConfig,
getCookie,
}

View File

@ -1,3 +1,4 @@
const Document = require('./document')
const EventTarget = require('./event/event-target')
const Event = require('./event/event')
const OriginalCustomEvent = require('./event/custom-event')
@ -13,8 +14,28 @@ const SessionStorage = require('./bom/session-storage')
const Performance = require('./bom/performance')
const Node = require('./node/node')
const Element = require('./node/element')
const TextNode = require('./node/text-node')
const Comment = require('./node/comment')
const ClassList = require('./node/class-list')
const Style = require('./node/style')
const Attribute = require('./node/attribute')
let lastRafTime = 0
const WINDOW_PROTOTYPE_MAP = {
location: Location.prototype,
navigator: Navigator.prototype,
performance: Performance.prototype,
screen: Screen.prototype,
history: History.prototype,
localStorage: LocalStorage.prototype,
sessionStorage: SessionStorage.prototype,
event: Event.prototype,
}
const ELEMENT_PROTOTYPE_MAP = {
attribute: Attribute.prototype,
classList: ClassList.prototype,
style: Style.prototype,
}
class Window extends EventTarget {
constructor(pageId) {
@ -113,6 +134,47 @@ class Window extends EventTarget {
}
}
/**
* 拉取处理切面必要的信息
*/
$_getAspectInfo(descriptor) {
if (!descriptor || typeof descriptor !== 'string') return
descriptor = descriptor.split('.')
const main = descriptor[0]
const sub = descriptor[1]
let method = descriptor[1]
let type = descriptor[2]
let prototype
// 找出对象原型
if (main === 'window') {
if (WINDOW_PROTOTYPE_MAP[sub]) {
prototype = WINDOW_PROTOTYPE_MAP[sub]
method = type
type = descriptor[3]
} else {
prototype = Window.prototype
}
} else if (main === 'document') {
prototype = Document.prototype
} else if (main === 'element') {
if (ELEMENT_PROTOTYPE_MAP[sub]) {
prototype = ELEMENT_PROTOTYPE_MAP[sub]
method = type
type = descriptor[3]
} else {
prototype = Element.prototype
}
} else if (main === 'textNode') {
prototype = TextNode.prototype
} else if (main === 'comment') {
prototype = Comment.prototype
}
return {prototype, method, type}
}
/**
* 暴露给小程序用的对象
*/
@ -142,6 +204,177 @@ class Window extends EventTarget {
tool.flushThrottleCache()
}
/**
* 触发节点事件
*/
$$trigger(eventName, options = {}) {
if (eventName === 'error' && typeof options.event === 'string') {
// 此处触发自 App.onError 钩子
const errStack = options.event
const errLines = errStack.split('\n')
let message = ''
for (let i = 0, len = errLines.length; i < len; i++) {
const line = errLines[i]
if (line.trim().indexOf('at') !== 0) {
message += (line + '\n')
} else {
break
}
}
const error = new Error(message)
error.stack = errStack
options.event = new this.$_customEventConstructor('error', {
target: this,
$$extra: {
message,
filename: '',
lineno: 0,
colno: 0,
error,
},
})
options.args = [message, error]
// window.onerror 比较特殊,需要调整参数
if (typeof this.onerror === 'function' && !this.onerror.$$isOfficial) {
const oldOnError = this.onerror
this.onerror = (event, message, error) => {
oldOnError.call(this, message, '', 0, 0, error)
}
this.onerror.$$isOfficial = true // 标记为官方封装的方法
}
}
super.$$trigger(eventName, options)
}
/**
* 获取原型
*/
$$getPrototype(descriptor) {
if (!descriptor || typeof descriptor !== 'string') return
descriptor = descriptor.split('.')
const main = descriptor[0]
const sub = descriptor[1]
if (main === 'window') {
if (WINDOW_PROTOTYPE_MAP[sub]) {
return WINDOW_PROTOTYPE_MAP[sub]
} else if (!sub) {
return Window.prototype
}
} else if (main === 'document') {
if (!sub) {
return Document.prototype
}
} else if (main === 'element') {
if (ELEMENT_PROTOTYPE_MAP[sub]) {
return ELEMENT_PROTOTYPE_MAP[sub]
} else if (!sub) {
return Element.prototype
}
} else if (main === 'textNode') {
if (!sub) {
return TextNode.prototype
}
} else if (main === 'comment') {
if (!sub) {
return Comment.prototype
}
}
}
/**
* 扩展 dom/bom 对象
*/
$$extend(descriptor, options) {
if (!descriptor || !options || typeof descriptor !== 'string' || typeof options !== 'object') return
const prototype = this.$$getPrototype(descriptor)
const keys = Object.keys(options)
if (prototype) keys.forEach(key => prototype[key] = options[key])
}
/**
* dom/bom 对象方法追加切面方法
*/
$$addAspect(descriptor, func) {
if (!descriptor || !func || typeof descriptor !== 'string' || typeof func !== 'function') return
const {prototype, method, type} = this.$_getAspectInfo(descriptor)
// 处理切面
if (prototype && method && type) {
const methodInPrototype = prototype[method]
if (typeof methodInPrototype !== 'function') return
// 重写原始方法
if (!methodInPrototype.$$isHook) {
prototype[method] = function(...args) {
const beforeFuncs = prototype[method].$$before || []
const afterFuncs = prototype[method].$$after || []
if (beforeFuncs.length) {
for (const beforeFunc of beforeFuncs) {
const isStop = beforeFunc.apply(this, args)
if (isStop) return
}
}
const res = methodInPrototype.apply(this, args)
if (afterFuncs.length) {
for (const afterFunc of afterFuncs) {
afterFunc.call(this, res)
}
}
return res
}
prototype[method].$$isHook = true
prototype[method].$$originalMethod = methodInPrototype
}
// 追加切面方法
if (type === 'before') {
prototype[method].$$before = prototype[method].$$before || []
prototype[method].$$before.push(func)
} else if (type === 'after') {
prototype[method].$$after = prototype[method].$$after || []
prototype[method].$$after.push(func)
}
}
}
/**
* 删除对 dom/bom 对象方法追加前置/后置处理
*/
$$removeAspect(descriptor, func) {
if (!descriptor || !func || typeof descriptor !== 'string' || typeof func !== 'function') return
const {prototype, method, type} = this.$_getAspectInfo(descriptor)
// 处理切面
if (prototype && method && type) {
const methodInPrototype = prototype[method]
if (typeof methodInPrototype !== 'function' || !methodInPrototype.$$isHook) return
// 移除切面方法
if (type === 'before' && methodInPrototype.$$before) {
methodInPrototype.$$before.splice(methodInPrototype.$$before.indexOf(func), 1)
} else if (type === 'after' && methodInPrototype.$$after) {
methodInPrototype.$$after.splice(methodInPrototype.$$after.indexOf(func), 1)
}
if ((!methodInPrototype.$$before || !methodInPrototype.$$before.length) && (!methodInPrototype.$$after || !methodInPrototype.$$after.length)) {
prototype[method] = methodInPrototype.$$originalMethod
}
}
}
/**
* 对外属性和方法
*/
@ -165,6 +398,10 @@ class Window extends EventTarget {
return this.$_customEventConstructor
}
get Event() {
return Event
}
get self() {
return this
}
@ -257,6 +494,10 @@ class Window extends EventTarget {
return Date
}
get Symbol() {
return Symbol
}
get performance() {
return this.$_performance
}