diff --git a/jsconfig.json b/jsconfig.json index 5fb0b37701d289d66d02dd34c835c945f48adbec..5b9eea21335f2dfb2af18b35c5fa0a1f462b1060 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,6 +2,15 @@ "compilerOptions": { "baseUrl": ".", "paths": { + "uni-shared": [ + "./src/shared/index.js" + ], + "uni-helpers/*": [ + "./src/core/helpers/*" + ], + "uni-mixins/*": [ + "./src/core/view/mixins/*" + ], "uni-core/*": [ "./src/core/*" ], diff --git a/packages/uni-template-compiler/lib/app/parser/tag-parser.js b/packages/uni-template-compiler/lib/app/parser/tag-parser.js index 0ac94d9801c952ca4f4a67ecfaf2992ed62fd217..70e7d0dc0b4249c023802950ceb63ca0673d7c12 100644 --- a/packages/uni-template-compiler/lib/app/parser/tag-parser.js +++ b/packages/uni-template-compiler/lib/app/parser/tag-parser.js @@ -4,8 +4,17 @@ const { const tags = require('@dcloudio/uni-cli-shared/lib/tags') +// web components +const elements = ['uni-view'] + // 仅限 view 层 module.exports = function parseTag (el) { + const tag = el.tag + const element = elements.find(element => tag === element || 'uni-' + tag === element) + if (element) { + el.tag = element + return + } if (el.tag.indexOf('v-uni-') !== 0 && hasOwn(tags, el.tag)) { el.tag = 'v-uni-' + el.tag } diff --git a/packages/uni-template-compiler/lib/util.js b/packages/uni-template-compiler/lib/util.js index fa0ef6afaf359ce1641751695de3bfcc6000d762..5e68bb7dc448d310a7861de4cece433f8d41069c 100644 --- a/packages/uni-template-compiler/lib/util.js +++ b/packages/uni-template-compiler/lib/util.js @@ -210,7 +210,7 @@ function isComponent (tagName) { return false } } - return !hasOwn(tags, getTagName(tagName.replace('v-uni-', ''))) + return !hasOwn(tags, getTagName(tagName.replace(/^(v-)?uni-/, ''))) } function makeMap (str, expectsLowerCase) { diff --git a/src/core/helpers/custom-elements-define.js b/src/core/helpers/custom-elements-define.js new file mode 100644 index 0000000000000000000000000000000000000000..1729cbd43c758c4c2d39e081e258c5cd257606d0 --- /dev/null +++ b/src/core/helpers/custom-elements-define.js @@ -0,0 +1,51 @@ +/** + * customElements.define + */ +(function () { + const defineProperty = Object.defineProperty + const createElement = document.createElement + const classes = new Map() + const registry = new Map() + + if ('customElements' in window && customElements && customElements.define) { + return + } + + function HTMLBuiltIn () { + const constructor = this.constructor + if (!classes.has(constructor)) { + throw new TypeError('Illegal constructor') + } + const is = classes.get(constructor) + const element = createElement.call(document, is) + return Object.setPrototypeOf(element, constructor.prototype) + } + + defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', { + value: HTMLBuiltIn + }) + defineProperty(window, 'HTMLElement', { + configurable: true, + value: HTMLBuiltIn + }) + defineProperty(document, 'createElement', { + configurable: true, + value: function value (name, options) { + const is = options && options.is + const Class = is ? registry.get(is) : registry.get(name) + return Class ? new Class() : createElement.call(document, name) + } + }) + defineProperty(window, 'customElements', { + configurable: true, + value: { + define: function define (is, Class) { + if (registry.has(is)) { + throw new Error('the name "'.concat(is, '" has already been used with this registry')) + } + classes.set(Class, is) + registry.set(is, Class) + } + } + }) +})() diff --git a/src/core/helpers/index.js b/src/core/helpers/index.js index 257e6c483778ab89392183ce054687d30b789b0d..7a98e8d7376b536ca51209e55740ba42a2b1724c 100644 --- a/src/core/helpers/index.js +++ b/src/core/helpers/index.js @@ -63,7 +63,7 @@ export function getTargetDataset (target) { $parent = $parent.$parent } } else { - dataset = target.dataset || {} + dataset = Object.assign({}, target.dataset, target.__uniDataset) } return normalizeDataset(dataset) } diff --git a/src/core/view/components/index.js b/src/core/view/components/index.js index 3564472fb89b30be2ab7ac65d24987a01cdf230a..e18b703c5f3b9f0ec534f508909141867036376c 100644 --- a/src/core/view/components/index.js +++ b/src/core/view/components/index.js @@ -9,6 +9,19 @@ const requireComponents = [ require.context('../../../platforms/' + __PLATFORM__ + '/view/components', true, /index\.vue$/) ] +let elements = {} + +if (__PLATFORM__ === 'app-plus') { + // TODO use full polyfill + require('uni-core/helpers/custom-elements-define') + const module = require('../../../platforms/app-plus/view/elements/index.js') + elements = module.default || module + for (const key in elements) { + // TODO use kebabCase + customElements.define(`uni-${key.toLowerCase()}`, elements[key]) + } +} + requireComponents.forEach((components, index) => { components.keys().forEach(fileName => { // 获取组件配置 @@ -18,10 +31,12 @@ requireComponents.forEach((components, index) => { componentConfig.mixins = componentConfig.mixins ? [].concat(baseMixin, componentConfig.mixins) : [baseMixin] - componentConfig.mixins.push(animation) + if (!componentConfig.functional) { + componentConfig.mixins.push(animation) + } + + componentConfig.name = 'VUni' + componentConfig.name - componentConfig.name = 'VUni' + componentConfig.name - componentConfig.isReserved = true // 全局注册组件 diff --git a/src/core/view/mixins/animation.js b/src/core/view/mixins/animation.js index 9ae7baff0adc527f68b29c48bb9681d95fda7d60..a3577a3dd794024b07bf8022f598789c263cf0df 100644 --- a/src/core/view/mixins/animation.js +++ b/src/core/view/mixins/animation.js @@ -51,7 +51,7 @@ function getStyle (action) { return style } -function startAnimation (context) { +export function startAnimation (context) { const animation = context.animation if (!animation || !animation.actions || !animation.actions.length) { return diff --git a/src/platforms/app-plus/view/components/view/index.vue b/src/platforms/app-plus/view/components/view/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..151086931c4c518992719bc3538445b5b58cedc0 --- /dev/null +++ b/src/platforms/app-plus/view/components/view/index.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/platforms/app-plus/view/elements/animation.js b/src/platforms/app-plus/view/elements/animation.js new file mode 100644 index 0000000000000000000000000000000000000000..cdc85f69fc18ce6956018a951932975cc5b4b9d0 --- /dev/null +++ b/src/platforms/app-plus/view/elements/animation.js @@ -0,0 +1,14 @@ +import UniElement from './element' +import { startAnimation } from 'uni-core/view/mixins/animation' + +export default class UniAnimationElement extends UniElement { + setAttribute (key, value) { + if (key === 'animation') { + startAnimation({ + $el: this, + animation: value + }) + } + super.setAttribute(key, value) + } +} diff --git a/src/platforms/app-plus/view/elements/element.js b/src/platforms/app-plus/view/elements/element.js new file mode 100644 index 0000000000000000000000000000000000000000..4b510875e247394b612645fa2dbc5df7ec733c50 --- /dev/null +++ b/src/platforms/app-plus/view/elements/element.js @@ -0,0 +1,24 @@ +import { + camelize +} from 'uni-shared' + +function formatKey (key) { + return camelize(key.substring(5)) +} + +export default class UniElement extends HTMLElement { + setAttribute (key, value) { + if (key.startsWith('data-')) { + const dataset = this.__uniDataset || (this.__uniDataset = {}) + dataset[formatKey(key)] = value + } + super.setAttribute(key, value) + } + + removeAttribute (key) { + if (this.__uniDataset && key.startsWith('data-')) { + delete this.__uniDataset[formatKey(key)] + } + super.removeAttribute(key) + } +} diff --git a/src/platforms/app-plus/view/elements/hover.js b/src/platforms/app-plus/view/elements/hover.js new file mode 100644 index 0000000000000000000000000000000000000000..deee362ba8749cd94481ceea45b18f5adc498bff --- /dev/null +++ b/src/platforms/app-plus/view/elements/hover.js @@ -0,0 +1,109 @@ +import UniAnimationElement from './animation' + +export default class UniHoverElement extends UniAnimationElement { + setAttribute (key, value) { + console.log('setAttribute:', key, value) + if (key === 'hover-class') { + this._updateHoverClass(value) + } + super.setAttribute(key, value) + } + + removeAttribute (key) { + if (key === 'hover-class') { + this._updateHoverClass() + } + super.removeAttribute(key) + } + + get hovering () { + return this._hovering + } + + set hovering (hovering) { + this._hovering = hovering + const hoverClass = this.getAttribute('hover-class') + if (hovering) { + this.classList.add(hoverClass) + } else { + this.classList.remove(hoverClass) + } + } + + _updateHoverClass (hoverClass) { + hoverClass = hoverClass || 'none' + if (hoverClass === 'none') { + this._removeEventListener() + } else { + this._addEventListener() + } + } + + _addEventListener () { + if (!this.__hoverTouchStart) { + this.addEventListener('touchstart', this.__hoverTouchStart = this._hoverTouchStart.bind(this)) + this.addEventListener('touchend', this.__hoverTouchEnd = this._hoverTouchEnd.bind(this)) + this.addEventListener('touchcancel', this.__hoverTouchCancel = this._hoverTouchCancel.bind(this)) + } + } + + _removeEventListener () { + if (this.__hoverTouchStart) { + this.removeEventListener('touchstart', this.__hoverTouchStart) + delete this.__hoverTouchStart + this.removeEventListener('touchend', this.__hoverTouchEnd) + delete this.__hoverTouchEnd + this.removeEventListener('touchcancel', this.__hoverTouchCancel) + delete this.__hoverTouchCancel + } + } + + _hoverTouchStart (evt) { + if (evt._hoverPropagationStopped) { + return + } + if (this.disabled) { + return + } + if (evt.touches.length > 1) { + return + } + if (this.getAttribute('hover-stop-propagation')) { + evt._hoverPropagationStopped = true + } + this._hoverTouch = true + const hoverStartTimeDefault = 50 + const hoverStartTime = Number(this.getAttribute('hover-start-time') || hoverStartTimeDefault) + this._hoverStartTimer = setTimeout(() => { + this.hovering = true + if (!this._hoverTouch) { + // 防止在hoverStartTime时间内触发了 touchend 或 touchcancel + this._hoverReset() + } + }, isNaN(hoverStartTime) ? hoverStartTimeDefault : hoverStartTime) + } + + _hoverTouchEnd () { + this._hoverTouch = false + if (this.hovering) { + this._hoverReset() + } + } + + _hoverReset () { + requestAnimationFrame(() => { + clearTimeout(this._hoverStayTimer) + const hoverStayTimeDefault = 400 + const hoverStayTime = Number(this.getAttribute('hover-stay-time') || hoverStayTimeDefault) + this._hoverStayTimer = setTimeout(() => { + this.hovering = false + }, isNaN(hoverStayTime) ? hoverStayTimeDefault : hoverStayTime) + }) + } + + _hoverTouchCancel () { + this._hoverTouch = false + this.hovering = false + clearTimeout(this._hoverStartTimer) + } +} diff --git a/src/platforms/app-plus/view/elements/index.js b/src/platforms/app-plus/view/elements/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7fd9d6ed6a380e7d24103c5a77b4bb1adbbd2f7f --- /dev/null +++ b/src/platforms/app-plus/view/elements/index.js @@ -0,0 +1,5 @@ +import View from './view' + +export default { + View +} diff --git a/src/platforms/app-plus/view/elements/view.js b/src/platforms/app-plus/view/elements/view.js new file mode 100644 index 0000000000000000000000000000000000000000..9ccac76150fff464b2caaf378751623561d9e653 --- /dev/null +++ b/src/platforms/app-plus/view/elements/view.js @@ -0,0 +1,5 @@ +import UniHoverElement from './hover' + +export default class UniView extends UniHoverElement { + +} diff --git a/src/core/view/components/view/README.md b/src/platforms/h5/view/components/view/README.md similarity index 100% rename from src/core/view/components/view/README.md rename to src/platforms/h5/view/components/view/README.md diff --git a/src/core/view/components/view/index.vue b/src/platforms/h5/view/components/view/index.vue similarity index 100% rename from src/core/view/components/view/index.vue rename to src/platforms/h5/view/components/view/index.vue