UNPKG

7.04 kBPlain TextView Raw
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 */
14import {TemplateResult} from 'lit-html';
15import {render} from 'lit-html/lib/shady-render';
16
17import {PropertyValues, UpdatingElement} from './lib/updating-element.js';
18
19export * from './lib/updating-element.js';
20export * from './lib/decorators.js';
21export {html, svg, TemplateResult, SVGTemplateResult} from 'lit-html/lit-html';
22import {supportsAdoptingStyleSheets, CSSResult} from './lib/css-tag.js';
23export * from './lib/css-tag.js';
24
25export 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}