lit / lit-element Public
Permalink
lit-element/src/lit-element.ts
Newer
100644
332 lines (306 sloc)
12.5 KB
1
/**
2
* @license
3
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4
* This code may only be used under the BSD style license found at
5
* http://polymer.github.io/LICENSE.txt
6
* The complete set of authors may be found at
7
* http://polymer.github.io/AUTHORS.txt
8
* The complete set of contributors may be found at
9
* http://polymer.github.io/CONTRIBUTORS.txt
10
* Code distributed by Google as part of the polymer project is also
11
* subject to an additional IP rights grant found at
12
* http://polymer.github.io/PATENTS.txt
13
*/
16
* The main LitElement module, which defines the [[`LitElement`]] base class and
17
* related APIs.
19
* LitElement components can define a template and a set of observed
20
* properties. Changing an observed property triggers a re-render of the
21
* element.
23
* Import [[`LitElement`]] and [[`html`]] from this module to create a
24
* component:
25
*
26
* ```js
27
* import {LitElement, html} from 'lit-element';
28
*
29
* class MyElement extends LitElement {
30
*
31
* // Declare observed properties
32
* static get properties() {
33
* return {
34
* adjective: {}
35
* }
36
* }
37
*
38
* constructor() {
39
* this.adjective = 'awesome';
40
* }
41
*
42
* // Define the element's template
43
* render() {
44
* return html`<p>your ${adjective} template here</p>`;
45
* }
46
* }
47
*
48
* customElements.define('my-element', MyElement);
49
* ```
50
*
51
* `LitElement` extends [[`UpdatingElement`]] and adds lit-html templating.
52
* The `UpdatingElement` class is provided for users that want to build
53
* their own custom element base classes that don't use lit-html.
54
*
55
* @packageDocumentation
56
*/
57
import {render, ShadyRenderOptions} from 'lit-html/lib/shady-render.js';
59
import {PropertyValues, UpdatingElement} from './lib/updating-element.js';
62
export {UpdatingElement as ReactiveElement} from './lib/updating-element.js';
64
export {html, svg, TemplateResult, SVGTemplateResult} from 'lit-html/lit-html.js';
65
import {supportsAdoptingStyleSheets, CSSResult, unsafeCSS} from './lib/css-tag.js';
68
declare global {
69
interface Window {
70
litElementVersions: string[];
71
}
72
}
73
74
// IMPORTANT: do not change the property name or the assignment expression.
75
// This line will be used in regexes to search for LitElement usage.
76
// TODO(justinfagnani): inject version number at build time
77
(window['litElementVersions'] || (window['litElementVersions'] = []))
82
export interface CSSResultArray extends
83
Array<CSSResultOrNative|CSSResultArray> {}
85
export type CSSResultGroup = CSSResultOrNative|CSSResultArray;
86
87
/**
88
* Sentinal value used to avoid calling lit-html's render function when
89
* subclasses do not implement `render`
90
*/
91
const renderNotImplemented = {};
92
93
/**
94
* Base element class that manages element properties and attributes, and
95
* renders a lit-html template.
98
* `render` method to provide the component's template. Define properties
99
* using the [[`properties`]] property or the [[`property`]] decorator.
101
export class LitElement extends UpdatingElement {
102
/**
103
* Ensure this class is marked as `finalized` as an optimization ensuring
104
* it will not needlessly try to `finalize`.
105
*
106
* Note this property name is a string to prevent breaking Closure JS Compiler
107
* optimizations. See updating-element.ts for more information.
108
*/
112
* Reference to the underlying library method used to render the element's
113
* DOM. By default, points to the `render` method from lit-html's shady-render
114
* module.
117
*
118
* This property should not be confused with the `render` instance method,
121
* Advanced users creating a new base class based on LitElement can override
122
* this property to point to a custom render method with a signature that
123
* matches [shady-render's `render`
124
* method](https://lit-html.polymer-project.org/api/modules/shady_render.html#render).
128
static render:
129
(result: unknown, container: Element|DocumentFragment,
130
options: ShadyRenderOptions) => void = render;
132
/**
133
* Array of styles to apply to the element. The styles should be defined
136
static styles?: CSSResultGroup;
139
static shadowRootOptions: ShadowRootInit = {mode: 'open'};
141
private static _styles: Array<CSSResultOrNative|CSSResult>|undefined;
143
/**
144
* Return the array of styles to apply to the element.
145
* Override this method to integrate into a style management system.
146
*
147
* @nocollapse
148
*/
149
static getStyles(): CSSResultGroup|undefined {
150
return this.styles;
154
private static _getUniqueStyles() {
155
// Only gather styles once per class
156
if (this.hasOwnProperty(JSCompiler_renameProperty('_styles', this))) {
157
return;
158
}
159
// Take care not to call `this.getStyles()` multiple times since this
160
// generates new CSSResults each time.
161
// TODO(sorvell): Since we do not cache CSSResults by input, any
162
// shared styles will generate new stylesheet objects, which is wasteful.
163
// This should be addressed when a browser ships constructable
164
// stylesheets.
168
// De-duplicate styles preserving the _last_ instance in the set.
169
// This is a performance optimization to avoid duplicated styles that can
170
// occur especially when composing via subclassing.
171
// The last item is kept to try to preserve the cascade order with the
172
// assumption that it's most important that last added styles override
173
// previous styles.
174
const addStyles = (styles: CSSResultArray, set: Set<CSSResultOrNative>):
175
Set<CSSResultOrNative> => styles.reduceRight(
176
(set: Set<CSSResultOrNative>, s) =>
177
// Note: On IE set.add() does not return the set
178
Array.isArray(s) ? addStyles(s, set) : (set.add(s), set),
179
set);
180
// Array.from does not work on Set in IE, otherwise return
181
// Array.from(addStyles(userStyles, new Set<CSSResult>())).reverse()
182
const set = addStyles(userStyles, new Set<CSSResultOrNative>());
183
const styles: CSSResultOrNative[] = [];
184
set.forEach((v) => styles.unshift(v));
185
this._styles = styles;
187
this._styles = userStyles === undefined ? [] : [userStyles];
190
// Ensure that there are no invalid CSSStyleSheet instances here. They are
191
// invalid in two conditions.
192
// (1) the sheet is non-constructible (`sheet` of a HTMLStyleElement), but
193
// this is impossible to check except via .replaceSync or use
194
// (2) the ShadyCSS polyfill is enabled (:. supportsAdoptingStyleSheets is
195
// false)
197
if (s instanceof CSSStyleSheet && !supportsAdoptingStyleSheets) {
198
// Flatten the cssText from the passed constructible stylesheet (or
199
// undetectable non-constructible stylesheet). The user might have
200
// expected to update their stylesheets over time, but the alternative
201
// is a crash.
202
const cssText = Array.prototype.slice.call(s.cssRules)
203
.reduce((css, rule) => css + rule.cssText, '');
212
/**
213
* Node or ShadowRoot into which element DOM should be rendered. Defaults
214
* to an open shadowRoot.
215
*/
219
* Performs element initialization. By default this calls
220
* [[`createRenderRoot`]] to create the element [[`renderRoot`]] node and
221
* captures any pre-set values for registered properties.
225
(this.constructor as typeof LitElement)._getUniqueStyles();
229
// Note, if renderRoot is not a shadowRoot, styles would/could apply to the
230
// element's getRootNode(). While this could be done, we're choosing not to
231
// support this now since it would require different logic around de-duping.
232
if (window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot) {
233
this.adoptStyles();
234
}
235
}
236
237
/**
238
* Returns the node into which the element should render and by default
239
* creates and returns an open shadowRoot. Implement to customize where the
240
* element's DOM is rendered. For example, to render into the element's
241
* childNodes, return `this`.
242
* @returns {Element|DocumentFragment} Returns a node into which to render.
243
*/
246
(this.constructor as typeof LitElement).shadowRootOptions);
251
* property. Styling will apply using `shadowRoot.adoptedStyleSheets` where
252
* available and will fallback otherwise. When Shadow DOM is polyfilled,
253
* ShadyCSS scopes styles and adds them to the document. When Shadow DOM
254
* is available but `adoptedStyleSheets` is not, styles are appended to the
255
* end of the `shadowRoot` to [mimic spec
256
* behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).
259
const styles = (this.constructor as typeof LitElement)._styles!;
260
if (styles.length === 0) {
261
return;
262
}
263
// There are three separate cases here based on Shadow DOM support.
264
// (1) shadowRoot polyfilled: use ShadyCSS
265
// (2) shadowRoot.adoptedStyleSheets available: use it
266
// (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after
267
// rendering
268
if (window.ShadyCSS !== undefined && !window.ShadyCSS.nativeShadow) {
269
window.ShadyCSS.ScopingShim!.prepareAdoptedCssText(
270
styles.map((s) => (s as CSSResult).cssText), this.localName);
272
(this.renderRoot as ShadowRoot).adoptedStyleSheets =
273
styles.map((s) => s instanceof CSSStyleSheet ? s : s.styleSheet!);
275
// This must be done after rendering so the actual style insertion is done
276
// in `update`.
278
}
279
}
280
281
connectedCallback() {
282
super.connectedCallback();
283
// Note, first update/render handles styleElement so we only call this if
284
// connected after first update.
291
* Updates the element. This method reflects property values to attributes
292
* and calls `render` to render DOM via lit-html. Setting properties inside
294
* @param _changedProperties Map of changed properties with old values
297
// Setting properties in `render` should not trigger an update. Since
298
// updates are allowed after super.update, it's important to call `render`
299
// before that.
300
const templateResult = this.render();
302
// If render is not implemented by the component, don't call lit-html render
303
if (templateResult !== renderNotImplemented) {
304
(this.constructor as typeof LitElement)
305
.render(
306
templateResult,
307
this.renderRoot,
308
{scopeName: this.localName, eventContext: this});
310
// When native Shadow DOM is used but adoptedStyles are not supported,
311
// insert styling after rendering to ensure adoptedStyles have highest
312
// priority.
313
if (this._needsShimAdoptedStyleSheets) {
314
this._needsShimAdoptedStyleSheets = false;
315
(this.constructor as typeof LitElement)._styles!.forEach((s) => {
324
* Invoked on each update to perform rendering tasks. This method may return
325
* any value renderable by lit-html's `NodePart` - typically a
326
* `TemplateResult`. Setting properties inside this method will *not* trigger
327
* the element to update.
330
return renderNotImplemented;