(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory() : typeof define === 'function' && define.amd ? define(factory) : factory(); })(this, function () { 'use strict'; /** * @license * Copyright (c) 2017 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 */ const directives = new WeakMap(); const isDirective = o => { return typeof o === 'function' && directives.has(o); }; /** * @license * Copyright (c) 2017 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 */ /** * True if the custom elements polyfill is in use. */ const isCEPolyfill = window.customElements !== undefined && window.customElements.polyfillWrapFlushCallback !== undefined; /** * Reparents nodes, starting from `start` (inclusive) to `end` (exclusive), * into another container (could be the same container), before `before`. If * `before` is null, it appends the nodes to the container. */ const reparentNodes = (container, start, end = null, before = null) => { while (start !== end) { const n = start.nextSibling; container.insertBefore(start, before); start = n; } }; /** * Removes nodes, starting from `start` (inclusive) to `end` (exclusive), from * `container`. */ const removeNodes = (container, start, end = null) => { while (start !== end) { const n = start.nextSibling; container.removeChild(start); start = n; } }; /** * @license * Copyright (c) 2018 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 */ /** * A sentinel value that signals that a value was handled by a directive and * should not be written to the DOM. */ const noChange = {}; /** * A sentinel value that signals a NodePart to fully clear its content. */ const nothing = {}; /** * @license * Copyright (c) 2017 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 */ /** * An expression marker with embedded unique key to avoid collision with * possible text in templates. */ const marker = `{{lit-${String(Math.random()).slice(2)}}}`; /** * An expression marker used text-positions, multi-binding attributes, and * attributes with markup-like text values. */ const nodeMarker = ``; const markerRegex = new RegExp(`${marker}|${nodeMarker}`); /** * Suffix appended to all bound attribute names. */ const boundAttributeSuffix = '$lit$'; /** * An updateable Template that tracks the location of dynamic parts. */ class Template { constructor(result, element) { this.parts = []; this.element = element; const nodesToRemove = []; const stack = []; // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null const walker = document.createTreeWalker(element.content, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */ , null, false); // Keeps track of the last index associated with a part. We try to delete // unnecessary nodes, but we never want to associate two different parts // to the same index. They must have a constant node between. let lastPartIndex = 0; let index = -1; let partIndex = 0; const { strings, values: { length } } = result; while (partIndex < length) { const node = walker.nextNode(); if (node === null) { // We've exhausted the content inside a nested template element. // Because we still have parts (the outer for-loop), we know: // - There is a template in the stack // - The walker will find a nextNode outside the template walker.currentNode = stack.pop(); continue; } index++; if (node.nodeType === 1 /* Node.ELEMENT_NODE */ ) { if (node.hasAttributes()) { const attributes = node.attributes; const { length } = attributes; // Per // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap, // attributes are not guaranteed to be returned in document order. // In particular, Edge/IE can return them out of order, so we cannot // assume a correspondence between part index and attribute index. let count = 0; for (let i = 0; i < length; i++) { if (endsWith(attributes[i].name, boundAttributeSuffix)) { count++; } } while (count-- > 0) { // Get the template literal section leading up to the first // expression in this attribute const stringForPart = strings[partIndex]; // Find the attribute name const name = lastAttributeNameRegex.exec(stringForPart)[2]; // Find the corresponding attribute // All bound attributes have had a suffix added in // TemplateResult#getHTML to opt out of special attribute // handling. To look up the attribute value we also need to add // the suffix. const attributeLookupName = name.toLowerCase() + boundAttributeSuffix; const attributeValue = node.getAttribute(attributeLookupName); node.removeAttribute(attributeLookupName); const statics = attributeValue.split(markerRegex); this.parts.push({ type: 'attribute', index, name, strings: statics }); partIndex += statics.length - 1; } } if (node.tagName === 'TEMPLATE') { stack.push(node); walker.currentNode = node.content; } } else if (node.nodeType === 3 /* Node.TEXT_NODE */ ) { const data = node.data; if (data.indexOf(marker) >= 0) { const parent = node.parentNode; const strings = data.split(markerRegex); const lastIndex = strings.length - 1; // Generate a new text node for each literal section // These nodes are also used as the markers for node parts for (let i = 0; i < lastIndex; i++) { let insert; let s = strings[i]; if (s === '') { insert = createMarker(); } else { const match = lastAttributeNameRegex.exec(s); if (match !== null && endsWith(match[2], boundAttributeSuffix)) { s = s.slice(0, match.index) + match[1] + match[2].slice(0, -boundAttributeSuffix.length) + match[3]; } insert = document.createTextNode(s); } parent.insertBefore(insert, node); this.parts.push({ type: 'node', index: ++index }); } // If there's no text, we must insert a comment to mark our place. // Else, we can trust it will stick around after cloning. if (strings[lastIndex] === '') { parent.insertBefore(createMarker(), node); nodesToRemove.push(node); } else { node.data = strings[lastIndex]; } // We have a part for each match found partIndex += lastIndex; } } else if (node.nodeType === 8 /* Node.COMMENT_NODE */ ) { if (node.data === marker) { const parent = node.parentNode; // Add a new marker node to be the startNode of the Part if any of // the following are true: // * We don't have a previousSibling // * The previousSibling is already the start of a previous part if (node.previousSibling === null || index === lastPartIndex) { index++; parent.insertBefore(createMarker(), node); } lastPartIndex = index; this.parts.push({ type: 'node', index }); // If we don't have a nextSibling, keep this node so we have an end. // Else, we can remove it to save future costs. if (node.nextSibling === null) { node.data = ''; } else { nodesToRemove.push(node); index--; } partIndex++; } else { let i = -1; while ((i = node.data.indexOf(marker, i + 1)) !== -1) { // Comment node has a binding marker inside, make an inactive part // The binding won't work, but subsequent bindings will // TODO (justinfagnani): consider whether it's even worth it to // make bindings in comments work this.parts.push({ type: 'node', index: -1 }); partIndex++; } } } } // Remove text binding nodes after the walk to not disturb the TreeWalker for (const n of nodesToRemove) { n.parentNode.removeChild(n); } } } const endsWith = (str, suffix) => { const index = str.length - suffix.length; return index >= 0 && str.slice(index) === suffix; }; const isTemplatePartActive = part => part.index !== -1; // Allows `document.createComment('')` to be renamed for a // small manual size-savings. const createMarker = () => document.createComment(''); /** * This regex extracts the attribute name preceding an attribute-position * expression. It does this by matching the syntax allowed for attributes * against the string literal directly preceding the expression, assuming that * the expression is in an attribute-value position. * * See attributes in the HTML spec: * https://www.w3.org/TR/html5/syntax.html#elements-attributes * * " \x09\x0a\x0c\x0d" are HTML space characters: * https://www.w3.org/TR/html5/infrastructure.html#space-characters * * "\0-\x1F\x7F-\x9F" are Unicode control characters, which includes every * space character except " ". * * So an attribute is: * * The name: any character except a control character, space character, ('), * ("), ">", "=", or "/" * * Followed by zero or more space characters * * Followed by "=" * * Followed by zero or more space characters * * Followed by: * * Any character except space, ('), ("), "<", ">", "=", (`), or * * (") then any non-("), or * * (') then any non-(') */ const lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/; /** * @license * Copyright (c) 2017 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 */ /** * An instance of a `Template` that can be attached to the DOM and updated * with new values. */ class TemplateInstance { constructor(template, processor, options) { this.__parts = []; this.template = template; this.processor = processor; this.options = options; } update(values) { let i = 0; for (const part of this.__parts) { if (part !== undefined) { part.setValue(values[i]); } i++; } for (const part of this.__parts) { if (part !== undefined) { part.commit(); } } } _clone() { // There are a number of steps in the lifecycle of a template instance's // DOM fragment: // 1. Clone - create the instance fragment // 2. Adopt - adopt into the main document // 3. Process - find part markers and create parts // 4. Upgrade - upgrade custom elements // 5. Update - set node, attribute, property, etc., values // 6. Connect - connect to the document. Optional and outside of this // method. // // We have a few constraints on the ordering of these steps: // * We need to upgrade before updating, so that property values will pass // through any property setters. // * We would like to process before upgrading so that we're sure that the // cloned fragment is inert and not disturbed by self-modifying DOM. // * We want custom elements to upgrade even in disconnected fragments. // // Given these constraints, with full custom elements support we would // prefer the order: Clone, Process, Adopt, Upgrade, Update, Connect // // But Safari dooes not implement CustomElementRegistry#upgrade, so we // can not implement that order and still have upgrade-before-update and // upgrade disconnected fragments. So we instead sacrifice the // process-before-upgrade constraint, since in Custom Elements v1 elements // must not modify their light DOM in the constructor. We still have issues // when co-existing with CEv0 elements like Polymer 1, and with polyfills // that don't strictly adhere to the no-modification rule because shadow // DOM, which may be created in the constructor, is emulated by being placed // in the light DOM. // // The resulting order is on native is: Clone, Adopt, Upgrade, Process, // Update, Connect. document.importNode() performs Clone, Adopt, and Upgrade // in one step. // // The Custom Elements v1 polyfill supports upgrade(), so the order when // polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update, // Connect. const fragment = isCEPolyfill ? this.template.element.content.cloneNode(true) : document.importNode(this.template.element.content, true); const stack = []; const parts = this.template.parts; // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */ , null, false); let partIndex = 0; let nodeIndex = 0; let part; let node = walker.nextNode(); // Loop through all the nodes and parts of a template while (partIndex < parts.length) { part = parts[partIndex]; if (!isTemplatePartActive(part)) { this.__parts.push(undefined); partIndex++; continue; } // Progress the tree walker until we find our next part's node. // Note that multiple parts may share the same node (attribute parts // on a single element), so this loop may not run at all. while (nodeIndex < part.index) { nodeIndex++; if (node.nodeName === 'TEMPLATE') { stack.push(node); walker.currentNode = node.content; } if ((node = walker.nextNode()) === null) { // We've exhausted the content inside a nested template element. // Because we still have parts (the outer for-loop), we know: // - There is a template in the stack // - The walker will find a nextNode outside the template walker.currentNode = stack.pop(); node = walker.nextNode(); } } // We've arrived at our part's node. if (part.type === 'node') { const part = this.processor.handleTextExpression(this.options); part.insertAfterNode(node.previousSibling); this.__parts.push(part); } else { this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options)); } partIndex++; } if (isCEPolyfill) { document.adoptNode(fragment); customElements.upgrade(fragment); } return fragment; } } /** * @license * Copyright (c) 2017 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 */ const commentMarker = ` ${marker} `; /** * The return type of `html`, which holds a Template and the values from * interpolated expressions. */ class TemplateResult { constructor(strings, values, type, processor) { this.strings = strings; this.values = values; this.type = type; this.processor = processor; } /** * Returns a string of HTML used to create a `