1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-02-24 01:22:26 +01:00
BezierInfo-2/lib/custom-element/custom-element.js
2020-08-03 18:27:33 -07:00

135 lines
3.5 KiB
JavaScript

const REG_KEY = `registered as custom element`;
// helper function
function NotImplemented(instance, fname) {
console.warn(
`missing implementation for ${fname}(...data) in ${instance.__proto__.constructor.name}`
);
}
// helper function for turning "ClassName" into "class-name"
function getElementTagName(cls) {
return cls.prototype.constructor.name.replace(
/([A-Z])([a-z])/g,
(a, b, c, d) => {
const r = `${b.toLowerCase()}${c}`;
return d > 0 ? `-${r}` : r;
}
);
}
/**
* This is an enrichment class to make working with custom elements
* actually pleasant, rather than a ridiculous exercise in figuring
* out a low-level spec.
*/
class CustomElement extends HTMLElement {
static register(cls) {
if (!cls[REG_KEY]) {
const tagName = cls.tagName || getElementTagName(cls);
customElements.define(tagName, cls);
cls[REG_KEY] = true;
return customElements.whenDefined(tagName);
}
return Promise.resolve();
}
static get tagName() {
return getElementTagName(this);
}
constructor(options = {}) {
super();
if (!customElements.resolveScope) {
customElements.resolveScope = function(scope) {
try {
return scope.getRootNode().host;
} catch (e) {
console.warn(e);
}
return window;
}
}
this._options = options;
const route = {
childList: (record) => {
this.handleChildChanges(
Array.from(record.addedNodes),
Array.from(record.removedNodes)
);
this.render();
},
attributes: (record) => {
this.handleAttributeChange(record.attributeName, record.oldValue, this.getAttribute(record.attributeName));
this.render();
},
};
// Set up a mutation observer because there are no custom
// element lifecycle functions for changes to the childNodes
// nodelist.
this._observer = new MutationObserver((records) => {
if (this.isConnected) {
records.forEach((record) => {
// console.log(record);
route[record.type](record);
});
}
});
this._observer.observe(this, {
childList: true,
attributes: true,
});
// Set up an open shadow DOM, because the web is open,
// and hiding your internals is ridiculous.
const shadowProps = {
mode: `open`,
delegatesFocus: !!this._options.focus,
};
this._shadow = this.attachShadow(shadowProps);
this._style = document.createElement(`style`);
this._style.textContent = this.getStyle();
if (this._options.header !== false)
this._header = document.createElement(`header`);
if (this._options.slot !== false && this._options.void !== true)
this._slot = document.createElement(`slot`);
if (this._options.footer !== false)
this._footer = document.createElement(`footer`);
}
connectedCallback() {
this.render();
}
handleChildChanges(added, removed) {
if (!this._options.void) NotImplemented(this, `handleChildChanges`);
}
handleAttributeChange(name, oldValue, newValue) {
NotImplemented(this, `handleAttributeChange`);
}
getStyle() {
return ``;
}
render() {
this._shadow.innerHTML = ``;
this._shadow.append(this._style);
if (this._options.header !== false) this._shadow.append(this._header);
if (this._options.slot !== false) this._shadow.append(this._slot);
if (this._options.footer !== false) this._shadow.append(this._footer);
}
}
export { CustomElement };