UNPKG

es5-lit-html

Version:
283 lines (234 loc) 9.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.lastAttributeNameRegex = exports.createMarker = exports.isTemplatePartActive = exports.Template = exports.boundAttributeSuffix = exports.markerRegex = exports.nodeMarker = exports.marker = void 0; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * @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. */ var marker = "{{lit-".concat(String(Math.random()).slice(2), "}}"); /** * An expression marker used text-positions, multi-binding attributes, and * attributes with markup-like text values. */ exports.marker = marker; var nodeMarker = "<!--".concat(marker, "-->"); exports.nodeMarker = nodeMarker; var markerRegex = new RegExp("".concat(marker, "|").concat(nodeMarker)); /** * Suffix appended to all bound attribute names. */ exports.markerRegex = markerRegex; var boundAttributeSuffix = '$lit$'; /** * An updateable Template that tracks the location of dynamic parts. */ exports.boundAttributeSuffix = boundAttributeSuffix; var Template = function Template(result, element) { _classCallCheck(this, Template); this.parts = []; this.element = element; var nodesToRemove = []; var stack = []; // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null var 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. var lastPartIndex = 0; var index = -1; var partIndex = 0; var strings = result.strings, length = result.values.length; while (partIndex < length) { var 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()) { var attributes = node.attributes; var _length = attributes.length; // 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. var count = 0; for (var 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 var stringForPart = strings[partIndex]; // Find the attribute name var 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. var attributeLookupName = name.toLowerCase() + boundAttributeSuffix; var attributeValue = node.getAttribute(attributeLookupName); node.removeAttribute(attributeLookupName); var statics = attributeValue.split(markerRegex); this.parts.push({ type: 'attribute', index: index, name: 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 */ ) { var data = node.data; if (data.indexOf(marker) >= 0) { var parent = node.parentNode; var _strings = data.split(markerRegex); var 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 (var _i = 0; _i < lastIndex; _i++) { var insert = void 0; var s = _strings[_i]; if (s === '') { insert = createMarker(); } else { var 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) { var _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: 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 { var _i2 = -1; while ((_i2 = node.data.indexOf(marker, _i2 + 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 (var _i3 = 0, _nodesToRemove = nodesToRemove; _i3 < _nodesToRemove.length; _i3++) { var n = _nodesToRemove[_i3]; n.parentNode.removeChild(n); } }; exports.Template = Template; var endsWith = function endsWith(str, suffix) { var index = str.length - suffix.length; return index >= 0 && str.slice(index) === suffix; }; var isTemplatePartActive = function isTemplatePartActive(part) { return part.index !== -1; }; // Allows `document.createComment('')` to be renamed for a // small manual size-savings. exports.isTemplatePartActive = isTemplatePartActive; var createMarker = function createMarker() { return 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-(') */ exports.createMarker = createMarker; var lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/; exports.lastAttributeNameRegex = lastAttributeNameRegex;