277 lines
12 KiB
JavaScript
277 lines
12 KiB
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
|
|
*/
|
|
/**
|
|
* The main LitElement module, which defines the [[`LitElement`]] base class and
|
|
* related APIs.
|
|
*
|
|
* LitElement components can define a template and a set of observed
|
|
* properties. Changing an observed property triggers a re-render of the
|
|
* element.
|
|
*
|
|
* Import [[`LitElement`]] and [[`html`]] from this module to create a
|
|
* component:
|
|
*
|
|
* ```js
|
|
* import {LitElement, html} from 'lit-element';
|
|
*
|
|
* class MyElement extends LitElement {
|
|
*
|
|
* // Declare observed properties
|
|
* static get properties() {
|
|
* return {
|
|
* adjective: {}
|
|
* }
|
|
* }
|
|
*
|
|
* constructor() {
|
|
* this.adjective = 'awesome';
|
|
* }
|
|
*
|
|
* // Define the element's template
|
|
* render() {
|
|
* return html`<p>your ${adjective} template here</p>`;
|
|
* }
|
|
* }
|
|
*
|
|
* customElements.define('my-element', MyElement);
|
|
* ```
|
|
*
|
|
* `LitElement` extends [[`UpdatingElement`]] and adds lit-html templating.
|
|
* The `UpdatingElement` class is provided for users that want to build
|
|
* their own custom element base classes that don't use lit-html.
|
|
*
|
|
* @packageDocumentation
|
|
*/
|
|
import { render } from 'lit-html/lib/shady-render.js';
|
|
import { UpdatingElement } from './lib/updating-element.js';
|
|
export * from './lib/updating-element.js';
|
|
export { UpdatingElement as ReactiveElement } from './lib/updating-element.js';
|
|
export * from './lib/decorators.js';
|
|
export { html, svg, TemplateResult, SVGTemplateResult } from 'lit-html/lit-html.js';
|
|
import { supportsAdoptingStyleSheets, unsafeCSS } from './lib/css-tag.js';
|
|
export * from './lib/css-tag.js';
|
|
// IMPORTANT: do not change the property name or the assignment expression.
|
|
// This line will be used in regexes to search for LitElement usage.
|
|
// TODO(justinfagnani): inject version number at build time
|
|
(window['litElementVersions'] || (window['litElementVersions'] = []))
|
|
.push('2.5.1');
|
|
/**
|
|
* Sentinal value used to avoid calling lit-html's render function when
|
|
* subclasses do not implement `render`
|
|
*/
|
|
const renderNotImplemented = {};
|
|
/**
|
|
* Base element class that manages element properties and attributes, and
|
|
* renders a lit-html template.
|
|
*
|
|
* To define a component, subclass `LitElement` and implement a
|
|
* `render` method to provide the component's template. Define properties
|
|
* using the [[`properties`]] property or the [[`property`]] decorator.
|
|
*/
|
|
export class LitElement extends UpdatingElement {
|
|
/**
|
|
* Return the array of styles to apply to the element.
|
|
* Override this method to integrate into a style management system.
|
|
*
|
|
* @nocollapse
|
|
*/
|
|
static getStyles() {
|
|
return this.styles;
|
|
}
|
|
/** @nocollapse */
|
|
static _getUniqueStyles() {
|
|
// Only gather styles once per class
|
|
if (this.hasOwnProperty(JSCompiler_renameProperty('_styles', this))) {
|
|
return;
|
|
}
|
|
// Take care not to call `this.getStyles()` multiple times since this
|
|
// generates new CSSResults each time.
|
|
// TODO(sorvell): Since we do not cache CSSResults by input, any
|
|
// shared styles will generate new stylesheet objects, which is wasteful.
|
|
// This should be addressed when a browser ships constructable
|
|
// stylesheets.
|
|
const userStyles = this.getStyles();
|
|
if (Array.isArray(userStyles)) {
|
|
// De-duplicate styles preserving the _last_ instance in the set.
|
|
// This is a performance optimization to avoid duplicated styles that can
|
|
// occur especially when composing via subclassing.
|
|
// The last item is kept to try to preserve the cascade order with the
|
|
// assumption that it's most important that last added styles override
|
|
// previous styles.
|
|
const addStyles = (styles, set) => styles.reduceRight((set, s) =>
|
|
// Note: On IE set.add() does not return the set
|
|
Array.isArray(s) ? addStyles(s, set) : (set.add(s), set), set);
|
|
// Array.from does not work on Set in IE, otherwise return
|
|
// Array.from(addStyles(userStyles, new Set<CSSResult>())).reverse()
|
|
const set = addStyles(userStyles, new Set());
|
|
const styles = [];
|
|
set.forEach((v) => styles.unshift(v));
|
|
this._styles = styles;
|
|
}
|
|
else {
|
|
this._styles = userStyles === undefined ? [] : [userStyles];
|
|
}
|
|
// Ensure that there are no invalid CSSStyleSheet instances here. They are
|
|
// invalid in two conditions.
|
|
// (1) the sheet is non-constructible (`sheet` of a HTMLStyleElement), but
|
|
// this is impossible to check except via .replaceSync or use
|
|
// (2) the ShadyCSS polyfill is enabled (:. supportsAdoptingStyleSheets is
|
|
// false)
|
|
this._styles = this._styles.map((s) => {
|
|
if (s instanceof CSSStyleSheet && !supportsAdoptingStyleSheets) {
|
|
// Flatten the cssText from the passed constructible stylesheet (or
|
|
// undetectable non-constructible stylesheet). The user might have
|
|
// expected to update their stylesheets over time, but the alternative
|
|
// is a crash.
|
|
const cssText = Array.prototype.slice.call(s.cssRules)
|
|
.reduce((css, rule) => css + rule.cssText, '');
|
|
return unsafeCSS(cssText);
|
|
}
|
|
return s;
|
|
});
|
|
}
|
|
/**
|
|
* Performs element initialization. By default this calls
|
|
* [[`createRenderRoot`]] to create the element [[`renderRoot`]] node and
|
|
* captures any pre-set values for registered properties.
|
|
*/
|
|
initialize() {
|
|
super.initialize();
|
|
this.constructor._getUniqueStyles();
|
|
this.renderRoot = this.createRenderRoot();
|
|
// Note, if renderRoot is not a shadowRoot, styles would/could apply to the
|
|
// element's getRootNode(). While this could be done, we're choosing not to
|
|
// support this now since it would require different logic around de-duping.
|
|
if (window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot) {
|
|
this.adoptStyles();
|
|
}
|
|
}
|
|
/**
|
|
* Returns the node into which the element should render and by default
|
|
* creates and returns an open shadowRoot. Implement to customize where the
|
|
* element's DOM is rendered. For example, to render into the element's
|
|
* childNodes, return `this`.
|
|
* @returns {Element|DocumentFragment} Returns a node into which to render.
|
|
*/
|
|
createRenderRoot() {
|
|
return this.attachShadow(this.constructor.shadowRootOptions);
|
|
}
|
|
/**
|
|
* Applies styling to the element shadowRoot using the [[`styles`]]
|
|
* property. Styling will apply using `shadowRoot.adoptedStyleSheets` where
|
|
* available and will fallback otherwise. When Shadow DOM is polyfilled,
|
|
* ShadyCSS scopes styles and adds them to the document. When Shadow DOM
|
|
* is available but `adoptedStyleSheets` is not, styles are appended to the
|
|
* end of the `shadowRoot` to [mimic spec
|
|
* behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).
|
|
*/
|
|
adoptStyles() {
|
|
const styles = this.constructor._styles;
|
|
if (styles.length === 0) {
|
|
return;
|
|
}
|
|
// There are three separate cases here based on Shadow DOM support.
|
|
// (1) shadowRoot polyfilled: use ShadyCSS
|
|
// (2) shadowRoot.adoptedStyleSheets available: use it
|
|
// (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after
|
|
// rendering
|
|
if (window.ShadyCSS !== undefined && !window.ShadyCSS.nativeShadow) {
|
|
window.ShadyCSS.ScopingShim.prepareAdoptedCssText(styles.map((s) => s.cssText), this.localName);
|
|
}
|
|
else if (supportsAdoptingStyleSheets) {
|
|
this.renderRoot.adoptedStyleSheets =
|
|
styles.map((s) => s instanceof CSSStyleSheet ? s : s.styleSheet);
|
|
}
|
|
else {
|
|
// This must be done after rendering so the actual style insertion is done
|
|
// in `update`.
|
|
this._needsShimAdoptedStyleSheets = true;
|
|
}
|
|
}
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
// Note, first update/render handles styleElement so we only call this if
|
|
// connected after first update.
|
|
if (this.hasUpdated && window.ShadyCSS !== undefined) {
|
|
window.ShadyCSS.styleElement(this);
|
|
}
|
|
}
|
|
/**
|
|
* Updates the element. This method reflects property values to attributes
|
|
* and calls `render` to render DOM via lit-html. Setting properties inside
|
|
* this method will *not* trigger another update.
|
|
* @param _changedProperties Map of changed properties with old values
|
|
*/
|
|
update(changedProperties) {
|
|
// Setting properties in `render` should not trigger an update. Since
|
|
// updates are allowed after super.update, it's important to call `render`
|
|
// before that.
|
|
const templateResult = this.render();
|
|
super.update(changedProperties);
|
|
// If render is not implemented by the component, don't call lit-html render
|
|
if (templateResult !== renderNotImplemented) {
|
|
this.constructor
|
|
.render(templateResult, this.renderRoot, { scopeName: this.localName, eventContext: this });
|
|
}
|
|
// When native Shadow DOM is used but adoptedStyles are not supported,
|
|
// insert styling after rendering to ensure adoptedStyles have highest
|
|
// priority.
|
|
if (this._needsShimAdoptedStyleSheets) {
|
|
this._needsShimAdoptedStyleSheets = false;
|
|
this.constructor._styles.forEach((s) => {
|
|
const style = document.createElement('style');
|
|
style.textContent = s.cssText;
|
|
this.renderRoot.appendChild(style);
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Invoked on each update to perform rendering tasks. This method may return
|
|
* any value renderable by lit-html's `NodePart` - typically a
|
|
* `TemplateResult`. Setting properties inside this method will *not* trigger
|
|
* the element to update.
|
|
*/
|
|
render() {
|
|
return renderNotImplemented;
|
|
}
|
|
}
|
|
/**
|
|
* Ensure this class is marked as `finalized` as an optimization ensuring
|
|
* it will not needlessly try to `finalize`.
|
|
*
|
|
* Note this property name is a string to prevent breaking Closure JS Compiler
|
|
* optimizations. See updating-element.ts for more information.
|
|
*/
|
|
LitElement['finalized'] = true;
|
|
/**
|
|
* Reference to the underlying library method used to render the element's
|
|
* DOM. By default, points to the `render` method from lit-html's shady-render
|
|
* module.
|
|
*
|
|
* **Most users will never need to touch this property.**
|
|
*
|
|
* This property should not be confused with the `render` instance method,
|
|
* which should be overridden to define a template for the element.
|
|
*
|
|
* Advanced users creating a new base class based on LitElement can override
|
|
* this property to point to a custom render method with a signature that
|
|
* matches [shady-render's `render`
|
|
* method](https://lit-html.polymer-project.org/api/modules/shady_render.html#render).
|
|
*
|
|
* @nocollapse
|
|
*/
|
|
LitElement.render = render;
|
|
/** @nocollapse */
|
|
LitElement.shadowRootOptions = { mode: 'open' };
|
|
//# sourceMappingURL=lit-element.js.map
|