UNPKG

@lit/reactive-element

Version:

A simple low level base class for creating fast, lightweight web components

129 lines 5.13 kB
/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ /** * Whether the current browser supports `adoptedStyleSheets`. */ export const supportsAdoptingStyleSheets = window.ShadowRoot && (window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) && 'adoptedStyleSheets' in Document.prototype && 'replace' in CSSStyleSheet.prototype; const constructionToken = Symbol(); const cssTagCache = new WeakMap(); /** * A container for a string of CSS text, that may be used to create a CSSStyleSheet. * * CSSResult is the return value of `css`-tagged template literals and * `unsafeCSS()`. In order to ensure that CSSResults are only created via the * `css` tag and `unsafeCSS()`, CSSResult cannot be constructed directly. */ export class CSSResult { constructor(cssText, strings, safeToken) { // This property needs to remain unminified. this['_$cssResult$'] = true; if (safeToken !== constructionToken) { throw new Error('CSSResult is not constructable. Use `unsafeCSS` or `css` instead.'); } this.cssText = cssText; this._strings = strings; } // 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() { // If `supportsAdoptingStyleSheets` is true then we assume CSSStyleSheet is // constructable. let styleSheet = this._styleSheet; const strings = this._strings; if (supportsAdoptingStyleSheets && styleSheet === undefined) { const cacheable = strings !== undefined && strings.length === 1; if (cacheable) { styleSheet = cssTagCache.get(strings); } if (styleSheet === undefined) { (this._styleSheet = styleSheet = new CSSStyleSheet()).replaceSync(this.cssText); if (cacheable) { cssTagCache.set(strings, styleSheet); } } } return styleSheet; } toString() { return this.cssText; } } const textFromCSSResult = (value) => { // This property needs to remain unminified. if (value['_$cssResult$'] === true) { return value.cssText; } else if (typeof value === 'number') { return value; } else { throw new Error(`Value passed to 'css' function must be a 'css' function result: ` + `${value}. Use 'unsafeCSS' to pass non-literal values, but take care ` + `to ensure page security.`); } }; /** * Wrap a value for interpolation in a {@linkcode css} tagged template literal. * * This is unsafe because untrusted CSS text can be used to phone home * or exfiltrate data to an attacker controlled site. Take care to only use * this with trusted input. */ export const unsafeCSS = (value) => new CSSResult(typeof value === 'string' ? value : String(value), undefined, constructionToken); /** * A template literal tag which can be used with LitElement's * {@linkcode LitElement.styles} property to set element styles. * * For security reasons, only literal string values and number may be used in * embedded expressions. To incorporate non-literal values {@linkcode unsafeCSS} * may be used inside an expression. */ export const css = (strings, ...values) => { const cssText = strings.length === 1 ? strings[0] : values.reduce((acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], strings[0]); return new CSSResult(cssText, strings, constructionToken); }; /** * Applies the given styles to a `shadowRoot`. When Shadow DOM is * available but `adoptedStyleSheets` is not, styles are appended to the * `shadowRoot` to [mimic spec behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets). * Note, when shimming is used, any styles that are subsequently placed into * the shadowRoot should be placed *before* any shimmed adopted styles. This * will match spec behavior that gives adopted sheets precedence over styles in * shadowRoot. */ export const adoptStyles = (renderRoot, styles) => { if (supportsAdoptingStyleSheets) { renderRoot.adoptedStyleSheets = styles.map((s) => s instanceof CSSStyleSheet ? s : s.styleSheet); } else { styles.forEach((s) => { const style = document.createElement('style'); // eslint-disable-next-line @typescript-eslint/no-explicit-any const nonce = window['litNonce']; if (nonce !== undefined) { style.setAttribute('nonce', nonce); } style.textContent = s.cssText; renderRoot.appendChild(style); }); } }; const cssResultFromStyleSheet = (sheet) => { let cssText = ''; for (const rule of sheet.cssRules) { cssText += rule.cssText; } return unsafeCSS(cssText); }; export const getCompatibleStyle = supportsAdoptingStyleSheets ? (s) => s : (s) => s instanceof CSSStyleSheet ? cssResultFromStyleSheet(s) : s; //# sourceMappingURL=css-tag.js.map