`;
}
}
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "topLeftImageAccessor", void 0);
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "topRightImageAccessor", void 0);
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "bottomLeftImageAccessor", void 0);
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "bottomRightImageAccessor", void 0);
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "magnifiedPixel", void 0);
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "dimensions", void 0);
__decorate$3([
n({ type: Object })
], Images4Up.prototype, "containerRect", void 0);
customElements.define('images-4-up', Images4Up);
/**
@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 nativeShadow = !(
window['ShadyDOM'] && window['ShadyDOM']['inUse']
);
/** @type {boolean} */
let nativeCssVariables_;
/**
* @param {(ShadyCSSOptions | ShadyCSSInterface)=} settings
*/
function calcCssVariables(settings) {
if (settings && settings.shimcssproperties) {
nativeCssVariables_ = false;
} else {
// chrome 49 has semi-working css vars, check if box-shadow works
// safari 9.1 has a recalc bug: https://bugs.webkit.org/show_bug.cgi?id=155782
// However, shim css custom properties are only supported with ShadyDOM enabled,
// so fall back on native if we do not detect ShadyDOM
// Edge 15: custom properties used in ::before and ::after will also be used in the parent element
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12414257/
nativeCssVariables_ =
nativeShadow ||
Boolean(
!navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/) &&
window.CSS &&
CSS.supports &&
CSS.supports('box-shadow', '0 0 0 var(--foo)')
);
}
}
/** @type {string | undefined} */
let cssBuild;
if (window.ShadyCSS && window.ShadyCSS.cssBuild !== undefined) {
cssBuild = window.ShadyCSS.cssBuild;
}
/** @type {boolean} */
const disableRuntime = Boolean(
window.ShadyCSS && window.ShadyCSS.disableRuntime
);
if (window.ShadyCSS && window.ShadyCSS.nativeCss !== undefined) {
nativeCssVariables_ = window.ShadyCSS.nativeCss;
} else if (window.ShadyCSS) {
calcCssVariables(window.ShadyCSS);
// reset window variable to let ShadyCSS API take its place
window.ShadyCSS = undefined;
} else {
calcCssVariables(window['WebComponents'] && window['WebComponents']['flags']);
}
// Hack for type error under new type inference which doesn't like that
// nativeCssVariables is updated in a function and assigns the type
// `function(): ?` instead of `boolean`.
const nativeCssVariables = /** @type {boolean} */ (nativeCssVariables_);
/**
@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
*/
/** @unrestricted */
class StyleNode {
constructor() {
/** @type {number} */
this['start'] = 0;
/** @type {number} */
this['end'] = 0;
/** @type {StyleNode} */
this['previous'] = null;
/** @type {StyleNode} */
this['parent'] = null;
/** @type {Array} */
this['rules'] = null;
/** @type {string} */
this['parsedCssText'] = '';
/** @type {string} */
this['cssText'] = '';
/** @type {boolean} */
this['atRule'] = false;
/** @type {number} */
this['type'] = 0;
/** @type {string} */
this['keyframesName'] = '';
/** @type {string} */
this['selector'] = '';
/** @type {string} */
this['parsedSelector'] = '';
}
}
// given a string of css, return a simple rule tree
/**
* @param {string} text
* @return {StyleNode}
*/
function parse(text) {
text = clean(text);
return parseCss(lex(text), text);
}
// remove stuff we don't care about that may hinder parsing
/**
* @param {string} cssText
* @return {string}
*/
function clean(cssText) {
return cssText.replace(RX.comments, '').replace(RX.port, '');
}
// super simple {...} lexer that returns a node tree
/**
* @param {string} text
* @return {!StyleNode}
*/
function lex(text) {
let root = new StyleNode();
root['start'] = 0;
root['end'] = text.length;
let n = root;
for (let i = 0, l = text.length; i < l; i++) {
if (text[i] === OPEN_BRACE) {
if (!n['rules']) {
n['rules'] = [];
}
let p = n;
let previous = p['rules'][p['rules'].length - 1] || null;
n = new StyleNode();
n['start'] = i + 1;
n['parent'] = p;
n['previous'] = previous;
p['rules'].push(n);
} else if (text[i] === CLOSE_BRACE) {
n['end'] = i + 1;
n = n['parent'] || root;
}
}
return root;
}
// add selectors/cssText to node tree
/**
* @param {StyleNode} node
* @param {string} text
* @return {!StyleNode}
*/
function parseCss(node, text) {
let t = text.substring(node['start'], node['end'] - 1);
node['parsedCssText'] = node['cssText'] = t.trim();
if (node['parent']) {
let ss = node['previous']
? node['previous']['end']
: node['parent']['start'];
t = text.substring(ss, node['start'] - 1);
t = _expandUnicodeEscapes(t);
t = t.replace(RX.multipleSpaces, ' ');
// TODO(sorvell): ad hoc; make selector include only after last ;
// helps with mixin syntax
t = t.substring(t.lastIndexOf(';') + 1);
let s = (node['parsedSelector'] = node['selector'] = t.trim());
node['atRule'] = s.indexOf(AT_START) === 0;
// note, support a subset of rule types...
if (node['atRule']) {
if (s.indexOf(MEDIA_START) === 0) {
node['type'] = types.MEDIA_RULE;
} else if (s.match(RX.keyframesRule)) {
node['type'] = types.KEYFRAMES_RULE;
node['keyframesName'] = node['selector'].split(RX.multipleSpaces).pop();
}
} else {
if (s.indexOf(VAR_START) === 0) {
node['type'] = types.MIXIN_RULE;
} else {
node['type'] = types.STYLE_RULE;
}
}
}
let r$ = node['rules'];
if (r$) {
for (let i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
parseCss(r, text);
}
}
return node;
}
/**
* conversion of sort unicode escapes with spaces like `\33 ` (and longer) into
* expanded form that doesn't require trailing space `\000033`
* @param {string} s
* @return {string}
*/
function _expandUnicodeEscapes(s) {
return s.replace(/\\([0-9a-f]{1,6})\s/gi, function () {
let code = arguments[1],
repeat = 6 - code.length;
while (repeat--) {
code = '0' + code;
}
return '\\' + code;
});
}
/**
* stringify parsed css.
* @param {StyleNode} node
* @param {boolean=} preserveProperties
* @param {string=} text
* @return {string}
*/
function stringify(node, preserveProperties, text = '') {
// calc rule cssText
let cssText = '';
if (node['cssText'] || node['rules']) {
let r$ = node['rules'];
if (r$ && !_hasMixinRules(r$)) {
for (let i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
cssText = stringify(r, preserveProperties, cssText);
}
} else {
cssText = preserveProperties
? node['cssText']
: removeCustomProps(node['cssText']);
cssText = cssText.trim();
if (cssText) {
cssText = ' ' + cssText + '\n';
}
}
}
// emit rule if there is cssText
if (cssText) {
if (node['selector']) {
text += node['selector'] + ' ' + OPEN_BRACE + '\n';
}
text += cssText;
if (node['selector']) {
text += CLOSE_BRACE + '\n\n';
}
}
return text;
}
/**
* @param {Array} rules
* @return {boolean}
*/
function _hasMixinRules(rules) {
let r = rules[0];
return (
Boolean(r) &&
Boolean(r['selector']) &&
r['selector'].indexOf(VAR_START) === 0
);
}
/**
* @param {string} cssText
* @return {string}
*/
function removeCustomProps(cssText) {
cssText = removeCustomPropAssignment(cssText);
return removeCustomPropApply(cssText);
}
/**
* @param {string} cssText
* @return {string}
*/
function removeCustomPropAssignment(cssText) {
return cssText.replace(RX.customProp, '').replace(RX.mixinProp, '');
}
/**
* @param {string} cssText
* @return {string}
*/
function removeCustomPropApply(cssText) {
return cssText.replace(RX.mixinApply, '').replace(RX.varApply, '');
}
/** @enum {number} */
const types = {
STYLE_RULE: 1,
KEYFRAMES_RULE: 7,
MEDIA_RULE: 4,
MIXIN_RULE: 1000,
};
const OPEN_BRACE = '{';
const CLOSE_BRACE = '}';
// helper regexp's
const RX = {
comments: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
port: /@import[^;]*;/gim,
customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
mixinApply: /@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,
varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
keyframesRule: /^@[^\s]*keyframes/,
multipleSpaces: /\s+/g,
};
const VAR_START = '--';
const MEDIA_START = '@media';
const AT_START = '@';
/**
@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 VAR_ASSIGN = /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gi;
const MIXIN_MATCH = /(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi;
/**
@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
*/
/** @type {!Set} */
const styleTextSet = new Set();
const scopingAttribute = 'shady-unscoped';
/**
* Add a specifically-marked style to the document directly, and only one copy of that style.
*
* @param {!HTMLStyleElement} style
* @return {undefined}
*/
function processUnscopedStyle(style) {
const text = style.textContent;
if (!styleTextSet.has(text)) {
styleTextSet.add(text);
const newStyle = document.createElement('style');
newStyle.setAttribute('shady-unscoped', '');
newStyle.textContent = text;
document.head.appendChild(newStyle);
}
}
/**
* Check if a style is supposed to be unscoped
* @param {!HTMLStyleElement} style
* @return {boolean} true if the style has the unscoping attribute
*/
function isUnscopedStyle(style) {
return style.hasAttribute(scopingAttribute);
}
/**
@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
*/
/**
* @param {string|StyleNode} rules
* @param {function(StyleNode)=} callback
* @return {string}
*/
function toCssText(rules, callback) {
if (!rules) {
return '';
}
if (typeof rules === 'string') {
rules = parse(rules);
}
return stringify(rules, nativeCssVariables);
}
/**
* @param {HTMLStyleElement} style
* @return {StyleNode}
*/
function rulesForStyle(style) {
if (!style['__cssRules'] && style.textContent) {
style['__cssRules'] = parse(style.textContent);
}
return style['__cssRules'] || null;
}
/**
* @param {StyleNode} node
* @param {Function=} styleRuleCallback
* @param {Function=} keyframesRuleCallback
* @param {boolean=} onlyActiveRules
*/
function forEachRule(
node,
styleRuleCallback,
keyframesRuleCallback,
onlyActiveRules
) {
if (!node) {
return;
}
let skipRules = false;
let type = node['type'];
if (type === types.STYLE_RULE) {
styleRuleCallback(node);
} else if (type === types.MIXIN_RULE) {
skipRules = true;
}
let r$ = node['rules'];
if (r$ && !skipRules) {
for (let i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
forEachRule(r, styleRuleCallback);
}
}
}
/**
* Walk from text[start] matching parens and
* returns position of the outer end paren
* @param {string} text
* @param {number} start
* @return {number}
*/
function findMatchingParen(text, start) {
let level = 0;
for (let i = start, l = text.length; i < l; i++) {
if (text[i] === '(') {
level++;
} else if (text[i] === ')') {
if (--level === 0) {
return i;
}
}
}
return -1;
}
/**
* @param {string} str
* @param {function(string, string, string, string)} callback
*/
function processVariableAndFallback(str, callback) {
// find 'var('
let start = str.indexOf('var(');
if (start === -1) {
// no var?, everything is prefix
return callback(str, '', '', '');
}
//${prefix}var(${inner})${suffix}
let end = findMatchingParen(str, start + 3);
let inner = str.substring(start + 4, end);
let prefix = str.substring(0, start);
// suffix may have other variables
let suffix = processVariableAndFallback(str.substring(end + 1), callback);
let comma = inner.indexOf(',');
// value and fallback args should be trimmed to match in property lookup
if (comma === -1) {
// variable, no fallback
return callback(prefix, inner.trim(), '', suffix);
}
// var(${value},${fallback})
let value = inner.substring(0, comma).trim();
let fallback = inner.substring(comma + 1).trim();
return callback(prefix, value, fallback, suffix);
}
/**
* @type {function(*):*}
*/
(window['ShadyDOM'] && window['ShadyDOM']['wrap']) || ((node) => node);
/**
* @param {Element | {is: string, extends: string}} element
* @return {{is: string, typeExtension: string}}
*/
function getIsExtends(element) {
let localName = element['localName'];
let is = '',
typeExtension = '';
/*
NOTE: technically, this can be wrong for certain svg elements
with `-` in the name like ``
*/
if (localName) {
if (localName.indexOf('-') > -1) {
is = localName;
} else {
typeExtension = localName;
is = (element.getAttribute && element.getAttribute('is')) || '';
}
} else {
is = /** @type {?} */ (element).is;
typeExtension = /** @type {?} */ (element).extends;
}
return {is, typeExtension};
}
/**
* @param {Element|DocumentFragment} element
* @return {string}
*/
function gatherStyleText(element) {
/** @type {!Array} */
const styleTextParts = [];
const styles = /** @type {!NodeList} */ (element.querySelectorAll(
'style'
));
for (let i = 0; i < styles.length; i++) {
const style = styles[i];
if (isUnscopedStyle(style)) {
if (!nativeShadow) {
processUnscopedStyle(style);
style.parentNode.removeChild(style);
}
} else {
styleTextParts.push(style.textContent);
style.parentNode.removeChild(style);
}
}
return styleTextParts.join('').trim();
}
const CSS_BUILD_ATTR = 'css-build';
/**
* Return the polymer-css-build "build type" applied to this element
*
* @param {!HTMLElement} element
* @return {string} Can be "", "shady", or "shadow"
*/
function getCssBuild(element) {
if (cssBuild !== undefined) {
return /** @type {string} */ (cssBuild);
}
if (element.__cssBuild === undefined) {
// try attribute first, as it is the common case
const attrValue = element.getAttribute(CSS_BUILD_ATTR);
if (attrValue) {
element.__cssBuild = attrValue;
} else {
const buildComment = getBuildComment(element);
if (buildComment !== '') {
// remove build comment so it is not needlessly copied into every element instance
removeBuildComment(element);
}
element.__cssBuild = buildComment;
}
}
return element.__cssBuild || '';
}
/**
* Check if the given element, either a or
*
${this.partialTemplate}
* ${super.template}
* `;
* }
* static get partialTemplate() { return html`Partial!`; }
*
* @param {!ITemplateArray} strings Constant parts of tagged template literal
* @param {...*} values Variable parts of tagged template literal
* @return {!HTMLTemplateElement} Constructed HTMLTemplateElement
*/
const html = function html(strings, ...values) {
assertValidTemplateStringParameters(strings, values);
const template =
/** @type {!HTMLTemplateElement} */ (document.createElement('template'));
let value = values.reduce(
(acc, v, idx) => acc + htmlValue(v) + strings[idx + 1], strings[0]);
if (policy) {
value = policy.createHTML(value);
}
template.innerHTML = value;
return template;
};
/**
* @param {!ITemplateArray} strings Constant parts of tagged template literal
* @param {!Array<*>} values Array of values from quasis
*/
const assertValidTemplateStringParameters = (strings, values) => {
// Note: if/when https://github.com/tc39/proposal-array-is-template-object
// is standardized, use that instead when available, as it can perform an
// unforgable check (though of course, the function itself can be forged).
if (!Array.isArray(strings) || !Array.isArray(strings.raw) ||
(values.length !== strings.length - 1)) {
// This is either caused by a browser bug, a compiler bug, or someone
// calling the html template tag function as a regular function.
//
throw new TypeError('Invalid call to the html template tag');
}
};
/**
@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
*/
/**
* Base class that provides the core API for Polymer's meta-programming
* features including template stamping, data-binding, attribute deserialization,
* and property change observation.
*
* @customElement
* @polymer
* @constructor
* @implements {Polymer_ElementMixin}
* @extends HTMLElement
* @appliesMixin ElementMixin
* @summary Custom element base class that provides the core API for Polymer's
* key meta-programming features including template stamping, data-binding,
* attribute deserialization, and property change observation
*/
const PolymerElement = ElementMixin(HTMLElement);
/**
@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
*/
/**
* @constructor
* @implements {Polymer_OptionalMutableData}
* @extends {PolymerElement}
* @private
*/
const domRepeatBase = OptionalMutableData(PolymerElement);
/**
* The `` element will automatically stamp and binds one instance
* of template content to each object in a user-provided array.
* `dom-repeat` accepts an `items` property, and one instance of the template
* is stamped for each item into the DOM at the location of the `dom-repeat`
* element. The `item` property will be set on each instance's binding
* scope, thus templates should bind to sub-properties of `item`.
*
* Example:
*
* ```html
*
*
*
*
*
Employee list:
*
*
*
First name: {{item.first}}
*
Last name: {{item.last}}
*
*
*
*
*
*
* ```
*
* With the following custom element definition:
*
* ```js
* class EmployeeList extends PolymerElement {
* static get is() { return 'employee-list'; }
* static get properties() {
* return {
* employees: {
* value() {
* return [
* {first: 'Bob', last: 'Smith'},
* {first: 'Sally', last: 'Johnson'},
* ...
* ];
* }
* }
* };
* }
* }
* ```
*
* Notifications for changes to items sub-properties will be forwarded to template
* instances, which will update via the normal structured data notification system.
*
* Mutations to the `items` array itself should be made using the Array
* mutation API's on the PropertyEffects mixin (`push`, `pop`, `splice`,
* `shift`, `unshift`), and template instances will be kept in sync with the
* data in the array.
*
* Events caught by event handlers within the `dom-repeat` template will be
* decorated with a `model` property, which represents the binding scope for
* each template instance. The model should be used to manipulate data on the
* instance, for example `event.model.set('item.checked', true);`.
*
* Alternatively, the model for a template instance for an element stamped by
* a `dom-repeat` can be obtained using the `modelForElement` API on the
* `dom-repeat` that stamped it, for example
* `this.$.domRepeat.modelForElement(event.target).set('item.checked', true);`.
* This may be useful for manipulating instance data of event targets obtained
* by event handlers on parents of the `dom-repeat` (event delegation).
*
* A view-specific filter/sort may be applied to each `dom-repeat` by supplying a
* `filter` and/or `sort` property. This may be a string that names a function on
* the host, or a function may be assigned to the property directly. The functions
* should implemented following the standard `Array` filter/sort API.
*
* In order to re-run the filter or sort functions based on changes to sub-fields
* of `items`, the `observe` property may be set as a space-separated list of
* `item` sub-fields that should cause a re-filter/sort when modified. If
* the filter or sort function depends on properties not contained in `items`,
* the user should observe changes to those properties and call `render` to update
* the view based on the dependency change.
*
* For example, for an `dom-repeat` with a filter of the following:
*
* ```js
* isEngineer(item) {
* return item.type == 'engineer' || item.manager.type == 'engineer';
* }
* ```
*
* Then the `observe` property should be configured as follows:
*
* ```html
*
* ```
*
* @customElement
* @polymer
* @extends {domRepeatBase}
* @appliesMixin OptionalMutableData
* @summary Custom element for stamping instance of a template bound to
* items in an array.
*/
class DomRepeat extends domRepeatBase {
// Not needed to find template; can be removed once the analyzer
// can find the tag name from customElements.define call
static get is() { return 'dom-repeat'; }
static get template() { return null; }
static get properties() {
/**
* Fired whenever DOM is added or removed by this template (by
* default, rendering occurs lazily). To force immediate rendering, call
* `render`.
*
* @event dom-change
*/
return {
/**
* An array containing items determining how many instances of the template
* to stamp and that that each template instance should bind to.
*/
items: {
type: Array
},
/**
* The name of the variable to add to the binding scope for the array
* element associated with a given template instance.
*/
as: {
type: String,
value: 'item'
},
/**
* The name of the variable to add to the binding scope with the index
* of the instance in the sorted and filtered list of rendered items.
* Note, for the index in the `this.items` array, use the value of the
* `itemsIndexAs` property.
*/
indexAs: {
type: String,
value: 'index'
},
/**
* The name of the variable to add to the binding scope with the index
* of the instance in the `this.items` array. Note, for the index of
* this instance in the sorted and filtered list of rendered items,
* use the value of the `indexAs` property.
*/
itemsIndexAs: {
type: String,
value: 'itemsIndex'
},
/**
* A function that should determine the sort order of the items. This
* property should either be provided as a string, indicating a method
* name on the element's host, or else be an actual function. The
* function should match the sort function passed to `Array.sort`.
* Using a sort function has no effect on the underlying `items` array.
*/
sort: {
type: Function,
observer: '__sortChanged'
},
/**
* A function that can be used to filter items out of the view. This
* property should either be provided as a string, indicating a method
* name on the element's host, or else be an actual function. The
* function should match the sort function passed to `Array.filter`.
* Using a filter function has no effect on the underlying `items` array.
*/
filter: {
type: Function,
observer: '__filterChanged'
},
/**
* When using a `filter` or `sort` function, the `observe` property
* should be set to a space-separated list of the names of item
* sub-fields that should trigger a re-sort or re-filter when changed.
* These should generally be fields of `item` that the sort or filter
* function depends on.
*/
observe: {
type: String,
observer: '__observeChanged'
},
/**
* When using a `filter` or `sort` function, the `delay` property
* determines a debounce time in ms after a change to observed item
* properties that must pass before the filter or sort is re-run.
* This is useful in rate-limiting shuffling of the view when
* item changes may be frequent.
*/
delay: Number,
/**
* Count of currently rendered items after `filter` (if any) has been applied.
* If "chunking mode" is enabled, `renderedItemCount` is updated each time a
* set of template instances is rendered.
*
*/
renderedItemCount: {
type: Number,
notify: !suppressTemplateNotifications,
readOnly: true
},
/**
* When greater than zero, defines an initial count of template instances
* to render after setting the `items` array, before the next paint, and
* puts the `dom-repeat` into "chunking mode". The remaining items (and
* any future items as a result of pushing onto the array) will be created
* and rendered incrementally at each animation frame thereof until all
* instances have been rendered.
*/
initialCount: {
type: Number
},
/**
* When `initialCount` is used, this property defines a frame rate (in
* fps) to target by throttling the number of instances rendered each
* frame to not exceed the budget for the target frame rate. The
* framerate is effectively the number of `requestAnimationFrame`s that
* it tries to allow to actually fire in a given second. It does this
* by measuring the time between `rAF`s and continuously adjusting the
* number of items created each `rAF` to maintain the target framerate.
* Setting this to a higher number allows lower latency and higher
* throughput for event handlers and other tasks, but results in a
* longer time for the remaining items to complete rendering.
*/
targetFramerate: {
type: Number,
value: 20
},
_targetFrameTime: {
type: Number,
computed: '__computeFrameTime(targetFramerate)'
},
/**
* When the global `suppressTemplateNotifications` setting is used, setting
* `notifyDomChange: true` will enable firing `dom-change` events on this
* element.
*/
notifyDomChange: {
type: Boolean
},
/**
* When chunking is enabled via `initialCount` and the `items` array is
* set to a new array, this flag controls whether the previously rendered
* instances are reused or not.
*
* When `true`, any previously rendered template instances are updated in
* place to their new item values synchronously in one shot, and then any
* further items (if any) are chunked out. When `false`, the list is
* returned back to its `initialCount` (any instances over the initial
* count are discarded) and the remainder of the list is chunked back in.
* Set this to `true` to avoid re-creating the list and losing scroll
* position, although note that when changing the list to completely
* different data the render thread will be blocked until all existing
* instances are updated to their new data.
*/
reuseChunkedInstances: {
type: Boolean
}
};
}
static get observers() {
return [ '__itemsChanged(items.*)' ];
}
constructor() {
super();
this.__instances = [];
this.__renderDebouncer = null;
this.__itemsIdxToInstIdx = {};
this.__chunkCount = null;
this.__renderStartTime = null;
this.__itemsArrayChanged = false;
this.__shouldMeasureChunk = false;
this.__shouldContinueChunking = false;
this.__chunkingId = 0;
this.__sortFn = null;
this.__filterFn = null;
this.__observePaths = null;
/** @type {?function(new:TemplateInstanceBase, Object=)} */
this.__ctor = null;
this.__isDetached = true;
this.template = null;
/** @type {TemplateInfo} */
this._templateInfo;
}
/**
* @override
* @return {void}
*/
disconnectedCallback() {
super.disconnectedCallback();
this.__isDetached = true;
for (let i=0; i {
if (this.querySelector('template')) {
observer.disconnect();
this.__render();
} else {
throw new Error('dom-repeat requires a child');
}
});
observer.observe(this, {childList: true});
return false;
}
// Template instance props that should be excluded from forwarding
let instanceProps = {};
instanceProps[this.as] = true;
instanceProps[this.indexAs] = true;
instanceProps[this.itemsIndexAs] = true;
this.__ctor = templatize(template, this, {
mutableData: this.mutableData,
parentModel: true,
instanceProps: instanceProps,
/**
* @this {DomRepeat}
* @param {string} prop Property to set
* @param {*} value Value to set property to
*/
forwardHostProp: function(prop, value) {
let i$ = this.__instances;
for (let i=0, inst; (i 0 ? timeOut.after(delay) : microTask
, fn.bind(this));
enqueueDebouncer(this.__renderDebouncer);
}
/**
* Forces the element to render its content. Normally rendering is
* asynchronous to a provoking change. This is done for efficiency so
* that multiple changes trigger only a single render. The render method
* should be called if, for example, template rendering is required to
* validate application state.
* @return {void}
*/
render() {
// Queue this repeater, then flush all in order
this.__debounceRender(this.__render);
flush();
}
__render() {
if (!this.__ensureTemplatized()) {
// No template found yet
return;
}
let items = this.items || [];
// Sort and filter the items into a mapping array from instance->item
const isntIdxToItemsIdx = this.__sortAndFilterItems(items);
// If we're chunking, increase the limit if there are new instances to
// create and schedule the next chunk
const limit = this.__calculateLimit(isntIdxToItemsIdx.length);
// Create, update, and/or remove instances
this.__updateInstances(items, limit, isntIdxToItemsIdx);
// If we're chunking, schedule a rAF task to measure/continue chunking.
// Do this before any notifying events (renderedItemCount & dom-change)
// since those could modify items and enqueue a new full render which will
// pre-empt this measurement.
if (this.initialCount &&
(this.__shouldMeasureChunk || this.__shouldContinueChunking)) {
cancelAnimationFrame(this.__chunkingId);
this.__chunkingId = requestAnimationFrame(() => {
this.__chunkingId = null;
this.__continueChunking();
});
}
// Set rendered item count
this._setRenderedItemCount(this.__instances.length);
// Notify users
if (!suppressTemplateNotifications || this.notifyDomChange) {
this.dispatchEvent(new CustomEvent('dom-change', {
bubbles: true,
composed: true
}));
}
}
__sortAndFilterItems(items) {
// Generate array maping the instance index to the items array index
let isntIdxToItemsIdx = new Array(items.length);
for (let i=0; i
this.__filterFn(items[i], idx, array));
}
// Apply user sort
if (this.__sortFn) {
isntIdxToItemsIdx.sort((a, b) => this.__sortFn(items[a], items[b]));
}
return isntIdxToItemsIdx;
}
__calculateLimit(filteredItemCount) {
let limit = filteredItemCount;
const currentCount = this.__instances.length;
// When chunking, we increase the limit from the currently rendered count
// by the chunk count that is re-calculated after each rAF (with special
// cases for resetting the limit to initialCount after changing items)
if (this.initialCount) {
let newCount;
if (!this.__chunkCount ||
(this.__itemsArrayChanged && !this.reuseChunkedInstances)) {
// Limit next render to the initial count
limit = Math.min(filteredItemCount, this.initialCount);
// Subtract off any existing instances to determine the number of
// instances that will be created
newCount = Math.max(limit - currentCount, 0);
// Initialize the chunk size with how many items we're creating
this.__chunkCount = newCount || 1;
} else {
// The number of new instances that will be created is based on the
// existing instances, the new list size, and the chunk size
newCount = Math.min(
Math.max(filteredItemCount - currentCount, 0),
this.__chunkCount);
// Update the limit based on how many new items we're making, limited
// buy the total size of the list
limit = Math.min(currentCount + newCount, filteredItemCount);
}
// Record some state about chunking for use in `__continueChunking`
this.__shouldMeasureChunk = newCount === this.__chunkCount;
this.__shouldContinueChunking = limit < filteredItemCount;
this.__renderStartTime = performance.now();
}
this.__itemsArrayChanged = false;
return limit;
}
__continueChunking() {
// Simple auto chunkSize throttling algorithm based on feedback loop:
// measure actual time between frames and scale chunk count by ratio of
// target/actual frame time. Only modify chunk size if our measurement
// reflects the cost of a creating a full chunk's worth of instances; this
// avoids scaling up the chunk size if we e.g. quickly re-rendered instances
// in place
if (this.__shouldMeasureChunk) {
const renderTime = performance.now() - this.__renderStartTime;
const ratio = this._targetFrameTime / renderTime;
this.__chunkCount = Math.round(this.__chunkCount * ratio) || 1;
}
// Enqueue a new render if we haven't reached the full size of the list
if (this.__shouldContinueChunking) {
this.__debounceRender(this.__render);
}
}
__updateInstances(items, limit, isntIdxToItemsIdx) {
// items->inst map kept for item path forwarding
const itemsIdxToInstIdx = this.__itemsIdxToInstIdx = {};
let instIdx;
// Generate instances and assign items
for (instIdx=0; instIdx=instIdx; i--) {
this.__detachAndRemoveInstance(i);
}
}
__detachInstance(idx) {
let inst = this.__instances[idx];
const wrappedRoot = wrap(inst.root);
for (let i=0; i. path change,
// responsible for notifying item. changes to inst for key
__handleItemPath(path, value) {
let itemsPath = path.slice(6); // 'items.'.length == 6
let dot = itemsPath.indexOf('.');
let itemsIdx = dot < 0 ? itemsPath : itemsPath.substring(0, dot);
// If path was index into array...
if (itemsIdx == parseInt(itemsIdx, 10)) {
let itemSubPath = dot < 0 ? '' : itemsPath.substring(dot+1);
// If the path is observed, it will trigger a full refresh
this.__handleObservedPaths(itemSubPath);
// Note, even if a rull refresh is triggered, always do the path
// notification because unless mutableData is used for dom-repeat
// and all elements in the instance subtree, a full refresh may
// not trigger the proper update.
let instIdx = this.__itemsIdxToInstIdx[itemsIdx];
let inst = this.__instances[instIdx];
if (inst) {
let itemPath = this.as + (itemSubPath ? '.' + itemSubPath : '');
// This is effectively `notifyPath`, but avoids some of the overhead
// of the public API
inst._setPendingPropertyOrPath(itemPath, value, false, true);
inst._flushProperties();
}
return true;
}
}
/**
* Returns the item associated with a given element stamped by
* this `dom-repeat`.
*
* Note, to modify sub-properties of the item,
* `modelForElement(el).set('item.', value)`
* should be used.
*
* @param {!HTMLElement} el Element for which to return the item.
* @return {*} Item associated with the element.
*/
itemForElement(el) {
let instance = this.modelForElement(el);
return instance && instance[this.as];
}
/**
* Returns the inst index for a given element stamped by this `dom-repeat`.
* If `sort` is provided, the index will reflect the sorted order (rather
* than the original array order).
*
* @param {!HTMLElement} el Element for which to return the index.
* @return {?number} Row index associated with the element (note this may
* not correspond to the array index if a user `sort` is applied).
*/
indexForElement(el) {
let instance = this.modelForElement(el);
return instance && instance[this.indexAs];
}
/**
* Returns the template "model" associated with a given element, which
* serves as the binding scope for the template instance the element is
* contained in. A template model
* should be used to manipulate data associated with this template instance.
*
* Example:
*
* let model = modelForElement(el);
* if (model.index < 10) {
* model.set('item.checked', true);
* }
*
* @param {!HTMLElement} el Element for which to return a template model.
* @return {TemplateInstanceBase} Model representing the binding scope for
* the element.
*/
modelForElement(el) {
return modelForElement(this.template, el);
}
}
customElements.define(DomRepeat.is, DomRepeat);
/**
@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
*/
/**
* @customElement
* @polymer
* @extends PolymerElement
* @summary Base class for dom-if element; subclassed into concrete
* implementation.
*/
class DomIfBase extends PolymerElement {
// Not needed to find template; can be removed once the analyzer
// can find the tag name from customElements.define call
static get is() { return 'dom-if'; }
static get template() { return null; }
static get properties() {
return {
/**
* Fired whenever DOM is added or removed/hidden by this template (by
* default, rendering occurs lazily). To force immediate rendering, call
* `render`.
*
* @event dom-change
*/
/**
* A boolean indicating whether this template should stamp.
*/
if: {
type: Boolean,
observer: '__debounceRender'
},
/**
* When true, elements will be removed from DOM and discarded when `if`
* becomes false and re-created and added back to the DOM when `if`
* becomes true. By default, stamped elements will be hidden but left
* in the DOM when `if` becomes false, which is generally results
* in better performance.
*/
restamp: {
type: Boolean,
observer: '__debounceRender'
},
/**
* When the global `suppressTemplateNotifications` setting is used, setting
* `notifyDomChange: true` will enable firing `dom-change` events on this
* element.
*/
notifyDomChange: {
type: Boolean
}
};
}
constructor() {
super();
this.__renderDebouncer = null;
this._lastIf = false;
this.__hideTemplateChildren__ = false;
/** @type {!HTMLTemplateElement|undefined} */
this.__template;
/** @type {!TemplateInfo|undefined} */
this._templateInfo;
}
__debounceRender() {
// Render is async for 2 reasons:
// 1. To eliminate dom creation trashing if user code thrashes `if` in the
// same turn. This was more common in 1.x where a compound computed
// property could result in the result changing multiple times, but is
// mitigated to a large extent by batched property processing in 2.x.
// 2. To avoid double object propagation when a bag including values bound
// to the `if` property as well as one or more hostProps could enqueue
// the to flush before the 's host property
// forwarding. In that scenario creating an instance would result in
// the host props being set once, and then the enqueued changes on the
// template would set properties a second time, potentially causing an
// object to be set to an instance more than once. Creating the
// instance async from flushing data ensures this doesn't happen. If
// we wanted a sync option in the future, simply having flush
// (or clear) its template's pending host properties before creating
// the instance would also avoid the problem.
this.__renderDebouncer = Debouncer.debounce(
this.__renderDebouncer
, microTask
, () => this.__render());
enqueueDebouncer(this.__renderDebouncer);
}
/**
* @override
* @return {void}
*/
disconnectedCallback() {
super.disconnectedCallback();
const parent = wrap(this).parentNode;
if (!parent || (parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE &&
!wrap(parent).host)) {
this.__teardownInstance();
}
}
/**
* @override
* @return {void}
*/
connectedCallback() {
super.connectedCallback();
if (!hideElementsGlobally()) {
this.style.display = 'none';
}
if (this.if) {
this.__debounceRender();
}
}
/**
* Ensures a template has been assigned to `this.__template`. If it has not
* yet been, it querySelectors for it in its children and if it does not yet
* exist (e.g. in parser-generated case), opens a mutation observer and
* waits for it to appear (returns false if it has not yet been found,
* otherwise true). In the `removeNestedTemplates` case, the "template" will
* be the `dom-if` element itself.
*
* @return {boolean} True when a template has been found, false otherwise
*/
__ensureTemplate() {
if (!this.__template) {
// When `removeNestedTemplates` is true, the "template" is the element
// itself, which has been given a `_templateInfo` property
const thisAsTemplate = /** @type {!HTMLTemplateElement} */ (
/** @type {!HTMLElement} */ (this));
let template = thisAsTemplate._templateInfo ?
thisAsTemplate :
/** @type {!HTMLTemplateElement} */
(wrap(thisAsTemplate).querySelector('template'));
if (!template) {
// Wait until childList changes and template should be there by then
let observer = new MutationObserver(() => {
if (wrap(this).querySelector('template')) {
observer.disconnect();
this.__render();
} else {
throw new Error('dom-if requires a child');
}
});
observer.observe(this, {childList: true});
return false;
}
this.__template = template;
}
return true;
}
/**
* Ensures a an instance of the template has been created and inserted. This
* method may return false if the template has not yet been found or if
* there is no `parentNode` to insert the template into (in either case,
* connection or the template-finding mutation observer firing will queue
* another render, causing this method to be called again at a more
* appropriate time).
*
* Subclasses should implement the following methods called here:
* - `__hasInstance`
* - `__createAndInsertInstance`
* - `__getInstanceNodes`
*
* @return {boolean} True if the instance was created, false otherwise.
*/
__ensureInstance() {
let parentNode = wrap(this).parentNode;
if (!this.__hasInstance()) {
// Guard against element being detached while render was queued
if (!parentNode) {
return false;
}
// Find the template (when false, there was no template yet)
if (!this.__ensureTemplate()) {
return false;
}
this.__createAndInsertInstance(parentNode);
} else {
// Move instance children if necessary
let children = this.__getInstanceNodes();
if (children && children.length) {
// Detect case where dom-if was re-attached in new position
let lastChild = wrap(this).previousSibling;
if (lastChild !== children[children.length-1]) {
for (let i=0, n; (i} Array of child nodes stamped from the template
* instance.
*/
__getInstanceNodes() { }
/**
* Abstract API to be implemented by subclass: Creates an instance of the
* template and inserts it into the given parent node.
*
* @protected
* @param {Node} parentNode The parent node to insert the instance into
* @return {void}
*/
__createAndInsertInstance(parentNode) { } // eslint-disable-line no-unused-vars
/**
* Abstract API to be implemented by subclass: Removes nodes created by an
* instance of a template and any associated cleanup.
*
* @protected
* @return {void}
*/
__teardownInstance() { }
/**
* Abstract API to be implemented by subclass: Shows or hides any template
* instance childNodes based on the `if` state of the element and its
* `__hideTemplateChildren__` property.
*
* @protected
* @return {void}
*/
_showHideChildren() { }
/* eslint-enable valid-jsdoc */
}
/**
* The version of DomIf used when `fastDomIf` setting is in use, which is
* optimized for first-render (but adds a tax to all subsequent property updates
* on the host, whether they were used in a given `dom-if` or not).
*
* This implementation avoids use of `Templatizer`, which introduces a new scope
* (a non-element PropertyEffects instance), which is not strictly necessary
* since `dom-if` never introduces new properties to its scope (unlike
* `dom-repeat`). Taking advantage of this fact, the `dom-if` reaches up to its
* `__dataHost` and stamps the template directly from the host using the host's
* runtime `_stampTemplate` API, which binds the property effects of the
* template directly to the host. This both avoids the intermediary
* `Templatizer` instance, but also avoids the need to bind host properties to
* the `` element and forward those into the template instance.
*
* In this version of `dom-if`, the `this.__instance` method is the
* `DocumentFragment` returned from `_stampTemplate`, which also serves as the
* handle for later removing it using the `_removeBoundDom` method.
*/
class DomIfFast extends DomIfBase {
constructor() {
super();
this.__instance = null;
this.__syncInfo = null;
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* @override
* @return {boolean} True when an instance has been created.
*/
__hasInstance() {
return Boolean(this.__instance);
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* @override
* @return {Array} Array of child nodes stamped from the template
* instance.
*/
__getInstanceNodes() {
return this.__instance.templateInfo.childNodes;
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* Stamps the template by calling `_stampTemplate` on the `__dataHost` of this
* element and then inserts the resulting nodes into the given `parentNode`.
*
* @override
* @param {Node} parentNode The parent node to insert the instance into
* @return {void}
*/
__createAndInsertInstance(parentNode) {
const host = this.__dataHost || this;
if (strictTemplatePolicy) {
if (!this.__dataHost) {
throw new Error('strictTemplatePolicy: template owner not trusted');
}
}
// Pre-bind and link the template into the effects system
const templateInfo = host._bindTemplate(
/** @type {!HTMLTemplateElement} */ (this.__template), true);
// Install runEffects hook that prevents running property effects
// (and any nested template effects) when the `if` is false
templateInfo.runEffects = (runEffects, changedProps, hasPaths) => {
let syncInfo = this.__syncInfo;
if (this.if) {
// Mix any props that changed while the `if` was false into `changedProps`
if (syncInfo) {
// If there were properties received while the `if` was false, it is
// important to sync the hidden state with the element _first_, so that
// new bindings to e.g. `textContent` do not get stomped on by
// pre-hidden values if `_showHideChildren` were to be called later at
// the next render. Clearing `__invalidProps` here ensures
// `_showHideChildren`'s call to `__syncHostProperties` no-ops, so
// that we don't call `runEffects` more often than necessary.
this.__syncInfo = null;
this._showHideChildren();
changedProps = Object.assign(syncInfo.changedProps, changedProps);
}
runEffects(changedProps, hasPaths);
} else {
// Accumulate any values changed while `if` was false, along with the
// runEffects method to sync them, so that we can replay them once `if`
// becomes true
if (this.__instance) {
if (!syncInfo) {
syncInfo = this.__syncInfo = { runEffects, changedProps: {} };
}
if (hasPaths) {
// Store root object of any paths; this will ensure direct bindings
// like [[obj.foo]] bindings run after a `set('obj.foo', v)`, but
// note that path notifications like `set('obj.foo.bar', v)` will
// not propagate. Since batched path notifications are not
// supported, we cannot simply accumulate path notifications. This
// is equivalent to the non-fastDomIf case, which stores root(p) in
// __invalidProps.
for (const p in changedProps) {
const rootProp = root(p);
syncInfo.changedProps[rootProp] = this.__dataHost[rootProp];
}
} else {
Object.assign(syncInfo.changedProps, changedProps);
}
}
}
};
// Stamp the template, and set its DocumentFragment to the "instance"
this.__instance = host._stampTemplate(
/** @type {!HTMLTemplateElement} */ (this.__template), templateInfo);
wrap(parentNode).insertBefore(this.__instance, this);
}
/**
* Run effects for any properties that changed while the `if` was false.
*
* @return {void}
*/
__syncHostProperties() {
const syncInfo = this.__syncInfo;
if (syncInfo) {
this.__syncInfo = null;
syncInfo.runEffects(syncInfo.changedProps, false);
}
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* Remove the instance and any nodes it created. Uses the `__dataHost`'s
* runtime `_removeBoundDom` method.
*
* @override
* @return {void}
*/
__teardownInstance() {
const host = this.__dataHost || this;
if (this.__instance) {
host._removeBoundDom(this.__instance);
this.__instance = null;
this.__syncInfo = null;
}
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* Shows or hides the template instance top level child nodes. For
* text nodes, `textContent` is removed while "hidden" and replaced when
* "shown."
*
* @override
* @return {void}
* @protected
* @suppress {visibility}
*/
_showHideChildren() {
const hidden = this.__hideTemplateChildren__ || !this.if;
if (this.__instance && Boolean(this.__instance.__hidden) !== hidden) {
this.__instance.__hidden = hidden;
showHideChildren(hidden, this.__instance.templateInfo.childNodes);
}
if (!hidden) {
this.__syncHostProperties();
}
}
}
/**
* The "legacy" implementation of `dom-if`, implemented using `Templatizer`.
*
* In this version, `this.__instance` is the `TemplateInstance` returned
* from the templatized constructor.
*/
class DomIfLegacy extends DomIfBase {
constructor() {
super();
this.__ctor = null;
this.__instance = null;
this.__invalidProps = null;
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* @override
* @return {boolean} True when an instance has been created.
*/
__hasInstance() {
return Boolean(this.__instance);
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* @override
* @return {Array} Array of child nodes stamped from the template
* instance.
*/
__getInstanceNodes() {
return this.__instance.children;
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* Stamps the template by creating a new instance of the templatized
* constructor (which is created lazily if it does not yet exist), and then
* inserts its resulting `root` doc fragment into the given `parentNode`.
*
* @override
* @param {Node} parentNode The parent node to insert the instance into
* @return {void}
*/
__createAndInsertInstance(parentNode) {
// Ensure we have an instance constructor
if (!this.__ctor) {
this.__ctor = templatize(
/** @type {!HTMLTemplateElement} */ (this.__template), this, {
// dom-if templatizer instances require `mutable: true`, as
// `__syncHostProperties` relies on that behavior to sync objects
mutableData: true,
/**
* @param {string} prop Property to forward
* @param {*} value Value of property
* @this {DomIfLegacy}
*/
forwardHostProp: function(prop, value) {
if (this.__instance) {
if (this.if) {
this.__instance.forwardHostProp(prop, value);
} else {
// If we have an instance but are squelching host property
// forwarding due to if being false, note the invalidated
// properties so `__syncHostProperties` can sync them the next
// time `if` becomes true
this.__invalidProps =
this.__invalidProps || Object.create(null);
this.__invalidProps[root(prop)] = true;
}
}
}
});
}
// Create and insert the instance
this.__instance = new this.__ctor();
wrap(parentNode).insertBefore(this.__instance.root, this);
}
/**
* Implementation of abstract API needed by DomIfBase.
*
* Removes the instance and any nodes it created.
*
* @override
* @return {void}
*/
__teardownInstance() {
if (this.__instance) {
let c$ = this.__instance.children;
if (c$ && c$.length) {
// use first child parent, for case when dom-if may have been detached
let parent = wrap(c$[0]).parentNode;
// Instance children may be disconnected from parents when dom-if
// detaches if a tree was innerHTML'ed
if (parent) {
parent = wrap(parent);
for (let i=0, n; (i` element will stamp a light-dom `` child when
* the `if` property becomes truthy, and the template can use Polymer
* data-binding and declarative event features when used in the context of
* a Polymer element's template.
*
* When `if` becomes falsy, the stamped content is hidden but not
* removed from dom. When `if` subsequently becomes truthy again, the content
* is simply re-shown. This approach is used due to its favorable performance
* characteristics: the expense of creating template content is paid only
* once and lazily.
*
* Set the `restamp` property to true to force the stamped content to be
* created / destroyed when the `if` condition changes.
*
* @customElement
* @polymer
* @extends DomIfBase
* @constructor
* @summary Custom element that conditionally stamps and hides or removes
* template content based on a boolean flag.
*/
const DomIf = fastDomIf ? DomIfFast : DomIfLegacy;
customElements.define(DomIf.is, DomIf);
/**
@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
*/
/**
* Element mixin for recording dynamic associations between item paths in a
* master `items` array and a `selected` array such that path changes to the
* master array (at the host) element or elsewhere via data-binding) are
* correctly propagated to items in the selected array and vice-versa.
*
* The `items` property accepts an array of user data, and via the
* `select(item)` and `deselect(item)` API, updates the `selected` property
* which may be bound to other parts of the application, and any changes to
* sub-fields of `selected` item(s) will be kept in sync with items in the
* `items` array. When `multi` is false, `selected` is a property
* representing the last selected item. When `multi` is true, `selected`
* is an array of multiply selected items.
*
* @polymer
* @mixinFunction
* @appliesMixin ElementMixin
* @summary Element mixin for recording dynamic associations between item paths in a
* master `items` array and a `selected` array
*/
let ArraySelectorMixin = dedupingMixin(superClass => {
/**
* @constructor
* @implements {Polymer_ElementMixin}
* @private
*/
let elementBase = ElementMixin(superClass);
/**
* @polymer
* @mixinClass
* @implements {Polymer_ArraySelectorMixin}
* @unrestricted
*/
class ArraySelectorMixin extends elementBase {
static get properties() {
return {
/**
* An array containing items from which selection will be made.
*/
items: {
type: Array,
},
/**
* When `true`, multiple items may be selected at once (in this case,
* `selected` is an array of currently selected items). When `false`,
* only one item may be selected at a time.
*/
multi: {
type: Boolean,
value: false,
},
/**
* When `multi` is true, this is an array that contains any selected.
* When `multi` is false, this is the currently selected item, or `null`
* if no item is selected.
* @type {?Object|?Array}
*/
selected: {type: Object, notify: true},
/**
* When `multi` is false, this is the currently selected item, or `null`
* if no item is selected.
* @type {?Object}
*/
selectedItem: {type: Object, notify: true},
/**
* When `true`, calling `select` on an item that is already selected
* will deselect the item.
*/
toggle: {type: Boolean, value: false}
};
}
static get observers() {
return ['__updateSelection(multi, items.*)'];
}
constructor() {
super();
this.__lastItems = null;
this.__lastMulti = null;
this.__selectedMap = null;
}
__updateSelection(multi, itemsInfo) {
let path = itemsInfo.path;
if (path == JSCompiler_renameProperty('items', this)) {
// Case 1 - items array changed, so diff against previous array and
// deselect any removed items and adjust selected indices
let newItems = itemsInfo.base || [];
let lastItems = this.__lastItems;
let lastMulti = this.__lastMulti;
if (multi !== lastMulti) {
this.clearSelection();
}
if (lastItems) {
let splices = calculateSplices(newItems, lastItems);
this.__applySplices(splices);
}
this.__lastItems = newItems;
this.__lastMulti = multi;
} else if (itemsInfo.path == `${JSCompiler_renameProperty('items', this)}.splices`) {
// Case 2 - got specific splice information describing the array mutation:
// deselect any removed items and adjust selected indices
this.__applySplices(itemsInfo.value.indexSplices);
} else {
// Case 3 - an array element was changed, so deselect the previous
// item for that index if it was previously selected
let part = path.slice(`${JSCompiler_renameProperty('items', this)}.`.length);
let idx = parseInt(part, 10);
if ((part.indexOf('.') < 0) && part == idx) {
this.__deselectChangedIdx(idx);
}
}
}
__applySplices(splices) {
let selected = this.__selectedMap;
// Adjust selected indices and mark removals
for (let i=0; i {
if (idx < s.index) ; else if (idx >= s.index + s.removed.length) {
// adjust index
selected.set(item, idx + s.addedCount - s.removed.length);
} else {
// remove index
selected.set(item, -1);
}
});
for (let j=0; j {
if (idx < 0) {
if (this.multi) {
this.splice(JSCompiler_renameProperty('selected', this), sidx, 1);
} else {
this.selected = this.selectedItem = null;
}
selected.delete(item);
} else {
sidx++;
}
});
}
__updateLinks() {
this.__dataLinkedPaths = {};
if (this.multi) {
let sidx = 0;
this.__selectedMap.forEach(idx => {
if (idx >= 0) {
this.linkPaths(
`${JSCompiler_renameProperty('items', this)}.${idx}`,
`${JSCompiler_renameProperty('selected', this)}.${sidx++}`);
}
});
} else {
this.__selectedMap.forEach(idx => {
this.linkPaths(
JSCompiler_renameProperty('selected', this),
`${JSCompiler_renameProperty('items', this)}.${idx}`);
this.linkPaths(
JSCompiler_renameProperty('selectedItem', this),
`${JSCompiler_renameProperty('items', this)}.${idx}`);
});
}
}
/**
* Clears the selection state.
* @override
* @return {void}
*/
clearSelection() {
// Unbind previous selection
this.__dataLinkedPaths = {};
// The selected map stores 3 pieces of information:
// key: items array object
// value: items array index
// order: selected array index
this.__selectedMap = new Map();
// Initialize selection
this.selected = this.multi ? [] : null;
this.selectedItem = null;
}
/**
* Returns whether the item is currently selected.
*
* @override
* @param {*} item Item from `items` array to test
* @return {boolean} Whether the item is selected
*/
isSelected(item) {
return this.__selectedMap.has(item);
}
/**
* Returns whether the item is currently selected.
*
* @override
* @param {number} idx Index from `items` array to test
* @return {boolean} Whether the item is selected
*/
isIndexSelected(idx) {
return this.isSelected(this.items[idx]);
}
__deselectChangedIdx(idx) {
let sidx = this.__selectedIndexForItemIndex(idx);
if (sidx >= 0) {
let i = 0;
this.__selectedMap.forEach((idx, item) => {
if (sidx == i++) {
this.deselect(item);
}
});
}
}
__selectedIndexForItemIndex(idx) {
let selected = this.__dataLinkedPaths[`${JSCompiler_renameProperty('items', this)}.${idx}`];
if (selected) {
return parseInt(selected.slice(`${JSCompiler_renameProperty('selected', this)}.`.length), 10);
}
}
/**
* Deselects the given item if it is already selected.
*
* @override
* @param {*} item Item from `items` array to deselect
* @return {void}
*/
deselect(item) {
let idx = this.__selectedMap.get(item);
if (idx >= 0) {
this.__selectedMap.delete(item);
let sidx;
if (this.multi) {
sidx = this.__selectedIndexForItemIndex(idx);
}
this.__updateLinks();
if (this.multi) {
this.splice(JSCompiler_renameProperty('selected', this), sidx, 1);
} else {
this.selected = this.selectedItem = null;
}
}
}
/**
* Deselects the given index if it is already selected.
*
* @override
* @param {number} idx Index from `items` array to deselect
* @return {void}
*/
deselectIndex(idx) {
this.deselect(this.items[idx]);
}
/**
* Selects the given item. When `toggle` is true, this will automatically
* deselect the item if already selected.
*
* @override
* @param {*} item Item from `items` array to select
* @return {void}
*/
select(item) {
this.selectIndex(this.items.indexOf(item));
}
/**
* Selects the given index. When `toggle` is true, this will automatically
* deselect the item if already selected.
*
* @override
* @param {number} idx Index from `items` array to select
* @return {void}
*/
selectIndex(idx) {
let item = this.items[idx];
if (!this.isSelected(item)) {
if (!this.multi) {
this.__selectedMap.clear();
}
this.__selectedMap.set(item, idx);
this.__updateLinks();
if (this.multi) {
this.push(JSCompiler_renameProperty('selected', this), item);
} else {
this.selected = this.selectedItem = item;
}
} else if (this.toggle) {
this.deselectIndex(idx);
}
}
}
return ArraySelectorMixin;
});
/**
* @constructor
* @extends {PolymerElement}
* @implements {Polymer_ArraySelectorMixin}
* @private
*/
let baseArraySelector = ArraySelectorMixin(PolymerElement);
/**
* Element implementing the `ArraySelector` mixin, which records
* dynamic associations between item paths in a master `items` array and a
* `selected` array such that path changes to the master array (at the host)
* element or elsewhere via data-binding) are correctly propagated to items
* in the selected array and vice-versa.
*
* The `items` property accepts an array of user data, and via the
* `select(item)` and `deselect(item)` API, updates the `selected` property
* which may be bound to other parts of the application, and any changes to
* sub-fields of `selected` item(s) will be kept in sync with items in the
* `items` array. When `multi` is false, `selected` is a property
* representing the last selected item. When `multi` is true, `selected`
* is an array of multiply selected items.
*
* Example:
*
* ```js
* import {PolymerElement} from '@polymer/polymer';
* import '@polymer/polymer/lib/elements/array-selector.js';
*
* class EmployeeList extends PolymerElement {
* static get _template() {
* return html`
*
Employee list:
*
*
*
First name: {{item.first}}
*
Last name: {{item.last}}
*
*
*
*
*
*
*
Selected employees:
*
*
*
First name: {{item.first}}
*
Last name: {{item.last}}
*
* `;
* }
* static get is() { return 'employee-list'; }
* static get properties() {
* return {
* employees: {
* value() {
* return [
* {first: 'Bob', last: 'Smith'},
* {first: 'Sally', last: 'Johnson'},
* ...
* ];
* }
* }
* };
* }
* toggleSelection(e) {
* const item = this.$.employeeList.itemForElement(e.target);
* this.$.selector.select(item);
* }
* }
* ```
*
* @polymer
* @customElement
* @extends {baseArraySelector}
* @appliesMixin ArraySelectorMixin
* @summary Custom element that links paths between an input `items` array and
* an output `selected` item or array based on calls to its selection API.
*/
class ArraySelector extends baseArraySelector {
// Not needed to find template; can be removed once the analyzer
// can find the tag name from customElements.define call
static get is() { return 'array-selector'; }
static get template() { return null; }
}
customElements.define(ArraySelector.is, ArraySelector);
/**
@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 customStyleInterface = new CustomStyleInterface$1();
if (!window.ShadyCSS) {
window.ShadyCSS = {
/**
* @param {!HTMLTemplateElement} template
* @param {string} elementName
* @param {string=} elementExtends
*/
prepareTemplate(template, elementName, elementExtends) {}, // eslint-disable-line @typescript-eslint/no-unused-vars
/**
* @param {!HTMLTemplateElement} template
* @param {string} elementName
*/
prepareTemplateDom(template, elementName) {}, // eslint-disable-line @typescript-eslint/no-unused-vars
/**
* @param {!HTMLTemplateElement} template
* @param {string} elementName
* @param {string=} elementExtends
*/
prepareTemplateStyles(template, elementName, elementExtends) {}, // eslint-disable-line @typescript-eslint/no-unused-vars
/**
* @param {Element} element
* @param {Object=} properties
*/
styleSubtree(element, properties) {
customStyleInterface.processStyles();
updateNativeProperties(element, properties);
},
/**
* @param {Element} element
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
styleElement(element) {
customStyleInterface.processStyles();
},
/**
* @param {Object=} properties
*/
styleDocument(properties) {
customStyleInterface.processStyles();
updateNativeProperties(document.body, properties);
},
/**
* @param {Element} element
* @param {string} property
* @return {string}
*/
getComputedStyleValue(element, property) {
return getComputedStyleValue(element, property);
},
flushCustomStyles() {},
nativeCss: nativeCssVariables,
nativeShadow: nativeShadow,
cssBuild: cssBuild,
disableRuntime: disableRuntime,
};
}
window.ShadyCSS.CustomStyleInterface = customStyleInterface;
/**
@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 attr = 'include';
const CustomStyleInterface = window.ShadyCSS.CustomStyleInterface;
/**
* Custom element for defining styles in the main document that can take
* advantage of [shady DOM](https://github.com/webcomponents/shadycss) shims
* for style encapsulation, custom properties, and custom mixins.
*
* - Document styles defined in a `` are shimmed to ensure they
* do not leak into local DOM when running on browsers without native
* Shadow DOM.
* - Custom properties can be defined in a ``. Use the `html` selector
* to define custom properties that apply to all custom elements.
* - Custom mixins can be defined in a ``, if you import the optional
* [apply shim](https://github.com/webcomponents/shadycss#about-applyshim)
* (`shadycss/apply-shim.html`).
*
* To use:
*
* - Import `custom-style.html`.
* - Place a `` element in the main document, wrapping an inline `
*
* ```
*
* @customElement
* @extends HTMLElement
* @summary Custom element for defining styles in the main document that can
* take advantage of Polymer's style scoping and custom properties shims.
*/
class CustomStyle extends HTMLElement {
constructor() {
super();
this._style = null;
CustomStyleInterface.addCustomStyle(this);
}
/**
* Returns the light-DOM `
horizontal layout center alignment
`;
document.body.appendChild(template.content);
```
2. [Custom CSS
mixins](https://github.com/PolymerElements/iron-flex-layout/blob/master/iron-flex-layout.html).
The mixin stylesheet includes custom CSS mixins that can be applied inside a CSS
rule using the `@apply` function.
Please note that the old [/deep/ layout
classes](https://github.com/PolymerElements/iron-flex-layout/tree/master/classes)
are deprecated, and should not be used. To continue using layout properties
directly in markup, please switch to using the new `dom-module`-based
[layout
classes](https://github.com/PolymerElements/iron-flex-layout/tree/master/iron-flex-layout-classes.html).
Please note that the new version does not use `/deep/`, and therefore requires
you to import the `dom-modules` in every element that needs to use them.
@group Iron Elements
@pseudoElement iron-flex-layout
@demo demo/index.html
*/
const template$5 = html`
`;
template$5.setAttribute('style', 'display: none;');
document.head.appendChild(template$5.content);
var style = document.createElement('style');
style.textContent = '[hidden] { display: none !important; }';
document.head.appendChild(style);
/**
@license
Copyright (c) 2015 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
*/
/**
`iron-a11y-announcer` is a singleton element that is intended to add a11y
to features that require on-demand announcement from screen readers. In
order to make use of the announcer, it is best to request its availability
in the announcing element.
Example:
Polymer({
is: 'x-chatty',
attached: function() {
// This will create the singleton element if it has not
// been created yet:
Polymer.IronA11yAnnouncer.requestAvailability();
}
});
After the `iron-a11y-announcer` has been made available, elements can
make announces by firing bubbling `iron-announce` events.
Example:
this.fire('iron-announce', {
text: 'This is an announcement!'
}, { bubbles: true });
Note: announcements are only audible if you have a screen reader enabled.
@demo demo/index.html
*/
const IronA11yAnnouncer = Polymer({
/** @override */
_template: html`
[[_text]]
`,
is: 'iron-a11y-announcer',
properties: {
/**
* The value of mode is used to set the `aria-live` attribute
* for the element that will be announced. Valid values are: `off`,
* `polite` and `assertive`.
*/
mode: {type: String, value: 'polite'},
/**
* The timeout on refreshing the announcement text. Larger timeouts are
* needed for certain screen readers to re-announce the same message.
*/
timeout: {type: Number, value: 150},
_text: {type: String, value: ''},
},
/** @override */
created: function() {
if (!IronA11yAnnouncer.instance) {
IronA11yAnnouncer.instance = this;
}
document.addEventListener('iron-announce', this._onIronAnnounce.bind(this));
},
/**
* Cause a text string to be announced by screen readers.
*
* @param {string} text The text that should be announced.
*/
announce: function(text) {
this._text = '';
this.async(function() {
this._text = text;
}, this.timeout);
},
_onIronAnnounce: function(event) {
if (event.detail && event.detail.text) {
this.announce(event.detail.text);
}
}
});
IronA11yAnnouncer.instance = null;
IronA11yAnnouncer.requestAvailability = function() {
if (!IronA11yAnnouncer.instance) {
IronA11yAnnouncer.instance = document.createElement('iron-a11y-announcer');
}
if (document.body) {
document.body.appendChild(IronA11yAnnouncer.instance);
} else {
document.addEventListener('load', function() {
document.body.appendChild(IronA11yAnnouncer.instance);
});
}
};
/**
@license
Copyright (c) 2015 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
*/
class IronMeta {
/**
* @param {{
* type: (string|null|undefined),
* key: (string|null|undefined),
* value: *,
* }=} options
*/
constructor(options) {
IronMeta[' '](options);
/** @type {string} */
this.type = (options && options.type) || 'default';
/** @type {string|null|undefined} */
this.key = options && options.key;
if (options && 'value' in options) {
/** @type {*} */
this.value = options.value;
}
}
/** @return {*} */
get value() {
var type = this.type;
var key = this.key;
if (type && key) {
return IronMeta.types[type] && IronMeta.types[type][key];
}
}
/** @param {*} value */
set value(value) {
var type = this.type;
var key = this.key;
if (type && key) {
type = IronMeta.types[type] = IronMeta.types[type] || {};
if (value == null) {
delete type[key];
} else {
type[key] = value;
}
}
}
/** @return {!Array<*>} */
get list() {
var type = this.type;
if (type) {
var items = IronMeta.types[this.type];
if (!items) {
return [];
}
return Object.keys(items).map(function(key) {
return metaDatas[this.type][key];
}, this);
}
}
/**
* @param {string} key
* @return {*}
*/
byKey(key) {
this.key = key;
return this.value;
}
}
// This function is used to convince Closure not to remove constructor calls
// for instances that are not held anywhere. For example, when
// `new IronMeta({...})` is used only for the side effect of adding a value.
IronMeta[' '] = function() {};
IronMeta.types = {};
var metaDatas = IronMeta.types;
/**
`iron-meta` is a generic element you can use for sharing information across the
DOM tree. It uses [monostate pattern](http://c2.com/cgi/wiki?MonostatePattern)
such that any instance of iron-meta has access to the shared information. You
can use `iron-meta` to share whatever you want (or create an extension [like
x-meta] for enhancements).
The `iron-meta` instances containing your actual data can be loaded in an
import, or constructed in any way you see fit. The only requirement is that you
create them before you try to access them.
Examples:
If I create an instance like this:
Note that value="foo/bar" is the metadata I've defined. I could define more
attributes or use child nodes to define additional metadata.
Now I can access that element (and it's metadata) from any iron-meta instance
via the byKey method, e.g.
meta.byKey('info');
Pure imperative form would be like:
document.createElement('iron-meta').byKey('info');
Or, in a Polymer element, you can include a meta in your template:
...
this.$.meta.byKey('info');
@group Iron Elements
@demo demo/index.html
@element iron-meta
*/
Polymer({
is: 'iron-meta',
properties: {
/**
* The type of meta-data. All meta-data of the same type is stored
* together.
* @type {string}
*/
type: {
type: String,
value: 'default',
},
/**
* The key used to store `value` under the `type` namespace.
* @type {?string}
*/
key: {
type: String,
},
/**
* The meta-data to store or retrieve.
* @type {*}
*/
value: {
type: String,
notify: true,
},
/**
* If true, `value` is set to the iron-meta instance itself.
*/
self: {type: Boolean, observer: '_selfChanged'},
__meta: {type: Boolean, computed: '__computeMeta(type, key, value)'}
},
hostAttributes: {hidden: true},
__computeMeta: function(type, key, value) {
var meta = new IronMeta({type: type, key: key});
if (value !== undefined && value !== meta.value) {
meta.value = value;
} else if (this.value !== meta.value) {
this.value = meta.value;
}
return meta;
},
get list() {
return this.__meta && this.__meta.list;
},
_selfChanged: function(self) {
if (self) {
this.value = this;
}
},
/**
* Retrieves meta data value by key.
*
* @method byKey
* @param {string} key The key of the meta-data to be returned.
* @return {*}
*/
byKey: function(key) {
return new IronMeta({type: this.type, key: key}).value;
}
});
/**
@license
Copyright (c) 2015 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
*/
/**
* Singleton IronMeta instance.
*/
let IronValidatableBehaviorMeta = null;
/**
* `Use IronValidatableBehavior` to implement an element that validates
* user input. Use the related `IronValidatorBehavior` to add custom
* validation logic to an iron-input.
*
* By default, an `` element validates its fields when the user
* presses the submit button. To validate a form imperatively, call the form's
* `validate()` method, which in turn will call `validate()` on all its
* children. By using `IronValidatableBehavior`, your custom element
* will get a public `validate()`, which will return the validity of the
* element, and a corresponding `invalid` attribute, which can be used for
* styling.
*
* To implement the custom validation logic of your element, you must override
* the protected `_getValidity()` method of this behaviour, rather than
* `validate()`. See
* [this](https://github.com/PolymerElements/iron-form/blob/master/demo/simple-element.html)
* for an example.
*
* ### Accessibility
*
* Changing the `invalid` property, either manually or by calling `validate()`
* will update the `aria-invalid` attribute.
*
* @demo demo/index.html
* @polymerBehavior
*/
const IronValidatableBehavior = {
properties: {
/**
* Name of the validator to use.
*/
validator: {type: String},
/**
* True if the last call to `validate` is invalid.
*/
invalid: {
notify: true,
reflectToAttribute: true,
type: Boolean,
value: false,
observer: '_invalidChanged'
},
},
registered: function() {
IronValidatableBehaviorMeta = new IronMeta({type: 'validator'});
},
_invalidChanged: function() {
if (this.invalid) {
this.setAttribute('aria-invalid', 'true');
} else {
this.removeAttribute('aria-invalid');
}
},
/* Recompute this every time it's needed, because we don't know if the
* underlying IronValidatableBehaviorMeta has changed. */
get _validator() {
return IronValidatableBehaviorMeta &&
IronValidatableBehaviorMeta.byKey(this.validator);
},
/**
* @return {boolean} True if the validator `validator` exists.
*/
hasValidator: function() {
return this._validator != null;
},
/**
* Returns true if the `value` is valid, and updates `invalid`. If you want
* your element to have custom validation logic, do not override this method;
* override `_getValidity(value)` instead.
* @param {Object} value Deprecated: The value to be validated. By default,
* it is passed to the validator's `validate()` function, if a validator is
set.
* If this argument is not specified, then the element's `value` property
* is used, if it exists.
* @return {boolean} True if `value` is valid.
*/
validate: function(value) {
// If this is an element that also has a value property, and there was
// no explicit value argument passed, use the element's property instead.
if (value === undefined && this.value !== undefined)
this.invalid = !this._getValidity(this.value);
else
this.invalid = !this._getValidity(value);
return !this.invalid;
},
/**
* Returns true if `value` is valid. By default, it is passed
* to the validator's `validate()` function, if a validator is set. You
* should override this method if you want to implement custom validity
* logic for your element.
*
* @param {Object} value The value to be validated.
* @return {boolean} True if `value` is valid.
*/
_getValidity: function(value) {
if (this.hasValidator()) {
return this._validator.validate(value);
}
return true;
}
};
/**
@license
Copyright (c) 2015 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
*/
/**
`` is a wrapper to a native `` element, that adds two-way
binding and prevention of invalid input. To use it, you must distribute a native
`` yourself. You can continue to use the native `input` as you would
normally:
### Two-way binding
By default you can only get notified of changes to a native ``'s `value`
due to user input:
This means that if you imperatively set the value (i.e. `someNativeInput.value =
'foo'`), no events will be fired and this change cannot be observed.
`iron-input` adds the `bind-value` property that mirrors the native `input`'s
'`value` property; this property can be used for two-way data binding.
`bind-value` will notify if it is changed either by user input or by script.
Note: this means that if you want to imperatively set the native `input`'s, you
_must_ set `bind-value` instead, so that the wrapper `iron-input` can be
notified.
### Validation
`iron-input` uses the native `input`'s validation. For simplicity, `iron-input`
has a `validate()` method (which internally just checks the distributed
`input`'s validity), which sets an `invalid` attribute that can also be used for
styling.
To validate automatically as you type, you can use the `auto-validate`
attribute.
`iron-input` also fires an `iron-input-validate` event after `validate()` is
called. You can use it to implement a custom validator:
var CatsOnlyValidator = {
validate: function(ironInput) {
var valid = !ironInput.bindValue || ironInput.bindValue === 'cat';
ironInput.invalid = !valid;
return valid;
}
}
ironInput.addEventListener('iron-input-validate', function() {
CatsOnly.validate(input2);
});
You can also use an element implementing an
[`IronValidatorBehavior`](/element/PolymerElements/iron-validatable-behavior).
This example can also be found in the demo for this element:
### Preventing invalid input
It may be desirable to only allow users to enter certain characters. You can use
the `allowed-pattern` attribute to accomplish this. This feature is separate
from validation, and `allowed-pattern` does not affect how the input is
validated.
// Only allow typing digits, but a valid input has exactly 5 digits.
@demo demo/index.html
*/
Polymer({
_template: html`
`,
is: 'iron-input',
behaviors: [IronValidatableBehavior],
/**
* Fired whenever `validate()` is called.
*
* @event iron-input-validate
*/
properties: {
/**
* Use this property instead of `value` for two-way data binding, or to
* set a default value for the input. **Do not** use the distributed
* input's `value` property to set a default value.
*/
bindValue: {type: String, value: ''},
/**
* Computed property that echoes `bindValue` (mostly used for Polymer 1.0
* backcompatibility, if you were one-way binding to the Polymer 1.0
* `input is="iron-input"` value attribute).
*/
value: {type: String, computed: '_computeValue(bindValue)'},
/**
* Regex-like list of characters allowed as input; all characters not in the
* list will be rejected. The recommended format should be a list of allowed
* characters, for example, `[a-zA-Z0-9.+-!;:]`.
*
* This pattern represents the allowed characters for the field; as the user
* inputs text, each individual character will be checked against the
* pattern (rather than checking the entire value as a whole). If a
* character is not a match, it will be rejected.
*
* Pasted input will have each character checked individually; if any
* character doesn't match `allowedPattern`, the entire pasted string will
* be rejected.
*
* Note: if you were using `iron-input` in 1.0, you were also required to
* set `prevent-invalid-input`. This is no longer needed as of Polymer 2.0,
* and will be set automatically for you if an `allowedPattern` is provided.
*
*/
allowedPattern: {type: String},
/**
* Set to true to auto-validate the input value as you type.
*/
autoValidate: {type: Boolean, value: false},
/**
* The native input element.
*/
_inputElement: Object,
},
observers: ['_bindValueChanged(bindValue, _inputElement)'],
listeners: {'input': '_onInput', 'keypress': '_onKeypress'},
created: function() {
IronA11yAnnouncer.requestAvailability();
this._previousValidInput = '';
this._patternAlreadyChecked = false;
},
attached: function() {
// If the input is added at a later time, update the internal reference.
this._observer = dom(this).observeNodes(function(info) {
this._initSlottedInput();
}.bind(this));
},
detached: function() {
if (this._observer) {
dom(this).unobserveNodes(this._observer);
this._observer = null;
}
},
/**
* Returns the distributed input element.
*/
get inputElement() {
return this._inputElement;
},
_initSlottedInput: function() {
this._inputElement = this.getEffectiveChildren()[0];
if (this.inputElement && this.inputElement.value) {
this.bindValue = this.inputElement.value;
}
this.fire('iron-input-ready');
},
get _patternRegExp() {
var pattern;
if (this.allowedPattern) {
pattern = new RegExp(this.allowedPattern);
} else {
switch (this.inputElement.type) {
case 'number':
pattern = /[0-9.,e-]/;
break;
}
}
return pattern;
},
/**
* @suppress {checkTypes}
*/
_bindValueChanged: function(bindValue, inputElement) {
// The observer could have run before attached() when we have actually
// initialized this property.
if (!inputElement) {
return;
}
if (bindValue === undefined) {
inputElement.value = null;
} else if (bindValue !== inputElement.value) {
this.inputElement.value = bindValue;
}
if (this.autoValidate) {
this.validate();
}
// manually notify because we don't want to notify until after setting value
this.fire('bind-value-changed', {value: bindValue});
},
_onInput: function() {
// Need to validate each of the characters pasted if they haven't
// been validated inside `_onKeypress` already.
if (this.allowedPattern && !this._patternAlreadyChecked) {
var valid = this._checkPatternValidity();
if (!valid) {
this._announceInvalidCharacter(
'Invalid string of characters not entered.');
this.inputElement.value = this._previousValidInput;
}
}
this.bindValue = this._previousValidInput = this.inputElement.value;
this._patternAlreadyChecked = false;
},
_isPrintable: function(event) {
// What a control/printable character is varies wildly based on the browser.
// - most control characters (arrows, backspace) do not send a `keypress`
// event
// in Chrome, but the *do* on Firefox
// - in Firefox, when they do send a `keypress` event, control chars have
// a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
// - printable characters always send a keypress event.
// - in Firefox, printable chars always have a keyCode = 0. In Chrome, the
// keyCode
// always matches the charCode.
// None of this makes any sense.
// For these keys, ASCII code == browser keycode.
var anyNonPrintable = (event.keyCode == 8) || // backspace
(event.keyCode == 9) || // tab
(event.keyCode == 13) || // enter
(event.keyCode == 27); // escape
// For these keys, make sure it's a browser keycode and not an ASCII code.
var mozNonPrintable = (event.keyCode == 19) || // pause
(event.keyCode == 20) || // caps lock
(event.keyCode == 45) || // insert
(event.keyCode == 46) || // delete
(event.keyCode == 144) || // num lock
(event.keyCode == 145) || // scroll lock
(event.keyCode > 32 &&
event.keyCode < 41) || // page up/down, end, home, arrows
(event.keyCode > 111 && event.keyCode < 124); // fn keys
return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
},
_onKeypress: function(event) {
if (!this.allowedPattern && this.inputElement.type !== 'number') {
return;
}
var regexp = this._patternRegExp;
if (!regexp) {
return;
}
// Handle special keys and backspace
if (event.metaKey || event.ctrlKey || event.altKey) {
return;
}
// Check the pattern either here or in `_onInput`, but not in both.
this._patternAlreadyChecked = true;
var thisChar = String.fromCharCode(event.charCode);
if (this._isPrintable(event) && !regexp.test(thisChar)) {
event.preventDefault();
this._announceInvalidCharacter(
'Invalid character ' + thisChar + ' not entered.');
}
},
_checkPatternValidity: function() {
var regexp = this._patternRegExp;
if (!regexp) {
return true;
}
for (var i = 0; i < this.inputElement.value.length; i++) {
if (!regexp.test(this.inputElement.value[i])) {
return false;
}
}
return true;
},
/**
* Returns true if `value` is valid. The validator provided in `validator`
* will be used first, then any constraints.
* @return {boolean} True if the value is valid.
*/
validate: function() {
if (!this.inputElement) {
this.invalid = false;
return true;
}
// Use the nested input's native validity.
var valid = this.inputElement.checkValidity();
// Only do extra checking if the browser thought this was valid.
if (valid) {
// Empty, required input is invalid
if (this.required && this.bindValue === '') {
valid = false;
} else if (this.hasValidator()) {
valid = IronValidatableBehavior.validate.call(this, this.bindValue);
}
}
this.invalid = !valid;
this.fire('iron-input-validate');
return valid;
},
_announceInvalidCharacter: function(message) {
this.fire('iron-announce', {text: message});
},
_computeValue: function(bindValue) {
return bindValue;
}
});
/**
@license
Copyright (c) 2015 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
*/
// Give the user the choice to opt out of font loading.
if (!window.polymerSkipLoadingFontRoboto) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.crossOrigin = 'anonymous';
link.href =
'https://fonts.googleapis.com/css?family=Roboto+Mono:400,700|Roboto:400,300,300italic,400italic,500,500italic,700,700italic';
document.head.appendChild(link);
}
/**
@license
Copyright (c) 2015 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
*/
/*
Typographic styles are provided matching the Material Design standard styles:
http://www.google.com/design/spec/style/typography.html#typography-standard-styles
Note that these are English/Latin centric styles. You may need to further adjust
line heights and weights for CJK typesetting. See the notes in the Material
Design typography section.
*/
const template$4 = html``;
template$4.setAttribute('style', 'display: none;');
document.head.appendChild(template$4.content);
/**
@license
Copyright (c) 2015 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
*/
/**
* Use `Polymer.PaperInputAddonBehavior` to implement an add-on for
* ``. A add-on appears below the input, and may display
* information based on the input value and validity such as a character counter
* or an error message.
* @polymerBehavior
*/
const PaperInputAddonBehavior = {
attached: function() {
this.fire('addon-attached');
},
/**
* The function called by `` when the input value or
* validity changes.
* @param {{
* invalid: boolean,
* inputElement: (Element|undefined),
* value: (string|undefined)
* }} state -
* inputElement: The input element.
* value: The input value.
* invalid: True if the input value is invalid.
*/
update: function(state) {}
};
/**
@license
Copyright (c) 2015 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
*/
/*
`` is a character counter for use with
``. It shows the number of characters entered in the
input and the max length if it is specified.
### Styling
The following mixin is available for styling:
Custom property | Description | Default
----------------|-------------|----------
`--paper-input-char-counter` | Mixin applied to the element | `{}`
*/
Polymer({
/** @override */
_template: html`
[[_charCounterStr]]
`,
is: 'paper-input-char-counter',
behaviors: [PaperInputAddonBehavior],
properties: {_charCounterStr: {type: String, value: '0'}},
/**
* This overrides the update function in PaperInputAddonBehavior.
* @param {{
* inputElement: (Element|undefined),
* value: (string|undefined),
* invalid: boolean
* }} state -
* inputElement: The input element.
* value: The input value.
* invalid: True if the input value is invalid.
*/
update: function(state) {
if (!state.inputElement) {
return;
}
state.value = state.value || '';
var counter = state.value.toString().length.toString();
if (state.inputElement.hasAttribute('maxlength')) {
counter += '/' + state.inputElement.getAttribute('maxlength');
}
this._charCounterStr = counter;
}
});
/**
@license
Copyright (c) 2015 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 template$3 = html`
`;
template$3.setAttribute('style', 'display: none;');
document.head.appendChild(template$3.content);
/**
@license
Copyright (c) 2015 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
*/
/* Taken from
* https://www.google.com/design/spec/style/color.html#color-ui-color-application
*/
const template$2 = html`
`;
template$2.setAttribute('style', 'display: none;');
document.head.appendChild(template$2.content);
/**
@license
Copyright (c) 2015 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 template$1 = html`
`;
template$1.setAttribute('style', 'display: none;');
document.head.appendChild(template$1.content);
/*
`` is a container for a `