@leofavre/flip-card-component
Version:
A flip card web component.
1,218 lines (1,162 loc) • 52.5 kB
JavaScript
(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(() => {