UNPKG

es5-lit-html

Version:
332 lines (273 loc) 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "html", { enumerable: true, get: function get() { return _litHtml.html; } }); Object.defineProperty(exports, "svg", { enumerable: true, get: function get() { return _litHtml.svg; } }); Object.defineProperty(exports, "TemplateResult", { enumerable: true, get: function get() { return _litHtml.TemplateResult; } }); exports.render = void 0; var _dom = require("./dom.js"); var _modifyTemplate = require("./modify-template.js"); var _render = require("./render.js"); var _templateFactory = require("./template-factory.js"); var _templateInstance = require("./template-instance.js"); var _template = require("./template.js"); var _litHtml = require("../lit-html.js"); function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } // Get a key to lookup in `templateCaches`. var getTemplateCacheKey = function getTemplateCacheKey(type, scopeName) { return "".concat(type, "--").concat(scopeName); }; var compatibleShadyCSSVersion = true; if (typeof window.ShadyCSS === 'undefined') { compatibleShadyCSSVersion = false; } else if (typeof window.ShadyCSS.prepareTemplateDom === 'undefined') { console.warn("Incompatible ShadyCSS version detected. " + "Please update to at least @webcomponents/webcomponentsjs@2.0.2 and " + "@webcomponents/shadycss@1.3.1."); compatibleShadyCSSVersion = false; } /** * Template factory which scopes template DOM using ShadyCSS. * @param scopeName {string} */ var shadyTemplateFactory = function shadyTemplateFactory(scopeName) { return function (result) { var cacheKey = getTemplateCacheKey(result.type, scopeName); var templateCache = _templateFactory.templateCaches.get(cacheKey); if (templateCache === undefined) { templateCache = { stringsArray: new WeakMap(), keyString: new Map() }; _templateFactory.templateCaches.set(cacheKey, templateCache); } var template = templateCache.stringsArray.get(result.strings); if (template !== undefined) { return template; } var key = result.strings.join(_template.marker); template = templateCache.keyString.get(key); if (template === undefined) { var element = result.getTemplateElement(); if (compatibleShadyCSSVersion) { window.ShadyCSS.prepareTemplateDom(element, scopeName); } template = new _template.Template(result, element); templateCache.keyString.set(key, template); } templateCache.stringsArray.set(result.strings, template); return template; }; }; var TEMPLATE_TYPES = ['html', 'svg']; /** * Removes all style elements from Templates for the given scopeName. */ var removeStylesFromLitTemplates = function removeStylesFromLitTemplates(scopeName) { TEMPLATE_TYPES.forEach(function (type) { var templates = _templateFactory.templateCaches.get(getTemplateCacheKey(type, scopeName)); if (templates !== undefined) { templates.keyString.forEach(function (template) { var content = template.element.content; // IE 11 doesn't support the iterable param Set constructor var styles = new Set(); Array.from(content.querySelectorAll('style')).forEach(function (s) { styles.add(s); }); (0, _modifyTemplate.removeNodesFromTemplate)(template, styles); }); } }); }; var shadyRenderSet = new Set(); /** * For the given scope name, ensures that ShadyCSS style scoping is performed. * This is done just once per scope name so the fragment and template cannot * be modified. * (1) extracts styles from the rendered fragment and hands them to ShadyCSS * to be scoped and appended to the document * (2) removes style elements from all lit-html Templates for this scope name. * * Note, <style> elements can only be placed into templates for the * initial rendering of the scope. If <style> elements are included in templates * dynamically rendered to the scope (after the first scope render), they will * not be scoped and the <style> will be left in the template and rendered * output. */ var prepareTemplateStyles = function prepareTemplateStyles(scopeName, renderedDOM, template) { shadyRenderSet.add(scopeName); // If `renderedDOM` is stamped from a Template, then we need to edit that // Template's underlying template element. Otherwise, we create one here // to give to ShadyCSS, which still requires one while scoping. var templateElement = !!template ? template.element : document.createElement('template'); // Move styles out of rendered DOM and store. var styles = renderedDOM.querySelectorAll('style'); var length = styles.length; // If there are no styles, skip unnecessary work if (length === 0) { // Ensure prepareTemplateStyles is called to support adding // styles via `prepareAdoptedCssText` since that requires that // `prepareTemplateStyles` is called. // // ShadyCSS will only update styles containing @apply in the template // given to `prepareTemplateStyles`. If no lit Template was given, // ShadyCSS will not be able to update uses of @apply in any relevant // template. However, this is not a problem because we only create the // template for the purpose of supporting `prepareAdoptedCssText`, // which doesn't support @apply at all. window.ShadyCSS.prepareTemplateStyles(templateElement, scopeName); return; } var condensedStyle = document.createElement('style'); // Collect styles into a single style. This helps us make sure ShadyCSS // manipulations will not prevent us from being able to fix up template // part indices. // NOTE: collecting styles is inefficient for browsers but ShadyCSS // currently does this anyway. When it does not, this should be changed. for (var i = 0; i < length; i++) { var _style = styles[i]; _style.parentNode.removeChild(_style); condensedStyle.textContent += _style.textContent; } // Remove styles from nested templates in this scope. removeStylesFromLitTemplates(scopeName); // And then put the condensed style into the "root" template passed in as // `template`. var content = templateElement.content; if (!!template) { (0, _modifyTemplate.insertNodeIntoTemplate)(template, condensedStyle, content.firstChild); } else { content.insertBefore(condensedStyle, content.firstChild); } // Note, it's important that ShadyCSS gets the template that `lit-html` // will actually render so that it can update the style inside when // needed (e.g. @apply native Shadow DOM case). window.ShadyCSS.prepareTemplateStyles(templateElement, scopeName); var style = content.querySelector('style'); if (window.ShadyCSS.nativeShadow && style !== null) { // When in native Shadow DOM, ensure the style created by ShadyCSS is // included in initially rendered output (`renderedDOM`). renderedDOM.insertBefore(style.cloneNode(true), renderedDOM.firstChild); } else if (!!template) { // When no style is left in the template, parts will be broken as a // result. To fix this, we put back the style node ShadyCSS removed // and then tell lit to remove that node from the template. // There can be no style in the template in 2 cases (1) when Shady DOM // is in use, ShadyCSS removes all styles, (2) when native Shadow DOM // is in use ShadyCSS removes the style if it contains no content. // NOTE, ShadyCSS creates its own style so we can safely add/remove // `condensedStyle` here. content.insertBefore(condensedStyle, content.firstChild); var removes = new Set(); removes.add(condensedStyle); (0, _modifyTemplate.removeNodesFromTemplate)(template, removes); } }; /** * Extension to the standard `render` method which supports rendering * to ShadowRoots when the ShadyDOM (https://github.com/webcomponents/shadydom) * and ShadyCSS (https://github.com/webcomponents/shadycss) polyfills are used * or when the webcomponentsjs * (https://github.com/webcomponents/webcomponentsjs) polyfill is used. * * Adds a `scopeName` option which is used to scope element DOM and stylesheets * when native ShadowDOM is unavailable. The `scopeName` will be added to * the class attribute of all rendered DOM. In addition, any style elements will * be automatically re-written with this `scopeName` selector and moved out * of the rendered DOM and into the document `<head>`. * * It is common to use this render method in conjunction with a custom element * which renders a shadowRoot. When this is done, typically the element's * `localName` should be used as the `scopeName`. * * In addition to DOM scoping, ShadyCSS also supports a basic shim for css * custom properties (needed only on older browsers like IE11) and a shim for * a deprecated feature called `@apply` that supports applying a set of css * custom properties to a given location. * * Usage considerations: * * * Part values in `<style>` elements are only applied the first time a given * `scopeName` renders. Subsequent changes to parts in style elements will have * no effect. Because of this, parts in style elements should only be used for * values that will never change, for example parts that set scope-wide theme * values or parts which render shared style elements. * * * Note, due to a limitation of the ShadyDOM polyfill, rendering in a * custom element's `constructor` is not supported. Instead rendering should * either done asynchronously, for example at microtask timing (for example * `Promise.resolve()`), or be deferred until the first time the element's * `connectedCallback` runs. * * Usage considerations when using shimmed custom properties or `@apply`: * * * Whenever any dynamic changes are made which affect * css custom properties, `ShadyCSS.styleElement(element)` must be called * to update the element. There are two cases when this is needed: * (1) the element is connected to a new parent, (2) a class is added to the * element that causes it to match different custom properties. * To address the first case when rendering a custom element, `styleElement` * should be called in the element's `connectedCallback`. * * * Shimmed custom properties may only be defined either for an entire * shadowRoot (for example, in a `:host` rule) or via a rule that directly * matches an element with a shadowRoot. In other words, instead of flowing from * parent to child as do native css custom properties, shimmed custom properties * flow only from shadowRoots to nested shadowRoots. * * * When using `@apply` mixing css shorthand property names with * non-shorthand names (for example `border` and `border-width`) is not * supported. */ var render = function render(result, container, options) { if (!options || _typeof(options) !== 'object' || !options.scopeName) { throw new Error('The `scopeName` option is required.'); } var scopeName = options.scopeName; var hasRendered = _render.parts.has(container); var needsScoping = compatibleShadyCSSVersion && container.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ && !!container.host; // Handle first render to a scope specially... var firstScopeRender = needsScoping && !shadyRenderSet.has(scopeName); // On first scope render, render into a fragment; this cannot be a single // fragment that is reused since nested renders can occur synchronously. var renderContainer = firstScopeRender ? document.createDocumentFragment() : container; (0, _render.render)(result, renderContainer, Object.assign({ templateFactory: shadyTemplateFactory(scopeName) }, options)); // When performing first scope render, // (1) We've rendered into a fragment so that there's a chance to // `prepareTemplateStyles` before sub-elements hit the DOM // (which might cause them to render based on a common pattern of // rendering in a custom element's `connectedCallback`); // (2) Scope the template with ShadyCSS one time only for this scope. // (3) Render the fragment into the container and make sure the // container knows its `part` is the one we just rendered. This ensures // DOM will be re-used on subsequent renders. if (firstScopeRender) { var part = _render.parts.get(renderContainer); _render.parts["delete"](renderContainer); // ShadyCSS might have style sheets (e.g. from `prepareAdoptedCssText`) // that should apply to `renderContainer` even if the rendered value is // not a TemplateInstance. However, it will only insert scoped styles // into the document if `prepareTemplateStyles` has already been called // for the given scope name. var template = part.value instanceof _templateInstance.TemplateInstance ? part.value.template : undefined; prepareTemplateStyles(scopeName, renderContainer, template); (0, _dom.removeNodes)(container, container.firstChild); container.appendChild(renderContainer); _render.parts.set(container, part); } // After elements have hit the DOM, update styling if this is the // initial render to this container. // This is needed whenever dynamic changes are made so it would be // safest to do every render; however, this would regress performance // so we leave it up to the user to call `ShadyCSS.styleElement` // for dynamic changes. if (!hasRendered && needsScoping) { window.ShadyCSS.styleElement(container.host); } }; exports.render = render;