UNPKG

@leofavre/flip-card-component

Version:

A flip card web component.

1,218 lines (1,162 loc) 52.5 kB
(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) => 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 */ const isCEPolyfill = window.customElements !== undefined && window.customElements.polyfillWrapFlushCallback !== undefined; /** * Removes nodes, starting from `startNode` (inclusive) to `endNode` * (exclusive), from `container`. */ const removeNodes = (container, startNode, endNode = null) => { let node = startNode; while (node !== endNode) { const n = node.nextSibling; container.removeChild(node); node = n; } }; /** * A sentinel value that signals that a value was handled by a directive and * should not be written to the DOM. */ const noChange = {}; /** * @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, not attribute positions, * in template. */ const nodeMarker = `<!--${marker}-->`; const markerRegex = new RegExp(`${marker}|${nodeMarker}`); const rewritesStyleAttribute = (() => { const el = document.createElement('div'); el.setAttribute('style', '{{bad value}}'); return el.getAttribute('style') !== '{{bad value}}'; })(); /** * An updateable Template that tracks the location of dynamic parts. */ class Template { constructor(result, element) { this.parts = []; this.element = element; let index = -1; let partIndex = 0; const nodesToRemove = []; const _prepareTemplate = (template) => { const content = template.content; // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be // null const walker = document.createTreeWalker(content, 133 /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT */, null, false); // The actual previous node, accounting for removals: if a node is removed // it will never be the previousNode. let previousNode; // Used to set previousNode at the top of the loop. let currentNode; while (walker.nextNode()) { index++; previousNode = currentNode; const node = currentNode = walker.currentNode; if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { if (node.hasAttributes()) { const attributes = node.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 correspondance between part index and attribute index. let count = 0; for (let i = 0; i < attributes.length; i++) { if (attributes[i].value.indexOf(marker) >= 0) { count++; } } while (count-- > 0) { // Get the template literal section leading up to the first // expression in this attribute const stringForPart = result.strings[partIndex]; // Find the attribute name const name = lastAttributeNameRegex.exec(stringForPart)[2]; // Find the corresponding attribute // If the attribute name contains special characters, lower-case // it so that on XML nodes with case-sensitive getAttribute() we // can still find the attribute, which will have been lower-cased // by the parser. // // If the attribute name doesn't contain special character, it's // important to _not_ lower-case it, in case the name is // case-sensitive, like with XML attributes like "viewBox". const attributeLookupName = (rewritesStyleAttribute && name === 'style') ? 'style$' : /^[a-zA-Z-]*$/.test(name) ? name : name.toLowerCase(); const attributeValue = node.getAttribute(attributeLookupName); const strings = attributeValue.split(markerRegex); this.parts.push({ type: 'attribute', index, name, strings }); node.removeAttribute(attributeLookupName); partIndex += strings.length - 1; } } if (node.tagName === 'TEMPLATE') { _prepareTemplate(node); } } else if (node.nodeType === 3 /* Node.TEXT_NODE */) { const nodeValue = node.nodeValue; if (nodeValue.indexOf(marker) < 0) { continue; } const parent = node.parentNode; const strings = nodeValue.split(markerRegex); const lastIndex = strings.length - 1; // We have a part for each match found partIndex += lastIndex; // 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++) { parent.insertBefore((strings[i] === '') ? createMarker() : document.createTextNode(strings[i]), node); this.parts.push({ type: 'node', index: index++ }); } parent.insertBefore(strings[lastIndex] === '' ? createMarker() : document.createTextNode(strings[lastIndex]), node); nodesToRemove.push(node); } else if (node.nodeType === 8 /* Node.COMMENT_NODE */) { if (node.nodeValue === 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 // * previousSibling is being removed (thus it's not the // `previousNode`) // * previousSibling is not a Text node // // TODO(justinfagnani): We should be able to use the previousNode // here as the marker node and reduce the number of extra nodes we // add to a template. See // https://github.com/PolymerLabs/lit-html/issues/147 const previousSibling = node.previousSibling; if (previousSibling === null || previousSibling !== previousNode || previousSibling.nodeType !== Node.TEXT_NODE) { parent.insertBefore(createMarker(), node); } else { index--; } this.parts.push({ type: 'node', index: index++ }); nodesToRemove.push(node); // If we don't have a nextSibling add a marker node. // We don't have to check if the next node is going to be removed, // because that node will induce a new marker if so. if (node.nextSibling === null) { parent.insertBefore(createMarker(), node); } else { index--; } currentNode = previousNode; partIndex++; } else { let i = -1; while ((i = node.nodeValue.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 }); } } } } }; _prepareTemplate(element); // Remove text binding nodes after the walk to not disturb the TreeWalker for (const n of nodesToRemove) { n.parentNode.removeChild(n); } } } 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#attributes-0 * * "\0-\x1F\x7F-\x9F" are Unicode control characters * * " \x09\x0a\x0c\x0d" are HTML space characters: * https://www.w3.org/TR/html5/infrastructure.html#space-character * * 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]*(?:[^ \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() { // When using the Custom Elements polyfill, clone the node, rather than // importing it, to keep the fragment in the template's document. This // leaves the fragment inert so custom elements won't upgrade and // potentially modify their contents by creating a polyfilled ShadowRoot // while we traverse the tree. const fragment = isCEPolyfill ? this.template.element.content.cloneNode(true) : document.importNode(this.template.element.content, true); const parts = this.template.parts; let partIndex = 0; let nodeIndex = 0; const _prepareInstance = (fragment) => { // 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 node = walker.nextNode(); // Loop through all the nodes and parts of a template while (partIndex < parts.length && node !== null) { const part = parts[partIndex]; // Consecutive Parts may have the same node index, in the case of // multiple bound attributes on an element. So each iteration we either // increment the nodeIndex, if we aren't on a node with a part, or the // partIndex if we are. By not incrementing the nodeIndex when we find a // part, we allow for the next part to be associated with the current // node if neccessasry. if (!isTemplatePartActive(part)) { this._parts.push(undefined); partIndex++; } else if (nodeIndex === part.index) { if (part.type === 'node') { const part = this.processor.handleTextExpression(this.options); part.insertAfterNode(node); this._parts.push(part); } else { this._parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options)); } partIndex++; } else { nodeIndex++; if (node.nodeName === 'TEMPLATE') { _prepareInstance(node.content); } node = walker.nextNode(); } } }; _prepareInstance(fragment); 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 */ /** * 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 `<template>` element. */ getHTML() { const l = this.strings.length - 1; let html = ''; let isTextBinding = true; for (let i = 0; i < l; i++) { const s = this.strings[i]; html += s; const close = s.lastIndexOf('>'); // We're in a text position if the previous string closed its last tag, an // attribute position if the string opened an unclosed tag, and unchanged // if the string had no brackets at all: // // "...>...": text position. open === -1, close > -1 // "...<...": attribute position. open > -1 // "...": no change. open === -1, close === -1 isTextBinding = (close > -1 || isTextBinding) && s.indexOf('<', close + 1) === -1; if (!isTextBinding && rewritesStyleAttribute) { html = html.replace(lastAttributeNameRegex, (match, p1, p2, p3) => { return (p2 === 'style') ? `${p1}style$${p3}` : match; }); } html += isTextBinding ? nodeMarker : marker; } html += this.strings[l]; return html; } getTemplateElement() { const template = document.createElement('template'); template.innerHTML = this.getHTML(); return template; } } /** * @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 isPrimitive = (value) => (value === null || !(typeof value === 'object' || typeof value === 'function')); /** * Sets attribute values for AttributeParts, so that the value is only set once * even if there are multiple parts for an attribute. */ class AttributeCommitter { constructor(element, name, strings) { this.dirty = true; this.element = element; this.name = name; this.strings = strings; this.parts = []; for (let i = 0; i < strings.length - 1; i++) { this.parts[i] = this._createPart(); } } /** * Creates a single part. Override this to create a differnt type of part. */ _createPart() { return new AttributePart(this); } _getValue() { const strings = this.strings; const l = strings.length - 1; let text = ''; for (let i = 0; i < l; i++) { text += strings[i]; const part = this.parts[i]; if (part !== undefined) { const v = part.value; if (v != null && (Array.isArray(v) || typeof v !== 'string' && v[Symbol.iterator])) { for (const t of v) { text += typeof t === 'string' ? t : String(t); } } else { text += typeof v === 'string' ? v : String(v); } } } text += strings[l]; return text; } commit() { if (this.dirty) { this.dirty = false; this.element.setAttribute(this.name, this._getValue()); } } } class AttributePart { constructor(comitter) { this.value = undefined; this.committer = comitter; } setValue(value) { if (value !== noChange && (!isPrimitive(value) || value !== this.value)) { this.value = value; // If the value is a not a directive, dirty the committer so that it'll // call setAttribute. If the value is a directive, it'll dirty the // committer if it calls setValue(). if (!isDirective(value)) { this.committer.dirty = true; } } } commit() { while (isDirective(this.value)) { const directive$$1 = this.value; this.value = noChange; directive$$1(this); } if (this.value === noChange) { return; } this.committer.commit(); } } class NodePart { constructor(options) { this.value = undefined; this._pendingValue = undefined; this.options = options; } /** * Inserts this part into a container. * * This part must be empty, as its contents are not automatically moved. */ appendInto(container) { this.startNode = container.appendChild(createMarker()); this.endNode = container.appendChild(createMarker()); } /** * Inserts this part between `ref` and `ref`'s next sibling. Both `ref` and * its next sibling must be static, unchanging nodes such as those that appear * in a literal section of a template. * * This part must be empty, as its contents are not automatically moved. */ insertAfterNode(ref) { this.startNode = ref; this.endNode = ref.nextSibling; } /** * Appends this part into a parent part. * * This part must be empty, as its contents are not automatically moved. */ appendIntoPart(part) { part._insert(this.startNode = createMarker()); part._insert(this.endNode = createMarker()); } /** * Appends this part after `ref` * * This part must be empty, as its contents are not automatically moved. */ insertAfterPart(ref) { ref._insert(this.startNode = createMarker()); this.endNode = ref.endNode; ref.endNode = this.startNode; } setValue(value) { this._pendingValue = value; } commit() { while (isDirective(this._pendingValue)) { const directive$$1 = this._pendingValue; this._pendingValue = noChange; directive$$1(this); } const value = this._pendingValue; if (value === noChange) { return; } if (isPrimitive(value)) { if (value !== this.value) { this._commitText(value); } } else if (value instanceof TemplateResult) { this._commitTemplateResult(value); } else if (value instanceof Node) { this._commitNode(value); } else if (Array.isArray(value) || value[Symbol.iterator]) { this._commitIterable(value); } else if (value.then !== undefined) { this._commitPromise(value); } else { // Fallback, will render the string representation this._commitText(value); } } _insert(node) { this.endNode.parentNode.insertBefore(node, this.endNode); } _commitNode(value) { if (this.value === value) { return; } this.clear(); this._insert(value); this.value = value; } _commitText(value) { const node = this.startNode.nextSibling; value = value == null ? '' : value; if (node === this.endNode.previousSibling && node.nodeType === Node.TEXT_NODE) { // If we only have a single text node between the markers, we can just // set its value, rather than replacing it. // TODO(justinfagnani): Can we just check if this.value is primitive? node.textContent = value; } else { this._commitNode(document.createTextNode(typeof value === 'string' ? value : String(value))); } this.value = value; } _commitTemplateResult(value) { const template = this.options.templateFactory(value); if (this.value && this.value.template === template) { this.value.update(value.values); } else { // Make sure we propagate the template processor from the TemplateResult // so that we use its syntax extension, etc. The template factory comes // from the render function options so that it can control template // caching and preprocessing. const instance = new TemplateInstance(template, value.processor, this.options); const fragment = instance._clone(); instance.update(value.values); this._commitNode(fragment); this.value = instance; } } _commitIterable(value) { // For an Iterable, we create a new InstancePart per item, then set its // value to the item. This is a little bit of overhead for every item in // an Iterable, but it lets us recurse easily and efficiently update Arrays // of TemplateResults that will be commonly returned from expressions like: // array.map((i) => html`${i}`), by reusing existing TemplateInstances. // If _value is an array, then the previous render was of an // iterable and _value will contain the NodeParts from the previous // render. If _value is not an array, clear this part and make a new // array for NodeParts. if (!Array.isArray(this.value)) { this.value = []; this.clear(); } // Lets us keep track of how many items we stamped so we can clear leftover // items from a previous render const itemParts = this.value; let partIndex = 0; let itemPart; for (const item of value) { // Try to reuse an existing part itemPart = itemParts[partIndex]; // If no existing part, create a new one if (itemPart === undefined) { itemPart = new NodePart(this.options); itemParts.push(itemPart); if (partIndex === 0) { itemPart.appendIntoPart(this); } else { itemPart.insertAfterPart(itemParts[partIndex - 1]); } } itemPart.setValue(item); itemPart.commit(); partIndex++; } if (partIndex < itemParts.length) { // Truncate the parts array so _value reflects the current state itemParts.length = partIndex; this.clear(itemPart && itemPart.endNode); } } _commitPromise(value) { this.value = value; value.then((v) => { if (this.value === value) { this.setValue(v); this.commit(); } }); } clear(startNode = this.startNode) { removeNodes(this.startNode.parentNode, startNode.nextSibling, this.endNode); } } /** * Implements a boolean attribute, roughly as defined in the HTML * specification. * * If the value is truthy, then the attribute is present with a value of * ''. If the value is falsey, the attribute is removed. */ class BooleanAttributePart { constructor(element, name, strings) { this.value = undefined; this._pendingValue = undefined; if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') { throw new Error('Boolean attributes can only contain a single expression'); } this.element = element; this.name = name; this.strings = strings; } setValue(value) { this._pendingValue = value; } commit() { while (isDirective(this._pendingValue)) { const directive$$1 = this._pendingValue; this._pendingValue = noChange; directive$$1(this); } if (this._pendingValue === noChange) { return; } const value = !!this._pendingValue; if (this.value !== value) { if (value) { this.element.setAttribute(this.name, ''); } else { this.element.removeAttribute(this.name); } } this.value = value; this._pendingValue = noChange; } } /** * Sets attribute values for PropertyParts, so that the value is only set once * even if there are multiple parts for a property. * * If an expression controls the whole property value, then the value is simply * assigned to the property under control. If there are string literals or * multiple expressions, then the strings are expressions are interpolated into * a string first. */ class PropertyCommitter extends AttributeCommitter { constructor(element, name, strings) { super(element, name, strings); this.single = (strings.length === 2 && strings[0] === '' && strings[1] === ''); } _createPart() { return new PropertyPart(this); } _getValue() { if (this.single) { return this.parts[0].value; } return super._getValue(); } commit() { if (this.dirty) { this.dirty = false; this.element[this.name] = this._getValue(); } } } class PropertyPart extends AttributePart { } // Detect event listener options support. If the `capture` property is read // from the options object, then options are supported. If not, then the thrid // argument to add/removeEventListener is interpreted as the boolean capture // value so we should only pass the `capture` property. let eventOptionsSupported = false; try { const options = { get capture() { eventOptionsSupported = true; return false; } }; window.addEventListener('test', options, options); window.removeEventListener('test', options, options); } catch (_e) { } class EventPart { constructor(element, eventName, eventContext) { this.value = undefined; this._pendingValue = undefined; this.element = element; this.eventName = eventName; this.eventContext = eventContext; } setValue(value) { this._pendingValue = value; } commit() { while (isDirective(this._pendingValue)) { const directive$$1 = this._pendingValue; this._pendingValue = noChange; directive$$1(this); } if (this._pendingValue === noChange) { return; } const newListener = this._pendingValue; const oldListener = this.value; const shouldRemoveListener = newListener == null || oldListener != null && (newListener.capture !== oldListener.capture || newListener.once !== oldListener.once || newListener.passive !== oldListener.passive); const shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener); if (shouldRemoveListener) { this.element.removeEventListener(this.eventName, this, this._options); } this._options = getOptions(newListener); if (shouldAddListener) { this.element.addEventListener(this.eventName, this, this._options); } this.value = newListener; this._pendingValue = noChange; } handleEvent(event) { const listener = (typeof this.value === 'function') ? this.value : (typeof this.value.handleEvent === 'function') ? this.value.handleEvent : () => null; listener.call(this.eventContext || this.element, event); } } // We copy options because of the inconsistent behavior of browsers when reading // the third argument of add/removeEventListener. IE11 doesn't support options // at all. Chrome 41 only reads `capture` if the argument is an object. const getOptions = (o) => o && (eventOptionsSupported ? { capture: o.capture, passive: o.passive, once: o.once } : o.capture); /** * @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 */ /** * Creates Parts when a template is instantiated. */ class DefaultTemplateProcessor { /** * Create parts for an attribute-position binding, given the event, attribute * name, and string literals. * * @param element The element containing the binding * @param name The attribute name * @param strings The string literals. There are always at least two strings, * event for fully-controlled bindings with a single expression. */ handleAttributeExpressions(element, name, strings, options) { const prefix = name[0]; if (prefix === '.') { const comitter = new PropertyCommitter(element, name.slice(1), strings); return comitter.parts; } if (prefix === '@') { return [new EventPart(element, name.slice(1), options.eventContext)]; } if (prefix === '?') { return [new BooleanAttributePart(element, name.slice(1), strings)]; } const comitter = new AttributeCommitter(element, name, strings); return comitter.parts; } /** * Create parts for a text-position binding. * @param templateFactory */ handleTextExpression(options) { return new NodePart(options); } } const defaultTemplateProcessor = new DefaultTemplateProcessor(); /** * @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 */ /** * The default TemplateFactory which caches Templates keyed on * result.type and result.strings. */ function templateFactory(result) { let templateCache = templateCaches.get(result.type); if (templateCache === undefined) { templateCache = new Map(); templateCaches.set(result.type, templateCache); } let template = templateCache.get(result.strings); if (template === undefined) { template = new Template(result, result.getTemplateElement()); templateCache.set(result.strings, template); } return template; } // The first argument to JS template tags retain identity across multiple // calls to a tag for the same literal, so we can cache work done per literal // in a Map. const templateCaches = new Map(); /** * @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 parts = new WeakMap(); /** * Renders a template to a container. * * To update a container with new values, reevaluate the template literal and * call `render` with the new result. * * @param result a TemplateResult created by evaluating a template tag like * `html` or `svg`. * @param container A DOM parent to render to. The entire contents are either * replaced, or efficiently updated if the same result type was previous * rendered there. * @param options RenderOptions for the entire render tree rendered to this * container. Render options must *not* change between renders to the same * container, as those changes will not effect previously rendered DOM. */ const render = (result, container, options) => { let part = parts.get(container); if (part === undefined) { removeNodes(container, container.firstChild); parts.set(container, part = new NodePart(Object.assign({ templateFactory }, options))); part.appendInto(container); } part.setValue(result); part.commit(); }; /** * @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 */ /** * Interprets a template literal as an HTML template that can efficiently * render to and update a container. */ const html = (strings, ...values) => new TemplateResult(strings, values, 'html', defaultTemplateProcessor); var isFunction = arg => typeof arg === 'function'; var withRendererRaw = (render, TemplateResult) => (Base = class {}) => class extends Base { constructor () { super(); if (isFunction(this.attachShadow)) { this.attachShadow({ mode: 'open' }); } } connectedCallback () { if (isFunction(super.connectedCallback)) { super.connectedCallback(); } this.updateLayout(); } updateLayout () { let renderResult; if (isFunction(this.render)) { renderResult = this.render(this); } if (this.shadowRoot && renderResult != null) { if (renderResult.constructor === TemplateResult) { render(renderResult, this.shadowRoot); } else { this.shadowRoot.innerHTML = renderResult; } } } }; const UPDATE_ON_CONNECTED = Symbol('UPDATE_ON_CONNECTED'); const withObservedProperties = (Base = class {}) => class extends Base { constructor () { super(); const { observedProperties = [] } = this.constructor; this[UPDATE_ON_CONNECTED] = []; if (typeof this.propertyChangedCallback === 'function') { observedProperties.forEach(propName => { const initialValue = this[propName]; const CACHED_VALUE = Symbol(propName); this[CACHED_VALUE] = initialValue; Object.defineProperty(this, propName, { get () { return this[CACHED_VALUE]; }, set (value) { const oldValue = this[CACHED_VALUE]; this[CACHED_VALUE] = value; this.propertyChangedCallback(propName, oldValue, value); } }); if (typeof initialValue !== 'undefined') { this[UPDATE_ON_CONNECTED].push(propName); } }); } } connectedCallback () { this[UPDATE_ON_CONNECTED] .forEach(propName => { this.propertyChangedCallback(propName, undefined, this[propName]); }); super.connectedCallback && super.connectedCallback(); } }; const CALLBACKS = Symbol('CALLBACKS'); const IS_CONNECTED = Symbol('IS_CONNECTED'); var withWhenConnected = (Base = class {}) => class extends Base { constructor () { super(); this[CALLBACKS] = []; } whenConnected (callback) { if (this[IS_CONNECTED]) { callback(); } else { this[CALLBACKS].push(callback); } } connectedCallback () { if (isFunction(super.connectedCallback)) { super.connectedCallback(); } this[IS_CONNECTED] = true; this[CALLBACKS].forEach(callback => callback()); } }; var parseParameters = ({ parameters = [] }) => parameters .map(param => { if (param.attrProp != null) { const { attrProp, ...remaining } = param; const attr = attrProp; const prop = attrProp; return { attr, prop, ...remaining }; } return param; }); var selectParameter = (ctor, type, paramName) => parseParameters(ctor) .find(param => param[type] === paramName) || {}; var capitalize = (str = '') => `${str.slice(0, 1).toUpperCase()}${str.slice(1)}`; var isString = arg => typeof arg === 'string' || arg instanceof String; var withReactions = (Base = class {}) => class extends withWhenConnected(Base) { attributeChangedCallback (attrName, oldValue, newValue) { if (isFunction(super.attributeChangedCallback)) { super.attributeChangedCallback(attrName, oldValue, newValue); } if (oldValue !== newValue) { this.handleReaction('attr', attrName, oldValue, newValue); } } propertyChangedCallback (propName, oldValue, newValue) { if (isFunction(super.propertyChangedCallback)) { super.propertyChangedCallback(propName, oldValue, newValue); } if (oldValue !== newValue) { this.handleReaction('prop', propName, oldValue, newValue); } } handleReaction (type, paramName, oldValue, newValue) { const parameter = selectParameter(this.constructor, type, paramName); const oneOrManyCallbacks = parameter[`on${capitalize(type)}Changed`]; if (oneOrManyCallbacks != null) { this.handleCallback(oneOrManyCallbacks, newValue, oldValue); } } handleCallback (oneOrManyCallbacks, ...args) { if (Array.isArray(oneOrManyCallbacks)) { oneOrManyCallbacks.map(callback => this.handleCallback(callback, ...args)); } else { let fn; const callback = oneOrManyCallbacks; if (isString(callback) && isFunction(this[callback])) { fn = this[callback].bind(this); } else if (isFunction(callback)) { fn = callback.bind(this); } else { return undefined; } this.whenConnected(() => { fn(...args); }); } } }; var setAttr = (domEl, attrName, attrValue) => { if (!attrValue && attrValue !== 0 && attrValue !== '') { domEl.removeAttribute(attrName); } else { const parsedAttribute = (attrValue === true) ? '' : attrValue; domEl.setAttribute(attrName, parsedAttribute); } }; var same = arg => arg; var withReflections = (Base = class {}) => class extends withWhenConnected(Base) { static get observedAttributes () { const parameters = parseParameters(this); return [ ...(super.observedAttributes || []), ...parameters.map(param => param.attr).filter(value => value != null) ]; } static get observedProperties () { const parameters = parseParameters(this); return [ ...(super.observedProperties || []), ...parameters.map(param => param.prop).filter(value => value != null) ]; } attributeChangedCallback (paramName, oldValue, newValue) { if (isFunction(super.attributeChangedCallback)) { super.attributeChangedCallback(paramName, oldValue, newValue); } const parameter = selectParameter(this.constructor, 'attr', paramName); const name = parameter.prop; const coerce = parameter.toProp || same; if (name != null && oldValue !== newValue) { this.whenConnected(() => { this[name] = coerce(newValue); }); } } propertyChangedCallback (paramName, oldValue, newValue) { if (isFunction(super.propertyChangedCallback)) { super.propertyChangedCallback(paramName, oldValue, newValue); } const parameter = selectParameter(this.constructor, 'prop', paramName); const name = parameter.attr; const coerce = parameter.toAttr || same; if (name != null && oldValue !== newValue) { this.whenConnected(() => {