@polymer/polymer
Advanced tools
Comparing version 3.3.1 to 3.4.0
@@ -33,3 +33,2 @@ /** | ||
* @param {string} property Name of the property | ||
* @return {void} | ||
*/ | ||
@@ -83,2 +82,7 @@ Polymer_PropertiesChanged.prototype._addPropertyToAttributeMap = function(property){}; | ||
/** | ||
* @param {string} property Name of the property | ||
* @return {boolean} | ||
*/ | ||
Polymer_PropertiesChanged.prototype._isPropertyPending = function(property){}; | ||
/** | ||
* @override | ||
@@ -214,2 +218,8 @@ * @return {void} | ||
* @override | ||
* @param {string} prop Property name | ||
* @return {boolean} | ||
*/ | ||
Polymer_PropertyAccessors.prototype._isPropertyPending = function(prop){}; | ||
/** | ||
* @override | ||
* @param {*} value Property value to serialize. | ||
@@ -247,8 +257,2 @@ * @return {(string | undefined)} | ||
/** | ||
* @override | ||
* @param {string} prop Property name | ||
* @return {boolean} | ||
*/ | ||
Polymer_PropertyAccessors.prototype._isPropertyPending = function(prop){}; | ||
/** | ||
* @param {string} property Property to convert | ||
@@ -269,5 +273,8 @@ * @return {string} | ||
* @param {!HTMLTemplateElement} template Template to stamp | ||
* @param {TemplateInfo=} templateInfo Optional template info associated | ||
with the template to be stamped; if omitted the template will be | ||
automatically parsed. | ||
* @return {!StampedTemplate} | ||
*/ | ||
Polymer_TemplateStamp.prototype._stampTemplate = function(template){}; | ||
Polymer_TemplateStamp.prototype._stampTemplate = function(template, templateInfo){}; | ||
/** | ||
@@ -403,2 +410,5 @@ * @override | ||
/** @type {Object} */ | ||
Polymer_PropertyEffects.prototype.__computeInfo; | ||
/** @type {Object} */ | ||
Polymer_PropertyEffects.prototype.__reflectEffects; | ||
@@ -421,3 +431,6 @@ | ||
/** @type {!Object.<string, string>} */ | ||
/** @type {boolean} */ | ||
Polymer_PropertyEffects.prototype._overrideLegacyUndefined; | ||
/** @type {undefined} */ | ||
Polymer_PropertyEffects.prototype.PROPERTY_EFFECT_TYPES; | ||
@@ -428,5 +441,8 @@ | ||
* @param {!HTMLTemplateElement} template Template to stamp | ||
* @param {TemplateInfo=} templateInfo Optional bound template info associated | ||
with the template to be stamped; if omitted the template will be | ||
automatically bound. | ||
* @return {!StampedTemplate} | ||
*/ | ||
Polymer_PropertyEffects.prototype._stampTemplate = function(template){}; | ||
Polymer_PropertyEffects.prototype._stampTemplate = function(template, templateInfo){}; | ||
/** | ||
@@ -471,7 +487,2 @@ * @override | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_PropertyEffects.prototype._flushProperties = function(){}; | ||
/** | ||
* @override | ||
* @param {!Object} currentProps Bag of all current accessor values | ||
@@ -492,2 +503,6 @@ * @param {?Object} changedProps Bag of properties changed since the last | ||
/** | ||
* @return {void} | ||
*/ | ||
Polymer_PropertyEffects.prototype._registerHost = function(){}; | ||
/** | ||
* @override | ||
@@ -596,2 +611,10 @@ * @param {string} property Property that should trigger the effect | ||
/** | ||
* @param {*} templateInfo | ||
* @param {*} changedProps | ||
* @param {*} oldProps | ||
* @param {*} hasPaths | ||
* @return {void} | ||
*/ | ||
Polymer_PropertyEffects.prototype._runEffectsForTemplate = function(templateInfo, changedProps, oldProps, hasPaths){}; | ||
/** | ||
* @override | ||
@@ -674,3 +697,3 @@ * @param {(string | !Array.<(string | number)>)} to Target path to link. | ||
* @param {...*} items Items to insert into array. | ||
* @return {Array} | ||
* @return {!Array} | ||
*/ | ||
@@ -748,8 +771,8 @@ Polymer_PropertyEffects.prototype.splice = function(path, start, deleteCount, items){}; | ||
* @param {!HTMLTemplateElement} template Template containing binding | ||
bindings | ||
bindings | ||
* @param {boolean=} instanceBinding When false (default), performs | ||
"prototypical" binding of the template and overwrites any previously | ||
bound template for the class. When true (as passed from | ||
`_stampTemplate`), the template info is instanced and linked into | ||
the list of bound templates. | ||
"prototypical" binding of the template and overwrites any previously | ||
bound template for the class. When true (as passed from | ||
`_stampTemplate`), the template info is instanced and linked into the | ||
list of bound templates. | ||
* @return {!TemplateInfo} | ||
@@ -943,2 +966,8 @@ */ | ||
* @override | ||
* @param {string} property Name of the property | ||
* @return {boolean} | ||
*/ | ||
Polymer_ElementMixin.prototype._canApplyPropertyDefault = function(property){}; | ||
/** | ||
* @override | ||
* @param {StampedTemplate} dom to attach to the element. | ||
@@ -1067,3 +1096,45 @@ * @return {ShadowRoot} | ||
* @extends {Polymer_ElementMixin} | ||
*/ | ||
function Polymer_DisableUpgradeMixin(){} | ||
/** @type {(boolean | undefined)} */ | ||
Polymer_DisableUpgradeMixin.prototype.__isUpgradeDisabled; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype._initializeProperties = function(){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype._enableProperties = function(){}; | ||
/** | ||
* @override | ||
* @param {string} name Attribute name. | ||
* @param {?string} old The previous value for the attribute. | ||
* @param {?string} value The new value for the attribute. | ||
* @param {?string} namespace The XML namespace for the attribute. | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype.attributeChangedCallback = function(name, old, value, namespace){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype.connectedCallback = function(){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype.disconnectedCallback = function(){}; | ||
/** | ||
* @override | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype._canApplyPropertyDefault = function(property){}; | ||
/** | ||
* @interface | ||
* @extends {Polymer_ElementMixin} | ||
* @extends {Polymer_GestureEventListeners} | ||
* @extends {Polymer_DirMixin} | ||
*/ | ||
@@ -1080,2 +1151,11 @@ function Polymer_LegacyElementMixin(){} | ||
/** @type {(boolean | undefined)} */ | ||
Polymer_LegacyElementMixin.prototype.__isUpgradeDisabled; | ||
/** @type {(boolean | undefined)} */ | ||
Polymer_LegacyElementMixin.prototype.__needsAttributesAtConnected; | ||
/** @type {(boolean | undefined)} */ | ||
Polymer_LegacyElementMixin.prototype._legacyForceObservedAttributes; | ||
/** @type {?Node} */ | ||
@@ -1099,2 +1179,7 @@ Polymer_LegacyElementMixin.prototype.domHost; | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_LegacyElementMixin.prototype._enableProperties = function(){}; | ||
/** | ||
* @override | ||
* @param {string} name Name of attribute. | ||
@@ -1119,2 +1204,6 @@ * @param {?string} old Old value of attribute. | ||
* @override | ||
*/ | ||
Polymer_LegacyElementMixin.prototype._canApplyPropertyDefault = function(property){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
@@ -1127,2 +1216,12 @@ */ | ||
*/ | ||
Polymer_LegacyElementMixin.prototype.setAttribute = function(name, value){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_LegacyElementMixin.prototype.removeAttribute = function(name){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_LegacyElementMixin.prototype.attached = function(){}; | ||
@@ -1143,2 +1242,6 @@ /** | ||
/** | ||
* @return {void} | ||
*/ | ||
Polymer_LegacyElementMixin.prototype._takeAttributes = function(){}; | ||
/** | ||
* @override | ||
@@ -1469,3 +1572,3 @@ * @return {void} | ||
* @param {...*} args Array of strings or objects to log | ||
* @return {Array} | ||
* @return {!Array} | ||
*/ | ||
@@ -1572,37 +1675,3 @@ Polymer_LegacyElementMixin.prototype._logf = function(methodName, args){}; | ||
* @interface | ||
* @extends {Polymer_ElementMixin} | ||
*/ | ||
function Polymer_DisableUpgradeMixin(){} | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype._initializeProperties = function(){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype._enableProperties = function(){}; | ||
/** | ||
* @override | ||
* @param {string} name Attribute name. | ||
* @param {?string} old The previous value for the attribute. | ||
* @param {?string} value The new value for the attribute. | ||
* @param {?string} namespace The XML namespace for the attribute. | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype.attributeChangedCallback = function(name, old, value, namespace){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype.connectedCallback = function(){}; | ||
/** | ||
* @override | ||
* @return {void} | ||
*/ | ||
Polymer_DisableUpgradeMixin.prototype.disconnectedCallback = function(){}; | ||
/** | ||
* @interface | ||
*/ | ||
function Polymer_LegacyDataMixin(){} | ||
@@ -1609,0 +1678,0 @@ /** |
@@ -17,3 +17,3 @@ /** | ||
*/ | ||
let PolymerDomApi = function() {}; | ||
var PolymerDomApi = function() {}; | ||
@@ -20,0 +20,0 @@ /** |
@@ -138,2 +138,26 @@ /** | ||
/** @type {boolean} */ | ||
Polymer.legacyUndefined; | ||
/** @type {boolean} */ | ||
Polymer.legacyNoBatch; | ||
/** @type {boolean} */ | ||
Polymer.legacyWarnings; | ||
/** @type {boolean} */ | ||
Polymer.legacyNotifyOrder; | ||
/** @type {boolean} */ | ||
Polymer.orderedComputed; | ||
/** @type {boolean} */ | ||
Polymer.fastDomIf; | ||
/** @type {boolean} */ | ||
Polymer.removeNestedTemplates; | ||
/** @type {boolean} */ | ||
Polymer.suppressTemplateNotifications; | ||
// nb. This is explicitly 'var', as Closure Compiler checks that this is the case. | ||
@@ -140,0 +164,0 @@ /** |
@@ -64,5 +64,9 @@ /** | ||
/** @type {TemplateInfo | undefined} */ | ||
TemplateInfo.prototype.nextTemplateInfo; | ||
TemplateInfo.prototype.nextSibling; | ||
/** @type {TemplateInfo | undefined} */ | ||
TemplateInfo.prototype.previousTemplateInfo; | ||
TemplateInfo.prototype.previousSibling; | ||
/** @type {TemplateInfo | undefined} */ | ||
TemplateInfo.prototype.firstChild; | ||
/** @type {TemplateInfo | undefined} */ | ||
TemplateInfo.prototype.parent; | ||
/** @type {!Array<!Node>} */ | ||
@@ -69,0 +73,0 @@ TemplateInfo.prototype.childNodes; |
/** | ||
* @fileoverview Externs for webcomponents polyfills | ||
* @externs | ||
* @suppress {duplicate} | ||
* | ||
@@ -15,39 +16,46 @@ * @license | ||
var HTMLImports = { | ||
/** | ||
* @param {function()} callback | ||
*/ | ||
whenReady(callback) {}, | ||
/** | ||
* @param {!Node} element | ||
* @return {?HTMLLinkElement|?Document|undefined} | ||
*/ | ||
importForElement(element) {} | ||
}; | ||
var HTMLImports = {}; | ||
/** | ||
* @param {function()=} callback | ||
*/ | ||
HTMLImports.whenReady = function(callback) {}; | ||
/** | ||
* Returns the import document containing the element. | ||
* @param {!Node} element | ||
* @return {?HTMLLinkElement|?Document|undefined} | ||
*/ | ||
HTMLImports.importForElement = function(element) {}; | ||
window.HTMLImports = HTMLImports; | ||
var ShadyDOM = { | ||
inUse: false, | ||
flush() {}, | ||
/** | ||
* @param {!Node} target | ||
* @param {function(Array<MutationRecord>, MutationObserver)} callback | ||
* @return {MutationObserver} | ||
*/ | ||
observeChildren(target, callback) {}, | ||
/** | ||
* @param {MutationObserver} observer | ||
*/ | ||
unobserveChildren(observer) {}, | ||
/** | ||
* @param {Node} node | ||
*/ | ||
patch(node) {}, | ||
/** | ||
* @param {!ShadowRoot} shadowroot | ||
*/ | ||
flushInitial(shadowroot) {} | ||
}; | ||
var ShadyDOM = {}; | ||
ShadyDOM.inUse; | ||
ShadyDOM.flush = function() {}; | ||
/** | ||
* @param {!Node} target | ||
* @param {function(Array<MutationRecord>, MutationObserver)} callback | ||
* @return {MutationObserver} | ||
*/ | ||
ShadyDOM.observeChildren = function(target, callback) {}; | ||
/** | ||
* @param {MutationObserver} observer | ||
*/ | ||
ShadyDOM.unobserveChildren = function(observer) {}; | ||
/** | ||
* @param {Node} node | ||
*/ | ||
ShadyDOM.patch = function(node) {}; | ||
/** | ||
* @param {!ShadowRoot} shadowroot | ||
*/ | ||
ShadyDOM.flushInitial = function(shadowroot) {}; | ||
window.ShadyDOM = ShadyDOM; | ||
@@ -70,1 +78,6 @@ | ||
CustomElementRegistry.prototype.polyfillWrapFlushCallback = function(cb){}; | ||
/** | ||
* @param {string} cssText | ||
*/ | ||
CSSStyleSheet.prototype.replaceSync = function(cssText) {}; |
@@ -119,4 +119,2 @@ /** | ||
propertyEffects: Object; | ||
nextTemplateInfo?: TemplateInfo; | ||
previousTemplateInfo?: TemplateInfo; | ||
childNodes: Node[]; | ||
@@ -123,0 +121,0 @@ wasPreBound: boolean; |
@@ -16,4 +16,2 @@ /** | ||
import {templatize} from '../utils/templatize.js'; | ||
import {Debouncer} from '../utils/debounce.js'; | ||
@@ -29,20 +27,6 @@ | ||
export {DomIf}; | ||
import {showHideChildren, templatize} from '../utils/templatize.js'; | ||
/** | ||
* The `<dom-if>` element will stamp a light-dom `<template>` 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. | ||
*/ | ||
declare class DomIf extends PolymerElement { | ||
declare class DomIfBase extends PolymerElement { | ||
_templateInfo: TemplateInfo|undefined; | ||
@@ -62,2 +46,9 @@ /** | ||
restamp: boolean|null|undefined; | ||
/** | ||
* When the global `suppressTemplateNotifications` setting is used, setting | ||
* `notifyDomChange: true` will enable firing `dom-change` events on this | ||
* element. | ||
*/ | ||
notifyDomChange: boolean|null|undefined; | ||
connectedCallback(): void; | ||
@@ -76,6 +67,37 @@ disconnectedCallback(): void; | ||
/** | ||
* Shows or hides the template instance top level child elements. For | ||
* text nodes, `textContent` is removed while "hidden" and replaced when | ||
* "shown." | ||
* Abstract API to be implemented by subclass: Returns true if a template | ||
* instance has been created and inserted. | ||
* | ||
* @returns True when an instance has been created. | ||
*/ | ||
__hasInstance(): boolean; | ||
/** | ||
* Abstract API to be implemented by subclass: Returns the child nodes stamped | ||
* from a template instance. | ||
* | ||
* @returns Array of child nodes stamped from the template | ||
* instance. | ||
*/ | ||
__getInstanceNodes(): Array<Node|null>|null; | ||
/** | ||
* Abstract API to be implemented by subclass: Creates an instance of the | ||
* template and inserts it into the given parent node. | ||
* | ||
* @param parentNode The parent node to insert the instance into | ||
*/ | ||
__createAndInsertInstance(parentNode: Node|null): void; | ||
/** | ||
* Abstract API to be implemented by subclass: Removes nodes created by an | ||
* instance of a template and any associated cleanup. | ||
*/ | ||
__teardownInstance(): void; | ||
/** | ||
* 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. | ||
*/ | ||
_showHideChildren(): void; | ||
@@ -87,4 +109,77 @@ } | ||
interface HTMLElementTagNameMap { | ||
"dom-if": DomIf; | ||
"dom-if": DomIfBase; | ||
} | ||
} | ||
/** | ||
* 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 `<template>` 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. | ||
*/ | ||
declare class DomIfFast extends DomIfBase { | ||
constructor(); | ||
/** | ||
* 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." | ||
*/ | ||
_showHideChildren(): void; | ||
} | ||
/** | ||
* The "legacy" implementation of `dom-if`, implemented using `Templatizer`. | ||
* | ||
* In this version, `this.__instance` is the `TemplateInstance` returned | ||
* from the templatized constructor. | ||
*/ | ||
declare class DomIfLegacy extends DomIfBase { | ||
constructor(); | ||
/** | ||
* Implementation of abstract API needed by DomIfBase. | ||
* | ||
* Shows or hides the template instance top level child elements. For | ||
* text nodes, `textContent` is removed while "hidden" and replaced when | ||
* "shown." | ||
*/ | ||
_showHideChildren(): void; | ||
} | ||
export {DomIf}; | ||
/** | ||
* The `<dom-if>` element will stamp a light-dom `<template>` 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. | ||
*/ | ||
declare class DomIf extends DomIfBase { | ||
} | ||
import {TemplateInfo} from '../../interfaces'; |
@@ -12,3 +12,2 @@ /** | ||
import { templatize } from '../utils/templatize.js'; | ||
import { Debouncer } from '../utils/debounce.js'; | ||
@@ -20,25 +19,13 @@ import { enqueueDebouncer, flush } from '../utils/flush.js'; | ||
import { hideElementsGlobally } from '../utils/hide-template-controls.js'; | ||
import { fastDomIf, strictTemplatePolicy, suppressTemplateNotifications } from '../utils/settings.js'; | ||
import { showHideChildren, templatize } from '../utils/templatize.js'; | ||
/** | ||
* The `<dom-if>` element will stamp a light-dom `<template>` 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 PolymerElement | ||
* @summary Custom element that conditionally stamps and hides or removes | ||
* template content based on a boolean flag. | ||
* @summary Base class for dom-if element; subclassed into concrete | ||
* implementation. | ||
*/ | ||
export class DomIf extends PolymerElement { | ||
class DomIfBase extends PolymerElement { | ||
@@ -81,4 +68,12 @@ // Not needed to find template; can be removed once the analyzer | ||
observer: '__debounceRender' | ||
}, | ||
/** | ||
* When the global `suppressTemplateNotifications` setting is used, setting | ||
* `notifyDomChange: true` will enable firing `dom-change` events on this | ||
* element. | ||
*/ | ||
notifyDomChange: { | ||
type: Boolean | ||
} | ||
}; | ||
@@ -91,7 +86,8 @@ | ||
this.__renderDebouncer = null; | ||
this.__invalidProps = null; | ||
this.__instance = null; | ||
this._lastIf = false; | ||
this.__ctor = null; | ||
this.__hideTemplateChildren__ = false; | ||
/** @type {!HTMLTemplateElement|undefined} */ | ||
this.__template; | ||
/** @type {!TemplateInfo|undefined} */ | ||
this._templateInfo; | ||
} | ||
@@ -151,2 +147,83 @@ | ||
/** | ||
* 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 <template> 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<children.length) && (n=children[i]); i++) { | ||
wrap(parentNode).insertBefore(n, this); | ||
} | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
* Forces the element to render its content. Normally rendering is | ||
@@ -157,2 +234,3 @@ * asynchronous to a provoking change. This is done for efficiency so | ||
* validate application state. | ||
* | ||
* @return {void} | ||
@@ -164,2 +242,11 @@ */ | ||
/** | ||
* Performs the key rendering steps: | ||
* 1. Ensure a template instance has been stamped (when true) | ||
* 2. Remove the template instance (when false and restamp:true) | ||
* 3. Sync the hidden state of the instance nodes with the if/restamp state | ||
* 4. Fires the `dom-change` event when necessary | ||
* | ||
* @return {void} | ||
*/ | ||
__render() { | ||
@@ -171,10 +258,8 @@ if (this.if) { | ||
} | ||
this._showHideChildren(); | ||
} else if (this.restamp) { | ||
this.__teardownInstance(); | ||
} | ||
if (!this.restamp && this.__instance) { | ||
this._showHideChildren(); | ||
} | ||
if (this.if != this._lastIf) { | ||
this._showHideChildren(); | ||
if ((!suppressTemplateNotifications || this.notifyDomChange) | ||
&& this.if != this._lastIf) { | ||
this.dispatchEvent(new CustomEvent('dom-change', { | ||
@@ -188,79 +273,321 @@ bubbles: true, | ||
__ensureInstance() { | ||
let parentNode = wrap(this).parentNode; | ||
// Guard against element being detached while render was queued | ||
if (parentNode) { | ||
if (!this.__ctor) { | ||
let template = /** @type {HTMLTemplateElement} */(wrap(this).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 <template> child'); | ||
} | ||
}); | ||
observer.observe(this, {childList: true}); | ||
return false; | ||
// Ideally these would be annotated as abstract methods in an abstract class, | ||
// but closure compiler is finnicky | ||
/* eslint-disable valid-jsdoc */ | ||
/** | ||
* Abstract API to be implemented by subclass: Returns true if a template | ||
* instance has been created and inserted. | ||
* | ||
* @protected | ||
* @return {boolean} True when an instance has been created. | ||
*/ | ||
__hasInstance() { } | ||
/** | ||
* Abstract API to be implemented by subclass: Returns the child nodes stamped | ||
* from a template instance. | ||
* | ||
* @protected | ||
* @return {Array<Node>} 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 `<template>` 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<Node>} 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); | ||
} | ||
this.__ctor = templatize(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 {DomIf} | ||
*/ | ||
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; | ||
} | ||
} | ||
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 (!this.__instance) { | ||
this.__instance = new this.__ctor(); | ||
wrap(parentNode).insertBefore(this.__instance.root, this); | ||
} else { | ||
this.__syncHostProperties(); | ||
let c$ = this.__instance.children; | ||
if (c$ && c$.length) { | ||
// Detect case where dom-if was re-attached in new position | ||
let lastChild = wrap(this).previousSibling; | ||
if (lastChild !== c$[c$.length-1]) { | ||
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) { | ||
wrap(parentNode).insertBefore(n, this); | ||
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); | ||
} | ||
} | ||
} | ||
} | ||
return true; | ||
}; | ||
// 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() { | ||
let props = this.__invalidProps; | ||
if (props) { | ||
for (let prop in props) { | ||
this.__instance._setPendingProperty(prop, this.__dataHost[prop]); | ||
} | ||
this.__invalidProps = null; | ||
this.__instance._flushProperties(); | ||
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<Node>} 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; | ||
@@ -279,4 +606,21 @@ if (c$ && c$.length) { | ||
} | ||
this.__invalidProps = null; | ||
this.__instance = null; | ||
} | ||
} | ||
/** | ||
* Forwards any properties that changed while the `if` was false into the | ||
* template instance and flushes it. | ||
* | ||
* @return {void} | ||
*/ | ||
__syncHostProperties() { | ||
let props = this.__invalidProps; | ||
if (props) { | ||
this.__invalidProps = null; | ||
for (let prop in props) { | ||
this.__instance._setPendingProperty(prop, this.__dataHost[prop]); | ||
} | ||
this.__instance._flushProperties(); | ||
} | ||
@@ -286,18 +630,49 @@ } | ||
/** | ||
* Implementation of abstract API needed by DomIfBase. | ||
* | ||
* Shows or hides the template instance top level child elements. For | ||
* text nodes, `textContent` is removed while "hidden" and replaced when | ||
* "shown." | ||
* | ||
* @override | ||
* @protected | ||
* @return {void} | ||
* @protected | ||
* @suppress {visibility} | ||
*/ | ||
_showHideChildren() { | ||
let hidden = this.__hideTemplateChildren__ || !this.if; | ||
if (this.__instance) { | ||
const hidden = this.__hideTemplateChildren__ || !this.if; | ||
if (this.__instance && Boolean(this.__instance.__hidden) !== hidden) { | ||
this.__instance.__hidden = hidden; | ||
this.__instance._showHideChildren(hidden); | ||
} | ||
if (!hidden) { | ||
this.__syncHostProperties(); | ||
} | ||
} | ||
} | ||
/** | ||
* The `<dom-if>` element will stamp a light-dom `<template>` 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. | ||
*/ | ||
export const DomIf = fastDomIf ? DomIfFast : DomIfLegacy; | ||
customElements.define(DomIf.is, DomIf); |
@@ -131,2 +131,3 @@ /** | ||
PolymerElement) { | ||
_templateInfo: TemplateInfo|null; | ||
@@ -205,7 +206,8 @@ /** | ||
/** | ||
* 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 will be created and rendered | ||
* incrementally at each animation frame therof until all instances have | ||
* been rendered. | ||
* 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. | ||
*/ | ||
@@ -228,2 +230,26 @@ initialCount: number|null|undefined; | ||
readonly _targetFrameTime: number|null|undefined; | ||
/** | ||
* When the global `suppressTemplateNotifications` setting is used, setting | ||
* `notifyDomChange: true` will enable firing `dom-change` events on this | ||
* element. | ||
*/ | ||
notifyDomChange: boolean|null|undefined; | ||
/** | ||
* 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: boolean|null|undefined; | ||
connectedCallback(): void; | ||
@@ -301,1 +327,3 @@ disconnectedCallback(): void; | ||
} | ||
import {TemplateInfo} from '../../interfaces'; |
@@ -20,2 +20,3 @@ /** | ||
import { hideElementsGlobally } from '../utils/hide-template-controls.js'; | ||
import { suppressTemplateNotifications } from '../utils/settings.js'; | ||
@@ -243,3 +244,3 @@ /** | ||
type: Number, | ||
notify: true, | ||
notify: !suppressTemplateNotifications, | ||
readOnly: true | ||
@@ -249,11 +250,11 @@ }, | ||
/** | ||
* 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 will be created and rendered | ||
* incrementally at each animation frame therof until all instances have | ||
* been rendered. | ||
* 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, | ||
observer: '__initializeChunking' | ||
type: Number | ||
}, | ||
@@ -281,2 +282,30 @@ | ||
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 | ||
} | ||
@@ -295,8 +324,10 @@ | ||
this.__instances = []; | ||
this.__limit = Infinity; | ||
this.__pool = []; | ||
this.__renderDebouncer = null; | ||
this.__itemsIdxToInstIdx = {}; | ||
this.__chunkCount = null; | ||
this.__lastChunkTime = null; | ||
this.__renderStartTime = null; | ||
this.__itemsArrayChanged = false; | ||
this.__shouldMeasureChunk = false; | ||
this.__shouldContinueChunking = false; | ||
this.__chunkingId = 0; | ||
this.__sortFn = null; | ||
@@ -309,2 +340,4 @@ this.__filterFn = null; | ||
this.template = null; | ||
/** @type {TemplateInfo} */ | ||
this._templateInfo; | ||
} | ||
@@ -348,5 +381,11 @@ | ||
if (!this.__ctor) { | ||
let template = this.template = /** @type {HTMLTemplateElement} */(this.querySelector('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 = this.template = thisAsTemplate._templateInfo ? | ||
thisAsTemplate : | ||
/** @type {!HTMLTemplateElement} */ (this.querySelector('template')); | ||
if (!template) { | ||
// // Wait until childList changes and template should be there by then | ||
// Wait until childList changes and template should be there by then | ||
let observer = new MutationObserver(() => { | ||
@@ -437,34 +476,2 @@ if (this.querySelector('template')) { | ||
__initializeChunking() { | ||
if (this.initialCount) { | ||
this.__limit = this.initialCount; | ||
this.__chunkCount = this.initialCount; | ||
this.__lastChunkTime = performance.now(); | ||
} | ||
} | ||
__tryRenderChunk() { | ||
// Debounced so that multiple calls through `_render` between animation | ||
// frames only queue one new rAF (e.g. array mutation & chunked render) | ||
if (this.items && this.__limit < this.items.length) { | ||
this.__debounceRender(this.__requestRenderChunk); | ||
} | ||
} | ||
__requestRenderChunk() { | ||
requestAnimationFrame(()=>this.__renderChunk()); | ||
} | ||
__renderChunk() { | ||
// 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 | ||
let currChunkTime = performance.now(); | ||
let ratio = this._targetFrameTime / (currChunkTime - this.__lastChunkTime); | ||
this.__chunkCount = Math.round(this.__chunkCount * ratio) || 1; | ||
this.__limit += this.__chunkCount; | ||
this.__lastChunkTime = currChunkTime; | ||
this.__debounceRender(this.__render); | ||
} | ||
__observeChanged() { | ||
@@ -475,16 +482,2 @@ this.__observePaths = this.observe && | ||
__itemsChanged(change) { | ||
if (this.items && !Array.isArray(this.items)) { | ||
console.warn('dom-repeat expected array for `items`, found', this.items); | ||
} | ||
// If path was to an item (e.g. 'items.3' or 'items.3.foo'), forward the | ||
// path to that instance synchronously (returns false for non-item paths) | ||
if (!this.__handleItemPath(change.path, change.value)) { | ||
// Otherwise, the array was reset ('items') or spliced ('items.splices'), | ||
// so queue a full refresh | ||
this.__initializeChunking(); | ||
this.__debounceRender(this.__render); | ||
} | ||
} | ||
__handleObservedPaths(path) { | ||
@@ -508,2 +501,19 @@ // Handle cases where path changes should cause a re-sort/filter | ||
__itemsChanged(change) { | ||
if (this.items && !Array.isArray(this.items)) { | ||
console.warn('dom-repeat expected array for `items`, found', this.items); | ||
} | ||
// If path was to an item (e.g. 'items.3' or 'items.3.foo'), forward the | ||
// path to that instance synchronously (returns false for non-item paths) | ||
if (!this.__handleItemPath(change.path, change.value)) { | ||
// Otherwise, the array was reset ('items') or spliced ('items.splices'), | ||
// so queue a render. Restart chunking when the items changed (for | ||
// backward compatibility), unless `reuseChunkedInstances` option is set | ||
if (change.path === 'items') { | ||
this.__itemsArrayChanged = true; | ||
} | ||
this.__debounceRender(this.__render); | ||
} | ||
} | ||
/** | ||
@@ -540,22 +550,32 @@ * @param {function(this:DomRepeat)} fn Function to debounce. | ||
} | ||
this.__applyFullRefresh(); | ||
// Reset the pool | ||
// TODO(kschaaf): Reuse pool across turns and nested templates | ||
// Now that objects/arrays are re-evaluated when set, we can safely | ||
// reuse pooled instances across turns, however we still need to decide | ||
// semantics regarding how long to hold, how many to hold, etc. | ||
this.__pool.length = 0; | ||
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.__continueChunking()); | ||
} | ||
// Set rendered item count | ||
this._setRenderedItemCount(this.__instances.length); | ||
// Notify users | ||
this.dispatchEvent(new CustomEvent('dom-change', { | ||
bubbles: true, | ||
composed: true | ||
})); | ||
// Check to see if we need to render more items | ||
this.__tryRenderChunk(); | ||
if (!suppressTemplateNotifications || this.notifyDomChange) { | ||
this.dispatchEvent(new CustomEvent('dom-change', { | ||
bubbles: true, | ||
composed: true | ||
})); | ||
} | ||
} | ||
__applyFullRefresh() { | ||
let items = this.items || []; | ||
__sortAndFilterItems(items) { | ||
// Generate array maping the instance index to the items array index | ||
let isntIdxToItemsIdx = new Array(items.length); | ||
@@ -574,8 +594,65 @@ for (let i=0; i<items.length; i++) { | ||
} | ||
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 reseting 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 = 0; | ||
let instIdx; | ||
// Generate instances and assign items | ||
const limit = Math.min(isntIdxToItemsIdx.length, this.__limit); | ||
for (; instIdx<limit; instIdx++) { | ||
for (instIdx=0; instIdx<limit; instIdx++) { | ||
let inst = this.__instances[instIdx]; | ||
@@ -617,6 +694,3 @@ let itemIdx = isntIdxToItemsIdx[instIdx]; | ||
__detachAndRemoveInstance(idx) { | ||
let inst = this.__detachInstance(idx); | ||
if (inst) { | ||
this.__pool.push(inst); | ||
} | ||
this.__detachInstance(idx); | ||
this.__instances.splice(idx, 1); | ||
@@ -634,13 +708,3 @@ } | ||
__insertInstance(item, instIdx, itemIdx) { | ||
let inst = this.__pool.pop(); | ||
if (inst) { | ||
// TODO(kschaaf): If the pool is shared across turns, hostProps | ||
// need to be re-set to reused instances in addition to item | ||
inst._setPendingProperty(this.as, item); | ||
inst._setPendingProperty(this.indexAs, instIdx); | ||
inst._setPendingProperty(this.itemsIndexAs, itemIdx); | ||
inst._flushProperties(); | ||
} else { | ||
inst = this.__stampInstance(item, instIdx, itemIdx); | ||
} | ||
const inst = this.__stampInstance(item, instIdx, itemIdx); | ||
let beforeRow = this.__instances[instIdx + 1]; | ||
@@ -647,0 +711,0 @@ let beforeNode = beforeRow ? beforeRow.children[0] : this; |
@@ -178,2 +178,4 @@ /** | ||
const LegacyElement = LegacyElementMixin(HTMLElement); | ||
/* Note about construction and extension of legacy classes. | ||
@@ -534,4 +536,4 @@ [Changed in Q4 2018 to optimize performance.] | ||
} | ||
let klass = mixin ? mixin(LegacyElementMixin(HTMLElement)) : | ||
LegacyElementMixin(HTMLElement); | ||
let klass = mixin ? mixin(LegacyElement) : | ||
LegacyElement; | ||
klass = GenerateClassFromInfo(info, klass, info.behaviors); | ||
@@ -538,0 +540,0 @@ // decorate klass with registration info |
@@ -35,2 +35,6 @@ /** | ||
import {findObservedAttributesGetter} from '../mixins/disable-upgrade-mixin.js'; | ||
import {register} from '../utils/telemetry.js'; | ||
export {LegacyElementMixin}; | ||
@@ -45,3 +49,3 @@ | ||
*/ | ||
declare function LegacyElementMixin<T extends new (...args: any[]) => {}>(base: T): T & LegacyElementMixinConstructor & ElementMixinConstructor & PropertyEffectsConstructor & TemplateStampConstructor & PropertyAccessorsConstructor & PropertiesChangedConstructor & PropertiesMixinConstructor & GestureEventListenersConstructor; | ||
declare function LegacyElementMixin<T extends new (...args: any[]) => {}>(base: T): T & LegacyElementMixinConstructor & ElementMixinConstructor & PropertyEffectsConstructor & TemplateStampConstructor & PropertyAccessorsConstructor & PropertiesChangedConstructor & PropertiesMixinConstructor & GestureEventListenersConstructor & DirMixinConstructor; | ||
@@ -62,2 +66,4 @@ import {ElementMixinConstructor} from '../mixins/element-mixin.js'; | ||
import {DirMixinConstructor} from '../mixins/dir-mixin.js'; | ||
interface LegacyElementMixinConstructor { | ||
@@ -69,5 +75,6 @@ new(...args: any[]): LegacyElementMixin; | ||
interface LegacyElementMixin extends ElementMixin, PropertyEffects, TemplateStamp, PropertyAccessors, PropertiesChanged, PropertiesMixin, GestureEventListeners { | ||
interface LegacyElementMixin extends ElementMixin, PropertyEffects, TemplateStamp, PropertyAccessors, PropertiesChanged, PropertiesMixin, GestureEventListeners, DirMixin { | ||
isAttached: boolean; | ||
_debouncers: {[key: string]: Function|null}|null; | ||
_legacyForceObservedAttributes: boolean|undefined; | ||
@@ -94,2 +101,3 @@ /** | ||
_initializeProperties(): void; | ||
_enableProperties(): void; | ||
@@ -118,2 +126,3 @@ /** | ||
disconnectedCallback(): void; | ||
_canApplyPropertyDefault(property: any): any; | ||
@@ -125,2 +134,4 @@ /** | ||
created(): void; | ||
setAttribute(name: any, value: any): void; | ||
removeAttribute(name: any): void; | ||
@@ -148,2 +159,3 @@ /** | ||
attributeChanged(name: string, old: string|null, value: string|null): void; | ||
_takeAttributes(): void; | ||
@@ -658,3 +670,3 @@ /** | ||
*/ | ||
_logf(methodName: string, ...args: any[]): any[]|null; | ||
_logf(methodName: string, ...args: any[]): any[]; | ||
} |
@@ -11,3 +11,3 @@ /** | ||
import '@webcomponents/shadycss/entrypoints/apply-shim.js'; | ||
import { ElementMixin } from '../mixins/element-mixin.js'; | ||
import { ElementMixin, builtCSS } from '../mixins/element-mixin.js'; | ||
import { GestureEventListeners } from '../mixins/gesture-event-listeners.js'; | ||
@@ -25,3 +25,8 @@ import { DirMixin } from '../mixins/dir-mixin.js'; | ||
import { scopeSubtree } from '../utils/scope-subtree.js'; | ||
import { legacyOptimizations, legacyNoObservedAttributes } from '../utils/settings.js'; | ||
import { findObservedAttributesGetter } from '../mixins/disable-upgrade-mixin.js'; | ||
import { register } from '../utils/telemetry.js'; | ||
const DISABLED_ATTR = 'disable-upgrade'; | ||
let styleInterface = window.ShadyCSS; | ||
@@ -39,2 +44,3 @@ | ||
* @appliesMixin GestureEventListeners | ||
* @appliesMixin DirMixin | ||
* @property isAttached {boolean} Set to `true` in this element's | ||
@@ -45,2 +51,5 @@ * `connectedCallback` and `false` in `disconnectedCallback` | ||
export const LegacyElementMixin = dedupingMixin((base) => { | ||
// TODO(kschaaf): Note, the `@implements {Polymer_DirMixin}` is required here | ||
// (rather than on legacyElementBase) for unknown reasons. | ||
/** | ||
@@ -54,5 +63,18 @@ * @constructor | ||
*/ | ||
const legacyElementBase = DirMixin(GestureEventListeners(ElementMixin(base))); | ||
const GesturesElement = GestureEventListeners(ElementMixin(base)); | ||
// Note, the DirMixin does nothing if css is built so avoid including it | ||
// in that case. | ||
/** | ||
* @constructor | ||
* @extends {GesturesElement} | ||
* @private | ||
*/ | ||
const legacyElementBase = builtCSS ? GesturesElement : | ||
DirMixin(GesturesElement); | ||
const observedAttributesGetter = findObservedAttributesGetter(legacyElementBase); | ||
/** | ||
* Map of simple names to touch action names | ||
@@ -85,2 +107,9 @@ * @dict | ||
this._debouncers; | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
/** @type {boolean|undefined} */ | ||
this.__isUpgradeDisabled; | ||
/** @type {boolean|undefined} */ | ||
this.__needsAttributesAtConnected; | ||
/** @type {boolean|undefined} */ | ||
this._legacyForceObservedAttributes; | ||
} | ||
@@ -110,2 +139,75 @@ | ||
/** | ||
* Processes an attribute reaction when the `legacyNoObservedAttributes` | ||
* setting is in use. | ||
* @param {string} name Name of attribute that changed | ||
* @param {?string} old Old attribute value | ||
* @param {?string} value New attribute value | ||
* @return {void} | ||
*/ | ||
__attributeReaction(name, old, value) { | ||
if ((this.__dataAttributes && this.__dataAttributes[name]) || name === DISABLED_ATTR) { | ||
this.attributeChangedCallback(name, old, value, null); | ||
} | ||
} | ||
/** @override */ | ||
setAttribute(name, value) { | ||
if (legacyNoObservedAttributes && !this._legacyForceObservedAttributes) { | ||
const oldValue = this.getAttribute(name); | ||
super.setAttribute(name, value); | ||
// value coerced to String for closure's benefit | ||
this.__attributeReaction(name, oldValue, String(value)); | ||
} else { | ||
super.setAttribute(name, value); | ||
} | ||
} | ||
/** @override */ | ||
removeAttribute(name) { | ||
if (legacyNoObservedAttributes && !this._legacyForceObservedAttributes) { | ||
const oldValue = this.getAttribute(name); | ||
super.removeAttribute(name); | ||
this.__attributeReaction(name, oldValue, null); | ||
} else { | ||
super.removeAttribute(name); | ||
} | ||
} | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
static get observedAttributes() { | ||
if (legacyNoObservedAttributes && !this.prototype._legacyForceObservedAttributes) { | ||
// Ensure this element is property registered with the telemetry system. | ||
if (!this.hasOwnProperty(JSCompiler_renameProperty('__observedAttributes', this))) { | ||
this.__observedAttributes = []; | ||
register(this.prototype); | ||
} | ||
return this.__observedAttributes; | ||
} else { | ||
return observedAttributesGetter.call(this).concat(DISABLED_ATTR); | ||
} | ||
} | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
// Prevent element from enabling properties when it's upgrade disabled. | ||
// Normally overriding connectedCallback would be enough, but dom-* elements | ||
/** @override */ | ||
_enableProperties() { | ||
if (!this.__isUpgradeDisabled) { | ||
super._enableProperties(); | ||
} | ||
} | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
// If the element starts upgrade-disabled and a property is set for | ||
// which an accessor exists, the default should not be applied. | ||
// This additional check is needed because defaults are applied via | ||
// `_initializeProperties` which is called after initial properties | ||
// have been set when the element starts upgrade-disabled. | ||
/** @override */ | ||
_canApplyPropertyDefault(property) { | ||
return super._canApplyPropertyDefault(property) && | ||
!(this.__isUpgradeDisabled && this._isPropertyPending(property)); | ||
} | ||
/** | ||
* Provides an implementation of `connectedCallback` | ||
@@ -117,5 +219,11 @@ * which adds Polymer legacy API's `attached` method. | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
this.isAttached = true; | ||
this.attached(); | ||
if (this.__needsAttributesAtConnected) { | ||
this._takeAttributes(); | ||
} | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
if (!this.__isUpgradeDisabled) { | ||
super.connectedCallback(); | ||
this.isAttached = true; | ||
this.attached(); | ||
} | ||
} | ||
@@ -138,5 +246,8 @@ | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
this.isAttached = false; | ||
this.detached(); | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
if (!this.__isUpgradeDisabled) { | ||
super.disconnectedCallback(); | ||
this.isAttached = false; | ||
this.detached(); | ||
} | ||
} | ||
@@ -164,4 +275,17 @@ | ||
if (old !== value) { | ||
super.attributeChangedCallback(name, old, value, namespace); | ||
this.attributeChanged(name, old, value); | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
if (name == DISABLED_ATTR) { | ||
// When disable-upgrade is removed, intialize properties and | ||
// provoke connectedCallback if the element is already connected. | ||
if (this.__isUpgradeDisabled && value == null) { | ||
this._initializeProperties(); | ||
this.__isUpgradeDisabled = false; | ||
if (wrap(this).isConnected) { | ||
this.connectedCallback(); | ||
} | ||
} | ||
} else { | ||
super.attributeChangedCallback(name, old, value, namespace); | ||
this.attributeChanged(name, old, value); | ||
} | ||
} | ||
@@ -191,18 +315,41 @@ } | ||
_initializeProperties() { | ||
let proto = Object.getPrototypeOf(this); | ||
if (!proto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', proto))) { | ||
this._registered(); | ||
// backstop in case the `_registered` implementation does not set this | ||
proto.__hasRegisterFinished = true; | ||
// NOTE: Inlined for perf from version of DisableUpgradeMixin. | ||
// Only auto-use disable-upgrade if legacyOptimizations is set. | ||
if (legacyOptimizations && this.hasAttribute(DISABLED_ATTR)) { | ||
this.__isUpgradeDisabled = true; | ||
} else { | ||
let proto = Object.getPrototypeOf(this); | ||
if (!proto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', proto))) { | ||
this._registered(); | ||
// backstop in case the `_registered` implementation does not set this | ||
proto.__hasRegisterFinished = true; | ||
} | ||
super._initializeProperties(); | ||
this.root = /** @type {HTMLElement} */(this); | ||
this.created(); | ||
// Pull all attribute values 1x if `legacyNoObservedAttributes` is set. | ||
if (legacyNoObservedAttributes && !this._legacyForceObservedAttributes) { | ||
if (this.hasAttributes()) { | ||
this._takeAttributes(); | ||
// Element created from scratch or parser generated | ||
} else if (!this.parentNode) { | ||
this.__needsAttributesAtConnected = true; | ||
} | ||
} | ||
// Ensure listeners are applied immediately so that they are | ||
// added before declarative event listeners. This allows an element to | ||
// decorate itself via an event prior to any declarative listeners | ||
// seeing the event. Note, this ensures compatibility with 1.x ordering. | ||
this._applyListeners(); | ||
} | ||
super._initializeProperties(); | ||
this.root = /** @type {HTMLElement} */(this); | ||
this.created(); | ||
// Ensure listeners are applied immediately so that they are | ||
// added before declarative event listeners. This allows an element to | ||
// decorate itself via an event prior to any declarative listeners | ||
// seeing the event. Note, this ensures compatibility with 1.x ordering. | ||
this._applyListeners(); | ||
} | ||
_takeAttributes() { | ||
const a = this.attributes; | ||
for (let i=0, l=a.length; i < l; i++) { | ||
const attr = a[i]; | ||
this.__attributeReaction(attr.name, null, attr.value); | ||
} | ||
} | ||
/** | ||
@@ -1065,3 +1212,3 @@ * Called automatically when an element is initializing. | ||
* @param {...*} args Array of strings or objects to log | ||
* @return {Array} Array with formatting information for `console` | ||
* @return {!Array} Array with formatting information for `console` | ||
* logging. | ||
@@ -1068,0 +1215,0 @@ * @override |
@@ -42,2 +42,6 @@ /** | ||
} | ||
// Copy opt out for `legacyNoObservedAttributes` from info object to class. | ||
if (info._legacyForceObservedAttributes) { | ||
klass.prototype._legacyForceObservedAttributes = info._legacyForceObservedAttributes; | ||
} | ||
customElements.define(klass.is, /** @type {!HTMLElement} */(klass)); | ||
@@ -44,0 +48,0 @@ return klass; |
@@ -106,3 +106,3 @@ /** | ||
*/ | ||
getOwnerRoot(): Node|null; | ||
getOwnerRoot(): Node; | ||
@@ -109,0 +109,0 @@ /** |
@@ -116,3 +116,3 @@ /** | ||
* | ||
* @return {Node} Top most element in the dom tree in which the node | ||
* @return {!Node} Top most element in the dom tree in which the node | ||
* exists. If the node is connected to a document this is either a | ||
@@ -119,0 +119,0 @@ * shadowRoot or the document; otherwise, it may be the node |
@@ -19,2 +19,6 @@ /** | ||
export {findObservedAttributesGetter}; | ||
declare function findObservedAttributesGetter(): any; | ||
export {DisableUpgradeMixin}; | ||
@@ -76,2 +80,3 @@ | ||
disconnectedCallback(): void; | ||
_canApplyPropertyDefault(property: any): any; | ||
} |
@@ -13,7 +13,18 @@ /** | ||
import { ElementMixin } from './element-mixin.js'; | ||
import { dedupingMixin } from '../utils/mixin.js'; | ||
import { wrap } from '../utils/wrap.js'; | ||
const DISABLED_ATTR = 'disable-upgrade'; | ||
export const findObservedAttributesGetter = (ctor) => { | ||
while (ctor) { | ||
const desc = Object.getOwnPropertyDescriptor(ctor, 'observedAttributes'); | ||
if (desc) { | ||
return desc.get; | ||
} | ||
ctor = Object.getPrototypeOf(ctor.prototype).constructor; | ||
} | ||
return () => []; | ||
}; | ||
/** | ||
@@ -55,2 +66,9 @@ * Element class mixin that allows the element to boot up in a non-enabled | ||
// Work around for closure bug #126934458. Using `super` in a property | ||
// getter does not work so instead we search the Base prototype for an | ||
// implementation of observedAttributes so that we can override and call | ||
// the `super` getter. Note, this is done one time ever because we assume | ||
// that `Base` is always comes from `Polymer.LegacyElementMixn`. | ||
let observedAttributesGetter = findObservedAttributesGetter(superClass); | ||
/** | ||
@@ -63,9 +81,42 @@ * @polymer | ||
/** | ||
* @suppress {missingProperties} go/missingfnprops | ||
*/ | ||
constructor() { | ||
super(); | ||
/** @type {boolean|undefined} */ | ||
this.__isUpgradeDisabled; | ||
} | ||
static get observedAttributes() { | ||
return super.observedAttributes.concat(DISABLED_ATTR); | ||
return observedAttributesGetter.call(this).concat(DISABLED_ATTR); | ||
} | ||
// Prevent element from initializing properties when it's upgrade disabled. | ||
/** @override */ | ||
_initializeProperties() { | ||
if (this.hasAttribute(DISABLED_ATTR)) { | ||
this.__isUpgradeDisabled = true; | ||
} else { | ||
super._initializeProperties(); | ||
} | ||
} | ||
// Prevent element from enabling properties when it's upgrade disabled. | ||
// Normally overriding connectedCallback would be enough, but dom-* elements | ||
/** @override */ | ||
_enableProperties() { | ||
if (!this.__isUpgradeDisabled) { | ||
super._enableProperties(); | ||
} | ||
} | ||
// If the element starts upgrade-disabled and a property is set for | ||
// which an accessor exists, the default should not be applied. | ||
// This additional check is needed because defaults are applied via | ||
// `_initializeProperties` which is called after initial properties | ||
// have been set when the element starts upgrade-disabled. | ||
/** @override */ | ||
_canApplyPropertyDefault(property) { | ||
return super._canApplyPropertyDefault(property) && | ||
!(this.__isUpgradeDisabled && this._isPropertyPending(property)); | ||
} | ||
/** | ||
@@ -81,4 +132,10 @@ * @override | ||
if (name == DISABLED_ATTR) { | ||
if (!this.__dataEnabled && value == null && this.isConnected) { | ||
super.connectedCallback(); | ||
// When disable-upgrade is removed, intialize properties and | ||
// provoke connectedCallback if the element is already connected. | ||
if (this.__isUpgradeDisabled && value == null) { | ||
super._initializeProperties(); | ||
this.__isUpgradeDisabled = false; | ||
if (wrap(this).isConnected) { | ||
super.connectedCallback(); | ||
} | ||
} | ||
@@ -91,14 +148,7 @@ } else { | ||
/* | ||
NOTE: cannot gate on attribute because this is called before | ||
attributes are delivered. Therefore, we stub this out and | ||
call `super._initializeProperties()` manually. | ||
*/ | ||
// Prevent element from connecting when it's upgrade disabled. | ||
// This prevents user code in `attached` from being called. | ||
/** @override */ | ||
_initializeProperties() {} | ||
// prevent user code in connected from running | ||
/** @override */ | ||
connectedCallback() { | ||
if (this.__dataEnabled || !this.hasAttribute(DISABLED_ATTR)) { | ||
if (!this.__isUpgradeDisabled) { | ||
super.connectedCallback(); | ||
@@ -108,17 +158,8 @@ } | ||
// prevent element from turning on properties | ||
// Prevent element from disconnecting when it's upgrade disabled. | ||
// This avoids allowing user code `detached` from being called without a | ||
// paired call to `attached`. | ||
/** @override */ | ||
_enableProperties() { | ||
if (!this.hasAttribute(DISABLED_ATTR)) { | ||
if (!this.__dataEnabled) { | ||
super._initializeProperties(); | ||
} | ||
super._enableProperties(); | ||
} | ||
} | ||
// only go if "enabled" | ||
/** @override */ | ||
disconnectedCallback() { | ||
if (this.__dataEnabled) { | ||
if (!this.__isUpgradeDisabled) { | ||
super.disconnectedCallback(); | ||
@@ -125,0 +166,0 @@ } |
@@ -214,2 +214,12 @@ /** | ||
/** | ||
* Determines if a property dfeault can be applied. For example, this | ||
* prevents a default from being applied when a property that has no | ||
* accessor is overridden by its host before upgrade (e.g. via a binding). | ||
* | ||
* @param property Name of the property | ||
* @returns Returns true if the property default can be applied. | ||
*/ | ||
_canApplyPropertyDefault(property: string): boolean; | ||
/** | ||
* Attaches an element's stamped dom to itself. By default, | ||
@@ -216,0 +226,0 @@ * this method creates a `shadowRoot` and adds the dom to it. |
@@ -14,3 +14,3 @@ /** | ||
import { rootPath, strictTemplatePolicy, allowTemplateFromDomModule, legacyOptimizations, syncInitialRender } from '../utils/settings.js'; | ||
import { rootPath, strictTemplatePolicy, allowTemplateFromDomModule, legacyOptimizations, legacyWarnings, syncInitialRender, supportsAdoptingStyleSheets, useAdoptedStyleSheetsWithBuiltCSS } from '../utils/settings.js'; | ||
import { dedupingMixin } from '../utils/mixin.js'; | ||
@@ -28,5 +28,5 @@ import { stylesFromTemplate, stylesFromModuleImports } from '../utils/style-gather.js'; | ||
*/ | ||
export const version = '3.3.1'; | ||
export const version = '3.4.0'; | ||
const builtCSS = window.ShadyCSS && window.ShadyCSS['cssBuild']; | ||
export const builtCSS = window.ShadyCSS && window.ShadyCSS['cssBuild']; | ||
@@ -299,2 +299,26 @@ /** | ||
} | ||
// Support for `adoptedStylesheets` relies on using native Shadow DOM | ||
// and built CSS. Built CSS is required because runtime transformation of | ||
// `@apply` is not supported. This is because ShadyCSS relies on being able | ||
// to update a `style` element in the element template and this is | ||
// removed when using `adoptedStyleSheets`. | ||
// Note, it would be more efficient to allow style includes to become | ||
// separate stylesheets; however, because of `@apply` these are | ||
// potentially not shareable and sharing the ones that could be shared | ||
// would require some coordination. To keep it simple, all the includes | ||
// and styles are collapsed into a single shareable stylesheet. | ||
if (useAdoptedStyleSheetsWithBuiltCSS && builtCSS && | ||
supportsAdoptingStyleSheets) { | ||
// Remove styles in template and make a shareable stylesheet | ||
const styles = template.content.querySelectorAll('style'); | ||
if (styles) { | ||
let css = ''; | ||
Array.from(styles).forEach(s => { | ||
css += s.textContent; | ||
s.parentNode.removeChild(s); | ||
}); | ||
klass._styleSheet = new CSSStyleSheet(); | ||
klass._styleSheet.replaceSync(css); | ||
} | ||
} | ||
} | ||
@@ -417,5 +441,9 @@ | ||
* | ||
* If a `static get is()` getter is defined, the default implementation | ||
* will return the first `<template>` in a `dom-module` whose `id` | ||
* matches this element's `is`. | ||
* If a `static get is()` getter is defined, the default implementation will | ||
* return the first `<template>` in a `dom-module` whose `id` matches this | ||
* element's `is` (note that a `_template` property on the class prototype | ||
* takes precedence over the `dom-module` template, to maintain legacy | ||
* element semantics; a subclass will subsequently fall back to its super | ||
* class template if neither a `prototype._template` or a `dom-module` for | ||
* the class's `is` was found). | ||
* | ||
@@ -467,9 +495,16 @@ * Users may override this getter to return an arbitrary template | ||
if (!this.hasOwnProperty(JSCompiler_renameProperty('_template', this))) { | ||
const protoTemplate = this.prototype.hasOwnProperty( | ||
JSCompiler_renameProperty('_template', this.prototype)) ? | ||
this.prototype._template : undefined; | ||
this._template = | ||
// If user has put template on prototype (e.g. in legacy via registered | ||
// callback or info object), prefer that first | ||
this.prototype.hasOwnProperty(JSCompiler_renameProperty('_template', this.prototype)) ? | ||
this.prototype._template : | ||
// callback or info object), prefer that first. Note that `null` is | ||
// used as a sentinel to indicate "no template" and can be used to | ||
// override a super template, whereas `undefined` is used as a | ||
// sentinel to mean "fall-back to default template lookup" via | ||
// dom-module and/or super.template. | ||
protoTemplate !== undefined ? protoTemplate : | ||
// Look in dom-module associated with this element's is | ||
(getTemplateFromDomModule(/** @type {PolymerElementConstructor}*/ (this).is) || | ||
((this.hasOwnProperty(JSCompiler_renameProperty('is', this)) && | ||
(getTemplateFromDomModule(/** @type {PolymerElementConstructor}*/ (this).is))) || | ||
// Next look for superclass template (call the super impl this | ||
@@ -570,6 +605,3 @@ // way so that `this` points to the superclass) | ||
let info = p$[p]; | ||
// Don't set default value if there is already an own property, which | ||
// happens when a `properties` property with default but no effects had | ||
// a property set (e.g. bound) by its host before upgrade | ||
if (!this.hasOwnProperty(p)) { | ||
if (this._canApplyPropertyDefault(p)) { | ||
let value = typeof info.value == 'function' ? | ||
@@ -590,2 +622,14 @@ info.value.call(this) : | ||
/** | ||
* Determines if a property dfeault can be applied. For example, this | ||
* prevents a default from being applied when a property that has no | ||
* accessor is overridden by its host before upgrade (e.g. via a binding). | ||
* @override | ||
* @param {string} property Name of the property | ||
* @return {boolean} Returns true if the property default can be applied. | ||
*/ | ||
_canApplyPropertyDefault(property) { | ||
return !this.hasOwnProperty(property); | ||
} | ||
/** | ||
* Gather style text for a style element in the template. | ||
@@ -701,2 +745,7 @@ * | ||
n.shadowRoot.appendChild(dom); | ||
// When `adoptedStyleSheets` is supported a stylesheet is made | ||
// available on the element constructor. | ||
if (this.constructor._styleSheet) { | ||
n.shadowRoot.adoptedStyleSheets = [this.constructor._styleSheet]; | ||
} | ||
} | ||
@@ -810,3 +859,11 @@ if (syncInitialRender && window.ShadyDOM) { | ||
// shorthand when attribute deserialization is not important. | ||
if (legacyOptimizations && !(prop in this._properties)) { | ||
if (legacyWarnings && !(prop in this._properties) && | ||
// Methods used in templates with no dependencies (or only literal | ||
// dependencies) become accessors with template effects; ignore these | ||
!(effect.info.part.signature && effect.info.part.signature.static) && | ||
// Warnings for bindings added to nested templates are handled by | ||
// templatizer so ignore both the host-to-template bindings | ||
// (`hostProp`) and TemplateInstance-to-child bindings | ||
// (`nestedTemplate`) | ||
!effect.info.part.hostProp && !templateInfo.nestedTemplate) { | ||
console.warn(`Property '${prop}' used in template but not declared in 'properties'; ` + | ||
@@ -813,0 +870,0 @@ `attribute will not be observed.`); |
@@ -93,3 +93,3 @@ /** | ||
*/ | ||
_addPropertyToAttributeMap(property: string): void; | ||
_addPropertyToAttributeMap(property: string): any; | ||
@@ -169,2 +169,8 @@ /** | ||
/** | ||
* @param property Name of the property | ||
* @returns Returns true if the property is pending. | ||
*/ | ||
_isPropertyPending(property: string): boolean; | ||
/** | ||
* Marks the properties as invalid, and enqueues an async | ||
@@ -171,0 +177,0 @@ * `_propertiesChanged` callback. |
@@ -137,6 +137,14 @@ /** | ||
} | ||
if (!this.__dataAttributes[property]) { | ||
const attr = this.constructor.attributeNameForProperty(property); | ||
// This check is technically not correct; it's an optimization that | ||
// assumes that if a _property_ name is already in the map (note this is | ||
// an attr->property map), the property mapped directly to the attribute | ||
// and it has already been mapped. This would fail if | ||
// `attributeNameForProperty` were overridden such that this was not the | ||
// case. | ||
let attr = this.__dataAttributes[property]; | ||
if (!attr) { | ||
attr = this.constructor.attributeNameForProperty(property); | ||
this.__dataAttributes[attr] = property; | ||
} | ||
return attr; | ||
} | ||
@@ -156,7 +164,11 @@ | ||
get() { | ||
return this._getProperty(property); | ||
// Inline for perf instead of using `_getProperty` | ||
return this.__data[property]; | ||
}, | ||
/** @this {PropertiesChanged} */ | ||
set: readOnly ? function () {} : function (value) { | ||
this._setProperty(property, value); | ||
// Inline for perf instead of using `_setProperty` | ||
if (this._setPendingProperty(property, value, true)) { | ||
this._invalidateProperties(); | ||
} | ||
} | ||
@@ -177,2 +189,5 @@ /* eslint-enable */ | ||
this.__dataInstanceProps = null; | ||
/** @type {number} */ | ||
// NOTE: used to track re-entrant calls to `_flushProperties` | ||
this.__dataCounter = 0; | ||
this.__serializing = false; | ||
@@ -304,2 +319,10 @@ this._initializeProperties(); | ||
/** | ||
* @param {string} property Name of the property | ||
* @return {boolean} Returns true if the property is pending. | ||
*/ | ||
_isPropertyPending(property) { | ||
return !!(this.__dataPending && this.__dataPending.hasOwnProperty(property)); | ||
} | ||
/** | ||
* Marks the properties as invalid, and enqueues an async | ||
@@ -358,2 +381,3 @@ * `_propertiesChanged` callback. | ||
_flushProperties() { | ||
this.__dataCounter++; | ||
const props = this.__data; | ||
@@ -367,2 +391,3 @@ const changedProps = this.__dataPending; | ||
} | ||
this.__dataCounter--; | ||
} | ||
@@ -369,0 +394,0 @@ |
@@ -20,4 +20,4 @@ /** | ||
* | ||
* @param {Object} props Properties to normalize | ||
* @return {Object} Copy of input `props` with normalized properties that | ||
* @param {!Object} props Properties to normalize | ||
* @return {!Object} Copy of input `props` with normalized properties that | ||
* are in the form {type: Type} | ||
@@ -126,3 +126,3 @@ * @private | ||
const props = this._properties; | ||
this.__observedAttributes = props ? Object.keys(props).map(p => this.attributeNameForProperty(p)) : []; | ||
this.__observedAttributes = props ? Object.keys(props).map(p => this.prototype._addPropertyToAttributeMap(p)) : []; | ||
} | ||
@@ -129,0 +129,0 @@ return this.__observedAttributes; |
@@ -102,2 +102,10 @@ /** | ||
/** | ||
* Returns true if the specified property has a pending change. | ||
* | ||
* @param prop Property name | ||
* @returns True if property has a pending change | ||
*/ | ||
_isPropertyPending(prop: string): boolean; | ||
/** | ||
* Overrides PropertiesChanged implemention to serialize objects as JSON. | ||
@@ -156,10 +164,2 @@ * | ||
_hasAccessor(property: string): boolean; | ||
/** | ||
* Returns true if the specified property has a pending change. | ||
* | ||
* @param prop Property name | ||
* @returns True if property has a pending change | ||
*/ | ||
_isPropertyPending(prop: string): boolean; | ||
} |
@@ -309,3 +309,4 @@ /** | ||
interface PropertyEffects extends TemplateStamp, PropertyAccessors, PropertiesChanged { | ||
readonly PROPERTY_EFFECT_TYPES: {[key: string]: string}; | ||
_overrideLegacyUndefined: boolean; | ||
readonly PROPERTY_EFFECT_TYPES: any; | ||
@@ -328,5 +329,8 @@ /** | ||
* @param template Template to stamp | ||
* @param templateInfo Optional bound template info associated | ||
* with the template to be stamped; if omitted the template will be | ||
* automatically bound. | ||
* @returns Cloned template content | ||
*/ | ||
_stampTemplate(template: HTMLTemplateElement): StampedTemplate; | ||
_stampTemplate(template: HTMLTemplateElement, templateInfo?: TemplateInfo|null): StampedTemplate; | ||
@@ -404,7 +408,2 @@ /** | ||
/** | ||
* Overrides superclass implementation. | ||
*/ | ||
_flushProperties(): void; | ||
/** | ||
* Implements `PropertyAccessors`'s properties changed callback. | ||
@@ -431,2 +430,3 @@ * | ||
_initializeProtoProperties(props: object|null): void; | ||
_registerHost(): void; | ||
@@ -600,2 +600,3 @@ /** | ||
_propagatePropertyChanges(changedProps: object|null, oldProps: object|null, hasPaths: boolean): void; | ||
_runEffectsForTemplate(templateInfo: any, changedProps: any, oldProps: any, hasPaths: any): void; | ||
@@ -739,3 +740,3 @@ /** | ||
*/ | ||
splice(path: string|Array<string|number>, start: number, deleteCount?: number, ...items: any[]): any[]|null; | ||
splice(path: string|Array<string|number>, start: number, deleteCount?: number, ...items: any[]): any[]; | ||
@@ -850,5 +851,4 @@ /** | ||
/** | ||
* Equivalent to static `bindTemplate` API but can be called on | ||
* an instance to add effects at runtime. See that method for | ||
* full API docs. | ||
* Equivalent to static `bindTemplate` API but can be called on an instance | ||
* to add effects at runtime. See that method for full API docs. | ||
* | ||
@@ -862,10 +862,10 @@ * This method may be called on the prototype (for prototypical template | ||
* @param template Template containing binding | ||
* bindings | ||
* bindings | ||
* @param instanceBinding When false (default), performs | ||
* "prototypical" binding of the template and overwrites any previously | ||
* bound template for the class. When true (as passed from | ||
* `_stampTemplate`), the template info is instanced and linked into | ||
* the list of bound templates. | ||
* "prototypical" binding of the template and overwrites any previously | ||
* bound template for the class. When true (as passed from | ||
* `_stampTemplate`), the template info is instanced and linked into the | ||
* list of bound templates. | ||
* @returns Template metadata object; for `runtimeBinding`, | ||
* this is an instance of the prototypical template info | ||
* this is an instance of the prototypical template info | ||
*/ | ||
@@ -872,0 +872,0 @@ _bindTemplate(template: HTMLTemplateElement, instanceBinding?: boolean): TemplateInfo; |
@@ -236,5 +236,8 @@ /** | ||
* @param template Template to stamp | ||
* @param templateInfo Optional template info associated | ||
* with the template to be stamped; if omitted the template will be | ||
* automatically parsed. | ||
* @returns Cloned template content | ||
*/ | ||
_stampTemplate(template: HTMLTemplateElement): StampedTemplate; | ||
_stampTemplate(template: HTMLTemplateElement, templateInfo?: TemplateInfo|null): StampedTemplate; | ||
@@ -241,0 +244,0 @@ /** |
@@ -122,5 +122,7 @@ /** | ||
// push configuration references at configure time | ||
function applyTemplateContent(inst, node, nodeInfo) { | ||
function applyTemplateInfo(inst, node, nodeInfo, parentTemplateInfo) { | ||
if (nodeInfo.templateInfo) { | ||
// Give the node an instance of this templateInfo and set its parent | ||
node._templateInfo = nodeInfo.templateInfo; | ||
node._parentTemplateInfo = parentTemplateInfo; | ||
} | ||
@@ -155,5 +157,2 @@ } | ||
* @summary Element class mixin that provides basic template parsing and stamping | ||
* @template T | ||
* @param {function(new:T)} superClass Class to apply mixin to. | ||
* @return {function(new:T)} superClass with mixin applied. | ||
*/ | ||
@@ -257,2 +256,3 @@ export const TemplateStamp = dedupingMixin( | ||
templateInfo.nodeInfoList = []; | ||
templateInfo.nestedTemplate = Boolean(outerTemplateInfo); | ||
templateInfo.stripWhiteSpace = | ||
@@ -311,3 +311,7 @@ (outerTemplateInfo && outerTemplateInfo.stripWhiteSpace) || | ||
} | ||
return noted; | ||
// Checking `nodeInfo.noted` allows a child node of this node (who gets | ||
// access to `parentInfo`) to cause the parent to be noted, which | ||
// otherwise has no return path via `_parseTemplateChildNodes` (used by | ||
// some optimizations) | ||
return noted || nodeInfo.noted; | ||
} | ||
@@ -491,6 +495,9 @@ | ||
* @param {!HTMLTemplateElement} template Template to stamp | ||
* @param {TemplateInfo=} templateInfo Optional template info associated | ||
* with the template to be stamped; if omitted the template will be | ||
* automatically parsed. | ||
* @return {!StampedTemplate} Cloned template content | ||
* @override | ||
*/ | ||
_stampTemplate(template) { | ||
_stampTemplate(template, templateInfo) { | ||
// Polyfill support: bootstrap the template if it has not already been | ||
@@ -501,3 +508,6 @@ if (template && !template.content && | ||
} | ||
let templateInfo = this.constructor._parseTemplate(template); | ||
// Accepting the `templateInfo` via an argument allows for creating | ||
// instances of the `templateInfo` by the caller, useful for adding | ||
// instance-time information to the prototypical data | ||
templateInfo = templateInfo || this.constructor._parseTemplate(template); | ||
let nodeInfo = templateInfo.nodeInfoList; | ||
@@ -513,3 +523,3 @@ let content = templateInfo.content || template.content; | ||
applyIdToMap(this, dom.$, node, info); | ||
applyTemplateContent(this, node, info); | ||
applyTemplateInfo(this, node, info, templateInfo); | ||
applyEventListener(this, node, info); | ||
@@ -516,0 +526,0 @@ } |
@@ -30,2 +30,3 @@ /** | ||
let microtaskNodeContent = 0; | ||
let microtaskScheduled = false; | ||
let microtaskNode = document.createTextNode(''); | ||
@@ -35,2 +36,3 @@ new window.MutationObserver(microtaskFlush).observe(microtaskNode, {characterData: true}); | ||
function microtaskFlush() { | ||
microtaskScheduled = false; | ||
const len = microtaskCallbacks.length; | ||
@@ -186,3 +188,6 @@ for (let i = 0; i < len; i++) { | ||
run(callback) { | ||
microtaskNode.textContent = microtaskNodeContent++; | ||
if (!microtaskScheduled) { | ||
microtaskScheduled = true; | ||
microtaskNode.textContent = microtaskNodeContent++; | ||
} | ||
microtaskCallbacks.push(callback); | ||
@@ -189,0 +194,0 @@ return microtaskCurrHandle++; |
@@ -55,9 +55,9 @@ /** | ||
map.set(base, extended); | ||
// copy inherited mixin set from the extended class, or the base class | ||
// NOTE: we avoid use of Set here because some browser (IE11) | ||
// cannot extend a base Set via the constructor. | ||
let mixinSet = Object.create(/** @type {!MixinFunction} */(extended).__mixinSet || baseSet || null); | ||
mixinSet[mixinDedupeId] = true; | ||
/** @type {!MixinFunction} */(extended).__mixinSet = mixinSet; | ||
} | ||
// copy inherited mixin set from the extended class, or the base class | ||
// NOTE: we avoid use of Set here because some browser (IE11) | ||
// cannot extend a base Set via the constructor. | ||
let mixinSet = Object.create(/** @type {!MixinFunction} */(extended).__mixinSet || baseSet || null); | ||
mixinSet[mixinDedupeId] = true; | ||
/** @type {!MixinFunction} */(extended).__mixinSet = mixinSet; | ||
return extended; | ||
@@ -64,0 +64,0 @@ } |
@@ -35,2 +35,12 @@ /** | ||
export {getSanitizeDOMValue}; | ||
/** | ||
* Gets sanitizeDOMValue, for environments that don't well support `export let`. | ||
* | ||
* @returns sanitizeDOMValue | ||
*/ | ||
declare function getSanitizeDOMValue(): ((p0: any, p1: string, p2: string, p3: Node|null) => any)|undefined; | ||
export {setPassiveTouchGestures}; | ||
@@ -69,2 +79,10 @@ | ||
export {setLegacyWarnings}; | ||
/** | ||
* Sets `legacyWarnings` globally for all elements to migration warnings. | ||
*/ | ||
declare function setLegacyWarnings(useLegacyWarnings: boolean): void; | ||
export {setSyncInitialRender}; | ||
@@ -79,2 +97,20 @@ | ||
export {setLegacyUndefined}; | ||
/** | ||
* Sets `legacyUndefined` globally for all elements to enable legacy | ||
* multi-property behavior for undefined values. | ||
*/ | ||
declare function setLegacyUndefined(useLegacyUndefined: boolean): void; | ||
export {setOrderedComputed}; | ||
/** | ||
* Sets `orderedComputed` globally for all elements to enable ordered computed | ||
* property computation. | ||
*/ | ||
declare function setOrderedComputed(useOrderedComputed: boolean): void; | ||
export {setCancelSyntheticClickEvents}; | ||
@@ -87,1 +123,43 @@ | ||
declare function setCancelSyntheticClickEvents(useCancelSyntheticClickEvents: boolean): void; | ||
export {setRemoveNestedTemplates}; | ||
/** | ||
* Sets `removeNestedTemplates` globally, to eliminate nested templates | ||
* inside `dom-if` and `dom-repeat` as part of template parsing. | ||
*/ | ||
declare function setRemoveNestedTemplates(useRemoveNestedTemplates: boolean): void; | ||
export {setFastDomIf}; | ||
/** | ||
* Sets `fastDomIf` globally, to put `dom-if` in a performance-optimized mode. | ||
*/ | ||
declare function setFastDomIf(useFastDomIf: boolean): void; | ||
export {setSuppressTemplateNotifications}; | ||
/** | ||
* Sets `suppressTemplateNotifications` globally, to disable `dom-change` and | ||
* `rendered-item-count` events from `dom-if` and `dom-repeat`. | ||
*/ | ||
declare function setSuppressTemplateNotifications(suppress: boolean): void; | ||
export {setLegacyNoObservedAttributes}; | ||
/** | ||
* Sets `legacyNoObservedAttributes` globally, to disable `observedAttributes`. | ||
*/ | ||
declare function setLegacyNoObservedAttributes(noObservedAttributes: boolean): void; | ||
export {setUseAdoptedStyleSheetsWithBuiltCSS}; | ||
/** | ||
* Sets `useAdoptedStyleSheetsWithBuiltCSS` globally. | ||
*/ | ||
declare function setUseAdoptedStyleSheetsWithBuiltCSS(value: boolean): void; |
@@ -11,9 +11,23 @@ /** | ||
import './boot.js'; | ||
import { pathFromUrl } from './resolve-url.js'; | ||
export const useShadow = !(window.ShadyDOM); | ||
export const useShadow = !(window.ShadyDOM) || !(window.ShadyDOM.inUse); | ||
export const useNativeCSSProperties = Boolean(!window.ShadyCSS || window.ShadyCSS.nativeCss); | ||
export const useNativeCustomElements = !(window.customElements.polyfillWrapFlushCallback); | ||
export const supportsAdoptingStyleSheets = useShadow && | ||
('adoptedStyleSheets' in Document.prototype) && | ||
('replaceSync' in CSSStyleSheet.prototype) && | ||
// Since spec may change, feature detect exact API we need | ||
(() => { | ||
try { | ||
const sheet = new CSSStyleSheet(); | ||
sheet.replaceSync(''); | ||
const host = document.createElement('div'); | ||
host.attachShadow({mode: 'open'}); | ||
host.shadowRoot.adoptedStyleSheets = [sheet]; | ||
return (host.shadowRoot.adoptedStyleSheets[0] === sheet); | ||
} catch(e) { | ||
return false; | ||
} | ||
})(); | ||
/** | ||
@@ -27,3 +41,4 @@ * Globally settable property that is automatically assigned to | ||
*/ | ||
export let rootPath = pathFromUrl(document.baseURI || window.location.href); | ||
export let rootPath = window.Polymer && window.Polymer.rootPath || | ||
pathFromUrl(document.baseURI || window.location.href); | ||
@@ -54,5 +69,6 @@ /** | ||
* | ||
* @type {(function(*,string,string,Node):*)|undefined} | ||
* @type {(function(*,string,string,?Node):*)|undefined} | ||
*/ | ||
export let sanitizeDOMValue = window.Polymer && window.Polymer.sanitizeDOMValue || undefined; | ||
export let sanitizeDOMValue = | ||
window.Polymer && window.Polymer.sanitizeDOMValue || undefined; | ||
@@ -63,3 +79,3 @@ /** | ||
* | ||
* @param {(function(*,string,string,Node):*)|undefined} newSanitizeDOMValue the global sanitizeDOMValue callback | ||
* @param {(function(*,string,string,?Node):*)|undefined} newSanitizeDOMValue the global sanitizeDOMValue callback | ||
* @return {void} | ||
@@ -72,2 +88,11 @@ */ | ||
/** | ||
* Gets sanitizeDOMValue, for environments that don't well support `export let`. | ||
* | ||
* @return {(function(*,string,string,?Node):*)|undefined} sanitizeDOMValue | ||
*/ | ||
export const getSanitizeDOMValue = function() { | ||
return sanitizeDOMValue; | ||
}; | ||
/** | ||
* Globally settable property to make Polymer Gestures use passive TouchEvent listeners when recognizing gestures. | ||
@@ -78,3 +103,4 @@ * When set to `true`, gestures made from touch will not be able to prevent scrolling, allowing for smoother | ||
*/ | ||
export let passiveTouchGestures = false; | ||
export let passiveTouchGestures = | ||
window.Polymer && window.Polymer.setPassiveTouchGestures || false; | ||
@@ -97,3 +123,4 @@ /** | ||
*/ | ||
export let strictTemplatePolicy = false; | ||
export let strictTemplatePolicy = | ||
window.Polymer && window.Polymer.strictTemplatePolicy || false; | ||
@@ -117,3 +144,4 @@ /** | ||
*/ | ||
export let allowTemplateFromDomModule = false; | ||
export let allowTemplateFromDomModule = | ||
window.Polymer && window.Polymer.allowTemplateFromDomModule || false; | ||
@@ -138,3 +166,4 @@ /** | ||
*/ | ||
export let legacyOptimizations = false; | ||
export let legacyOptimizations = | ||
window.Polymer && window.Polymer.legacyOptimizations || false; | ||
@@ -154,6 +183,23 @@ /** | ||
/** | ||
* Setting to add warnings useful when migrating from Polymer 1.x to 2.x. | ||
*/ | ||
export let legacyWarnings = | ||
window.Polymer && window.Polymer.legacyWarnings || false; | ||
/** | ||
* Sets `legacyWarnings` globally for all elements to migration warnings. | ||
* | ||
* @param {boolean} useLegacyWarnings enable or disable warnings | ||
* @return {void} | ||
*/ | ||
export const setLegacyWarnings = function(useLegacyWarnings) { | ||
legacyWarnings = useLegacyWarnings; | ||
}; | ||
/** | ||
* Setting to perform initial rendering synchronously when running under ShadyDOM. | ||
* This matches the behavior of Polymer 1. | ||
*/ | ||
export let syncInitialRender = false; | ||
export let syncInitialRender = | ||
window.Polymer && window.Polymer.syncInitialRender || false; | ||
@@ -173,2 +219,40 @@ /** | ||
/** | ||
* Setting to retain the legacy Polymer 1 behavior for multi-property | ||
* observers around undefined values. Observers and computed property methods | ||
* are not called until no argument is undefined. | ||
*/ | ||
export let legacyUndefined = | ||
window.Polymer && window.Polymer.legacyUndefined || false; | ||
/** | ||
* Sets `legacyUndefined` globally for all elements to enable legacy | ||
* multi-property behavior for undefined values. | ||
* | ||
* @param {boolean} useLegacyUndefined enable or disable legacy | ||
* multi-property behavior for undefined. | ||
* @return {void} | ||
*/ | ||
export const setLegacyUndefined = function(useLegacyUndefined) { | ||
legacyUndefined = useLegacyUndefined; | ||
}; | ||
/** | ||
* Setting to ensure computed properties are computed in order to ensure | ||
* re-computation never occurs in a given turn. | ||
*/ | ||
export let orderedComputed = | ||
window.Polymer && window.Polymer.orderedComputed || false; | ||
/** | ||
* Sets `orderedComputed` globally for all elements to enable ordered computed | ||
* property computation. | ||
* | ||
* @param {boolean} useOrderedComputed enable or disable ordered computed effects | ||
* @return {void} | ||
*/ | ||
export const setOrderedComputed = function(useOrderedComputed) { | ||
orderedComputed = useOrderedComputed; | ||
}; | ||
/** | ||
* Setting to cancel synthetic click events fired by older mobile browsers. Modern browsers | ||
@@ -190,1 +274,98 @@ * no longer fire synthetic click events, and the cancellation behavior can interfere | ||
}; | ||
/** | ||
* Setting to remove nested templates inside `dom-if` and `dom-repeat` as | ||
* part of element template parsing. This is a performance optimization that | ||
* eliminates most of the tax of needing two elements due to the loss of | ||
* type-extended templates as a result of the V1 specification changes. | ||
*/ | ||
export let removeNestedTemplates = | ||
window.Polymer && window.Polymer.removeNestedTemplates || false; | ||
/** | ||
* Sets `removeNestedTemplates` globally, to eliminate nested templates | ||
* inside `dom-if` and `dom-repeat` as part of template parsing. | ||
* | ||
* @param {boolean} useRemoveNestedTemplates enable or disable removing nested | ||
* templates during parsing | ||
* @return {void} | ||
*/ | ||
export const setRemoveNestedTemplates = function(useRemoveNestedTemplates) { | ||
removeNestedTemplates = useRemoveNestedTemplates; | ||
}; | ||
/** | ||
* Setting to place `dom-if` elements in a performance-optimized mode that takes | ||
* advantage of lighter-weight host runtime template stamping to eliminate the | ||
* need for an intermediate Templatizer `TemplateInstance` to mange the nodes | ||
* stamped by `dom-if`. Under this setting, any Templatizer-provided API's | ||
* such as `modelForElement` will not be available for nodes stamped by | ||
* `dom-if`. | ||
*/ | ||
export let fastDomIf = window.Polymer && window.Polymer.fastDomIf || false; | ||
/** | ||
* Sets `fastDomIf` globally, to put `dom-if` in a performance-optimized mode. | ||
* | ||
* @param {boolean} useFastDomIf enable or disable `dom-if` fast-mode | ||
* @return {void} | ||
*/ | ||
export const setFastDomIf = function(useFastDomIf) { | ||
fastDomIf = useFastDomIf; | ||
}; | ||
/** | ||
* Setting to disable `dom-change` and `rendered-item-count` events from | ||
* `dom-if` and `dom-repeat`. Users can opt back into `dom-change` events by | ||
* setting the `notify-dom-change` attribute (`notifyDomChange: true` property) | ||
* to `dom-if`/`don-repeat` instances. | ||
*/ | ||
export let suppressTemplateNotifications = | ||
window.Polymer && window.Polymer.suppressTemplateNotifications || false; | ||
/** | ||
* Sets `suppressTemplateNotifications` globally, to disable `dom-change` and | ||
* `rendered-item-count` events from `dom-if` and `dom-repeat`. | ||
* | ||
* @param {boolean} suppress enable or disable `suppressTemplateNotifications` | ||
* @return {void} | ||
*/ | ||
export const setSuppressTemplateNotifications = function(suppress) { | ||
suppressTemplateNotifications = suppress; | ||
}; | ||
/** | ||
* Setting to disable use of dynamic attributes. This is an optimization | ||
* to avoid setting `observedAttributes`. Instead attributes are read | ||
* once at create time and set/removeAttribute are patched. | ||
*/ | ||
export let legacyNoObservedAttributes = | ||
window.Polymer && window.Polymer.legacyNoObservedAttributes || false; | ||
/** | ||
* Sets `legacyNoObservedAttributes` globally, to disable `observedAttributes`. | ||
* | ||
* @param {boolean} noObservedAttributes enable or disable `legacyNoObservedAttributes` | ||
* @return {void} | ||
*/ | ||
export const setLegacyNoObservedAttributes = function(noObservedAttributes) { | ||
legacyNoObservedAttributes = noObservedAttributes; | ||
}; | ||
/** | ||
* Setting to enable use of `adoptedStyleSheets` for sharing style sheets | ||
* between component instances' shadow roots, if the app uses built Shady CSS | ||
* styles. | ||
*/ | ||
export let useAdoptedStyleSheetsWithBuiltCSS = | ||
window.Polymer && window.Polymer.useAdoptedStyleSheetsWithBuiltCSS || false; | ||
/** | ||
* Sets `useAdoptedStyleSheetsWithBuiltCSS` globally. | ||
* | ||
* @param {boolean} value enable or disable `useAdoptedStyleSheetsWithBuiltCSS` | ||
* @return {void} | ||
*/ | ||
export const setUseAdoptedStyleSheetsWithBuiltCSS = function(value) { | ||
useAdoptedStyleSheetsWithBuiltCSS = value; | ||
}; |
@@ -19,2 +19,6 @@ /** | ||
export {showHideChildren}; | ||
declare function showHideChildren(): void; | ||
declare class TemplateInstanceBase extends | ||
@@ -190,3 +194,3 @@ PropertyEffects( | ||
*/ | ||
declare function modelForElement(template: HTMLTemplateElement|null, node?: Node|null): TemplateInstanceBase|null; | ||
declare function modelForElement(template: HTMLElement|null, node?: Node|null): TemplateInstanceBase|null; | ||
@@ -193,0 +197,0 @@ export {TemplateInstanceBase}; |
@@ -52,3 +52,3 @@ /** | ||
import { MutableData } from '../mixins/mutable-data.js'; | ||
import { strictTemplatePolicy } from './settings.js'; | ||
import { strictTemplatePolicy, legacyWarnings } from './settings.js'; | ||
import { wrap } from './wrap.js'; | ||
@@ -106,7 +106,46 @@ | ||
*/ | ||
const templateInstanceBase = PropertyEffects( | ||
// This cast shouldn't be neccessary, but Closure doesn't understand that | ||
// "class {}" is a constructor function. | ||
/** @type {function(new:Object)} */(class {})); | ||
const templateInstanceBase = PropertyEffects(class {}); | ||
export function showHideChildren(hide, children) { | ||
for (let i=0; i<children.length; i++) { | ||
let n = children[i]; | ||
// Ignore non-changes | ||
if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) { | ||
// clear and restore text | ||
if (n.nodeType === Node.TEXT_NODE) { | ||
if (hide) { | ||
n.__polymerTextContent__ = n.textContent; | ||
n.textContent = ''; | ||
} else { | ||
n.textContent = n.__polymerTextContent__; | ||
} | ||
// remove and replace slot | ||
} else if (n.localName === 'slot') { | ||
if (hide) { | ||
n.__polymerReplaced__ = document.createComment('hidden-slot'); | ||
wrap(wrap(n).parentNode).replaceChild(n.__polymerReplaced__, n); | ||
} else { | ||
const replace = n.__polymerReplaced__; | ||
if (replace) { | ||
wrap(wrap(replace).parentNode).replaceChild(n, replace); | ||
} | ||
} | ||
} | ||
// hide and show nodes | ||
else if (n.style) { | ||
if (hide) { | ||
n.__polymerDisplay__ = n.style.display; | ||
n.style.display = 'none'; | ||
} else { | ||
n.style.display = n.__polymerDisplay__; | ||
} | ||
} | ||
} | ||
n.__hideTemplateChildren__ = hide; | ||
if (n._showHideChildren) { | ||
n._showHideChildren(hide); | ||
} | ||
} | ||
} | ||
/** | ||
@@ -217,41 +256,3 @@ * @polymer | ||
_showHideChildren(hide) { | ||
let c = this.children; | ||
for (let i=0; i<c.length; i++) { | ||
let n = c[i]; | ||
// Ignore non-changes | ||
if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) { | ||
if (n.nodeType === Node.TEXT_NODE) { | ||
if (hide) { | ||
n.__polymerTextContent__ = n.textContent; | ||
n.textContent = ''; | ||
} else { | ||
n.textContent = n.__polymerTextContent__; | ||
} | ||
// remove and replace slot | ||
} else if (n.localName === 'slot') { | ||
if (hide) { | ||
n.__polymerReplaced__ = document.createComment('hidden-slot'); | ||
wrap(wrap(n).parentNode).replaceChild(n.__polymerReplaced__, n); | ||
} else { | ||
const replace = n.__polymerReplaced__; | ||
if (replace) { | ||
wrap(wrap(replace).parentNode).replaceChild(n, replace); | ||
} | ||
} | ||
} | ||
else if (n.style) { | ||
if (hide) { | ||
n.__polymerDisplay__ = n.style.display; | ||
n.style.display = 'none'; | ||
} else { | ||
n.style.display = n.__polymerDisplay__; | ||
} | ||
} | ||
} | ||
n.__hideTemplateChildren__ = hide; | ||
if (n._showHideChildren) { | ||
n._showHideChildren(hide); | ||
} | ||
} | ||
showHideChildren(hide, this.children); | ||
} | ||
@@ -331,5 +332,5 @@ /** | ||
const MutableTemplateInstanceBase = MutableData( | ||
// This cast shouldn't be necessary, but Closure doesn't seem to understand | ||
// this constructor. | ||
/** @type {function(new:TemplateInstanceBase)} */(TemplateInstanceBase)); | ||
// This cast shouldn't be neccessary, but Closure doesn't understand that | ||
// TemplateInstanceBase is a constructor function. | ||
/** @type {function(new:TemplateInstanceBase)} */ (TemplateInstanceBase)); | ||
@@ -379,19 +380,47 @@ function findMethodHost(template) { | ||
* properties that the host binds to the template using the `_host_` prefix. | ||
* | ||
* | ||
* @suppress {missingProperties} class.prototype is not defined for some reason | ||
*/ | ||
function addPropagateEffects(template, templateInfo, options) { | ||
function addPropagateEffects(target, templateInfo, options, methodHost) { | ||
let userForwardHostProp = options.forwardHostProp; | ||
if (userForwardHostProp && templateInfo.hasHostProps) { | ||
// Under the `removeNestedTemplates` optimization, a custom element like | ||
// `dom-if` or `dom-repeat` can itself be treated as the "template"; this | ||
// flag is used to switch between upgrading a `<template>` to be a property | ||
// effects client vs. adding the effects directly to the custom element | ||
const isTemplate = target.localName == 'template'; | ||
// Provide data API and property effects on memoized template class | ||
let klass = templateInfo.templatizeTemplateClass; | ||
if (!klass) { | ||
/** | ||
* @constructor | ||
* @extends {DataTemplate} | ||
*/ | ||
let templatizedBase = options.mutableData ? MutableDataTemplate : DataTemplate; | ||
/** @private */ | ||
klass = templateInfo.templatizeTemplateClass = | ||
class TemplatizedTemplate extends templatizedBase {}; | ||
if (isTemplate) { | ||
/** | ||
* @constructor | ||
* @extends {DataTemplate} | ||
*/ | ||
let templatizedBase = | ||
options.mutableData ? MutableDataTemplate : DataTemplate; | ||
// NOTE: due to https://github.com/google/closure-compiler/issues/2928, | ||
// combining the next two lines into one assignment causes a spurious | ||
// type error. | ||
/** @private */ | ||
class TemplatizedTemplate extends templatizedBase {} | ||
klass = templateInfo.templatizeTemplateClass = TemplatizedTemplate; | ||
} else { | ||
/** | ||
* @constructor | ||
* @extends {PolymerElement} | ||
*/ | ||
const templatizedBase = target.constructor; | ||
// Create a cached subclass of the base custom element class onto which | ||
// to put the template-specific propagate effects | ||
// NOTE: due to https://github.com/google/closure-compiler/issues/2928, | ||
// combining the next two lines into one assignment causes a spurious | ||
// type error. | ||
/** @private */ | ||
class TemplatizedTemplateExtension extends templatizedBase {} | ||
klass = templateInfo.templatizeTemplateClass = | ||
TemplatizedTemplateExtension; | ||
} | ||
// Add template - >instances effects | ||
@@ -406,16 +435,36 @@ // and host <- template effects | ||
} | ||
if (legacyWarnings && methodHost) { | ||
warnOnUndeclaredProperties(templateInfo, options, methodHost); | ||
} | ||
} | ||
upgradeTemplate(template, klass); | ||
// Mix any pre-bound data into __data; no need to flush this to | ||
// instances since they pull from the template at instance-time | ||
if (template.__dataProto) { | ||
if (target.__dataProto) { | ||
// Note, generally `__dataProto` could be chained, but it's guaranteed | ||
// to not be since this is a vanilla template we just added effects to | ||
Object.assign(template.__data, template.__dataProto); | ||
Object.assign(target.__data, target.__dataProto); | ||
} | ||
// Clear any pending data for performance | ||
template.__dataTemp = {}; | ||
template.__dataPending = null; | ||
template.__dataOld = null; | ||
template._enableProperties(); | ||
if (isTemplate) { | ||
upgradeTemplate(target, klass); | ||
// Clear any pending data for performance | ||
target.__dataTemp = {}; | ||
target.__dataPending = null; | ||
target.__dataOld = null; | ||
target._enableProperties(); | ||
} else { | ||
// Swizzle the cached subclass prototype onto the custom element | ||
Object.setPrototypeOf(target, klass.prototype); | ||
// Check for any pre-bound instance host properties, and do the | ||
// instance property delete/assign dance for those (directly into data; | ||
// not need to go through accessor since they are pulled at instance time) | ||
const hostProps = templateInfo.hostProps; | ||
for (let prop in hostProps) { | ||
prop = '_host_' + prop; | ||
if (prop in target) { | ||
const val = target[prop]; | ||
delete target[prop]; | ||
target.__data[prop] = val; | ||
} | ||
} | ||
} | ||
} | ||
@@ -574,4 +623,5 @@ } | ||
} | ||
const methodHost = findMethodHost(template); | ||
// Host property forwarding must be installed onto template instance | ||
addPropagateEffects(template, templateInfo, options); | ||
addPropagateEffects(template, templateInfo, options, methodHost); | ||
// Subclass base class and add reference for this specific template | ||
@@ -581,3 +631,3 @@ /** @private */ | ||
/** @override */ | ||
klass.prototype._methodHost = findMethodHost(template); | ||
klass.prototype._methodHost = methodHost; | ||
/** @override */ | ||
@@ -593,2 +643,23 @@ klass.prototype.__dataHost = /** @type {!DataTemplate} */ (template); | ||
function warnOnUndeclaredProperties(templateInfo, options, methodHost) { | ||
const declaredProps = methodHost.constructor._properties; | ||
const {propertyEffects} = templateInfo; | ||
const {instanceProps} = options; | ||
for (let prop in propertyEffects) { | ||
// Ensure properties with template effects are declared on the outermost | ||
// host (`methodHost`), unless they are instance props or static functions | ||
if (!declaredProps[prop] && !(instanceProps && instanceProps[prop])) { | ||
const effects = propertyEffects[prop]; | ||
for (let i=0; i<effects.length; i++) { | ||
const {part} = effects[i].info; | ||
if (!(part.signature && part.signature.static)) { | ||
console.warn(`Property '${prop}' used in template but not ` + | ||
`declared in 'properties'; attribute will not be observed.`); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
@@ -608,4 +679,6 @@ * Returns the template "model" associated with a given element, which | ||
* | ||
* @param {HTMLTemplateElement} template The model will be returned for | ||
* elements stamped from this template | ||
* @param {HTMLElement} template The model will be returned for | ||
* elements stamped from this template (accepts either an HTMLTemplateElement) | ||
* or a `<dom-if>`/`<dom-repeat>` element when using `removeNestedTemplates` | ||
* optimization. | ||
* @param {Node=} node Node for which to return a template model. | ||
@@ -621,3 +694,3 @@ * @return {TemplateInstanceBase} Template instance representing the | ||
// its __dataHost matches `this`, meaning this dom-repeat stamped it | ||
if ((model = node.__templatizeInstance)) { | ||
if ((model = node.__dataHost ? node : node.__templatizeInstance)) { | ||
// Found an element stamped by another template; keep walking up | ||
@@ -624,0 +697,0 @@ // from its __dataHost |
{ | ||
"name": "@polymer/polymer", | ||
"version": "3.3.1", | ||
"version": "3.4.0", | ||
"description": "The Polymer library makes it easy to create your own web components. Give your element some markup and properties, and then use it on a site. Polymer provides features like dynamic templates and data binding to reduce the amount of boilerplate you need to write", | ||
@@ -5,0 +5,0 @@ "main": "polymer-element.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
1274871
24089