Skip to content

Instantly share code, notes, and snippets.

@jaredcwhite
Created October 21, 2020 17:18
Show Gist options
  • Save jaredcwhite/9ca35e07c3ec421ab1ba1772625f907d to your computer and use it in GitHub Desktop.
Save jaredcwhite/9ca35e07c3ec421ab1ba1772625f907d to your computer and use it in GitHub Desktop.
Ruby2JS output
import { LitElement, html } from "lit-element";
// Lambda for determining default node action
let defaultActionForNode = (node) => {
switch (node.nodeName.toLowerCase()) {
case "form":
return "submit";
case "input":
case "textarea":
return node.getAttribute("type") == "submit" ? "click" : "input";
case "select":
return "change";
default:
return "click"
}
};
export class CrystallineElement extends LitElement {
static define(name, options={}) {
if (options.shadowDom == false) {
this.prototype.createRenderRoot = function() { return this }
};
if (options.passThrough) this.render = () => () => null;
customElements.define(name, this)
};
constructor() {
super();
// Set initial default values
if (this.constructor.properties) {
for (let [property, config] of Object.entries(this.constructor.properties)) {
this[property] = config.default
}
};
// button@identifier => button[custom-element-id='identifier']
let swapInId = selector => (
selector.replace(/@([a-z-]+)/g, `[${this.nodeName}-id='$1']`)
);
// Add queries as instance properties
if (this.constructor.queries) {
for (let [name, selector] of Object.entries(this.constructor.queries)) {
if (Array.isArray(selector)) {
selector = swapInId(selector[0]);
Object.defineProperty(this, `_${name}`, {get: () => (
Array.from(this.querySelectorAll(selector)).filter(node => (
this._nestedNodes.filter(nestedNode => nestedNode.contains(node)).length == 0
))
)})
} else {
selector = swapInId(selector);
Object.defineProperty(this, `_${name}`, {get: () => {
let node = this.querySelector(selector);
if (this._nestedNodes.filter(nestedNode => nestedNode.contains(node)).length == 0) {
return node
}
}})
}
}
};
return this
};
// Set up MutationObserver and get ready to look for event definitions
connectedCallback() {
super.connectedCallback();
this._registeredActions = [];
this._nestedNodes = [];
this.handleNodechanges([{type: "attributes", target: this}]);
this._nodeObserver = new MutationObserver(this.handleNodechanges.bind(this));
let config = {attributes: true, childList: true, subtree: true};
return this._nodeObserver.observe(this, config)
};
disconnectedCallback() {
super.disconnectedCallback();
this._nodeObserver.disconnect();
this._registeredActions = [];
this._nestedNodes = [];
return this._nestedNodes
};
// Callback for MutationObserver
handleNodechanges(changes) {
let selfName = this.nodeName.toLowerCase();
let actionAttr = `${selfName}-action`;
// Lambda to set up event listeners
let setupListener = (node, includeSelfNode) => {
if (!includeSelfNode && node.nodeName == this.nodeName) {
this._nestedNodes.push(node);
return
};
// make sure node isn't inside a nested node
if (this._nestedNodes.find(nestedNode => nestedNode.contains(node))) return;
if (node.hasAttribute(actionAttr)) {
for (let actionPair of node.getAttribute(actionAttr).split(" ")) {
let [actionEvent, actionName] = actionPair.split("->");
if (typeof actionName === 'undefined') {
actionName = actionEvent;
actionEvent = defaultActionForNode(node)
};
actionEvent = actionEvent.trim();
if (this._registeredActions.find(action => (
action.node == node && action.event == actionEvent && action.name == actionName
))) continue;
node.addEventListener(actionEvent, this[actionName].bind(this));
this._registeredActions.push({
node,
event: actionEvent,
name: actionName
})
}
}
};
if (!this._nodeObserver) {
// First run situation, check all child nodes
for (let node of this.querySelectorAll("*")) {
setupListener(node, false)
}
};
// Loop through all the mutations
for (let change of changes) {
if (change.type == "childList") {
for (let node of change.addedNodes) {
if (node.nodeType != 1) continue;
setupListener(node, false)
};
for (let node of change.removedNodes) {
// clear out removed nested nodes
if (node.nodeName != this.nodeName) {
// only process element nodes
continue
};
this._nestedNodes = this._nestedNodes.filter(nestedNode => nestedNode != node)
}
} else if (change.type == "attributes") {
setupListener(change.target, true)
}
}
};
render() {
return html`<slot></slot>`
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment