Created
April 17, 2020 05:02
-
-
Save UpperCod/528a87de9e929cbed5ac53e94bcca881 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const Any = null; | |
export class BaseElement extends HTMLElement { | |
constructor() { | |
super(); | |
this.setup(); | |
} | |
async setup() { | |
this._attrs = {}; | |
this._props = {}; | |
this._init(); | |
this.mounted = new Promise((resolve) => (this.mount = resolve)); | |
this.unmounted = new Promise((resolve) => (this.unmount = resolve)); | |
await this.mounted; | |
this._update(); | |
} | |
_update() { | |
if (!this._prevent) { | |
this._prevent = true; | |
this.updated = Promise.resolve().then(() => { | |
this._prevent = false; | |
this.update(); | |
}); | |
} | |
} | |
static get observedAttributes() { | |
let { props = {} } = this; | |
let init = []; | |
let attrs = []; | |
for (let prop in props) | |
setProxy(this.prototype, prop, props[prop], attrs, init); | |
this.prototype._init = function () { | |
init.forEach((fn) => fn(this)); | |
}; | |
return attrs; | |
} | |
attributeChangedCallback(attr, oldValue, value) { | |
if (attr === this._ignoreAttr || oldValue === value) return; | |
this[this._attrs[attr]] = value; | |
} | |
connectedCallback() { | |
this.mount(); | |
} | |
disconnectedCallback() { | |
this.unmount(); | |
} | |
} | |
const TRUE_VALUES = [true, 1, "", "1", "true"]; | |
const NOT_CALLABLE = [Function, Any]; | |
function setProxy(proto, prop, schema, attrs, init) { | |
if (!(prop in proto)) { | |
let { type, reflect, event, value, attr = getAttr(prop) } = | |
typeof schema == "object" ? schema : { type: schema }; | |
let isCallable = !NOT_CALLABLE.includes(type); | |
attrs.push(attr); | |
async function set(newValue) { | |
let oldValue = this[prop]; | |
let { error, value } = filterValue( | |
type, | |
isCallable && typeof newValue == "function" | |
? newValue(oldValue) | |
: newValue | |
); | |
if (error && value != null) { | |
//error | |
} | |
if (oldValue == value) return; | |
this._props[prop] = value; | |
await this.mounted; | |
this._update(); | |
await this.updated; | |
if (event) dispatchEvent(this, event); | |
if (reflect) { | |
this._ignoreAttr = attr; | |
reflectValue(this, type, attr, this[prop]); | |
this._ignoreAttr = null; | |
} | |
} | |
Object.defineProperty(proto, prop, { | |
set, | |
get() { | |
return this._props[prop]; | |
}, | |
}); | |
init.push((context) => { | |
if (value != null) context[prop] = value; | |
context._attrs[attr] = prop; | |
}); | |
} | |
} | |
function getAttr(prop) { | |
return prop.replace(/([A-Z])/g, "-$1").toLowerCase(); | |
} | |
function filterValue(type, value) { | |
if (type == Any) return { value }; | |
try { | |
if (type == Boolean) { | |
value = TRUE_VALUES.includes(value); | |
} else if (typeof value == "string") { | |
value = | |
type == Number | |
? Number(value) | |
: type == Object || type == Array | |
? JSON.parse(value) | |
: value; | |
} | |
if ({}.toString.call(value) == `[object ${type.name}]`) { | |
return { value, error: type == Number && Number.isNaN(value) }; | |
} | |
} catch (e) {} | |
return { value, error: true }; | |
} | |
function reflectValue(context, type, attr, value) { | |
value == null | |
? context.removeAttribute(attr) | |
: context.setAttribute( | |
attr, | |
typeof value == "object" | |
? JSON.stringify(value) | |
: type == Boolean | |
? "" | |
: value | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment