es5-lit-html
Version:
An ES5 transpiled version of lit-html
332 lines (273 loc) • 13.8 kB
JavaScript
;
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;