-
Notifications
You must be signed in to change notification settings - Fork 319
/
Copy pathlit-element.ts
332 lines (306 loc) · 12.5 KB
/
lit-element.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/**
* @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, ShadyRenderOptions} from 'lit-html/lib/shady-render.js';
import {PropertyValues, 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, CSSResult, unsafeCSS} from './lib/css-tag.js';
export * from './lib/css-tag.js';
declare global {
interface Window {
litElementVersions: string[];
}
}
// 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');
export type CSSResultOrNative = CSSResult|CSSStyleSheet;
export interface CSSResultArray extends
Array<CSSResultOrNative|CSSResultArray> {}
export type CSSResultGroup = CSSResultOrNative|CSSResultArray;
/**
* 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 {
/**
* 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.
*/
protected static['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
*/
static render:
(result: unknown, container: Element|DocumentFragment,
options: ShadyRenderOptions) => void = render;
/**
* Array of styles to apply to the element. The styles should be defined
* using the [[`css`]] tag function or via constructible stylesheets.
*/
static styles?: CSSResultGroup;
/** @nocollapse */
static shadowRootOptions: ShadowRootInit = {mode: 'open'};
private static _styles: Array<CSSResultOrNative|CSSResult>|undefined;
/**
* Return the array of styles to apply to the element.
* Override this method to integrate into a style management system.
*
* @nocollapse
*/
static getStyles(): CSSResultGroup|undefined {
return this.styles;
}
/** @nocollapse */
private 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: CSSResultArray, set: Set<CSSResultOrNative>):
Set<CSSResultOrNative> => styles.reduceRight(
(set: Set<CSSResultOrNative>, 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<CSSResultOrNative>());
const styles: CSSResultOrNative[] = [];
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;
});
}
private _needsShimAdoptedStyleSheets?: boolean;
/**
* Node or ShadowRoot into which element DOM should be rendered. Defaults
* to an open shadowRoot.
*/
readonly renderRoot!: Element|DocumentFragment;
/**
* Performs element initialization. By default this calls
* [[`createRenderRoot`]] to create the element [[`renderRoot`]] node and
* captures any pre-set values for registered properties.
*/
protected initialize() {
super.initialize();
(this.constructor as typeof LitElement)._getUniqueStyles();
(this as {
renderRoot: Element|DocumentFragment;
}).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.
*/
protected createRenderRoot(): Element|ShadowRoot {
return this.attachShadow(
(this.constructor as typeof LitElement).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).
*/
protected adoptStyles() {
const styles = (this.constructor as typeof LitElement)._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 as CSSResult).cssText), this.localName);
} else if (supportsAdoptingStyleSheets) {
(this.renderRoot as ShadowRoot).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
*/
protected update(changedProperties: PropertyValues) {
// 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 as typeof LitElement)
.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 as typeof LitElement)._styles!.forEach((s) => {
const style = document.createElement('style');
style.textContent = (s as CSSResult).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.
*/
protected render(): unknown {
return renderNotImplemented;
}
}