Skip to content

Instantly share code, notes, and snippets.

@Legioth
Created February 12, 2020 11:50
Show Gist options
  • Save Legioth/2594a6043f54e391615cefea73a5a079 to your computer and use it in GitHub Desktop.
Save Legioth/2594a6043f54e391615cefea73a5a079 to your computer and use it in GitHub Desktop.
Stateful LitElement directive
import { html } from 'lit-element';
import { statefulDirective, StatefulLitElement } from './statefulDirective.js';
const simple = statefulDirective((param) => (part) => {
console.log("Activate simple", param, part);
part.setValue(param);
return () => console.log("Remove simple", param, part);
});
const withUpdate = statefulDirective((param) => (part) => {
console.log("Activate withUpdate", param, part);
part.setValue(param);
return {
'update': (newValue) => {
console.log("Update withUpdate", newValue, param, part);
part.setValue(newValue);
},
'remove': () => console.log("Remove withUpdate", param, part)
};
});
class MyElement extends StatefulLitElement {
static get properties() {
return {
value: { type: Number },
visible: { type: Boolean },
};
}
constructor() {
super();
this.value = 0;
this.visible = true;
}
render(){
return html`
${this.visible ? html`<div attr=${simple("attr")}>${withUpdate(this.value)}</div>` : html`<div>Hidden</div>`}
<button @click=${() => this.value++}>Update value</button>
<button @click=${() => this.visible = !this.visible}>Toggle visibility</button>
`;
}
}
customElements.define('my-element', MyElement);
import { LitElement } from 'lit-element';
import { directive, parts, NodePart, TemplateInstance } from 'lit-html'
let partToState = new WeakMap();
let active = new WeakSet();
function getChildren(part) {
if (part.value instanceof TemplateInstance) {
return new Set(part.value.__parts);
} else {
return new Set();
}
}
function activate(part) {
active.add(part);
getChildren(part).forEach(activate);
let state = partToState.get(part);
if (state && typeof state.activate == "function") {
let result = state.activate()(part);
part.commit();
if (typeof result == "function") {
state.remove = result;
} else {
state.remove = result.remove;
state.update = result.update;
}
}
}
function deactivate(part) {
active.delete(part);
getChildren(part).forEach(deactivate);
let state = partToState.get(part);
if (state && typeof state.remove == "function") {
state.remove();
}
}
const originalCommit = NodePart.prototype.commit;
NodePart.prototype.commit = function() {
if (active.has(this)) {
let childrenBefore = getChildren(this);
originalCommit.apply(this, arguments);
let childrenAfter = getChildren(this);
childrenBefore.forEach((child) => {
if (!childrenAfter.has(child)) {
deactivate(child);
}
});
childrenAfter.forEach((child) => {
if (!childrenBefore.has(child)) {
activate(child);
}
});
} else {
originalCommit.apply(this, arguments);
}
}
export function statefulDirective(factory) {
return directive((...args) => (part) => {
let state = partToState.get(part);
if (!state) {
partToState.set(part, state = {});
}
if (!active.has(part)) {
// Capture latest args in lambda
state.activate = () => factory(...args);
} else if (typeof state.update == 'function') {
state.update(...args);
}
});
}
function ensureActive(element) {
let rootPart = parts.get(element.renderRoot);
if (rootPart && !active.has(rootPart)) {
activate(rootPart);
}
}
export class StatefulLitElement extends LitElement {
disconnectedCallback() {
let rootPart = parts.get(this.renderRoot);
if (rootPart) {
deactivate(rootPart);
}
}
connectedCallback() {
super.connectedCallback();
ensureActive(this);
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
ensureActive(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment