Skip to content

Instantly share code, notes, and snippets.

@UpperCod
Created April 17, 2020 05:02
Show Gist options
  • Save UpperCod/528a87de9e929cbed5ac53e94bcca881 to your computer and use it in GitHub Desktop.
Save UpperCod/528a87de9e929cbed5ac53e94bcca881 to your computer and use it in GitHub Desktop.
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