UNPKG

@lv-saharan/omii

Version:

OMII,OMI + I ,quick web framework!

309 lines (270 loc) 8.33 kB
import { cssToDom, isArray, hyphenate, getValByPath, capitalize } from "./util"; import { diff } from "./vdom/diff"; import options from "./options"; import "weakmap-polyfill"; let id = 0; const adoptedStyleSheetsMap = new WeakMap(); export default class WeElement extends HTMLElement { static is = "WeElement"; constructor() { super(); // fix lazy load props missing this.props = Object.assign({}, this.constructor.defaultProps, this.props); this.elementId = id++; this.computed = {}; this.isInstalled = false; } async connectedCallback() { let p = this.parentNode; while (p && !this.store) { this.store = p.store; p = p.parentNode || p.host; } if (this.inject) { this.injection = {}; p = this.parentNode; let provide; while (p && !provide) { provide = p.provide; p = p.parentNode || p.host; } if (provide) { this.inject.forEach((injectKey) => { this.injection[injectKey] = provide[injectKey]; }); } else { throw "The provide prop was not found on the parent node or the provide type is incorrect."; } } await this.attrsToProps(); await this.beforeInstall(); await this.install(); await this.afterInstall(); let shadowRoot; if (this.constructor.isLightDom) { shadowRoot = this; } else { if (!this.shadowRoot) { shadowRoot = this.attachShadow({ mode: "open", }); } else { shadowRoot = this.shadowRoot; let fc; while ((fc = shadowRoot.firstChild)) { shadowRoot.removeChild(fc); } } } if (adoptedStyleSheetsMap.has(this.constructor)) { shadowRoot.adoptedStyleSheets = adoptedStyleSheetsMap.get( this.constructor ); } else { const css = this.constructor.css; if (css) { if (typeof css === "string") { const styleSheet = new CSSStyleSheet(); styleSheet.replaceSync(css); shadowRoot.adoptedStyleSheets = [styleSheet]; } else if (Object.prototype.toString.call(css) === "[object Array]") { const styleSheets = []; css.forEach((styleSheet) => { if (typeof styleSheet === "string") { const adoptedStyleSheet = new CSSStyleSheet(); adoptedStyleSheet.replaceSync(styleSheet); styleSheets.push(adoptedStyleSheet); } else { styleSheets.push(styleSheet); } shadowRoot.adoptedStyleSheets = styleSheets; }); } else if (css.default && typeof css.default === "string") { // [object Module] const styleSheet = new CSSStyleSheet(); styleSheet.replaceSync(css.default); shadowRoot.adoptedStyleSheets = [styleSheet]; } else { shadowRoot.adoptedStyleSheets = [css]; } adoptedStyleSheetsMap.set( this.constructor, shadowRoot.adoptedStyleSheets ); } } await this.beforeRender(); options.afterInstall && options.afterInstall(this); const rendered = await this.render(this.props, this.store); this.rootNode = await diff(null, rendered, null, this); await this.rendered(); if (this.css) { shadowRoot.appendChild( cssToDom(typeof this.css === "function" ? this.css() : this.css) ); } if (this.props.css) { this._customStyleElement = cssToDom(this.props.css); this._customStyleContent = this.props.css; shadowRoot.appendChild(this._customStyleElement); } if (isArray(this.rootNode)) { this.rootNode.forEach(function (item) { shadowRoot.appendChild(item); }); } else { this.rootNode && shadowRoot.appendChild(this.rootNode); } await this.installed(); this.isInstalled = true; } async disconnectedCallback() { await this.uninstall(); this.isInstalled = false; } async update(ignoreAttrs, updateSelf) { this._willUpdate = true; await this.beforeUpdate(); await this.beforeRender(); //fix null !== undefined if (this._customStyleContent != this.props.css) { this._customStyleContent = this.props.css; if (this._customStyleElement) { this._customStyleElement.textContent = this._customStyleContent; } else { // 当 prop css 开始没有值,后来有值 this._customStyleElement = cssToDom(this.props.css); this.shadowRoot.appendChild(this._customStyleElement); } } this.attrsToProps(ignoreAttrs); const rendered = await this.render(this.props, this.store); await this.rendered(); this.rootNode = await diff( this.rootNode, rendered, this.constructor.isLightDom ? this : this.shadowRoot, this, updateSelf ); this._willUpdate = false; await this.updated(); } async forceUpdate() { await this.update(true); } async updateProps(obj) { Object.keys(obj).forEach((key) => { this.props[key] = obj[key]; if (this.prevProps) { this.prevProps[key] = obj[key]; } }); await this.forceUpdate(); } async updateSelf(ignoreAttrs) { await this.update(ignoreAttrs, true); } removeAttribute(key) { super.removeAttribute(key); //Avoid executing removeAttribute methods before connectedCallback this.isInstalled && this.update(); } setAttribute(key, val) { if (val && typeof val === "object") { super.setAttribute(key, JSON.stringify(val)); } else { super.setAttribute(key, val); } //Avoid executing setAttribute methods before connectedCallback this.isInstalled && this.update(); } pureRemoveAttribute(key) { super.removeAttribute(key); } pureSetAttribute(key, val) { super.setAttribute(key, val); } attrsToProps(ignoreAttrs) { if ( ignoreAttrs || (this.store && this.store.ignoreAttrs) || this.props.ignoreAttrs ) return; const ele = this; ele.props["css"] = ele.getAttribute("css"); const attrs = this.constructor.propTypes; if (!attrs) return; Object.keys(attrs).forEach((key) => { const type = attrs[key]; const val = ele.getAttribute(hyphenate(key)); if (val !== null) { switch (type) { case String: ele.props[key] = val; break; case Number: ele.props[key] = Number(val); break; case Boolean: if (val === "false" || val === "0") { ele.props[key] = false; } else { ele.props[key] = true; } break; case Array: case Object: if (val[0] === ":") { ele.props[key] = getValByPath(val.substr(1), Omi.$); } else { try { ele.props[key] = JSON.parse(val); } catch (e) { console.warn( `The ${key} object prop does not comply with the JSON specification, the incorrect string is [${val}].` ); } } break; } } else { if ( ele.constructor.defaultProps && ele.constructor.defaultProps.hasOwnProperty(key) ) { ele.props[key] = ele.constructor.defaultProps[key]; } else { ele.props[key] = null; } } }); } fire(name, data) { const handler = this.props[`on${capitalize(name)}`]; if (handler) { handler( new CustomEvent(name, { detail: data, }) ); } else { this.dispatchEvent( new CustomEvent(name, { detail: data, }) ); } } beforeInstall() {} install() {} afterInstall() {} installed() {} uninstall() {} beforeUpdate() {} updated() {} beforeRender() {} rendered() {} receiveProps() {} }