@polymer/lit-element
Version:
Polymer based lit-html custom element
171 lines • 6.42 kB
JavaScript
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
const legacyCustomElement = (tagName, clazz) => {
window.customElements.define(tagName, clazz);
// Cast as any because TS doesn't recognize the return type as being a
// subtype of the decorated class when clazz is typed as
// `Constructor<HTMLElement>` for some reason.
// `Constructor<HTMLElement>` is helpful to make sure the decorator is
// applied to elements however.
return clazz;
};
const standardCustomElement = (tagName, 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);
}
};
};
/**
* Class decorator factory that defines the decorated class as a custom element.
*
* @param tagName the name of the custom element to define
*
* In TypeScript, the `tagName` passed to `customElement` should be a key of the
* `HTMLElementTagNameMap` interface. To add your element to the interface,
* declare the interface in this module:
*
* @customElement('my-element')
* export class MyElement extends LitElement {}
*
* declare global {
* interface HTMLElementTagNameMap {
* 'my-element': MyElement;
* }
* }
*
*/
export const customElement = (tagName) => (classOrDescriptor) => (typeof classOrDescriptor === 'function')
? legacyCustomElement(tagName, classOrDescriptor)
: standardCustomElement(tagName, classOrDescriptor);
const standardProperty = (options, element) => {
// createProperty() takes care of defining the property, but we still must
// return some kind of descriptor, so return a descriptor for an unused
// prototype field. The finisher calls createProperty().
return {
kind: 'field',
key: Symbol(),
placement: 'own',
descriptor: {},
// When @babel/plugin-proposal-decorators implements initializers,
// do this instead of the initializer below. See:
// https://github.com/babel/babel/issues/9260 extras: [
// {
// kind: 'initializer',
// placement: 'own',
// initializer: descriptor.initializer,
// }
// ],
initializer() {
if (typeof element.initializer === 'function') {
this[element.key] = element.initializer.call(this);
}
},
finisher(clazz) {
clazz.createProperty(element.key, options);
}
};
};
const legacyProperty = (options, proto, name) => {
proto.constructor.createProperty(name, options);
};
/**
* A property decorator which creates a LitElement property which reflects a
* corresponding attribute value. A `PropertyDeclaration` may optionally be
* supplied to configure property features.
*/
export const property = (options) => (protoOrDescriptor, name) => (name !== undefined)
? legacyProperty(options, protoOrDescriptor, name)
: standardProperty(options, protoOrDescriptor);
/**
* A property decorator that converts a class property into a getter that
* executes a querySelector on the element's renderRoot.
*/
export const query = _query((target, selector) => target.querySelector(selector));
/**
* A property decorator that converts a class property into a getter
* that executes a querySelectorAll on the element's renderRoot.
*/
export const queryAll = _query((target, selector) => target.querySelectorAll(selector));
const legacyQuery = (descriptor, proto, name) => { Object.defineProperty(proto, name, descriptor); };
const standardQuery = (descriptor, element) => ({
kind: 'method',
placement: 'prototype',
key: element.key,
descriptor,
});
/**
* Base-implementation of `@query` and `@queryAll` decorators.
*
* @param queryFn exectute a `selector` (ie, querySelector or querySelectorAll)
* against `target`.
*/
function _query(queryFn) {
return (selector) => (protoOrDescriptor, name) => {
const descriptor = {
get() { return queryFn(this.renderRoot, selector); },
enumerable: true,
configurable: true,
};
return (name !== undefined)
? legacyQuery(descriptor, protoOrDescriptor, name)
: standardQuery(descriptor, protoOrDescriptor);
};
}
const standardEventOptions = (options, element) => {
return Object.assign({}, element, { finisher(clazz) {
Object.assign(clazz.prototype[element.key], options);
} });
};
const legacyEventOptions = (options, proto, name) => { Object.assign(proto[name], options); };
/**
* Adds event listener options to a method used as an event listener in a
* lit-html template.
*
* @param options An object that specifis event listener options as accepted by
* `EventTarget#addEventListener` and `EventTarget#removeEventListener`.
*
* Current browsers support the `capture`, `passive`, and `once` options. See:
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters
*
* @example
*
* class MyElement {
*
* clicked = false;
*
* render() {
* return html`<div @click=${this._onClick}`><button></button></div>`;
* }
*
* @eventOptions({capture: true})
* _onClick(e) {
* this.clicked = true;
* }
* }
*/
export const eventOptions = (options) =>
// Return value typed as any to prevent TypeScript from complaining that
// standard decorator function signature does not match TypeScript decorator
// signature
// TODO(kschaaf): unclear why it was only failing on this decorator and not
// the others
((protoOrDescriptor, name) => (name !== undefined)
? legacyEventOptions(options, protoOrDescriptor, name)
: standardEventOptions(options, protoOrDescriptor));
//# sourceMappingURL=decorators.js.map