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 | */
|
14 | import {TemplateResult} from 'lit-html';
|
15 | import {render} from 'lit-html/lib/shady-render';
|
16 |
|
17 | import {PropertyValues, UpdatingElement} from './lib/updating-element.js';
|
18 |
|
19 | export * from './lib/updating-element.js';
|
20 | export * from './lib/decorators.js';
|
21 | export {html, svg, TemplateResult, SVGTemplateResult} from 'lit-html/lit-html';
|
22 | import {supportsAdoptingStyleSheets, CSSResult} from './lib/css-tag.js';
|
23 | export * from './lib/css-tag.js';
|
24 |
|
25 | export class LitElement extends UpdatingElement {
|
26 |
|
27 | /**
|
28 | * Ensure this class is marked as `finalized` as an optimization ensuring
|
29 | * it will not needlessly try to `finalize`.
|
30 | */
|
31 | protected static finalized = true;
|
32 |
|
33 | /**
|
34 | * Render method used to render the lit-html TemplateResult to the element's
|
35 | * DOM.
|
36 | * @param {TemplateResult} Template to render.
|
37 | * @param {Element|DocumentFragment} Node into which to render.
|
38 | * @param {String} Element name.
|
39 | * @nocollapse
|
40 | */
|
41 | static render = render;
|
42 |
|
43 | /**
|
44 | * Array of styles to apply to the element. The styles should be defined
|
45 | * using the `css` tag function.
|
46 | */
|
47 | static get styles(): CSSResult[] { return []; }
|
48 |
|
49 | private static _styles: CSSResult[]|undefined;
|
50 |
|
51 | private static get _uniqueStyles(): CSSResult[] {
|
52 | if (this._styles === undefined) {
|
53 | const styles = this.styles;
|
54 | // As a performance optimization to avoid duplicated styling that can
|
55 | // occur especially when composing via subclassing, de-duplicate styles
|
56 | // preserving the last item in the list. The last item is kept to
|
57 | // try to preserve cascade order with the assumption that it's most
|
58 | // important that last added styles override previous styles.
|
59 | const styleSet = styles.reduceRight((set, s) => {
|
60 | set.add(s);
|
61 | // on IE set.add does not return the set.
|
62 | return set;
|
63 | }, new Set());
|
64 | // Array.form does not work on Set in IE
|
65 | this._styles = [];
|
66 | styleSet.forEach((v) => this._styles!.unshift(v));
|
67 | }
|
68 | return this._styles;
|
69 | }
|
70 |
|
71 | private _needsShimAdoptedStyleSheets?: boolean;
|
72 |
|
73 | /**
|
74 | * Node or ShadowRoot into which element DOM should be rendered. Defaults
|
75 | * to an open shadowRoot.
|
76 | */
|
77 | protected renderRoot?: Element|DocumentFragment;
|
78 |
|
79 | /**
|
80 | * Performs element initialization. By default this calls `createRenderRoot`
|
81 | * to create the element `renderRoot` node and captures any pre-set values for
|
82 | * registered properties.
|
83 | */
|
84 | protected initialize() {
|
85 | super.initialize();
|
86 | this.renderRoot = this.createRenderRoot();
|
87 | // Note, if renderRoot is not a shadowRoot, styles would/could apply to the
|
88 | // element's getRootNode(). While this could be done, we're choosing not to
|
89 | // support this now since it would require different logic around de-duping.
|
90 | if (window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot) {
|
91 | this.adoptStyles();
|
92 | }
|
93 | }
|
94 |
|
95 | /**
|
96 | * Returns the node into which the element should render and by default
|
97 | * creates and returns an open shadowRoot. Implement to customize where the
|
98 | * element's DOM is rendered. For example, to render into the element's
|
99 | * childNodes, return `this`.
|
100 | * @returns {Element|DocumentFragment} Returns a node into which to render.
|
101 | */
|
102 | protected createRenderRoot(): Element|ShadowRoot {
|
103 | return this.attachShadow({mode : 'open'});
|
104 | }
|
105 |
|
106 | /**
|
107 | * Applies styling to the element shadowRoot using the `static get styles`
|
108 | * property. Styling will apply using `shadowRoot.adoptedStyleSheets` where
|
109 | * available and will fallback otherwise. When Shadow DOM is polyfilled,
|
110 | * ShadyCSS scopes styles and adds them to the document. When Shadow DOM
|
111 | * is available but `adoptedStyleSheets` is not, styles are appended to the
|
112 | * end of the `shadowRoot` to [mimic spec
|
113 | * behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).
|
114 | */
|
115 | protected adoptStyles() {
|
116 | const styles = (this.constructor as typeof LitElement)._uniqueStyles;
|
117 | if (styles.length === 0) {
|
118 | return;
|
119 | }
|
120 | // There are three separate cases here based on Shadow DOM support.
|
121 | // (1) shadowRoot polyfilled: use ShadyCSS
|
122 | // (2) shadowRoot.adoptedStyleSheets available: use it.
|
123 | // (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after
|
124 | // rendering
|
125 | if (window.ShadyCSS !== undefined && !window.ShadyCSS.nativeShadow) {
|
126 | window.ShadyCSS.ScopingShim.prepareAdoptedCssText(
|
127 | styles.map((s) => s.cssText), this.localName);
|
128 | } else if (supportsAdoptingStyleSheets) {
|
129 | (this.renderRoot as ShadowRoot).adoptedStyleSheets =
|
130 | styles.map((s) => s.styleSheet!);
|
131 | } else {
|
132 | // This must be done after rendering so the actual style insertion is done
|
133 | // in `update`.
|
134 | this._needsShimAdoptedStyleSheets = true;
|
135 | }
|
136 | }
|
137 |
|
138 | connectedCallback() {
|
139 | super.connectedCallback();
|
140 | // Note, first update/render handles styleElement so we only call this if
|
141 | // connected after first update.
|
142 | if (this.hasUpdated && window.ShadyCSS !== undefined) {
|
143 | window.ShadyCSS.styleElement(this);
|
144 | }
|
145 | }
|
146 |
|
147 | /**
|
148 | * Updates the element. This method reflects property values to attributes
|
149 | * and calls `render` to render DOM via lit-html. Setting properties inside
|
150 | * this method will *not* trigger another update.
|
151 | * * @param _changedProperties Map of changed properties with old values
|
152 | */
|
153 | protected update(changedProperties: PropertyValues) {
|
154 | super.update(changedProperties);
|
155 | const templateResult = this.render() as any;
|
156 | if (templateResult instanceof TemplateResult) {
|
157 | (this.constructor as typeof LitElement)
|
158 | .render(templateResult, this.renderRoot!,
|
159 | {scopeName : this.localName!, eventContext : this});
|
160 | }
|
161 | // When native Shadow DOM is used but adoptedStyles are not supported,
|
162 | // insert styling after rendering to ensure adoptedStyles have highest
|
163 | // priority.
|
164 | if (this._needsShimAdoptedStyleSheets) {
|
165 | this._needsShimAdoptedStyleSheets = false;
|
166 | (this.constructor as typeof LitElement)._uniqueStyles.forEach((s) => {
|
167 | const style = document.createElement('style');
|
168 | style.textContent = s.cssText;
|
169 | this.renderRoot!.appendChild(style);
|
170 | });
|
171 | }
|
172 | }
|
173 |
|
174 | /**
|
175 | * Invoked on each update to perform rendering tasks. This method must return
|
176 | * a lit-html TemplateResult. Setting properties inside this method will *not*
|
177 | * trigger the element to update.
|
178 | */
|
179 | protected render(): TemplateResult|void {}
|
180 | }
|