lit-element
Version:
A simple base class for creating fast, lightweight web components
197 lines • 7.06 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.
// tslint:disable-next-line:no-any
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
*/
export const customElement = (tagName) => (classOrDescriptor) => (typeof classOrDescriptor === 'function') ?
legacyCustomElement(tagName, classOrDescriptor) :
standardCustomElement(tagName, classOrDescriptor);
const standardProperty = (options, element) => {
// When decorating an accessor, pass it through and add property metadata.
// Note, the `hasOwnProperty` check in `createProperty` ensures we don't
// stomp over the user's accessor.
if (element.kind === 'method' && element.descriptor &&
!('value' in element.descriptor)) {
return Object.assign({}, element, { finisher(clazz) {
clazz.createProperty(element.key, options);
} });
}
else {
// 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.
*
* @ExportDecoratedItems
*/
export function property(options) {
// tslint:disable-next-line:no-any decorator
return (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.
*
* @ExportDecoratedItems
*/
export function query(selector) {
return (protoOrDescriptor,
// tslint:disable-next-line:no-any decorator
name) => {
const descriptor = {
get() {
return this.renderRoot.querySelector(selector);
},
enumerable: true,
configurable: true,
};
return (name !== undefined) ?
legacyQuery(descriptor, protoOrDescriptor, name) :
standardQuery(descriptor, protoOrDescriptor);
};
}
/**
* A property decorator that converts a class property into a getter
* that executes a querySelectorAll on the element's renderRoot.
*
* @ExportDecoratedItems
*/
export function queryAll(selector) {
return (protoOrDescriptor,
// tslint:disable-next-line:no-any decorator
name) => {
const descriptor = {
get() {
return this.renderRoot.querySelectorAll(selector);
},
enumerable: true,
configurable: true,
};
return (name !== undefined) ?
legacyQuery(descriptor, protoOrDescriptor, name) :
standardQuery(descriptor, protoOrDescriptor);
};
}
const legacyQuery = (descriptor, proto, name) => {
Object.defineProperty(proto, name, descriptor);
};
const standardQuery = (descriptor, element) => ({
kind: 'method',
placement: 'prototype',
key: element.key,
descriptor,
});
const standardEventOptions = (options, element) => {
return Object.assign({}, element, { finisher(clazz) {
Object.assign(clazz.prototype[element.key], options);
} });
};
const legacyEventOptions =
// tslint:disable-next-line:no-any legacy decorator
(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