-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds static get styles()
#401
Changes from 13 commits
670eb4c
b593e1d
fd881dc
8ae6692
8faa0c7
56bfa0b
58bd5be
6a4649e
ca6206d
dcbffef
583f515
352494a
6f6afec
80737c0
27eb2ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
@license | ||
Copyright (c) 2019 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 | ||
*/ | ||
|
||
export const supportsAdoptingStyleSheets = | ||
('adoptedStyleSheets' in Document.prototype); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
export class CSSResult { | ||
|
||
_styleSheet?: CSSStyleSheet|null; | ||
|
||
readonly cssText: string; | ||
|
||
constructor(cssText: string) { this.cssText = cssText; } | ||
|
||
// Note, this is a getter so that it's lazy. In practice, this means | ||
// stylesheets are not created until the first element instance is made. | ||
get styleSheet(): CSSStyleSheet|null { | ||
if (this._styleSheet === undefined) { | ||
// Note, if `adoptedStyleSheets` is supported then we assume CSSStyleSheet | ||
// is constructable. | ||
if (supportsAdoptingStyleSheets) { | ||
this._styleSheet = new CSSStyleSheet(); | ||
this._styleSheet.replaceSync(this.cssText); | ||
} else { | ||
this._styleSheet = null; | ||
} | ||
} | ||
return this._styleSheet; | ||
} | ||
} | ||
|
||
const textFromCSSResult = (value: CSSResult) => { | ||
if (value instanceof CSSResult) { | ||
return value.cssText; | ||
} else { | ||
throw new Error( | ||
`Value passed to 'css' function must be a 'css' function result: ${ | ||
value}.`); | ||
} | ||
}; | ||
|
||
export const css = | ||
(strings: TemplateStringsArray, ...values: CSSResult[]): CSSResult => { | ||
const cssText = values.reduce( | ||
(acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], | ||
strings[0]); | ||
return new CSSResult(cssText); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,8 @@ import {PropertyValues, UpdatingElement} from './lib/updating-element.js'; | |
export * from './lib/updating-element.js'; | ||
export * from './lib/decorators.js'; | ||
export {html, svg} from 'lit-html/lit-html'; | ||
import {supportsAdoptingStyleSheets, CSSResult} from './lib/css-tag.js'; | ||
export * from './lib/css-tag.js'; | ||
|
||
export class LitElement extends UpdatingElement { | ||
|
||
|
@@ -31,6 +33,104 @@ export class LitElement extends UpdatingElement { | |
*/ | ||
static render = render; | ||
|
||
/** | ||
* Array of styles to apply to the element. The styles should be defined | ||
* using the `css` tag function. | ||
*/ | ||
static get styles(): CSSResult[] { return []; } | ||
|
||
private static _styles: CSSResult[]|undefined; | ||
|
||
private static get _uniqueStyles(): CSSResult[] { | ||
if (this._styles === undefined) { | ||
const styles = this.styles; | ||
// As a performance optimization to avoid duplicated styling that can | ||
// occur especially when composing via subclassing, de-duplicate styles | ||
// preserving the last item in the list. The last item is kept to | ||
// try to preserve cascade order with the assumption that it's most | ||
// important that last added styles override previous styles. | ||
const styleSet = styles.reduceRight((set, s) => set.add(s), new Set()); | ||
this._styles = Array.from(styleSet).reverse(); | ||
} | ||
return this._styles; | ||
} | ||
|
||
private _needsShimAdoptedStyleSheets?: boolean; | ||
|
||
/** | ||
* Node or ShadowRoot into which element DOM should be rendered. Defaults | ||
* to an open shadowRoot. | ||
*/ | ||
protected 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.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({mode : 'open'}); | ||
} | ||
|
||
/** | ||
* Applies styling to the element shadowRoot using the `static get 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)._uniqueStyles; | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth moving more of the prep steps to the static one-time spot ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't seem worth it. |
||
window.ShadyCSS.ScopingShim.prepareAdoptedCssText(styles.map((s) => s.cssText), | ||
this.localName); | ||
} else if (supportsAdoptingStyleSheets) { | ||
(this.renderRoot as ShadowRoot).adoptedStyleSheets = | ||
styles.map((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 | ||
|
@@ -45,6 +145,17 @@ export class LitElement extends UpdatingElement { | |
.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)._uniqueStyles.forEach((s) => { | ||
const style = document.createElement('style'); | ||
style.textContent = s.cssText; | ||
this.renderRoot!.appendChild(style); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add
[breaking]
change notice about movingcreateRenderRoot
and related functionality fromUpdatingElement
toLitElement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.