Skip to content

Instantly share code, notes, and snippets.

@dy
Last active October 26, 2019 00:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dy/8c06e4c9ccb1c6a27ee184a85e1a76d7 to your computer and use it in GitHub Desktop.
Save dy/8c06e4c9ccb1c6a27ee184a85e1a76d7 to your computer and use it in GitHub Desktop.
Custom element decorators

Framework-agnostic decorators-based custom elements.

  • Optional renderer, like react, lit-html etc.
  • Builtin state/props - store.
  • Native elements, including extending.

Reference implementation, decorator names can be changed.

The code is mostly copy-paste from lit-element.

Requires .babelrc (handler with parcel automatically):

{
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "decoratorsBeforeExport": true
      }
    ],
    "@babel/plugin-proposal-class-properties"
  ]
}
export const customElement = (tagName, options) => (classOrDescriptor) => (typeof classOrDescriptor === 'function') ?
legacyCustomElement(tagName, options, classOrDescriptor) :
standardCustomElement(tagName, options, classOrDescriptor);
const standardCustomElement = (tagName, options, descriptor) => {
const { kind, elements } = descriptor;
return {
kind,
elements,
// This callback is called once the class is otherwise fully defined
finisher(clazz) {
window.customElements.define(tagName, clazz, options);
}
};
};
const legacyCustomElement = (tagName, options, clazz) => {
window.customElements.define(tagName, clazz, options);
return clazz;
};
export function prop(options) {
return (protoOrDescriptor, name) => (name !== undefined) ?
legacyUpdate(options, protoOrDescriptor, name) :
standardUpdate(options, protoOrDescriptor);
}
const standardProp = (options, element) => {
if (element.kind === 'method' && element.descriptor &&
!('value' in element.descriptor)) {
return Object.assign({}, element, {
finisher(clazz) {
createProp(clazz, element.key, options);
}
});
}
else {
return {
kind: 'field',
key: Symbol(),
placement: 'own',
descriptor: {},
initializer() {
if (typeof element.initializer === 'function') {
this[element.key] = element.initializer.call(this);
}
},
finisher(clazz) {
createProp(clazz, element.key, options);
}
};
}
};
const legacyProp = (options, proto, name) => {
createProp(proto.constructor, name, options);
};
function createProp(cls, prop, option) {
let _prop = Symbol(prop)
let planned
Object.defineProperty(cls.prototype, prop, {
get() { return this[_prop] },
set(value) {
this[_prop] = value
if (planned) return
planned = queueMicrotask(() => {
planned = null
this.render()
})
}
})
}
import { render, html } from 'lit-html'
import { customElement, update } from './ce-decorators.js'
@customElement('my-timer', { extends: 'p' })
class MyTimer extends HTMLParagraphElement {
@prop() count = 0
render () {
render(html`<p>${ this.textContent }<span>Count: ${ this.count }</span></p>`)
}
constructor () {
setTimeout(() => this.count++, 1000)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment